Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@ronin/compiler

Package Overview
Dependencies
Maintainers
0
Versions
141
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ronin/compiler - npm Package Compare versions

Comparing version 0.1.0 to 0.1.1-leo-ron-1083-experimental.58

LICENSE

1055

dist/index.js

@@ -1,8 +0,19 @@

// src/utils/index.ts
// src/utils/helpers.ts
import { init as cuid } from "@paralleldrive/cuid2";
var RONIN_SCHEMA_SYMBOLS = {
// Represents a sub query.
QUERY: "__RONIN_QUERY",
// Represents the value of a field in a schema.
FIELD: "__RONIN_FIELD_",
// Represents the old value of a field in a schema. Used for triggers.
FIELD_OLD: "__RONIN_FIELD_OLD_",
// Represents the new value of a field in a schema. Used for triggers.
FIELD_NEW: "__RONIN_FIELD_NEW_",
// Represents a value provided to a query preset.
VALUE: "__RONIN_VALUE"
};
var RONIN_SCHEMA_FIELD_REGEX = new RegExp(
`${RONIN_SCHEMA_SYMBOLS.FIELD}[a-zA-Z0-9]+`,
"g"
);
var RoninError = class extends Error {

@@ -29,3 +40,3 @@ code;

var SPLIT_REGEX = /(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|[\s.\-_]+/;
var generateRecordId = (prefix) => `${prefix || "rec"}_${cuid({ length: 16 })()}`;
var generateRecordId = (prefix) => `${prefix}_${cuid({ length: 16 })()}`;
var capitalize = (str) => {

@@ -48,11 +59,18 @@ if (!str || str.length === 0) return "";

var isObject = (value) => value != null && typeof value === "object" && Array.isArray(value) === false;
var replaceInObject = (obj, pattern, replacer) => {
var findInObject = (obj, pattern, replacer) => {
let found = false;
for (const key in obj) {
const value = obj[key];
if (isObject(value)) {
replaceInObject(value, pattern, replacer);
found = findInObject(value, pattern, replacer);
} else if (typeof value === "string" && value.startsWith(pattern)) {
obj[key] = value.replace(pattern, replacer);
found = true;
if (replacer) {
obj[key] = value.replace(pattern, replacer);
} else {
return found;
}
}
}
return found;
};

@@ -86,3 +104,208 @@ var flatten = (obj, prefix = "", res = {}) => {

// src/utils/statement.ts
var prepareStatementValue = (statementParams, value) => {
if (value === null) return "NULL";
if (!statementParams) return JSON.stringify(value);
let formattedValue = value;
if (Array.isArray(value) || isObject(value)) {
formattedValue = JSON.stringify(value);
} else if (typeof value === "boolean") {
formattedValue = value ? 1 : 0;
}
const index = statementParams.push(formattedValue);
return `?${index}`;
};
var composeFieldValues = (schemas, schema, statementParams, instructionName, value, options) => {
const { field: schemaField, fieldSelector: selector } = getFieldFromSchema(
schema,
options.fieldSlug,
instructionName,
options.rootTable
);
const isSubQuery = isObject(value) && Object.hasOwn(value, RONIN_SCHEMA_SYMBOLS.QUERY);
const collectStatementValue = options.type !== "fields";
let conditionSelector = selector;
let conditionValue = value;
if (isSubQuery && collectStatementValue) {
conditionValue = `(${compileQueryInput(
value[RONIN_SCHEMA_SYMBOLS.QUERY],
schemas,
statementParams
).main.statement})`;
} else if (typeof value === "string" && value.startsWith(RONIN_SCHEMA_SYMBOLS.FIELD)) {
let targetTable = `"${options.rootTable}"`;
let toReplace = RONIN_SCHEMA_SYMBOLS.FIELD;
if (value.startsWith(RONIN_SCHEMA_SYMBOLS.FIELD_OLD)) {
targetTable = "OLD";
toReplace = RONIN_SCHEMA_SYMBOLS.FIELD_OLD;
} else if (value.startsWith(RONIN_SCHEMA_SYMBOLS.FIELD_NEW)) {
targetTable = "NEW";
toReplace = RONIN_SCHEMA_SYMBOLS.FIELD_NEW;
}
conditionSelector = `${options.customTable ? `"${options.customTable}".` : ""}"${schemaField.slug}"`;
conditionValue = `${targetTable}."${value.replace(toReplace, "")}"`;
} else if (schemaField.type === "json" && instructionName === "to") {
conditionSelector = `"${schemaField.slug}"`;
if (collectStatementValue) {
const preparedValue = prepareStatementValue(statementParams, value);
conditionValue = `IIF(${conditionSelector} IS NULL, ${preparedValue}, json_patch(${conditionSelector}, ${preparedValue}))`;
}
} else if (collectStatementValue) {
conditionValue = prepareStatementValue(statementParams, value);
}
if (options.type === "fields") return conditionSelector;
if (options.type === "values") return conditionValue;
return `${conditionSelector} ${WITH_CONDITIONS[options.condition || "being"](conditionValue, value)}`;
};
var composeConditions = (schemas, schema, statementParams, instructionName, value, options) => {
const isNested = isObject(value) && Object.keys(value).length > 0;
if (isNested && Object.keys(value).every((key) => key in WITH_CONDITIONS)) {
const conditions = Object.entries(value).map(
([conditionType, checkValue]) => composeConditions(schemas, schema, statementParams, instructionName, checkValue, {
...options,
condition: conditionType
})
);
return conditions.join(" AND ");
}
if (options.fieldSlug) {
const fieldDetails = getFieldFromSchema(
schema,
options.fieldSlug,
instructionName,
options.rootTable
);
const { field: schemaField } = fieldDetails;
const consumeJSON = schemaField.type === "json" && instructionName === "to";
const isSubQuery = isNested && Object.hasOwn(value, RONIN_SCHEMA_SYMBOLS.QUERY);
if (!(isObject(value) || Array.isArray(value)) || isSubQuery || consumeJSON) {
return composeFieldValues(
schemas,
schema,
statementParams,
instructionName,
value,
{ ...options, fieldSlug: options.fieldSlug }
);
}
if (schemaField.type === "reference" && isNested) {
const keys = Object.keys(value);
const values = Object.values(value);
let recordTarget;
if (keys.length === 1 && keys[0] === "id") {
recordTarget = values[0];
} else {
const relatedSchema = getSchemaBySlug(schemas, schemaField.target.slug);
const subQuery = {
get: {
[relatedSchema.slug]: {
with: value,
selecting: ["id"]
}
}
};
recordTarget = {
[RONIN_SCHEMA_SYMBOLS.QUERY]: subQuery
};
}
return composeConditions(
schemas,
schema,
statementParams,
instructionName,
recordTarget,
options
);
}
}
if (isNested) {
const conditions = Object.entries(value).map(([field, value2]) => {
const nestedFieldSlug = options.fieldSlug ? `${options.fieldSlug}.${field}` : field;
return composeConditions(schemas, schema, statementParams, instructionName, value2, {
...options,
fieldSlug: nestedFieldSlug
});
});
const joiner = instructionName === "to" ? ", " : " AND ";
if (instructionName === "to") return `${conditions.join(joiner)}`;
return conditions.length === 1 ? conditions[0] : options.fieldSlug ? `(${conditions.join(joiner)})` : conditions.join(joiner);
}
if (Array.isArray(value)) {
const conditions = value.map(
(filter) => composeConditions(
schemas,
schema,
statementParams,
instructionName,
filter,
options
)
);
return conditions.join(" OR ");
}
throw new RoninError({
message: `The \`with\` instruction must not contain an empty field. The following fields are empty: \`${options.fieldSlug}\`. If you meant to query by an empty field, try using \`null\` instead.`,
code: "INVALID_WITH_VALUE",
queries: null
});
};
var formatIdentifiers = ({ identifiers }, queryInstructions) => {
if (!queryInstructions) return queryInstructions;
const type = "with" in queryInstructions ? "with" : null;
if (!type) return queryInstructions;
const nestedInstructions = queryInstructions[type];
if (!nestedInstructions || Array.isArray(nestedInstructions))
return queryInstructions;
const newNestedInstructions = { ...nestedInstructions };
for (const oldKey of Object.keys(newNestedInstructions)) {
if (oldKey !== "nameIdentifier" && oldKey !== "slugIdentifier") continue;
const identifierName = oldKey === "nameIdentifier" ? "name" : "slug";
const value = newNestedInstructions[oldKey];
const newKey = identifiers[identifierName];
newNestedInstructions[newKey] = value;
delete newNestedInstructions[oldKey];
}
return {
...queryInstructions,
[type]: newNestedInstructions
};
};
// src/instructions/with.ts
var getMatcher = (value, negative) => {
if (negative) {
if (value === null) return "IS NOT";
return "!=";
}
if (value === null) return "IS";
return "=";
};
var WITH_CONDITIONS = {
being: (value, baseValue) => `${getMatcher(baseValue, false)} ${value}`,
notBeing: (value, baseValue) => `${getMatcher(baseValue, true)} ${value}`,
startingWith: (value) => `LIKE ${value}%`,
notStartingWith: (value) => `NOT LIKE ${value}%`,
endingWith: (value) => `LIKE %${value}`,
notEndingWith: (value) => `NOT LIKE %${value}`,
containing: (value) => `LIKE %${value}%`,
notContaining: (value) => `NOT LIKE %${value}%`,
greaterThan: (value) => `> ${value}`,
greaterOrEqual: (value) => `>= ${value}`,
lessThan: (value) => `< ${value}`,
lessOrEqual: (value) => `<= ${value}`
};
var handleWith = (schemas, schema, statementParams, instruction, rootTable) => {
const subStatement = composeConditions(
schemas,
schema,
statementParams,
"with",
instruction,
{ rootTable }
);
return `(${subStatement})`;
};
// src/utils/schema.ts
import title from "title";
var getSchemaBySlug = (schemas, slug) => {

@@ -103,9 +326,7 @@ const schema = schemas.find((schema2) => {

};
var getSchemaName = (schema) => {
return schema.name || schema.slug;
};
var composeMetaSchemaSlug = (suffix) => convertToCamelCase(`ronin_${suffix}`);
var composeAssociationSchemaSlug = (schema, field) => composeMetaSchemaSlug(`${schema.pluralSlug}_${field.slug}`);
var composeAssociationSchemaSlug = (schema, field) => composeMetaSchemaSlug(`${schema.slug}_${field.slug}`);
var getFieldSelector = (field, fieldPath, rootTable) => {
const tablePrefix = rootTable ? `"${rootTable}".` : "";
const symbol = rootTable?.startsWith(RONIN_SCHEMA_SYMBOLS.FIELD) ? `${rootTable.replace(RONIN_SCHEMA_SYMBOLS.FIELD, "").slice(0, -1)}.` : "";
const tablePrefix = symbol || (rootTable ? `"${rootTable}".` : "");
if (field.type === "json") {

@@ -133,3 +354,3 @@ const dotParts = fieldPath.split(".");

throw new RoninError({
message: `${errorPrefix} does not exist in schema "${getSchemaName(schema)}".`,
message: `${errorPrefix} does not exist in schema "${schema.name}".`,
code: "FIELD_NOT_FOUND",

@@ -143,2 +364,49 @@ field: fieldPath,

};
var slugToName = (slug) => {
const name = slug.replace(/([a-z])([A-Z])/g, "$1 $2");
return title(name);
};
var VOWELS = ["a", "e", "i", "o", "u"];
var pluralize = (word) => {
const lastLetter = word.slice(-1).toLowerCase();
const secondLastLetter = word.slice(-2, -1).toLowerCase();
if (lastLetter === "y" && !VOWELS.includes(secondLastLetter)) {
return `${word.slice(0, -1)}ies`;
}
if (lastLetter === "s" || word.slice(-2).toLowerCase() === "ch" || word.slice(-2).toLowerCase() === "sh" || word.slice(-2).toLowerCase() === "ex") {
return `${word}es`;
}
return `${word}s`;
};
var schemaSettings = [
["pluralSlug", "slug", pluralize],
["name", "slug", slugToName],
["pluralName", "pluralSlug", slugToName],
["idPrefix", "slug", (slug) => slug.slice(0, 3)]
];
var addDefaultSchemaFields = (schema, isNew) => {
const copiedSchema = { ...schema };
for (const [setting, base, generator] of schemaSettings) {
if (copiedSchema[setting] || !copiedSchema[base]) continue;
copiedSchema[setting] = generator(copiedSchema[base]);
}
const newFields = copiedSchema.fields || [];
if (isNew || newFields.length > 0) {
if (!copiedSchema.identifiers) copiedSchema.identifiers = {};
if (!copiedSchema.identifiers.name) {
const suitableField = newFields.find(
(field) => field.type === "string" && field.required === true && ["name"].includes(field.slug)
);
copiedSchema.identifiers.name = suitableField?.slug || "id";
}
if (!copiedSchema.identifiers.slug) {
const suitableField = newFields.find(
(field) => field.type === "string" && field.unique === true && field.required === true && ["slug", "handle"].includes(field.slug)
);
copiedSchema.identifiers.slug = suitableField?.slug || "id";
}
copiedSchema.fields = [...SYSTEM_FIELDS, ...newFields];
}
return copiedSchema;
};
var SYSTEM_FIELDS = [

@@ -168,4 +436,3 @@ {

name: "RONIN - Created By",
type: "reference",
schema: "account",
type: "string",
slug: "ronin.createdBy"

@@ -180,4 +447,3 @@ },

name: "RONIN - Updated By",
type: "reference",
schema: "account",
type: "string",
slug: "ronin.updatedBy"

@@ -188,8 +454,8 @@ }

{
name: "Schema",
pluralName: "Schemas",
slug: "schema",
pluralSlug: "schemas",
identifiers: {
name: "name",
slug: "slug"
},
fields: [
...SYSTEM_FIELDS,
{ slug: "name", type: "string" },

@@ -201,35 +467,107 @@ { slug: "pluralName", type: "string" },

{ slug: "identifiers", type: "group" },
{ slug: "identifiers.title", type: "string" },
{ slug: "identifiers.slug", type: "string" }
{ slug: "identifiers.name", type: "string" },
{ slug: "identifiers.slug", type: "string" },
{ slug: "fields", type: "json" },
{ slug: "indexes", type: "json" },
{ slug: "triggers", type: "json" },
{ slug: "including", type: "json" },
{ slug: "for", type: "json" }
]
},
{
name: "Field",
pluralName: "Fields",
slug: "field",
pluralSlug: "fields",
identifiers: {
name: "name",
slug: "slug"
},
fields: [
...SYSTEM_FIELDS,
{ slug: "name", type: "string" },
{ slug: "slug", type: "string" },
{ slug: "type", type: "string" },
{ slug: "schema", type: "reference", schema: "schema" },
{ slug: "slug", type: "string", required: true },
{ slug: "type", type: "string", required: true },
{
slug: "schema",
type: "reference",
target: { slug: "schema" },
required: true
},
{ slug: "required", type: "boolean" },
{ slug: "defaultValue", type: "string" },
{ slug: "unique", type: "boolean" },
{ slug: "autoIncrement", type: "boolean" }
{ slug: "autoIncrement", type: "boolean" },
// Only allowed for fields of type "reference".
{ slug: "target", type: "reference", target: { slug: "schema" } },
{ slug: "kind", type: "string" },
{ slug: "actions", type: "group" },
{ slug: "actions.onDelete", type: "string" },
{ slug: "actions.onUpdate", type: "string" }
]
},
{
slug: "index",
identifiers: {
name: "slug",
slug: "slug"
},
fields: [
{ slug: "slug", type: "string", required: true },
{
slug: "schema",
type: "reference",
target: { slug: "schema" },
required: true
},
{ slug: "unique", type: "boolean" },
{ slug: "filter", type: "json" },
{ slug: "fields", type: "json", required: true }
]
},
{
slug: "trigger",
identifiers: {
name: "slug",
slug: "slug"
},
fields: [
{ slug: "slug", type: "string", required: true },
{
slug: "schema",
type: "reference",
target: { slug: "schema" },
required: true
},
{ slug: "when", type: "string", required: true },
{ slug: "action", type: "string", required: true },
{ slug: "filter", type: "json" },
{ slug: "effects", type: "json", required: true },
{ slug: "fields", type: "json" }
]
},
{
slug: "preset",
fields: [
{ slug: "slug", type: "string", required: true },
{
slug: "schema",
type: "reference",
target: { slug: "schema" },
required: true
},
{ slug: "instructions", type: "json", required: true }
]
}
];
].map((schema) => addDefaultSchemaFields(schema, true));
var SYSTEM_SCHEMA_SLUGS = SYSTEM_SCHEMAS.flatMap(({ slug, pluralSlug }) => [
slug,
pluralSlug
]);
var addSystemSchemas = (schemas) => {
const list = [...SYSTEM_SCHEMAS, ...schemas].map((schema) => ({ ...schema }));
for (const schema of list) {
const defaultIncluding = {};
const associativeSchemas = schemas.flatMap((schema) => {
const addedSchemas = [];
for (const field of schema.fields || []) {
if (field.type === "reference" && !field.slug.startsWith("ronin.")) {
const relatedSchema = getSchemaBySlug(list, field.schema);
const relatedSchema = getSchemaBySlug(schemas, field.target.slug);
let fieldSlug = relatedSchema.slug;
if (field.kind === "many") {
fieldSlug = composeAssociationSchemaSlug(schema, field);
list.push({
addedSchemas.push({
pluralSlug: fieldSlug,

@@ -239,5 +577,5 @@ slug: fieldSlug,

{
slug: "origin",
slug: "source",
type: "reference",
schema: schema.slug
target: { slug: schema.slug }
},

@@ -247,3 +585,3 @@ {

type: "reference",
schema: relatedSchema.slug
target: { slug: relatedSchema.slug }
}

@@ -253,33 +591,54 @@ ]

}
defaultIncluding[field.slug] = {
get: {
[fieldSlug]: {
with: {
// Compare the `id` field of the related schema to the reference field on
// the root schema (`field.slug`).
id: `${RONIN_SCHEMA_SYMBOLS.FIELD}${field.slug}`
}
}
}
return addedSchemas;
});
return [...SYSTEM_SCHEMAS, ...associativeSchemas, ...schemas];
};
var addDefaultSchemaShortcuts = (list, schema) => {
const defaultIncluding = {};
for (const field of schema.fields || []) {
if (field.type === "reference" && !field.slug.startsWith("ronin.")) {
const relatedSchema = getSchemaBySlug(list, field.target.slug);
let fieldSlug = relatedSchema.slug;
if (field.kind === "many") {
fieldSlug = composeAssociationSchemaSlug(schema, field);
}
defaultIncluding[field.slug] = {
get: {
[fieldSlug]: {
with: {
// Compare the `id` field of the related schema to the reference field on
// the root schema (`field.slug`).
id: `${RONIN_SCHEMA_SYMBOLS.FIELD}${field.slug}`
}
}
};
const relatedSchemaToModify = list.find((schema2) => schema2.slug === field.schema);
if (!relatedSchemaToModify) throw new Error("Missing related schema");
relatedSchemaToModify.including = {
[schema.pluralSlug]: {
get: {
[schema.pluralSlug]: {
with: {
[field.slug]: `${RONIN_SCHEMA_SYMBOLS.FIELD}id`
}
}
}
},
...relatedSchemaToModify.including
};
}
};
}
}
const childSchemas = list.map((subSchema) => {
const field = subSchema.fields?.find((field2) => {
return field2.type === "reference" && field2.target.slug === schema.slug;
});
if (!field) return null;
return { schema: subSchema, field };
}).filter((match) => match !== null);
for (const childMatch of childSchemas) {
const { schema: childSchema, field: childField } = childMatch;
const pluralSlug = childSchema.pluralSlug;
defaultIncluding[pluralSlug] = {
get: {
[pluralSlug]: {
with: {
[childField.slug]: `${RONIN_SCHEMA_SYMBOLS.FIELD}id`
}
}
}
}
schema.fields = [...SYSTEM_FIELDS, ...schema.fields || []];
};
}
if (Object.keys(defaultIncluding).length > 0) {
schema.including = { ...defaultIncluding, ...schema.including };
}
return list;
return schema;
};

@@ -308,19 +667,33 @@ var mappedInstructions = {

statement += ` DEFAULT ${field.defaultValue}`;
if (field.type === "reference") {
const actions = field.actions || {};
const targetTable = convertToSnakeCase(pluralize(field.target.slug));
statement += ` REFERENCES ${targetTable}("id")`;
for (const trigger in actions) {
const triggerName = trigger.toUpperCase().slice(2);
const action = actions[trigger];
statement += ` ON ${triggerName} ${action}`;
}
}
return statement;
};
var addSchemaQueries = (queryDetails, writeStatements) => {
const { queryType, querySchema, queryInstructions } = queryDetails;
if (!["create", "set", "drop"].includes(queryType)) return;
if (!["schema", "schemas", "field", "fields"].includes(querySchema)) return;
var addSchemaQueries = (schemas, queryDetails, dependencyStatements) => {
const {
queryType,
querySchema,
queryInstructions: queryInstructionsRaw
} = queryDetails;
const queryInstructions = queryInstructionsRaw;
if (!["create", "set", "drop"].includes(queryType)) return queryInstructions;
if (!SYSTEM_SCHEMA_SLUGS.includes(querySchema)) return queryInstructions;
const instructionName = mappedInstructions[queryType];
const instructionList = queryInstructions[instructionName];
const kind = ["schema", "schemas"].includes(querySchema) ? "schemas" : "fields";
const instructionTarget = kind === "schemas" ? instructionList : instructionList?.schema;
const kind = getSchemaBySlug(SYSTEM_SCHEMAS, querySchema).pluralSlug;
let tableAction = "ALTER";
let schemaPluralSlug = null;
let queryTypeReadable = null;
switch (queryType) {
case "create": {
if (kind === "schemas") tableAction = "CREATE";
schemaPluralSlug = instructionTarget?.pluralSlug;
if (kind === "schemas" || kind === "indexes" || kind === "triggers") {
tableAction = "CREATE";
}
queryTypeReadable = "creating";

@@ -331,3 +704,2 @@ break;

if (kind === "schemas") tableAction = "ALTER";
schemaPluralSlug = instructionTarget?.pluralSlug?.being || instructionTarget?.pluralSlug;
queryTypeReadable = "updating";

@@ -337,4 +709,5 @@ break;

case "drop": {
if (kind === "schemas") tableAction = "DROP";
schemaPluralSlug = instructionTarget?.pluralSlug?.being || instructionTarget?.pluralSlug;
if (kind === "schemas" || kind === "indexes" || kind === "triggers") {
tableAction = "DROP";
}
queryTypeReadable = "deleting";

@@ -344,17 +717,128 @@ break;

}
if (!schemaPluralSlug) {
const field = kind === "schemas" ? "pluralSlug" : "schema.pluralSlug";
const slug = instructionList?.slug?.being || instructionList?.slug;
if (!slug) {
throw new RoninError({
message: `When ${queryTypeReadable} ${kind}, a \`${field}\` field must be provided in the \`${instructionName}\` instruction.`,
message: `When ${queryTypeReadable} ${kind}, a \`slug\` field must be provided in the \`${instructionName}\` instruction.`,
code: "MISSING_FIELD",
fields: [field]
fields: ["slug"]
});
}
const table = convertToSnakeCase(schemaPluralSlug);
const fields = [...SYSTEM_FIELDS];
let statement = `${tableAction} TABLE "${table}"`;
const schemaInstruction = instructionList?.schema;
const schemaSlug = schemaInstruction?.slug?.being || schemaInstruction?.slug;
if (kind !== "schemas" && !schemaSlug) {
throw new RoninError({
message: `When ${queryTypeReadable} ${kind}, a \`schema.slug\` field must be provided in the \`${instructionName}\` instruction.`,
code: "MISSING_FIELD",
fields: ["schema.slug"]
});
}
const usableSlug = kind === "schemas" ? slug : schemaSlug;
const tableName = convertToSnakeCase(pluralize(usableSlug));
const targetSchema = kind === "schemas" && queryType === "create" ? null : getSchemaBySlug(schemas, usableSlug);
if (kind === "indexes") {
const indexName = convertToSnakeCase(slug);
const unique = instructionList?.unique;
const filterQuery = instructionList?.filter;
const fields = instructionList?.fields;
const params = [];
let statement2 = `${tableAction}${unique ? " UNIQUE" : ""} INDEX "${indexName}"`;
if (queryType === "create") {
const schema = targetSchema;
const columns = fields.map((field) => {
let fieldSelector = "";
if ("slug" in field) {
({ fieldSelector } = getFieldFromSchema(schema, field.slug, "to"));
} else if ("expression" in field) {
fieldSelector = field.expression.replace(RONIN_SCHEMA_FIELD_REGEX, (match) => {
const fieldSlug = match.replace(RONIN_SCHEMA_SYMBOLS.FIELD, "");
return getFieldFromSchema(schema, fieldSlug, "to").fieldSelector;
});
}
if (field.collation) fieldSelector += ` COLLATE ${field.collation}`;
if (field.order) fieldSelector += ` ${field.order}`;
return fieldSelector;
});
statement2 += ` ON "${tableName}" (${columns.join(", ")})`;
if (filterQuery) {
const withStatement = handleWith(
schemas,
targetSchema,
params,
filterQuery
);
statement2 += ` WHERE (${withStatement})`;
}
}
dependencyStatements.push({ statement: statement2, params });
return queryInstructions;
}
if (kind === "triggers") {
const triggerName = convertToSnakeCase(slug);
const params = [];
let statement2 = `${tableAction} TRIGGER "${triggerName}"`;
if (queryType === "create") {
const { when, action } = instructionList;
const statementParts = [`${when} ${action}`];
const effectQueries = instructionList?.effects;
const filterQuery = instructionList?.filter;
const fields = instructionList?.fields;
if (fields) {
if (action !== "UPDATE") {
throw new RoninError({
message: `When ${queryTypeReadable} ${kind}, targeting specific fields requires the \`UPDATE\` action.`,
code: "INVALID_SCHEMA_VALUE",
fields: ["action"]
});
}
const fieldSelectors = fields.map((field) => {
return getFieldFromSchema(targetSchema, field.slug, "to").fieldSelector;
});
statementParts.push(`OF (${fieldSelectors.join(", ")})`);
}
statementParts.push("ON", `"${tableName}"`);
if (filterQuery || effectQueries.some((query) => findInObject(query, RONIN_SCHEMA_SYMBOLS.FIELD))) {
statementParts.push("FOR EACH ROW");
}
if (filterQuery) {
const tablePlaceholder = action === "DELETE" ? RONIN_SCHEMA_SYMBOLS.FIELD_OLD : RONIN_SCHEMA_SYMBOLS.FIELD_NEW;
const withStatement = handleWith(
schemas,
targetSchema,
params,
filterQuery,
tablePlaceholder
);
statementParts.push("WHEN", `(${withStatement})`);
}
const effectStatements = effectQueries.map((effectQuery) => {
return compileQueryInput(effectQuery, schemas, params, {
returning: false
}).main.statement;
});
if (effectStatements.length > 1) statementParts.push("BEGIN");
statementParts.push(effectStatements.join("; "));
if (effectStatements.length > 1) statementParts.push("END");
statement2 += ` ${statementParts.join(" ")}`;
}
dependencyStatements.push({ statement: statement2, params });
return queryInstructions;
}
const statement = `${tableAction} TABLE "${tableName}"`;
if (kind === "schemas") {
if (queryType === "create" || queryType === "set") {
const schemaWithFields = addDefaultSchemaFields(
queryInstructions.to,
queryType === "create"
);
const schemaWithShortcuts = addDefaultSchemaShortcuts(schemas, schemaWithFields);
queryInstructions.to = schemaWithShortcuts;
}
if (queryType === "create") {
const { fields } = queryInstructions.to;
const columns = fields.map(getFieldStatement).filter(Boolean);
statement += ` (${columns.join(", ")})`;
dependencyStatements.push({
statement: `${statement} (${columns.join(", ")})`,
params: []
});
schemas.push(queryInstructions.to);
} else if (queryType === "set") {

@@ -364,14 +848,15 @@ const newSlug = queryInstructions.to?.pluralSlug;

const newTable = convertToSnakeCase(newSlug);
statement += ` RENAME TO "${newTable}"`;
dependencyStatements.push({
statement: `${statement} RENAME TO "${newTable}"`,
params: []
});
}
Object.assign(targetSchema, queryInstructions.to);
} else if (queryType === "drop") {
schemas.splice(schemas.indexOf(targetSchema), 1);
dependencyStatements.push({ statement, params: [] });
}
} else if (kind === "fields") {
const fieldSlug = instructionTarget?.slug?.being || instructionList?.slug;
if (!fieldSlug) {
throw new RoninError({
message: `When ${queryTypeReadable} fields, a \`slug\` field must be provided in the \`${instructionName}\` instruction.`,
code: "MISSING_FIELD",
fields: ["slug"]
});
}
return queryInstructions;
}
if (kind === "fields") {
if (queryType === "create") {

@@ -385,234 +870,32 @@ if (!instructionList.type) {

}
statement += ` ADD COLUMN ${getFieldStatement(instructionList)}`;
dependencyStatements.push({
statement: `${statement} ADD COLUMN ${getFieldStatement(instructionList)}`,
params: []
});
} else if (queryType === "set") {
const newSlug = queryInstructions.to?.slug;
if (newSlug) {
statement += ` RENAME COLUMN "${fieldSlug}" TO "${newSlug}"`;
dependencyStatements.push({
statement: `${statement} RENAME COLUMN "${slug}" TO "${newSlug}"`,
params: []
});
}
} else if (queryType === "drop") {
statement += ` DROP COLUMN "${fieldSlug}"`;
dependencyStatements.push({
statement: `${statement} DROP COLUMN "${slug}"`,
params: []
});
}
}
writeStatements.push(statement);
return queryInstructions;
};
// src/instructions/with.ts
var WITH_CONDITIONS = [
"being",
"notBeing",
"startingWith",
"notStartingWith",
"endingWith",
"notEndingWith",
"containing",
"notContaining",
"greaterThan",
"greaterOrEqual",
"lessThan",
"lessOrEqual"
];
var handleWith = (schemas, schema, statementValues, instruction, rootTable) => {
const subStatement = composeConditions(
schemas,
schema,
statementValues,
"with",
instruction,
{ rootTable }
);
return `(${subStatement})`;
};
// src/utils/statement.ts
var prepareStatementValue = (statementValues, value, bindNull = false) => {
if (!bindNull && value === null) return "NULL";
let formattedValue = value;
if (Array.isArray(value) || isObject(value)) {
formattedValue = JSON.stringify(value);
} else if (typeof value === "boolean") {
formattedValue = value ? 1 : 0;
}
const index = statementValues.push(formattedValue);
return `?${index}`;
};
var composeFieldValues = (schemas, schema, statementValues, instructionName, value, options) => {
const { field: schemaField, fieldSelector: selector } = getFieldFromSchema(
schema,
options.fieldSlug,
instructionName,
options.rootTable
);
const isSubQuery = isObject(value) && Object.hasOwn(value, RONIN_SCHEMA_SYMBOLS.QUERY);
const collectStatementValue = options.type !== "fields";
let conditionSelector = selector;
let conditionValue = value;
if (isSubQuery && collectStatementValue) {
conditionValue = `(${compileQueryInput(
value[RONIN_SCHEMA_SYMBOLS.QUERY],
schemas,
{ statementValues }
).readStatement})`;
} else if (typeof value === "string" && value.startsWith(RONIN_SCHEMA_SYMBOLS.FIELD)) {
conditionSelector = `"${options.customTable}"."${schemaField.slug}"`;
conditionValue = `"${options.rootTable}"."${value.replace(RONIN_SCHEMA_SYMBOLS.FIELD, "")}"`;
} else if (schemaField.type === "json" && instructionName === "to") {
conditionSelector = `"${schemaField.slug}"`;
if (collectStatementValue) {
const preparedValue = prepareStatementValue(statementValues, value, false);
conditionValue = `IIF(${conditionSelector} IS NULL, ${preparedValue}, json_patch(${conditionSelector}, ${preparedValue}))`;
}
} else if (collectStatementValue) {
conditionValue = prepareStatementValue(statementValues, value, false);
}
if (options.type === "fields") return conditionSelector;
if (options.type === "values") return conditionValue;
const conditionTypes = {
being: [getMatcher(value, false), conditionValue],
notBeing: [getMatcher(value, true), conditionValue],
startingWith: ["LIKE", `${conditionValue}%`],
notStartingWith: ["NOT LIKE", `${conditionValue}%`],
endingWith: ["LIKE", `%${conditionValue}`],
notEndingWith: ["NOT LIKE", `%${conditionValue}`],
containing: ["LIKE", `%${conditionValue}%`],
notContaining: ["NOT LIKE", `%${conditionValue}%`],
greaterThan: [">", conditionValue],
greaterOrEqual: [">=", conditionValue],
lessThan: ["<", conditionValue],
lessOrEqual: ["<=", conditionValue]
};
return `${conditionSelector} ${conditionTypes[options.condition || "being"].join(" ")}`;
};
var composeConditions = (schemas, schema, statementValues, instructionName, value, options) => {
const isNested = isObject(value) && Object.keys(value).length > 0;
if (isNested && Object.keys(value).every(
(key) => WITH_CONDITIONS.includes(key)
)) {
const conditions = Object.entries(value).map(
([conditionType, checkValue]) => composeConditions(schemas, schema, statementValues, instructionName, checkValue, {
...options,
condition: conditionType
})
);
return conditions.join(" AND ");
}
if (options.fieldSlug) {
const fieldDetails = getFieldFromSchema(
schema,
options.fieldSlug,
instructionName,
options.rootTable
);
const { field: schemaField } = fieldDetails;
const consumeJSON = schemaField.type === "json" && instructionName === "to";
const isSubQuery = isNested && Object.hasOwn(value, RONIN_SCHEMA_SYMBOLS.QUERY);
if (!(isObject(value) || Array.isArray(value)) || isSubQuery || consumeJSON) {
return composeFieldValues(
schemas,
schema,
statementValues,
instructionName,
value,
{ ...options, fieldSlug: options.fieldSlug }
);
}
if (schemaField.type === "reference" && isNested) {
const keys = Object.keys(value);
const values = Object.values(value);
let recordTarget;
if (keys.length === 1 && keys[0] === "id") {
recordTarget = values[0];
} else {
const relatedSchema = getSchemaBySlug(schemas, schemaField.schema);
const subQuery = {
get: {
[relatedSchema.slug]: {
with: value,
selecting: ["id"]
}
}
};
recordTarget = {
[RONIN_SCHEMA_SYMBOLS.QUERY]: subQuery
};
}
return composeConditions(
schemas,
schema,
statementValues,
instructionName,
recordTarget,
options
);
}
}
if (isNested) {
const conditions = Object.entries(value).map(([field, value2]) => {
const nestedFieldSlug = options.fieldSlug ? `${options.fieldSlug}.${field}` : field;
return composeConditions(schemas, schema, statementValues, instructionName, value2, {
...options,
fieldSlug: nestedFieldSlug
});
});
const joiner = instructionName === "to" ? ", " : " AND ";
if (instructionName === "to") return `${conditions.join(joiner)}`;
return conditions.length === 1 ? conditions[0] : options.fieldSlug ? `(${conditions.join(joiner)})` : conditions.join(joiner);
}
if (Array.isArray(value)) {
const conditions = value.map(
(filter) => composeConditions(
schemas,
schema,
statementValues,
instructionName,
filter,
options
)
);
return conditions.join(" OR ");
}
throw new RoninError({
message: `The \`with\` instruction must not contain an empty field. The following fields are empty: \`${options.fieldSlug}\`. If you meant to query by an empty field, try using \`null\` instead.`,
code: "INVALID_WITH_VALUE",
queries: null
});
};
var getMatcher = (value, negative) => {
if (negative) {
if (value === null) return "IS NOT";
return "!=";
}
if (value === null) return "IS";
return "=";
};
var formatIdentifiers = ({ identifiers }, queryInstructions) => {
if (!queryInstructions) return queryInstructions;
const type = "with" in queryInstructions ? "with" : null;
if (!type) return queryInstructions;
const nestedInstructions = queryInstructions[type];
if (!nestedInstructions || Array.isArray(nestedInstructions))
return queryInstructions;
const newNestedInstructions = { ...nestedInstructions };
for (const oldKey of Object.keys(newNestedInstructions)) {
if (oldKey !== "titleIdentifier" && oldKey !== "slugIdentifier") continue;
const identifierName = oldKey === "titleIdentifier" ? "title" : "slug";
const value = newNestedInstructions[oldKey];
const newKey = identifiers?.[identifierName] || "id";
newNestedInstructions[newKey] = value;
delete newNestedInstructions[oldKey];
}
return {
...queryInstructions,
[type]: newNestedInstructions
};
};
// src/instructions/before-after.ts
var CURSOR_SEPARATOR = ",";
var CURSOR_NULL_PLACEHOLDER = "RONIN_NULL";
var handleBeforeOrAfter = (schema, statementValues, instructions, rootTable) => {
var handleBeforeOrAfter = (schema, statementParams, instructions, rootTable) => {
if (!(instructions.before || instructions.after)) {
throw new RoninError({
message: "The `before` or `after` instruction must not be empty.",
code: "MISSING_INSTRUCTION",
queries: null
code: "MISSING_INSTRUCTION"
});

@@ -623,6 +906,14 @@ }

message: "The `before` and `after` instructions cannot co-exist. Choose one.",
code: "MUTUALLY_EXCLUSIVE_INSTRUCTIONS",
queries: null
code: "MUTUALLY_EXCLUSIVE_INSTRUCTIONS"
});
}
if (!instructions.limitedTo) {
let message = "When providing a pagination cursor in the `before` or `after`";
message += " instruction, a `limitedTo` instruction must be provided as well, to";
message += " define the page size.";
throw new RoninError({
message,
code: "MISSING_INSTRUCTION"
});
}
const { ascending = [], descending = [] } = instructions.orderedBy || {};

@@ -639,6 +930,6 @@ const clause = instructions.with ? "AND " : "";

if (field.type === "boolean") {
return prepareStatementValue(statementValues, value === "true");
return prepareStatementValue(statementParams, value === "true");
}
if (field.type === "number") {
return prepareStatementValue(statementValues, Number.parseInt(value));
return prepareStatementValue(statementParams, Number.parseInt(value));
}

@@ -648,3 +939,3 @@ if (field.type === "date") {

}
return prepareStatementValue(statementValues, value);
return prepareStatementValue(statementParams, value);
});

@@ -692,3 +983,3 @@ const compareOperators = [

// src/instructions/for.ts
var handleFor = (schemas, schema, statementValues, instruction, rootTable) => {
var handleFor = (schemas, schema, statementParams, instruction, rootTable) => {
let statement = "";

@@ -701,3 +992,3 @@ if (!instruction) return statement;

throw new RoninError({
message: `The provided \`for\` shortcut "${shortcut}" does not exist in schema "${getSchemaName(schema)}".`,
message: `The provided \`for\` shortcut "${shortcut}" does not exist in schema "${schema.name}".`,
code: "INVALID_FOR_VALUE"

@@ -707,3 +998,3 @@ });

const replacedForFilter = structuredClone(forFilter);
replaceInObject(
findInObject(
replacedForFilter,

@@ -716,3 +1007,3 @@ RONIN_SCHEMA_SYMBOLS.VALUE,

schema,
statementValues,
statementParams,
"for",

@@ -728,3 +1019,3 @@ replacedForFilter,

// src/instructions/including.ts
var handleIncluding = (schemas, statementValues, schema, instruction, rootTable) => {
var handleIncluding = (schemas, statementParams, schema, instruction, rootTable) => {
let statement = "";

@@ -737,3 +1028,3 @@ let rootTableSubQuery;

throw new RoninError({
message: `The provided \`including\` shortcut "${shortcut}" does not exist in schema "${getSchemaName(schema)}".`,
message: `The provided \`including\` shortcut "${shortcut}" does not exist in schema "${schema.name}".`,
code: "INVALID_INCLUDING_VALUE"

@@ -764,5 +1055,5 @@ });

schemas,
{ statementValues }
statementParams
);
relatedTableSelector = `(${subSelect.readStatement})`;
relatedTableSelector = `(${subSelect.main.statement})`;
}

@@ -778,3 +1069,3 @@ statement += `${joinType} JOIN ${relatedTableSelector} as ${tableAlias}`;

relatedSchema,
statementValues,
statementParams,
"including",

@@ -795,5 +1086,6 @@ queryInstructions?.with,

var handleLimitedTo = (single, instruction) => {
const pageSize = instruction || 100;
const finalPageSize = pageSize + 1;
return `LIMIT ${single ? "1" : finalPageSize} `;
let amount;
if (instruction) amount = instruction + 1;
if (single) amount = 1;
return `LIMIT ${amount} `;
};

@@ -834,3 +1126,3 @@

// src/instructions/selecting.ts
var handleSelecting = (schema, statementValues, instructions) => {
var handleSelecting = (schema, statementParams, instructions) => {
let statement = instructions.selecting ? instructions.selecting.map((slug) => {

@@ -846,3 +1138,3 @@ return getFieldFromSchema(schema, slug, "selecting").fieldSelector;

}).map(([key, value]) => {
return `${prepareStatementValue(statementValues, value)} as "${key}"`;
return `${prepareStatementValue(statementParams, value)} as "${key}"`;
}).join(", ");

@@ -854,3 +1146,3 @@ }

// src/instructions/to.ts
var handleTo = (schemas, schema, statementValues, queryType, writeStatements, instructions, rootTable) => {
var handleTo = (schemas, schema, statementParams, queryType, dependencyStatements, instructions, rootTable) => {
const currentTime = (/* @__PURE__ */ new Date()).toISOString();

@@ -897,5 +1189,3 @@ const { with: withInstruction, to: toInstruction } = instructions;

}
return compileQueryInput(subQuery, schemas, {
statementValues
}).readStatement;
return compileQueryInput(subQuery, schemas, statementParams).main.statement;
}

@@ -913,6 +1203,6 @@ Object.assign(toInstruction, defaultFields);

const composeStatement = (subQueryType, value) => {
const origin = queryType === "create" ? { id: toInstruction.id } : withInstruction;
const recordDetails = { origin };
const source = queryType === "create" ? { id: toInstruction.id } : withInstruction;
const recordDetails = { source };
if (value) recordDetails.target = value;
const { readStatement } = compileQueryInput(
return compileQueryInput(
{

@@ -924,17 +1214,17 @@ [subQueryType]: {

schemas,
{ statementValues, disableReturning: true }
);
return readStatement;
[],
{ returning: false }
).main;
};
if (Array.isArray(fieldValue)) {
writeStatements.push(composeStatement("drop"));
dependencyStatements.push(composeStatement("drop"));
for (const record of fieldValue) {
writeStatements.push(composeStatement("create", record));
dependencyStatements.push(composeStatement("create", record));
}
} else if (isObject(fieldValue)) {
for (const recordToAdd of fieldValue.containing || []) {
writeStatements.push(composeStatement("create", recordToAdd));
dependencyStatements.push(composeStatement("create", recordToAdd));
}
for (const recordToRemove of fieldValue.notContaining || []) {
writeStatements.push(composeStatement("drop", recordToRemove));
dependencyStatements.push(composeStatement("drop", recordToRemove));
}

@@ -947,3 +1237,3 @@ }

schema,
statementValues,
statementParams,
"to",

@@ -960,3 +1250,3 @@ toInstruction,

schema,
statementValues,
statementParams,
"to",

@@ -976,7 +1266,6 @@ toInstruction,

// src/index.ts
var compileQueryInput = (query, defaultSchemas, options) => {
// src/utils/index.ts
var compileQueryInput = (query, schemas, statementParams, options) => {
const parsedQuery = splitQuery(query);
const { queryType, querySchema, queryInstructions } = parsedQuery;
const schemas = addSystemSchemas(defaultSchemas);
const schema = getSchemaBySlug(schemas, querySchema);

@@ -986,6 +1275,10 @@ const single = querySchema !== schema.pluralSlug;

let table = getTableForSchema(schema);
const statementValues = options?.statementValues || [];
const writeStatements = [];
addSchemaQueries(parsedQuery, writeStatements);
const columns = handleSelecting(schema, statementValues, {
const dependencyStatements = [];
const returning = options?.returning ?? true;
instructions = addSchemaQueries(
schemas,
{ queryType, querySchema, queryInstructions: instructions },
dependencyStatements
);
const columns = handleSelecting(schema, statementParams, {
selecting: instructions?.selecting,

@@ -1019,3 +1312,3 @@ including: instructions?.including

rootTableName
} = handleIncluding(schemas, statementValues, schema, instructions?.including, table);
} = handleIncluding(schemas, statementParams, schema, instructions?.including, table);
if (rootTableSubQuery && rootTableName) {

@@ -1043,5 +1336,5 @@ table = rootTableName;

schema,
statementValues,
statementParams,
queryType,
writeStatements,
dependencyStatements,
{ with: instructions.with, to: instructions.to },

@@ -1057,3 +1350,3 @@ isJoining ? table : void 0

schema,
statementValues,
statementParams,
instructions?.with,

@@ -1068,3 +1361,3 @@ isJoining ? table : void 0

schema,
statementValues,
statementParams,
instructions?.for,

@@ -1075,3 +1368,3 @@ isJoining ? table : void 0

}
if ((queryType === "get" || queryType === "count") && !single) {
if ((queryType === "get" || queryType === "count") && !single && instructions?.limitedTo) {
instructions = instructions || {};

@@ -1098,3 +1391,3 @@ instructions.orderedBy = instructions.orderedBy || {};

schema,
statementValues,
statementParams,
{

@@ -1104,3 +1397,4 @@ before: instructions.before,

with: instructions.with,
orderedBy: instructions.orderedBy
orderedBy: instructions.orderedBy,
limitedTo: instructions.limitedTo
},

@@ -1126,17 +1420,42 @@ isJoining ? table : void 0

}
if (queryType === "get" && !isJoiningMultipleRows) {
if (queryType === "get" && !isJoiningMultipleRows && (single || instructions?.limitedTo)) {
statement += handleLimitedTo(single, instructions?.limitedTo);
}
if (["create", "set", "drop"].includes(queryType) && !options?.disableReturning) {
if (["create", "set", "drop"].includes(queryType) && returning) {
statement += "RETURNING * ";
}
const finalStatement = statement.trimEnd();
const mainStatement = {
statement: statement.trimEnd(),
params: statementParams || []
};
if (returning) mainStatement.returning = true;
return {
writeStatements,
readStatement: finalStatement,
values: statementValues
dependencies: dependencyStatements,
main: mainStatement
};
};
// src/index.ts
var compileQueries = (queries, schemas, options) => {
const schemaList = addSystemSchemas(schemas).map((schema) => {
return addDefaultSchemaFields(schema, true);
});
const schemaListWithShortcuts = schemaList.map((schema) => {
return addDefaultSchemaShortcuts(schemaList, schema);
});
const dependencyStatements = [];
const mainStatements = [];
for (const query of queries) {
const result = compileQueryInput(
query,
schemaListWithShortcuts,
options?.inlineValues ? null : []
);
dependencyStatements.push(...result.dependencies);
mainStatements.push(result.main);
}
return [...dependencyStatements, ...mainStatements];
};
export {
compileQueryInput
compileQueries
};
{
"name": "@ronin/compiler",
"version": "0.1.0",
"version": "0.1.1-leo-ron-1083-experimental.58",
"type": "module",

@@ -11,3 +11,5 @@ "description": "Compiles RONIN queries to SQL statements.",

"types": "./dist/index.d.ts",
"files": ["dist"],
"files": [
"dist"
],
"scripts": {

@@ -22,7 +24,12 @@ "lint": "bun run --bun lint:tsc && bun run --bun lint:biome",

},
"keywords": ["query", "compiler", "sql"],
"keywords": [
"query",
"compiler",
"sql"
],
"author": "ronin",
"license": "MIT",
"license": "Apache-2.0",
"dependencies": {
"@paralleldrive/cuid2": "2.2.2"
"@paralleldrive/cuid2": "2.2.2",
"title": "3.5.3"
},

@@ -32,2 +39,3 @@ "devDependencies": {

"@types/bun": "1.1.10",
"@types/title": "3.4.3",
"tsup": "8.3.0",

@@ -34,0 +42,0 @@ "typescript": "5.6.2",

# RONIN Compiler
This package compiles RONIN queries to SQL statements.
This package compiles [RONIN queries](https://ronin.co/docs/queries) to SQL statements.
## Usage
## Setup
You don't need to install this package explicitly, as it is already included in the [RONIN client](https://github.com/ronin-co/client).
However, we would be excited to welcome your feature suggestions or bug fixes for the RONIN compiler. Read on to learn more about how to suggest changes.
## Contributing
To start contributing code, first make sure you have [Bun](https://bun.sh) installed, which is a JavaScript runtime.
Next, [clone the repo](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) and install its dependencies:
```bash
bun install
```
Once that's done, link the package to make it available to all of your local projects:
```bash
bun link
```
Inside your project, you can then run the following command, which is similar to `bun add @ronin/compiler` or `npm install @ronin/compiler`, except that it doesn't install `@ronin/compiler` from npm, but instead uses your local clone of the package:
```bash
bun link @ronin/compiler
```
If your project is not yet compatible with [Bun](https://bun.sh), feel free to replace all of the occurrences of the word `bun` in the commands above with `npm` instead.
You will just need to make sure that, once you [create a pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request#creating-the-pull-request) on the current repo, it will not contain a `package-lock.json` file, which is usually generated by npm. Instead, we're using the `bun.lockb` file for this purpose (locking sub dependencies to a certain version).
### Developing
The programmatic API of the RONIN compiler looks like this:
```typescript
import { compileQueryInput } from '@ronin/compiler';
import {
compileQueries,
const query = {
type Query,
type Schema,
type Statement
} from '@ronin/compiler';
const queries: Array<Query> = [{
get: {
accounts: null,
},
};
accounts: null
}
}];
const schemas = [
{
pluralSlug: 'accounts',
slug: 'account',
},
];
const schemas: Array<Schema> = [{
slug: 'account'
}];
const { writeStatements, readStatement } = compileQueryInput(query, schemas);
const statements: Array<Statements> = compileQueries(queries, schemas);
// [{
// statement: 'SELECT * FROM "accounts"',
// params: [],
// returning: true,
// }]
```
console.log(readStatement);
// SELECT * FROM "accounts" ORDER BY "ronin.createdAt" DESC LIMIT 101
#### Options
To fine-tune the behavior of the compiler, you can pass the following options:
```typescript
compileQueries(queries, schemas, {
// Instead of returning an array of values for every statement (which allows for
// preventing SQL injections), all values are inlined directly into the SQL strings.
// This option should only be used if the generated SQL will be manually verified.
inlineValues: true
});
```
## Testing
#### Transpilation
Use the following command to run the test suite:
In order to be compatible with a wide range of projects, the source code of the `compiler` repo needs to be compiled (transpiled) whenever you make changes to it. To automate this, you can keep this command running in your terminal:
```bash
bun run dev
```
Whenever you make a change to the source code, it will then automatically be transpiled again.
### Running Tests
The RONIN compiler has 100% test coverage, which means that every single line of code is tested automatically, to ensure that any change to the source code doesn't cause a regression.
Before you create a pull request on the `compiler` repo, it is therefore advised to run those tests in order to ensure everything works as expected:
```bash
# Run all tests
bun test
# Alternatively, run a single test
bun test -t 'your test name'
```

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc