Files
low-code-react-app-b/packages/spark-tools/dist/db.js
2026-01-17 22:16:42 +00:00

330 lines
11 KiB
JavaScript

import { K as KVClient } from './kv-DBiZoNWq.js';
// These values should NEVER change. The values are precisely for
// generating ULIDs.
const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; // Crockford's Base32
const ENCODING_LEN = 32; // from ENCODING.length;
const RANDOM_LEN = 16;
const TIME_LEN = 10;
const TIME_MAX = 281474976710655; // from Math.pow(2, 48) - 1;
var ULIDErrorCode;
(function (ULIDErrorCode) {
ULIDErrorCode["Base32IncorrectEncoding"] = "B32_ENC_INVALID";
ULIDErrorCode["DecodeTimeInvalidCharacter"] = "DEC_TIME_CHAR";
ULIDErrorCode["DecodeTimeValueMalformed"] = "DEC_TIME_MALFORMED";
ULIDErrorCode["EncodeTimeNegative"] = "ENC_TIME_NEG";
ULIDErrorCode["EncodeTimeSizeExceeded"] = "ENC_TIME_SIZE_EXCEED";
ULIDErrorCode["EncodeTimeValueMalformed"] = "ENC_TIME_MALFORMED";
ULIDErrorCode["PRNGDetectFailure"] = "PRNG_DETECT";
ULIDErrorCode["ULIDInvalid"] = "ULID_INVALID";
ULIDErrorCode["Unexpected"] = "UNEXPECTED";
ULIDErrorCode["UUIDInvalid"] = "UUID_INVALID";
})(ULIDErrorCode || (ULIDErrorCode = {}));
class ULIDError extends Error {
constructor(errorCode, message) {
super(`${message} (${errorCode})`);
this.name = "ULIDError";
this.code = errorCode;
}
}
function randomChar(prng) {
// Currently PRNGs generate fractions from 0 to _less than_ 1, so no "%" is necessary.
// However, just in case a future PRNG can generate 1,
// we are applying "% ENCODING LEN" to wrap back to the first character
const randomPosition = Math.floor(prng() * ENCODING_LEN) % ENCODING_LEN;
return ENCODING.charAt(randomPosition);
}
/**
* Detect the best PRNG (pseudo-random number generator)
* @param root The root to check from (global/window)
* @returns The PRNG function
*/
function detectPRNG(root) {
const rootLookup = detectRoot();
const globalCrypto = (rootLookup && (rootLookup.crypto || rootLookup.msCrypto)) ||
(null);
if (typeof globalCrypto?.getRandomValues === "function") {
return () => {
const buffer = new Uint8Array(1);
globalCrypto.getRandomValues(buffer);
return buffer[0] / 256;
};
}
else if (typeof globalCrypto?.randomBytes === "function") {
return () => globalCrypto.randomBytes(1).readUInt8() / 256;
}
else ;
throw new ULIDError(ULIDErrorCode.PRNGDetectFailure, "Failed to find a reliable PRNG");
}
function detectRoot() {
if (inWebWorker())
return self;
if (typeof window !== "undefined") {
return window;
}
if (typeof global !== "undefined") {
return global;
}
if (typeof globalThis !== "undefined") {
return globalThis;
}
return null;
}
function encodeRandom(len, prng) {
let str = "";
for (; len > 0; len--) {
str = randomChar(prng) + str;
}
return str;
}
/**
* Encode the time portion of a ULID
* @param now The current timestamp
* @param len Length to generate
* @returns The encoded time
*/
function encodeTime(now, len = TIME_LEN) {
if (isNaN(now)) {
throw new ULIDError(ULIDErrorCode.EncodeTimeValueMalformed, `Time must be a number: ${now}`);
}
else if (now > TIME_MAX) {
throw new ULIDError(ULIDErrorCode.EncodeTimeSizeExceeded, `Cannot encode a time larger than ${TIME_MAX}: ${now}`);
}
else if (now < 0) {
throw new ULIDError(ULIDErrorCode.EncodeTimeNegative, `Time must be positive: ${now}`);
}
else if (Number.isInteger(now) === false) {
throw new ULIDError(ULIDErrorCode.EncodeTimeValueMalformed, `Time must be an integer: ${now}`);
}
let mod, str = "";
for (let currentLen = len; currentLen > 0; currentLen--) {
mod = now % ENCODING_LEN;
str = ENCODING.charAt(mod) + str;
now = (now - mod) / ENCODING_LEN;
}
return str;
}
function inWebWorker() {
// @ts-ignore
return typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope;
}
/**
* Generate a ULID
* @param seedTime Optional time seed
* @param prng Optional PRNG function
* @returns A ULID string
* @example
* ulid(); // "01HNZXD07M5CEN5XA66EMZSRZW"
*/
function ulid(seedTime, prng) {
const currentPRNG = detectPRNG();
const seed = Date.now() ;
return encodeTime(seed, TIME_LEN) + encodeRandom(RANDOM_LEN, currentPRNG);
}
const BASE_DB_SERVICE_URL = '/_spark/db';
/**
* DBClient provides methods to interact with Spark's document database.
*/
class DBClient {
kv;
constructor(kvClient) {
this.kv = kvClient || new KVClient();
}
/**
* Generate a unique document ID using ULID
* @returns A unique document ID
*/
generateDocId() {
return ulid();
}
/**
* Get all documents in a collection using the DB API
* @param collectionName The name of the collection
* @returns Array of all documents in the collection with id field
*/
async getAll(collectionName) {
try {
const response = await fetch(`${BASE_DB_SERVICE_URL}/collections/${collectionName}`, {
method: 'GET',
});
if (!response.ok) {
const errorMessage = `Failed to fetch DB collection: ${response.statusText}`;
throw new Error(errorMessage);
}
let json;
try {
json = await response.json();
}
catch (error) {
const errorMessage = 'Failed to parse DB collection response';
throw new Error(errorMessage);
}
if (!Array.isArray(json)) {
const errorMessage = 'DB collection response is not an array';
throw new Error(errorMessage);
}
const entries = json;
return entries
.map((entry) => {
return { _id: entry.key, ...JSON.parse(entry.value) };
});
}
catch (error) {
console.error(`Error getting collection ${collectionName}:`, error);
return [];
}
}
/**
* Insert a document into a collection with schema validation
* @param collectionName The name of the collection
* @param schema The Zod schema for validation
* @param data The document data to insert
* @returns The inserted document with generated id
*/
async insert(collectionName, schema, data) {
const id = this.generateDocId();
const { _id: _, ...dataWithoutId } = data;
const validatedData = schema.parse(dataWithoutId);
await this.kv.setKey(id, validatedData, collectionName);
return { ...validatedData, _id: id };
}
/**
* Get a document by ID from a collection
* @param collectionName The name of the collection
* @param id The document ID
* @returns The document with id field or null if not found
*/
async get(collectionName, id) {
try {
const doc = await this.kv.getKey(id, collectionName);
if (!doc)
return null;
return { ...doc, _id: id };
}
catch (error) {
console.error(`Error getting document ${id} from ${collectionName}:`, error);
return null;
}
}
/**
* Update a document with partial data and schema validation
* @param collectionName The name of the collection
* @param id The document ID
* @param schema The Zod schema for validation
* @param data Partial data to update
* @returns The updated document or null if not found
*/
async update(collectionName, id, schema, data) {
try {
const existing = await this.kv.getKey(id, collectionName);
if (!existing)
return null;
const { _id: _, ...dataWithoutId } = data;
const updated = { ...existing, ...dataWithoutId };
const validatedData = schema.parse(updated);
await this.kv.setKey(id, validatedData, collectionName);
return { ...validatedData, _id: id };
}
catch (error) {
console.error(`Error updating document ${id} in ${collectionName}:`, error);
return null;
}
}
/**
* Delete a document from a collection
* @param collectionName The name of the collection
* @param id The document ID to delete
* @returns true if document was deleted, false if not found
*/
async delete(collectionName, id) {
try {
await this.kv.deleteKey(id, collectionName);
return true;
}
catch (error) {
console.error(`Error deleting document ${id} from ${collectionName}:`, error);
return false;
}
}
/**
* Query documents with filtering
* @param collectionName The name of the collection
* @param filterFn Function to filter documents
* @returns Array of filtered documents
*/
async query(collectionName, filterFn) {
const collection = await this.getAll(collectionName);
return collection.filter(filterFn);
}
}
const db = new DBClient();
/**
* Create a collection instance with schema validation and clean API
* @param schema The Zod schema for the collection
* @param collectionName The name of the collection
* @returns Collection instance with CRUD operations
*/
function collection(schema, collectionName) {
return {
async insert(data) {
return db.insert(collectionName, schema, data);
},
async get(id) {
return db.get(collectionName, id);
},
async update(id, data) {
return db.update(collectionName, id, schema, data);
},
async delete(id) {
return db.delete(collectionName, id);
},
async getAll() {
return db.getAll(collectionName);
},
async query(options) {
// Get all documents first
let results = await db.getAll(collectionName);
// Apply where condition
if (options?.where) {
const condition = options.where;
results = results.filter((doc) => {
const fieldValue = doc[condition.field];
switch (condition.operator) {
case '==': return fieldValue === condition.value;
case '!=': return fieldValue !== condition.value;
case '>': return fieldValue > condition.value;
case '<': return fieldValue < condition.value;
case '>=': return fieldValue >= condition.value;
case '<=': return fieldValue <= condition.value;
default: return false;
}
});
}
// Apply sorting
if (options?.sortBy) {
results.sort((a, b) => {
const aVal = a[options.sortBy.field];
const bVal = b[options.sortBy.field];
if (aVal < bVal)
return options.sortBy.direction === 'asc' ? -1 : 1;
if (aVal > bVal)
return options.sortBy.direction === 'asc' ? 1 : -1;
return 0;
});
}
// Apply limit
if (options?.limit !== undefined) {
results = results.slice(0, options.limit);
}
return results;
}
};
}
export { DBClient as DB, collection };
//# sourceMappingURL=db.js.map