blade-client
Version:
Access your RONIN database via TypeScript.
1,589 lines (1,578 loc) • 48.7 kB
JavaScript
// src/utils/errors.ts
var ClientError = class extends Error {
message;
code;
constructor(details) {
super(details.message);
this.name = "ClientError";
this.message = details.message;
this.code = details.code;
}
};
var getResponseBody = async (response, options) => {
if (response.ok) return response.json();
const text = await response.text();
let json;
try {
json = JSON.parse(text);
} catch (_err) {
throw new ClientError({
message: `${options?.errorPrefix ? `${options.errorPrefix} ` : ""}${text}`,
code: "JSON_PARSE_ERROR"
});
}
if (json.error) {
json.error.message = `${options?.errorPrefix ? `${options.errorPrefix} ` : ""}${json.error.message}`;
throw new ClientError(json.error);
}
return json;
};
// src/storage.ts
var isStorableObject = (value) => typeof File !== "undefined" && value instanceof File || typeof ReadableStream !== "undefined" && value instanceof ReadableStream || typeof Blob !== "undefined" && value instanceof Blob || typeof ArrayBuffer !== "undefined" && value instanceof ArrayBuffer || typeof Buffer !== "undefined" && Buffer.isBuffer(value);
var extractStorableObjects = (queries) => queries.reduce(
(references, query, queryIndex) => {
return [
// biome-ignore lint/performance/noAccumulatingSpread: This code is too complex to refactor.
...references,
...Object.entries(query).reduce(
(references2, [queryType, query2]) => {
if (!["set", "add"].includes(queryType)) return references2;
return [
// biome-ignore lint/performance/noAccumulatingSpread: This code is too complex to refactor.
...references2,
...Object.entries(query2).reduce(
(references3, [schema, instructions]) => {
const fields = instructions[queryType === "set" ? "to" : "with"];
return [
// biome-ignore lint/performance/noAccumulatingSpread: This code is too complex to refactor.
...references3,
...Object.entries(fields || {}).reduce(
(references4, [name, value]) => {
if (!isStorableObject(value)) return references4;
const blobValue = value;
const storarableObject = {
query: {
index: queryIndex,
type: queryType
},
schema,
field: name,
value: blobValue
};
if ("type" in blobValue) {
storarableObject.contentType = blobValue.type;
}
if ("name" in blobValue) {
storarableObject.name = blobValue.name;
}
return [...references4, storarableObject];
},
[]
)
];
},
[]
)
];
},
[]
)
];
},
[]
);
var uploadStorableObjects = (storableObjects, options = {}) => {
const fetcher = typeof options?.fetch === "function" ? options.fetch : fetch;
const requests = storableObjects.map(
async ({ name, value, contentType }) => {
const headers = new Headers();
headers.set("Authorization", `Bearer ${options.token}`);
if (contentType) {
headers.set("Content-Type", contentType);
}
if (name) {
headers.set(
"Content-Disposition",
`form-data; filename="${encodeURIComponent(name)}"`
);
}
const request = new Request("https://storage.ronin.co/", {
method: "PUT",
body: value,
headers
});
const response = await fetcher(request);
return getResponseBody(response, {
errorPrefix: "An error occurred while uploading the binary objects included in the provided queries. Error:"
});
}
);
return Promise.all(requests);
};
var processStorableObjects = async (queries, upload) => {
const objects = extractStorableObjects(queries);
if (objects.length > 0) {
const storedObjects = await upload(objects);
for (let index = 0; index < objects.length; index++) {
const { query, schema, field } = objects[index];
const reference = storedObjects[index];
queries[query.index][query.type][schema][query.type === "set" ? "to" : "with"][field] = reference;
}
}
return queries;
};
// ../blade-compiler/dist/index.js
var DML_QUERY_TYPES_READ = ["get", "count"];
var DML_QUERY_TYPES_WRITE = ["set", "add", "remove"];
var DML_QUERY_TYPES = [
...DML_QUERY_TYPES_READ,
...DML_QUERY_TYPES_WRITE
];
var DDL_QUERY_TYPES_READ = ["list"];
var DDL_QUERY_TYPES_WRITE = ["create", "alter", "drop"];
var DDL_QUERY_TYPES = [
...DDL_QUERY_TYPES_READ,
...DDL_QUERY_TYPES_WRITE
];
var QUERY_TYPES_READ = [
...DML_QUERY_TYPES_READ,
...DDL_QUERY_TYPES_READ
];
var QUERY_TYPES_WRITE = [
...DML_QUERY_TYPES_WRITE,
...DDL_QUERY_TYPES_WRITE
];
var QUERY_TYPES = [...DML_QUERY_TYPES, ...DDL_QUERY_TYPES];
var QUERY_SYMBOLS = {
// Represents a sub query.
QUERY: "__RONIN_QUERY",
// Represents an expression that should be evaluated.
EXPRESSION: "__RONIN_EXPRESSION",
// Represents the value of a field in the model.
FIELD: "__RONIN_FIELD_",
// Represents the value of a field in the model of a parent query.
FIELD_PARENT: "__RONIN_FIELD_PARENT_",
// Represents a value provided to a query preset.
VALUE: "__RONIN_VALUE"
};
var RONIN_MODEL_FIELD_REGEX = new RegExp(
`${QUERY_SYMBOLS.FIELD}[_a-zA-Z0-9.]+`,
"g"
);
var CURRENT_TIME_EXPRESSION = {
[QUERY_SYMBOLS.EXPRESSION]: `strftime('%Y-%m-%dT%H:%M:%f', 'now') || 'Z'`
};
var SINGLE_QUOTE_REGEX = /'/g;
var DOUBLE_QUOTE_REGEX = /"/g;
var AMPERSAND_REGEX = /\s*&+\s*/g;
var SPECIAL_CHARACTERS_REGEX = /[^\w\s-]+/g;
var SPLIT_REGEX = /(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|[\s.\-_]+/;
var sanitize = (str) => {
if (!str || str.length === 0) return "";
return str.replace(SINGLE_QUOTE_REGEX, "").replace(DOUBLE_QUOTE_REGEX, "").replace(AMPERSAND_REGEX, " and ").replace(SPECIAL_CHARACTERS_REGEX, " ").trim();
};
var convertToSnakeCase = (str) => {
if (!str || str.length === 0) return "";
return sanitize(str).split(SPLIT_REGEX).map((part) => part.toLowerCase()).join("_");
};
var omit = (obj, properties) => Object.fromEntries(
Object.entries(obj).filter(([key]) => !properties.includes(key))
);
var conjunctions = [
"for",
"and",
"nor",
"but",
"or",
"yet",
"so"
];
var articles = [
"a",
"an",
"the"
];
var prepositions = [
"aboard",
"about",
"above",
"across",
"after",
"against",
"along",
"amid",
"among",
"anti",
"around",
"as",
"at",
"before",
"behind",
"below",
"beneath",
"beside",
"besides",
"between",
"beyond",
"but",
"by",
"concerning",
"considering",
"despite",
"down",
"during",
"except",
"excepting",
"excluding",
"following",
"for",
"from",
"in",
"inside",
"into",
"like",
"minus",
"near",
"of",
"off",
"on",
"onto",
"opposite",
"over",
"past",
"per",
"plus",
"regarding",
"round",
"save",
"since",
"than",
"through",
"to",
"toward",
"towards",
"under",
"underneath",
"unlike",
"until",
"up",
"upon",
"versus",
"via",
"with",
"within",
"without"
];
var lowerCase = /* @__PURE__ */ new Set([
...conjunctions,
...articles,
...prepositions
]);
var specials = [
"ZEIT",
"ZEIT Inc.",
"Vercel",
"Vercel Inc.",
"CLI",
"API",
"HTTP",
"HTTPS",
"JSX",
"DNS",
"URL",
"now.sh",
"now.json",
"vercel.app",
"vercel.json",
"CI",
"CD",
"CDN",
"package.json",
"package.lock",
"yarn.lock",
"GitHub",
"GitLab",
"CSS",
"Sass",
"JS",
"JavaScript",
"TypeScript",
"HTML",
"WordPress",
"Next.js",
"Node.js",
"Webpack",
"Docker",
"Bash",
"Kubernetes",
"SWR",
"TinaCMS",
"UI",
"UX",
"TS",
"TSX",
"iPhone",
"iPad",
"watchOS",
"iOS",
"iPadOS",
"macOS",
"PHP",
"composer.json",
"composer.lock",
"CMS",
"SQL",
"C",
"C#",
"GraphQL",
"GraphiQL",
"JWT",
"JWTs"
];
var word = `[^\\s'\u2019\\(\\)!?;:"-]`;
var regex = new RegExp(`(?:(?:(\\s?(?:^|[.\\(\\)!?;:"-])\\s*)(${word}))|(${word}))(${word}*[\u2019']*${word}*)`, "g");
var convertToRegExp = (specials22) => specials22.map((s) => [new RegExp(`\\b${s}\\b`, "gi"), s]);
function parseMatch(match) {
const firstCharacter = match[0];
if (/\s/.test(firstCharacter)) {
return match.slice(1);
}
if (/[\(\)]/.test(firstCharacter)) {
return null;
}
return match;
}
var src_default = (str, options = {}) => {
str = str.toLowerCase().replace(regex, (m, lead = "", forced, lower, rest, offset, string) => {
const isLastWord = m.length + offset >= string.length;
const parsedMatch = parseMatch(m);
if (!parsedMatch) {
return m;
}
if (!forced) {
const fullLower = lower + rest;
if (lowerCase.has(fullLower) && !isLastWord) {
return parsedMatch;
}
}
return lead + (lower || forced).toUpperCase() + rest;
});
const customSpecials = options.special || [];
const replace = [...specials, ...customSpecials];
const replaceRegExp = convertToRegExp(replace);
replaceRegExp.forEach(([pattern, s]) => {
str = str.replace(pattern, s);
});
return str;
};
var slugToName = (slug) => {
const name = slug.replace(/([a-z])([A-Z])/g, "$1 $2");
return src_default(name);
};
var VOWELS = ["a", "e", "i", "o", "u"];
var pluralize = (word22) => {
const lastLetter = word22.slice(-1).toLowerCase();
const secondLastLetter = word22.slice(-2, -1).toLowerCase();
if (lastLetter === "y" && !VOWELS.includes(secondLastLetter)) {
return `${word22.slice(0, -1)}ies`;
}
if (lastLetter === "s" || word22.slice(-2).toLowerCase() === "ch" || word22.slice(-2).toLowerCase() === "sh" || word22.slice(-2).toLowerCase() === "ex") {
return `${word22}es`;
}
return `${word22}s`;
};
var modelAttributes = [
["pluralSlug", "slug", pluralize, true],
["name", "slug", slugToName, false],
["pluralName", "pluralSlug", slugToName, false],
["idPrefix", "slug", (slug) => slug.slice(0, 3).toLowerCase(), false],
["table", "pluralSlug", convertToSnakeCase, true]
];
var getRecordIdentifier = (prefix) => {
return `${prefix}_${Array.from(crypto.getRandomValues(new Uint8Array(12))).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 16).toLowerCase()}`;
};
var addDefaultModelAttributes = (model, isNew) => {
const copiedModel = { ...model };
if (isNew && !copiedModel.id) copiedModel.id = getRecordIdentifier("mod");
for (const [setting, base, generator, mustRegenerate] of modelAttributes) {
if (!(isNew || mustRegenerate)) continue;
if (copiedModel[setting] || !copiedModel[base]) continue;
copiedModel[setting] = generator(copiedModel[base]);
}
const newFields = copiedModel.fields || [];
if (isNew || Object.keys(newFields).length > 0) {
if (!copiedModel.identifiers) copiedModel.identifiers = {};
if (!copiedModel.identifiers.name) {
const suitableField = Object.entries(newFields).find(
([fieldSlug, field]) => field.type === "string" && field.required === true && ["name"].includes(fieldSlug)
);
copiedModel.identifiers.name = suitableField?.[0] || "id";
}
if (!copiedModel.identifiers.slug) {
const suitableField = Object.entries(newFields).find(
([fieldSlug, field]) => field.type === "string" && field.unique === true && field.required === true && ["slug", "handle"].includes(fieldSlug)
);
copiedModel.identifiers.slug = suitableField?.[0] || "id";
}
}
return copiedModel;
};
var ROOT_MODEL = {
// Provide the ID ahead of time, because certain runtime environments (e.g. Cloudflare
// Workers) do not support the `crypto` module running in global scope, which would
// happen when generating the default attributes of the root model.
id: "mod_26cedf5fc602c3ba",
// While the `slug` is required, we are providing the other attributes in order to
// avoid needing to generate them at runtime.
slug: "roninModel",
pluralSlug: "roninModels",
name: "Ronin Model",
pluralName: "Ronin Models",
identifiers: {
name: "name",
slug: "slug"
},
// The default ID prefix would be `ron_` based on the slug, but we want `mod_`.
idPrefix: "mod",
// This name mimics the `sqlite_schema` table in SQLite.
table: "ronin_schema",
// Indicates that the model was automatically generated by RONIN.
system: { model: "root" },
fields: {
name: { type: "string" },
pluralName: { type: "string" },
slug: { type: "string" },
pluralSlug: { type: "string" },
idPrefix: { type: "string" },
table: { type: "string" },
"identifiers.name": { type: "string" },
"identifiers.slug": { type: "string" },
// Providing an empty object as a default value allows us to use `json_insert`
// without needing to fall back to an empty object in the insertion statement,
// which makes the statement shorter.
fields: { type: "json", defaultValue: {} },
indexes: { type: "json", defaultValue: {} },
presets: { type: "json", defaultValue: {} }
}
};
var ROOT_MODEL_WITH_ATTRIBUTES = addDefaultModelAttributes(ROOT_MODEL, true);
var PLURAL_MODEL_ENTITIES = {
field: "fields",
index: "indexes",
preset: "presets"
};
var PLURAL_MODEL_ENTITIES_VALUES = Object.values(PLURAL_MODEL_ENTITIES);
var CLEAN_ROOT_MODEL = omit(ROOT_MODEL, ["system"]);
// src/utils/constants.ts
var WRITE_QUERY_TYPES = [
...DML_QUERY_TYPES_WRITE,
...DDL_QUERY_TYPES.filter((item) => item !== "list")
];
// ../blade-syntax/dist/chunk-TVPG4FNB.js
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 setPropertyViaPathSegments = (obj, pathSegments, value) => {
let current = obj;
for (let i = 0; i < pathSegments.length; i++) {
const key = pathSegments[i];
const isLastKey = i === pathSegments.length - 1;
if (isLastKey) {
current[key] = typeof value === "function" ? value(current[key]) : value;
} else {
if (!Object.prototype.hasOwnProperty.call(current, key) || typeof current[key] !== "object") {
current[key] = {};
}
current = current[key];
}
}
};
var setProperty = (obj, path, value) => {
if (path === ".") {
Object.assign(obj, value);
} else {
const segments = getPathSegments(path);
setPropertyViaPathSegments(obj, segments, value);
}
return obj;
};
var getProperty = (obj, path) => {
const pathSegments = getPathSegments(path);
let current = obj;
for (const key of pathSegments) {
if (current[key] === null || current[key] === void 0) return void 0;
current = current[key];
}
return current;
};
var isPlainObject = (value) => {
return Object.prototype.toString.call(value) === "[object Object]";
};
var mutateStructure = (obj, callback) => {
if (Array.isArray(obj)) {
obj.map((item) => mutateStructure(item, callback));
return obj;
}
if (isPlainObject(obj)) {
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
if (obj[key] === void 0) {
delete obj[key];
continue;
}
if (isPlainObject(obj[key])) {
mutateStructure(obj[key], callback);
continue;
}
if (Array.isArray(obj[key])) {
obj[key].map((item) => mutateStructure(item, callback));
continue;
}
obj[key] = callback(obj[key]);
}
}
return obj;
}
return callback(obj);
};
var DML_QUERY_TYPES_READ2 = ["get", "count"];
var DML_QUERY_TYPES_WRITE2 = ["set", "add", "remove"];
var DML_QUERY_TYPES2 = [
...DML_QUERY_TYPES_READ2,
...DML_QUERY_TYPES_WRITE2
];
var DDL_QUERY_TYPES_READ2 = ["list"];
var DDL_QUERY_TYPES_WRITE2 = ["create", "alter", "drop"];
var DDL_QUERY_TYPES2 = [
...DDL_QUERY_TYPES_READ2,
...DDL_QUERY_TYPES_WRITE2
];
var QUERY_TYPES_READ2 = [
...DML_QUERY_TYPES_READ2,
...DDL_QUERY_TYPES_READ2
];
var QUERY_TYPES_WRITE2 = [
...DML_QUERY_TYPES_WRITE2,
...DDL_QUERY_TYPES_WRITE2
];
var QUERY_TYPES2 = [...DML_QUERY_TYPES2, ...DDL_QUERY_TYPES2];
var QUERY_SYMBOLS2 = {
// Represents a sub query.
QUERY: "__RONIN_QUERY",
// Represents an expression that should be evaluated.
EXPRESSION: "__RONIN_EXPRESSION",
// Represents the value of a field in the model.
FIELD: "__RONIN_FIELD_",
// Represents the value of a field in the model of a parent query.
FIELD_PARENT: "__RONIN_FIELD_PARENT_",
// Represents a value provided to a query preset.
VALUE: "__RONIN_VALUE"
};
var RONIN_MODEL_FIELD_REGEX2 = new RegExp(
`${QUERY_SYMBOLS2.FIELD}[_a-zA-Z0-9.]+`,
"g"
);
var CURRENT_TIME_EXPRESSION2 = {
[QUERY_SYMBOLS2.EXPRESSION]: `strftime('%Y-%m-%dT%H:%M:%f', 'now') || 'Z'`
};
var SINGLE_QUOTE_REGEX2 = /'/g;
var DOUBLE_QUOTE_REGEX2 = /"/g;
var AMPERSAND_REGEX2 = /\s*&+\s*/g;
var SPECIAL_CHARACTERS_REGEX2 = /[^\w\s-]+/g;
var SPLIT_REGEX2 = /(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|[\s.\-_]+/;
var sanitize2 = (str) => {
if (!str || str.length === 0) return "";
return str.replace(SINGLE_QUOTE_REGEX2, "").replace(DOUBLE_QUOTE_REGEX2, "").replace(AMPERSAND_REGEX2, " and ").replace(SPECIAL_CHARACTERS_REGEX2, " ").trim();
};
var convertToSnakeCase2 = (str) => {
if (!str || str.length === 0) return "";
return sanitize2(str).split(SPLIT_REGEX2).map((part) => part.toLowerCase()).join("_");
};
var omit2 = (obj, properties) => Object.fromEntries(
Object.entries(obj).filter(([key]) => !properties.includes(key))
);
var conjunctions2 = [
"for",
"and",
"nor",
"but",
"or",
"yet",
"so"
];
var articles2 = [
"a",
"an",
"the"
];
var prepositions2 = [
"aboard",
"about",
"above",
"across",
"after",
"against",
"along",
"amid",
"among",
"anti",
"around",
"as",
"at",
"before",
"behind",
"below",
"beneath",
"beside",
"besides",
"between",
"beyond",
"but",
"by",
"concerning",
"considering",
"despite",
"down",
"during",
"except",
"excepting",
"excluding",
"following",
"for",
"from",
"in",
"inside",
"into",
"like",
"minus",
"near",
"of",
"off",
"on",
"onto",
"opposite",
"over",
"past",
"per",
"plus",
"regarding",
"round",
"save",
"since",
"than",
"through",
"to",
"toward",
"towards",
"under",
"underneath",
"unlike",
"until",
"up",
"upon",
"versus",
"via",
"with",
"within",
"without"
];
var lowerCase2 = /* @__PURE__ */ new Set([
...conjunctions2,
...articles2,
...prepositions2
]);
var specials2 = [
"ZEIT",
"ZEIT Inc.",
"Vercel",
"Vercel Inc.",
"CLI",
"API",
"HTTP",
"HTTPS",
"JSX",
"DNS",
"URL",
"now.sh",
"now.json",
"vercel.app",
"vercel.json",
"CI",
"CD",
"CDN",
"package.json",
"package.lock",
"yarn.lock",
"GitHub",
"GitLab",
"CSS",
"Sass",
"JS",
"JavaScript",
"TypeScript",
"HTML",
"WordPress",
"Next.js",
"Node.js",
"Webpack",
"Docker",
"Bash",
"Kubernetes",
"SWR",
"TinaCMS",
"UI",
"UX",
"TS",
"TSX",
"iPhone",
"iPad",
"watchOS",
"iOS",
"iPadOS",
"macOS",
"PHP",
"composer.json",
"composer.lock",
"CMS",
"SQL",
"C",
"C#",
"GraphQL",
"GraphiQL",
"JWT",
"JWTs"
];
var word2 = `[^\\s'\u2019\\(\\)!?;:"-]`;
var regex2 = new RegExp(`(?:(?:(\\s?(?:^|[.\\(\\)!?;:"-])\\s*)(${word2}))|(${word2}))(${word2}*[\u2019']*${word2}*)`, "g");
var convertToRegExp2 = (specials22) => specials22.map((s) => [new RegExp(`\\b${s}\\b`, "gi"), s]);
function parseMatch2(match) {
const firstCharacter = match[0];
if (/\s/.test(firstCharacter)) {
return match.slice(1);
}
if (/[\(\)]/.test(firstCharacter)) {
return null;
}
return match;
}
var src_default2 = (str, options = {}) => {
str = str.toLowerCase().replace(regex2, (m, lead = "", forced, lower, rest, offset, string) => {
const isLastWord = m.length + offset >= string.length;
const parsedMatch = parseMatch2(m);
if (!parsedMatch) {
return m;
}
if (!forced) {
const fullLower = lower + rest;
if (lowerCase2.has(fullLower) && !isLastWord) {
return parsedMatch;
}
}
return lead + (lower || forced).toUpperCase() + rest;
});
const customSpecials = options.special || [];
const replace = [...specials2, ...customSpecials];
const replaceRegExp = convertToRegExp2(replace);
replaceRegExp.forEach(([pattern, s]) => {
str = str.replace(pattern, s);
});
return str;
};
var slugToName2 = (slug) => {
const name = slug.replace(/([a-z])([A-Z])/g, "$1 $2");
return src_default2(name);
};
var VOWELS2 = ["a", "e", "i", "o", "u"];
var pluralize2 = (word22) => {
const lastLetter = word22.slice(-1).toLowerCase();
const secondLastLetter = word22.slice(-2, -1).toLowerCase();
if (lastLetter === "y" && !VOWELS2.includes(secondLastLetter)) {
return `${word22.slice(0, -1)}ies`;
}
if (lastLetter === "s" || word22.slice(-2).toLowerCase() === "ch" || word22.slice(-2).toLowerCase() === "sh" || word22.slice(-2).toLowerCase() === "ex") {
return `${word22}es`;
}
return `${word22}s`;
};
var modelAttributes2 = [
["pluralSlug", "slug", pluralize2, true],
["name", "slug", slugToName2, false],
["pluralName", "pluralSlug", slugToName2, false],
["idPrefix", "slug", (slug) => slug.slice(0, 3).toLowerCase(), false],
["table", "pluralSlug", convertToSnakeCase2, true]
];
var getRecordIdentifier2 = (prefix) => {
return `${prefix}_${Array.from(crypto.getRandomValues(new Uint8Array(12))).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 16).toLowerCase()}`;
};
var addDefaultModelAttributes2 = (model, isNew) => {
const copiedModel = { ...model };
if (isNew && !copiedModel.id) copiedModel.id = getRecordIdentifier2("mod");
for (const [setting, base, generator, mustRegenerate] of modelAttributes2) {
if (!(isNew || mustRegenerate)) continue;
if (copiedModel[setting] || !copiedModel[base]) continue;
copiedModel[setting] = generator(copiedModel[base]);
}
const newFields = copiedModel.fields || [];
if (isNew || Object.keys(newFields).length > 0) {
if (!copiedModel.identifiers) copiedModel.identifiers = {};
if (!copiedModel.identifiers.name) {
const suitableField = Object.entries(newFields).find(
([fieldSlug, field]) => field.type === "string" && field.required === true && ["name"].includes(fieldSlug)
);
copiedModel.identifiers.name = suitableField?.[0] || "id";
}
if (!copiedModel.identifiers.slug) {
const suitableField = Object.entries(newFields).find(
([fieldSlug, field]) => field.type === "string" && field.unique === true && field.required === true && ["slug", "handle"].includes(fieldSlug)
);
copiedModel.identifiers.slug = suitableField?.[0] || "id";
}
}
return copiedModel;
};
var ROOT_MODEL2 = {
// Provide the ID ahead of time, because certain runtime environments (e.g. Cloudflare
// Workers) do not support the `crypto` module running in global scope, which would
// happen when generating the default attributes of the root model.
id: "mod_26cedf5fc602c3ba",
// While the `slug` is required, we are providing the other attributes in order to
// avoid needing to generate them at runtime.
slug: "roninModel",
pluralSlug: "roninModels",
name: "Ronin Model",
pluralName: "Ronin Models",
identifiers: {
name: "name",
slug: "slug"
},
// The default ID prefix would be `ron_` based on the slug, but we want `mod_`.
idPrefix: "mod",
// This name mimics the `sqlite_schema` table in SQLite.
table: "ronin_schema",
// Indicates that the model was automatically generated by RONIN.
system: { model: "root" },
fields: {
name: { type: "string" },
pluralName: { type: "string" },
slug: { type: "string" },
pluralSlug: { type: "string" },
idPrefix: { type: "string" },
table: { type: "string" },
"identifiers.name": { type: "string" },
"identifiers.slug": { type: "string" },
// Providing an empty object as a default value allows us to use `json_insert`
// without needing to fall back to an empty object in the insertion statement,
// which makes the statement shorter.
fields: { type: "json", defaultValue: {} },
indexes: { type: "json", defaultValue: {} },
presets: { type: "json", defaultValue: {} }
}
};
var ROOT_MODEL_WITH_ATTRIBUTES2 = addDefaultModelAttributes2(ROOT_MODEL2, true);
var PLURAL_MODEL_ENTITIES2 = {
field: "fields",
index: "indexes",
preset: "presets"
};
var PLURAL_MODEL_ENTITIES_VALUES2 = Object.values(PLURAL_MODEL_ENTITIES2);
var CLEAN_ROOT_MODEL2 = omit2(ROOT_MODEL2, ["system"]);
var MULTILINE_SQL_COMMENTS = /\/\*[\s\S]*?\*\//g;
var SINGLELINE_SQL_COMMENTS = /--[^\n]*\n/g;
var IN_SQL_BATCH = false;
var getSyntaxProxySQL = (options) => {
return (strings, ...values) => {
let text = "";
const params = [];
strings.forEach((string, i) => {
const processedString = string.replace(MULTILINE_SQL_COMMENTS, "").replace(SINGLELINE_SQL_COMMENTS, "\n");
text += processedString;
if (i < values.length) {
text += `$${i + 1}`;
params.push(values[i]);
}
});
const statement = {
// Collapse whitespace and newlines into single spaces, then trim leading or
// trailing spaces.
statement: text.replace(/\s+/g, " ").trim(),
params
};
if (IN_SQL_BATCH) return statement;
return options.callback(statement);
};
};
var getBatchProxySQL = (operations) => {
let statements = [];
IN_SQL_BATCH = true;
statements = operations();
IN_SQL_BATCH = false;
return statements;
};
var getSyntaxProxy = (config) => {
const propertyValue = typeof config?.propertyValue === "undefined" ? {} : config.propertyValue;
const shouldAllowChaining = config?.chaining ?? true;
const createProxy = (path = [], targetProps, assign) => {
let target;
if (assign) {
target = { ...targetProps };
} else {
target = () => void 0;
target();
delete target.name;
}
return new Proxy(target, {
apply(_, __, args) {
let value = args[0];
const options = args[1];
if (typeof value === "undefined") {
value = propertyValue;
} else {
const shouldUseParentSymbols = Boolean(globalThis.IN_RONIN_QUERY);
value = mutateStructure(value, (inner) => {
const serialized = serializeValue(inner, config?.replacer);
if (shouldUseParentSymbols && typeof serialized === "string") {
return serialized.replaceAll(
QUERY_SYMBOLS2.FIELD,
QUERY_SYMBOLS2.FIELD_PARENT
);
}
return serialized;
});
}
const structure = { ...targetProps };
const pathParts = config?.root ? [config.root, ...path] : path;
const pathJoined = pathParts.length > 0 ? pathParts.join(".") : ".";
setProperty(structure, pathJoined, value);
if (globalThis.IN_RONIN_BATCH || !config?.callback) {
const newPath = path.slice(0, -1);
const details = { ...structure };
if (options) details.options = options;
return shouldAllowChaining ? createProxy(newPath, details, true) : details;
}
return config.callback(structure, options);
},
get(target2, nextProp, receiver) {
if (Object.hasOwn(target2, nextProp)) {
return Reflect.get(target2, nextProp, receiver);
}
if (nextProp === "toJSON") return targetProps;
return createProxy(path.concat([nextProp]), targetProps);
}
});
};
return createProxy();
};
var getBatchProxy = (operations) => {
let queries = [];
globalThis.IN_RONIN_BATCH = true;
try {
queries = operations();
} finally {
globalThis.IN_RONIN_BATCH = false;
}
return queries.map((details) => {
if (!isPlainObject(details)) return { structure: details };
const item = {
structure: details[QUERY_SYMBOLS2.QUERY]
};
if ("options" in details) item.options = details.options;
return item;
});
};
var serializeValue = (defaultValue, replacer) => {
let value = defaultValue;
if (typeof value === "undefined") return value;
if (typeof value === "function") {
const ORIGINAL_IN_RONIN_BATCH = globalThis.IN_RONIN_BATCH;
globalThis.IN_RONIN_BATCH = true;
const fieldProxy = new Proxy(
{},
{
get(_target, property) {
const name = property.toString();
return {
[QUERY_SYMBOLS2.EXPRESSION]: `${QUERY_SYMBOLS2.FIELD}${name}`
};
}
}
);
try {
const ORIGINAL_IN_RONIN_QUERY = globalThis.IN_RONIN_QUERY;
globalThis.IN_RONIN_QUERY = true;
value = value(fieldProxy);
globalThis.IN_RONIN_QUERY = ORIGINAL_IN_RONIN_QUERY;
} finally {
globalThis.IN_RONIN_BATCH = ORIGINAL_IN_RONIN_BATCH;
}
}
if (replacer) {
const replacedValue = replacer(value);
if (typeof replacedValue !== "undefined") return replacedValue;
}
return JSON.parse(JSON.stringify(value));
};
// src/utils/helpers.ts
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()}`);
};
var formatDateFields = (record, dateFields) => {
for (const field of dateFields) {
const value = getProperty(record, field);
if (typeof value === "undefined" || value === null) continue;
setProperty(record, field, new Date(value));
}
};
var mergeOptions = (...options) => {
return options.reduce((acc, opt) => {
const resolvedOpt = typeof opt === "function" ? opt() : opt;
Object.assign(acc, resolvedOpt);
return acc;
}, {});
};
var validateToken = (options = {}) => {
if (!options.token) {
const token = import.meta?.env?.RONIN_TOKEN || process?.env?.RONIN_TOKEN;
if (!token || token === "undefined") {
const message = typeof process === "undefined" ? "When invoking RONIN from an edge runtime, the `token` option must be set." : "Please specify the `RONIN_TOKEN` environment variable or set the `token` option when invoking RONIN.";
throw new Error(message);
}
options.token = token;
}
};
var omit3 = (obj, keys) => {
if (!obj) return {};
if (!keys || keys.length === 0) return obj;
return keys.reduce(
(acc, key) => {
delete acc[key];
return acc;
},
{ ...obj }
);
};
// src/utils/triggers.ts
var EMPTY = Symbol("empty");
var getModel = (instruction) => {
const key = Object.keys(instruction)[0];
let model = String(key);
let multipleRecords = false;
if (model.endsWith("s")) {
model = model.substring(0, model.length - 1);
multipleRecords = true;
}
return {
key,
// Convert camel case (e.g. `subscriptionItems`) into slugs
// (e.g. `subscription-items`).
model: toDashCase(model),
multipleRecords
};
};
var getMethodName = (triggerType, queryType) => {
const capitalizedQueryType = queryType[0].toUpperCase() + queryType.slice(1);
return triggerType === "during" ? queryType : triggerType + capitalizedQueryType;
};
var normalizeResults = (result) => {
const value = Array.isArray(result) ? result : result === EMPTY ? [] : [result];
return structuredClone(value);
};
var invokeTriggers = async (triggers, triggerType, definition, options) => {
const { database, client, waitUntil, implicit } = options;
const { query } = definition;
const queryType = Object.keys(query)[0];
let queryModel;
let queryModelDashed;
let multipleRecords;
let oldInstruction;
if (DDL_QUERY_TYPES.includes(queryType)) {
queryModel = queryModelDashed = "model";
multipleRecords = false;
oldInstruction = query[queryType];
} else {
const queryInstructions = query[queryType];
({
key: queryModel,
model: queryModelDashed,
multipleRecords
} = getModel(queryInstructions));
oldInstruction = queryInstructions[queryModel];
}
const triggerFile = database ? "sink" : queryModelDashed;
const triggersForModel = triggers[triggerFile];
const triggerName = getMethodName(triggerType, queryType);
const queryInstruction = oldInstruction ? structuredClone(oldInstruction) : {};
if (triggersForModel && triggerName in triggersForModel) {
const trigger = triggersForModel[triggerName];
const triggerOptions = {
implicit,
client,
waitUntil,
...triggerFile === "sink" ? { model: queryModel, database } : {}
};
const triggerResult = await (triggerType === "following" ? trigger(
queryInstruction,
multipleRecords,
normalizeResults(definition.resultBefore),
normalizeResults(definition.resultAfter),
triggerOptions
) : trigger(
queryInstruction,
multipleRecords,
triggerOptions
));
if (triggerType === "before") {
return { queries: triggerResult };
}
if (triggerType === "during") {
const result = triggerResult;
let newQuery = query;
if (result && QUERY_TYPES.some((type) => type in result)) {
newQuery = result;
} else {
newQuery = {
[queryType]: {
[queryModel]: result
}
};
}
return { queries: [newQuery] };
}
if (triggerType === "after") {
return { queries: triggerResult };
}
if (triggerType === "resolving") {
const result = triggerResult;
return { queries: [], result };
}
}
return { queries: [], result: EMPTY };
};
var runQueriesWithTriggers = async (queries, options = {}) => {
const { triggers, waitUntil, requireTriggers, implicit: implicitRoot } = options;
const triggerErrorType = requireTriggers !== "all" ? ` ${requireTriggers}` : "";
const triggerError = new ClientError({
message: `Please define "during" triggers for the provided${triggerErrorType} queries.`,
code: "TRIGGER_REQUIRED"
});
if (!triggers) {
if (requireTriggers) throw triggerError;
return runQueries(queries, options);
}
const client = createSyntaxFactory({
...omit3(options, ["requireTriggers"]),
implicit: true
});
if (typeof process === "undefined" && !waitUntil) {
let message = 'In the case that the "ronin" package receives a value for';
message += " its `triggers` 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 "following" triggers have been executed.';
throw new Error(message);
}
let queryList = queries.map(({ query, database }) => ({
query,
result: EMPTY,
database
}));
await Promise.all(
queryList.map(async ({ query, database, implicit }, index) => {
const triggerResults = await invokeTriggers(
triggers,
"before",
{ query },
{ database, client, waitUntil, implicit: Boolean(implicitRoot || implicit) }
);
const queriesToInsert = triggerResults.queries.map((query2) => ({
query: query2,
result: EMPTY,
database,
implicit: true
}));
queryList.splice(index, 0, ...queriesToInsert);
})
);
await Promise.all(
queryList.map(async ({ query, database, implicit }, index) => {
const triggerResults = await invokeTriggers(
triggers,
"during",
{ query },
{ database, client, waitUntil, implicit: Boolean(implicitRoot || implicit) }
);
if (triggerResults.queries && triggerResults.queries.length > 0) {
queryList[index].query = triggerResults.queries[0];
return;
}
if (requireTriggers) {
const queryType = Object.keys(query)[0];
const requiredTypes = requireTriggers === "read" ? QUERY_TYPES_READ : requireTriggers === "write" ? QUERY_TYPES_WRITE : QUERY_TYPES;
if (requiredTypes.includes(queryType)) throw triggerError;
}
})
);
await Promise.all(
queryList.map(async ({ query, database, implicit }, index) => {
const triggerResults = await invokeTriggers(
triggers,
"after",
{ query },
{ database, client, waitUntil, implicit: Boolean(implicitRoot || implicit) }
);
const queriesToInsert = triggerResults.queries.map((query2) => ({
query: query2,
result: EMPTY,
database,
implicit: true
}));
queryList.splice(index + 1, 0, ...queriesToInsert);
})
);
queryList = queryList.flatMap((details, index) => {
const { query, database } = details;
if (query.set || query.alter) {
let newQuery;
if (query.set) {
const modelSlug = Object.keys(query.set)[0];
newQuery = {
get: {
[modelSlug]: {
with: query.set[modelSlug].with
}
}
};
} else {
newQuery = {
list: {
model: query.alter.model
}
};
}
const diffQuery = {
query: newQuery,
diffForIndex: index + 1,
result: EMPTY,
database
};
return [diffQuery, details];
}
return [details];
});
await Promise.all(
queryList.map(async ({ query, database, implicit }, index) => {
const triggerResults = await invokeTriggers(
triggers,
"resolving",
{ query },
{ database, client, waitUntil, implicit: Boolean(implicitRoot || implicit) }
);
queryList[index].result = triggerResults.result;
})
);
const queriesWithoutResults = queryList.map((query, index) => ({ ...query, index })).filter((query) => query.result === EMPTY);
if (queriesWithoutResults.length > 0) {
const resultsFromDatabase = await runQueries(queriesWithoutResults, options);
for (let index = 0; index < resultsFromDatabase.length; index++) {
const query = queriesWithoutResults[index];
const result = resultsFromDatabase[index].result;
queryList[query.index].result = result;
}
}
for (let index = 0; index < queryList.length; index++) {
const { query, result, database, implicit } = queryList[index];
const queryType = Object.keys(query)[0];
if (!WRITE_QUERY_TYPES.includes(queryType)) continue;
const diffMatch = queryList.find((item) => item.diffForIndex === index);
let resultBefore = diffMatch ? diffMatch.result : EMPTY;
let resultAfter = result;
if (queryType === "remove" || queryType === "drop") {
resultBefore = result;
resultAfter = EMPTY;
}
const promise = invokeTriggers(
triggers,
"following",
{ query, resultBefore, resultAfter },
{ database, client, waitUntil, implicit: Boolean(implicitRoot || implicit) }
);
const clearPromise = promise.then(
() => {
},
(error) => Promise.reject(error)
);
if (waitUntil) waitUntil(clearPromise);
}
return queryList.filter(
(query) => typeof query.diffForIndex === "undefined" && typeof query.implicit === "undefined"
).map(({ result, database }) => ({
result,
database
}));
};
// src/queries.ts
var runQueries = async (queries, options = {}) => {
validateToken(options);
let hasWriteQuery = null;
let hasSingleQuery = true;
const operations = queries.reduce(
(acc, details) => {
const { database = "default" } = details;
if (!acc[database]) acc[database] = {};
if (database !== "default") hasSingleQuery = false;
if ("query" in details) {
const { query } = details;
if (!acc[database].queries) acc[database].queries = [];
acc[database].queries.push(query);
const queryType = Object.keys(query)[0];
hasWriteQuery = hasWriteQuery || WRITE_QUERY_TYPES.includes(queryType);
return acc;
}
const { statement } = details;
if (!acc[database].nativeQueries) acc[database].nativeQueries = [];
acc[database].nativeQueries.push({
query: statement.statement,
values: statement.params
});
return acc;
},
{}
);
const requestBody = hasSingleQuery ? operations.default : operations;
const hasCachingSupport = "cache" in new Request("https://ronin.co");
const request = new Request("https://data.ronin.co", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${options.token}`
},
body: JSON.stringify(requestBody),
// Disable cache if write queries are performed, as those must be
// guaranteed to reach RONIN.
...hasWriteQuery && hasCachingSupport ? { cache: "no-store" } : {},
// Allow for passing custom `fetch` options (e.g. in Next.js).
...typeof options?.fetch === "object" ? options.fetch : {}
});
const fetcher = typeof options?.fetch === "function" ? options.fetch : fetch;
const response = await fetcher(request);
const responseResults = await getResponseBody(response);
const startFormatting = performance.now();
const formattedResults = [];
if ("results" in responseResults) {
const usableResults = responseResults.results;
const finalResults = formatResults(usableResults);
formattedResults.push(...finalResults.map((result) => ({ result })));
} else {
for (const [database, { results }] of Object.entries(responseResults)) {
const finalResults = formatResults(results);
formattedResults.push(...finalResults.map((result) => ({ result, database })));
}
}
const endFormatting = performance.now();
const VERBOSE_LOGGING = typeof process !== "undefined" && process?.env && process.env.__RENDER_DEBUG_LEVEL === "verbose" || typeof import.meta?.env !== "undefined" && import.meta.env.__RENDER_DEBUG_LEVEL === "verbose";
if (VERBOSE_LOGGING) {
console.log(`Formatting took ${endFormatting - startFormatting}ms`);
}
return formattedResults;
};
async function runQueriesWithStorageAndTriggers(queries, options = {}) {
const singleDatabase = Array.isArray(queries);
const normalizedQueries = singleDatabase ? { default: queries } : queries;
const queriesWithReferences = (await Promise.all(
Object.entries(normalizedQueries).map(async ([database, queries2]) => {
const populatedQueries = await processStorableObjects(queries2, (objects) => {
return uploadStorableObjects(objects, options);
});
return populatedQueries.map((query) => ({
query,
database: database === "default" ? void 0 : database
}));
})
)).flat();
const results = await runQueriesWithTriggers(queriesWithReferences, options);
if (singleDatabase)
return results.filter(({ database }) => !database).map(({ result }) => result);
return results.reduce(
(acc, { result, database = "default" }) => {
if (!acc[database]) acc[database] = [];
acc[database].push(result);
return acc;
},
{}
);
}
var formatIndividualResult = (result) => {
if ("amount" in result && typeof result.amount !== "undefined" && result.amount !== null) {
return Number(result.amount);
}
const dateFields = "modelFields" in result ? Object.entries(result.modelFields).filter(([, type]) => type === "date").map(([slug]) => slug) : [];
if ("record" in result) {
if (result.record === null) return null;
formatDateFields(result.record, dateFields);
return result.record;
}
if ("records" in result) {
for (const record of result.records) {
formatDateFields(record, dateFields);
}
const formattedRecords = result.records;
if (typeof result.moreBefore !== "undefined")
formattedRecords.moreBefore = result.moreBefore;
if (typeof result.moreAfter !== "undefined")
formattedRecords.moreAfter = result.moreAfter;
return formattedRecords;
}
return result;
};
var formatResults = (results) => {
const formattedResults = [];
for (const result of results) {
if ("models" in result) {
formattedResults.push(
Object.fromEntries(
Object.entries(result.models).map(([model, result2]) => {
return [model, formatIndividualResult(result2)];
})
)
);
continue;
}
formattedResults.push(formatIndividualResult(result));
}
return formattedResults;
};
// src/utils/handlers.ts
var queriesHandler = async (queries, options = {}) => {
if ("statements" in queries) {
const results = await runQueries(
queries.statements.map((statement) => ({ statement })),
options
);
return results.map(({ result }) => result);
}
if (options.database) {
const queryList = { [options.database]: queries };
const result = await runQueriesWithStorageAndTriggers(queryList, options);
return result[options.database];
}
return runQueriesWithStorageAndTriggers(queries, options);
};
var queryHandler = async (query, options) => {
const input = "statement" in query ? { statements: [query.statement] } : [query];
const results = await queriesHandler(input, options);
return results[0];
};
// src/index.ts
var createSyntaxFactory = (options) => {
const callback = (defaultQuery, queryOptions) => {
const query = defaultQuery;
return queryHandler(query[QUERY_SYMBOLS.QUERY], mergeOptions(options, queryOptions));
};
const replacer = (value) => isStorableObject(value) ? value : void 0;
return {
// Query types for interacting with records.
get: getSyntaxProxy({
root: `${QUERY_SYMBOLS.QUERY}.get`,
callback,
replacer
}),
set: getSyntaxProxy({
root: `${QUERY_SYMBOLS.QUERY}.set`,
callback,
replacer
}),
add: getSyntaxProxy({
root: `${QUERY_SYMBOLS.QUERY}.add`,
callback,
replacer
}),
remove: getSyntaxProxy({
root: `${QUERY_SYMBOLS.QUERY}.remove`,
callback,
replacer
}),
count: getSyntaxProxy({
root: `${QUERY_SYMBOLS.QUERY}.count`,
callback,
replacer
}),
// Query types for interacting with the database schema.
list: getSyntaxProxy({
root: `${QUERY_SYMBOLS.QUERY}.list`,
callback,
replacer
}),
create: getSyntaxProxy({
root: `${QUERY_SYMBOLS.QUERY}.create`,
callback,
replacer
}),
alter: getSyntaxProxy({
root: `${QUERY_SYMBOLS.QUERY}.alter`,
callback,
replacer
}),
drop: getSyntaxProxy({
root: `${QUERY_SYMBOLS.QUERY}.drop`,
callback,
replacer
}),
// Function for executing a transaction containing multiple queries.
batch: (operations, queryOptions) => {
const batchOperations = operations;
const queries = getBatchProxy(batchOperations).map(({ structure }) => structure);
const finalOptions = mergeOptions(options, queryOptions);
return queriesHandler(queries, finalOptions);
},
sql: getSyntaxProxySQL({
callback: (statement) => queryHandler({ statement }, mergeOptions(options, {}))
}),
sqlBatch: (operations, queryOptions) => {
const batchOperations = operations;
const statements = getBatchProxySQL(batchOperations);
const finalOptions = mergeOptions(options, queryOptions);
return queriesHandler({ statements }, finalOptions);
}
};
};
var factory = createSyntaxFactory({});
var get = factory.get;
var set = factory.set;
var add = factory.add;
var remove = factory.remove;
var count = factory.count;
var list = factory.list;
var create = factory.create;
var alter = factory.alter;
var drop = factory.drop;
var batch = factory.batch;
var sql = factory.sql;
var sqlBatch = factory.sqlBatch;
var index_default = createSyntaxFactory;
export {
ClientError,
isStorableObject,
processStorableObjects,
runQueriesWithStorageAndTriggers,
createSyntaxFactory,
get,
set,
add,
remove,
count,
list,
create,
alter,
drop,
batch,
sql,
sqlBatch,
index_default
};