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] / 0xff;
        };
    }
    else if (typeof globalCrypto?.randomBytes === "function") {
        return () => globalCrypto.randomBytes(1).readUInt8() / 0xff;
    }
    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
