var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // index.ts var ronin_exports = {}; __export(ronin_exports, { default: () => ronin_default, getQueriesFromScratchPad: () => getQueriesFromScratchPad, processReadables: () => processStorableObjects, runQueries: () => runQueries2 }); module.exports = __toCommonJS(ronin_exports); // utils/fetch.ts var $fetch = async (url, init, fetcher = fetch) => fetcher(url, init).then((res) => res.json()); var fetch_default = $fetch; // utils/helpers.ts var combineKeys = (parent, child) => { const nested = child.replaceAll(".", "\\."); return parent ? `${parent}.${nested}` : nested; }; var objectFromAccessor = (accessor, value) => setProperty({}, accessor, value); var findSafeIndex = (map, index) => { const values = Array.from(map.values()); const existing = values.find((item) => item[0] === index); if (existing) { return findSafeIndex(map, index + 1); } return index; }; var getPathSegments = (path) => { const segments = path.replace(/\\\./g, "\u200B").split(/[[.]/g).map((s) => s.replace(/\u200B/g, ".")).filter((x) => !!x.trim()).map((x) => x.replaceAll("\\.", ".")); return segments; }; var setProperty = (obj, path, value, overrideExisting = true) => { const segments = getPathSegments(path); const set = (node) => { if (segments.length > 1) { const key = segments.shift(); node[key] = overrideExisting || !Object.prototype.hasOwnProperty.call(node, key) ? {} : node[key]; set(node[key]); } else { node[segments[0]] = value; } }; set(obj); return obj; }; var getProperty = (obj, path, defaultValue = void 0) => { const segments = getPathSegments(path); let current = obj; for (const key of segments) { if (current[key] === null) return defaultValue; if (current[key] === void 0) return defaultValue; current = current[key]; } return current; }; var toDashCase = (string) => { const capitalize = (str) => { const lower = str.toLowerCase(); return lower.substring(0, 1).toUpperCase() + lower.substring(1, lower.length); }; const parts = string?.replace(/([A-Z])+/g, capitalize)?.split(/(?=[A-Z])|[.\-\s_]/).map((x) => x.toLowerCase()) ?? []; if (parts.length === 0) return ""; if (parts.length === 1) return parts[0]; return parts.reduce((acc, part) => `${acc}-${part.toLowerCase()}`); }; // utils/scratchpads.ts var extractStorableObjects = (queries) => queries.reduce((references, query, queryIndex) => { return [ ...references, ...Object.entries(query).reduce((references2, [queryType, query2]) => { if (!["set", "create"].includes(queryType)) return references2; return [ ...references2, ...Object.entries(query2).reduce((references3, [schema, instructions]) => { const fields = instructions[queryType === "set" ? "to" : "with"]; return [ ...references3, ...Object.entries(fields).reduce((references4, [name, value]) => { const isStorableObject = typeof File === "function" && value instanceof File || value instanceof ReadableStream || typeof Buffer !== "undefined" && Buffer.isBuffer(value); if (!isStorableObject) return references4; return [ ...references4, { query: { index: queryIndex, type: queryType }, schema, field: name, value, contentType: typeof File === "function" && value instanceof File ? value.type : "application/octet-stream" } ]; }, []) ]; }, []) ]; }, []) ]; }, []); var getQueriesFromScratchPad = async (scratchPad) => { const queries = []; const queriesWithoutSetters = /* @__PURE__ */ new Map(); let getters = 0; const getValidator = (queryType) => { const validator = { get(target, key) { const freshValidator = Object.assign({}, validator); freshValidator.id = this.id || `${key}-${++getters}`; freshValidator.parentKey = combineKeys(this.parentKey, key); const queriesWithoutSettersIndex = findSafeIndex(queriesWithoutSetters, queries.length); queriesWithoutSetters.set(freshValidator.id, [ queriesWithoutSettersIndex, { accessor: freshValidator.parentKey, queryType } ]); return new Proxy(target[key] || {}, freshValidator); }, set(target, key, value) { const acessor = combineKeys(this.parentKey, key); const expanded = objectFromAccessor(acessor, value); queriesWithoutSetters.delete(this.id); if (["get", "set", "drop", "create", "count"].includes(queryType)) { queries.push({ [queryType]: expanded }); } return true; } }; return validator; }; const getProxy = (type) => new Proxy({}, getValidator(type)); await scratchPad({ get: getProxy("get"), set: getProxy("set"), drop: getProxy("drop"), create: getProxy("create"), count: getProxy("count") }); for (const [index, details] of Array.from(queriesWithoutSetters.values())) { const { queryType, accessor } = details; queries.splice(index, 0, objectFromAccessor(`${queryType}.${accessor}`, null)); } return queries; }; // storage/index.ts var uploadStorableObjects = async (storableObjects, token, options) => { const requests = storableObjects.map( ({ value, contentType }, index) => fetch_default(options?.endpoint || "https://data.ronin.co/writable", { method: "PUT", body: value, headers: { "Content-Type": contentType, Authorization: `Bearer ${token}` } }).then((reference) => ({ ...storableObjects[index], reference })) ); return Promise.all(requests); }; var processStorableObjects = async (queries, token, options) => { const objects = extractStorableObjects(queries); if (objects.length > 0) { const storedObjects = await uploadStorableObjects(objects, token, options); for (const object of storedObjects) { const { query, schema, field, reference } = object; queries[query.index][query.type][schema][query.type === "set" ? "to" : "with"][field] = reference; } } return queries; }; // utils/errors.ts var InvalidQueryError = class extends Error { message; query; path; details; constructor(details) { super(details.message); this.name = "InvalidQueryError"; this.message = details.message; this.query = details.query; this.path = details.path; this.details = details.details; } }; var getDotNotatedPath = (segments = []) => segments.length > 0 ? segments.reduce((path, segment, index) => { if (typeof segment === "number") return `${path}[${segment}]`; if (index === 0) return `${segment}`; return `${path}.${segment}`; }, "") : null; // queries/index.ts var formatField = (value, fieldType) => { switch (fieldType) { case "time": if (typeof value === "number" || typeof value === "string") return new Date(value); return value; default: return value; } }; var formatRecord = (record, schema) => { const clonedRecord = structuredClone(record); Object.entries(schema).forEach(([key, fieldType]) => { const formattedField = formatField(getProperty(clonedRecord, key), fieldType); setProperty(clonedRecord, key, formattedField, false); }); return clonedRecord; }; var runQueries = async (queries, options = {}) => { const url = new URL(options.queryProcessingOptions?.endpoint || "https://data.ronin.co"); const dataSelector = options.queryProcessingOptions?.dataSelector; if (dataSelector) { url.searchParams.set("data-selector", dataSelector); } const { results, error } = await fetch_default( url.href, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${options.token}` }, body: JSON.stringify({ queries }) }, options?.queryProcessingOptions?.fetcher ); if (error) throw new Error(error.message); return results.map((result) => { if ("error" in result && result.error) { const message = result.error.code === "BAD_REQUEST" ? "Invalid query provided." : result.error.message; const path = result.error.issues?.[0]?.path ? getDotNotatedPath(result.error.issues[0].path) : null; const query = path && typeof result.error.issues[0].path[1] === "number" ? queries[result.error.issues[0].path[1]] : null; const instruction = query ? getProperty(query, path.replace(/queries\[\d+\]\./, "")) : null; const details = result.error.issues?.[0] ? result.error.issues[0].message : null; throw new InvalidQueryError({ message, query: query ? `${path.replace(/queries\[\d+\]\./, "")} = ${JSON.stringify(instruction)}` : null, path, details }); } if ("record" in result && typeof result.record !== "undefined") { if (result.record === null) return null; return formatRecord(result.record, result.schema); } if ("amount" in result && typeof result.amount !== "undefined" && result.amount !== null) { return result.amount; } if ("records" in result) { result.records = result.records.map((record) => formatRecord(record, result.schema)); if (typeof result.moreBefore !== "undefined") result.records.moreBefore = result.moreBefore; if (typeof result.moreAfter !== "undefined") result.records.moreAfter = result.moreAfter; } return result.records; }); }; // utils/data-hooks.ts var EMPTY = Symbol("empty"); var getSchema = (instruction) => { const key = Object.keys(instruction)[0]; let schema = String(key); let multipleRecords = false; if (schema.endsWith("s")) { schema = schema.substring(0, schema.length - 1); multipleRecords = true; } return { key, // Convert camel case (e.g. `subscriptionItems`) into slugs (e.g. `subscription-items`). schema: toDashCase(schema), multipleRecords }; }; var getMethodName = (hookType, queryType) => { const capitalizedQueryType = queryType[0].toUpperCase() + queryType.slice(1); return hookType === "during" ? queryType : hookType + capitalizedQueryType; }; var invokeHook = async (hooks, hookType, query) => { const hooksForSchema = hooks?.[query.schema]; const hookName = getMethodName(hookType, query.type); const queryInstruction = query.instruction ? structuredClone(query.instruction) : {}; const hookArguments = [queryInstruction, query.plural]; if (hookType === "after") { const queryResult = structuredClone(query.result); hookArguments.push(queryResult); } if (hooksForSchema?.[hookName]) { const result = await hooksForSchema[hookName](...hookArguments); return { ran: true, result }; } return { ran: false, result: null }; }; var invokeHooks = async (hooks, hookType, modifiableQueries, modifiableResults, query) => { const queryType = Object.keys(query.definition)[0]; const queryInstructions = query.definition[queryType]; const { key, schema, multipleRecords } = getSchema(queryInstructions); const oldInstruction = queryInstructions[key]; const executedHookResults = await invokeHook(hooks, hookType, { type: queryType, schema, plural: multipleRecords, instruction: oldInstruction, // For "after" hooks, we want to pass the final result associated with a // particular query, so that the hook can read it. result: hookType === "after" ? query.result : null }); const { ran, result: hookResult } = executedHookResults; switch (hookType) { case "before": if (!ran) break; queryInstructions[key] = hookResult; modifiableQueries[query.index] = { [queryType]: queryInstructions }; break; case "during": if (ran) { modifiableQueries[query.index] = EMPTY; modifiableResults[query.index] = hookResult; } else { modifiableResults[query.index] = EMPTY; } break; case "after": break; } }; var runQueriesWithHooks = async (queries, options = {}) => { let modifiableQueries = Array.from(queries); const modifiableResults = new Array(); const { hooks, waitUntil } = options; if (typeof process === "undefined" && hooks && !waitUntil) { let message = 'In the case that the "ronin" package receives a value for'; message += " its `hooks` option, it must also receive a value for its"; message += " `waitUntil` option. This requirement only applies when using"; message += " an edge runtime and ensures that the edge worker continues to"; message += ' execute until all "after" hooks have been executed.'; throw new Error(message); } await Promise.all( queries.map((query, index) => { return invokeHooks(hooks, "before", modifiableQueries, modifiableResults, { definition: query, index, result: null }); }) ); await Promise.all( queries.map((query, index) => { return invokeHooks(hooks, "during", modifiableQueries, modifiableResults, { definition: query, index, result: null }); }) ); modifiableQueries = modifiableQueries.filter((query) => query !== EMPTY); if (modifiableQueries.length === 0) return modifiableResults; const results = await runQueries(modifiableQueries, options); for (let index = 0; index < modifiableResults.length; index++) { const existingResult = modifiableResults[index]; if (existingResult === EMPTY) { continue; } results.splice(index, 0, existingResult); } for (let queryIndex = 0; queryIndex < queries.length; queryIndex++) { const query = queries[queryIndex]; const queryType = Object.keys(query)[0]; if (queryType !== "set" && queryType !== "drop" && queryType !== "create") { continue; } const queryResult = Array.isArray(results[queryIndex]) ? results[queryIndex] : [results[queryIndex]]; const promise = invokeHooks(hooks, "after", queries, null, { definition: query, index: queryIndex, result: queryResult }); if (waitUntil) waitUntil(promise); } return results; }; // index.ts var runScratchpad = async (scratchPad, options = {}) => { const queries = await getQueriesFromScratchPad(scratchPad); if (typeof process !== "undefined") { options.token = options.token || process.env?.RONIN_TOKEN; if (!options.token) { const message = "Please specify the `RONIN_TOKEN` environment variable or set the `token` option when invoking RONIN."; throw new Error(message); } } if (!options.token) { let message = "When invoking RONIN from an edge runtime, the"; message += " `token` option must be set."; throw new Error(message); } const queriesPopulatedWithReferences = await processStorableObjects(queries, options.token); if (queriesPopulatedWithReferences.length === 0) return []; return runQueriesWithHooks(queriesPopulatedWithReferences, options); }; var runQueries2 = runQueriesWithHooks; var createScratchpadFactory = (config) => { return (scratchPad) => runScratchpad(scratchPad, config); }; function handleScratchPadOrConfig(...args) { const [scratchPadOrConfig, config] = args; if (typeof scratchPadOrConfig === "function") return runScratchpad(scratchPadOrConfig, config); if (typeof scratchPadOrConfig === "undefined" || typeof scratchPadOrConfig === "object" && !Array.isArray(scratchPadOrConfig) && scratchPadOrConfig !== null) return createScratchpadFactory(scratchPadOrConfig); const message = 'The function exported by the "ronin" package must receive either a scratchpad (function) or an initial configuration (object).'; throw new Error(message); } var ronin_default = handleScratchPadOrConfig; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { getQueriesFromScratchPad, processReadables, runQueries }); module.exports = ronin_default;