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
157
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.29

LICENSE

737

dist/index.js

@@ -1,6 +0,13 @@

// 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"

@@ -29,3 +36,3 @@ };

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 +55,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 +100,207 @@ var flatten = (obj, prefix = "", res = {}) => {

// src/utils/statement.ts
var prepareStatementValue = (statementValues, value) => {
if (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)) {
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(statementValues, value);
conditionValue = `IIF(${conditionSelector} IS NULL, ${preparedValue}, json_patch(${conditionSelector}, ${preparedValue}))`;
}
} else if (collectStatementValue) {
conditionValue = prepareStatementValue(statementValues, 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, statementValues, 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, 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.target.slug);
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 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, statementValues, instruction, rootTable) => {
const subStatement = composeConditions(
schemas,
schema,
statementValues,
"with",
instruction,
{ rootTable }
);
return `(${subStatement})`;
};
// src/utils/schema.ts
import title from "title";
var getSchemaBySlug = (schemas, slug) => {

@@ -103,9 +321,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 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 +349,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",

@@ -167,4 +383,3 @@ field: fieldPath,

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

@@ -179,4 +394,3 @@ },

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

@@ -191,2 +405,6 @@ }

pluralSlug: "schemas",
identifiers: {
name: "name",
slug: "slug"
},
fields: [

@@ -200,4 +418,7 @@ ...SYSTEM_FIELDS,

{ 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" }
]

@@ -210,17 +431,88 @@ },

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" }
]
},
{
name: "Index",
pluralName: "Indexes",
slug: "index",
pluralSlug: "indexes",
identifiers: {
name: "slug",
slug: "slug"
},
fields: [
...SYSTEM_FIELDS,
{ slug: "slug", type: "string", required: true },
{
slug: "schema",
type: "reference",
target: { slug: "schema" },
required: true
},
{ slug: "unique", type: "boolean" },
{ slug: "filter", type: "json" }
]
},
{
name: "Trigger",
pluralName: "Triggers",
slug: "trigger",
pluralSlug: "triggers",
identifiers: {
name: "slug",
slug: "slug"
},
fields: [
...SYSTEM_FIELDS,
{ slug: "slug", type: "string", required: true },
{ slug: "schema", type: "reference", target: { slug: "schema" }, required: true },
{ slug: "cause", type: "string", required: true },
{ slug: "filter", type: "json" },
{ slug: "effects", type: "json", required: true }
]
}
];
var SYSTEM_SCHEMA_SLUGS = SYSTEM_SCHEMAS.flatMap(({ slug, pluralSlug }) => [
slug,
pluralSlug
]);
var prepareSchema = (schema) => {
const copiedSchema = { ...schema };
if (!copiedSchema.pluralSlug) copiedSchema.pluralSlug = pluralize(copiedSchema.slug);
if (!copiedSchema.name) copiedSchema.name = slugToName(copiedSchema.slug);
if (!copiedSchema.pluralName)
copiedSchema.pluralName = slugToName(copiedSchema.pluralSlug);
if (!copiedSchema.idPrefix) copiedSchema.idPrefix = copiedSchema.slug.slice(0, 3);
if (!copiedSchema.identifiers) copiedSchema.identifiers = {};
if (!copiedSchema.identifiers.name) copiedSchema.identifiers.name = "id";
if (!copiedSchema.identifiers.slug) copiedSchema.identifiers.slug = "id";
return copiedSchema;
};
var addSystemSchemas = (schemas) => {
const list = [...SYSTEM_SCHEMAS, ...schemas].map((schema) => ({ ...schema }));
const list = [...SYSTEM_SCHEMAS, ...schemas].map(prepareSchema);
for (const schema of list) {

@@ -230,3 +522,3 @@ const defaultIncluding = {};

if (field.type === "reference" && !field.slug.startsWith("ronin.")) {
const relatedSchema = getSchemaBySlug(list, field.schema);
const relatedSchema = getSchemaBySlug(list, field.target.slug);
let fieldSlug = relatedSchema.slug;

@@ -238,7 +530,11 @@ if (field.kind === "many") {

slug: fieldSlug,
identifiers: {
name: "id",
slug: "id"
},
fields: [
{
slug: "origin",
slug: "source",
type: "reference",
schema: schema.slug
target: schema
},

@@ -248,3 +544,3 @@ {

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

@@ -265,3 +561,3 @@ ]

};
const relatedSchemaToModify = list.find((schema2) => schema2.slug === field.schema);
const relatedSchemaToModify = getSchemaBySlug(list, field.target.slug);
if (!relatedSchemaToModify) throw new Error("Missing related schema");

@@ -309,19 +605,33 @@ relatedSchemaToModify.including = {

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, statementValues, queryDetails, writeStatements) => {
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";

@@ -332,3 +642,2 @@ break;

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

@@ -338,4 +647,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";

@@ -345,14 +655,85 @@ 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 tableName = convertToSnakeCase(pluralize(kind === "schemas" ? slug : schemaSlug));
if (kind === "indexes") {
const indexName = convertToSnakeCase(slug);
const unique = instructionList?.unique;
const filterQuery = instructionList?.filter;
let statement2 = `${tableAction}${unique ? " UNIQUE" : ""} INDEX "${indexName}"`;
if (queryType === "create") {
statement2 += ` ON "${tableName}"`;
if (filterQuery) {
const targetSchema = getSchemaBySlug(schemas, schemaSlug);
const withStatement = handleWith(
schemas,
targetSchema,
statementValues,
filterQuery
);
statement2 += ` WHERE (${withStatement})`;
}
}
writeStatements.push(statement2);
return queryInstructions;
}
if (kind === "triggers") {
const triggerName = convertToSnakeCase(slug);
let statement2 = `${tableAction} TRIGGER "${triggerName}"`;
if (queryType === "create") {
const cause = slugToName(instructionList?.cause).toUpperCase();
const statementParts = [cause, "ON", `"${tableName}"`];
const effectQueries = instructionList?.effects;
const filterQuery = instructionList?.filter;
if (filterQuery || effectQueries.some((query) => findInObject(query, RONIN_SCHEMA_SYMBOLS.FIELD))) {
statementParts.push("FOR EACH ROW");
}
if (filterQuery) {
const targetSchema = getSchemaBySlug(schemas, schemaSlug);
const tablePlaceholder = cause.endsWith("DELETE") ? RONIN_SCHEMA_SYMBOLS.FIELD_OLD : RONIN_SCHEMA_SYMBOLS.FIELD_NEW;
const withStatement = handleWith(
schemas,
targetSchema,
statementValues,
filterQuery,
tablePlaceholder
);
statementParts.push("WHEN", `(${withStatement})`);
}
const effectStatements = effectQueries.map((effectQuery) => {
return compileQueryInput(effectQuery, schemas, {
statementValues,
disableReturning: true
}).readStatement;
});
if (effectStatements.length > 1) statementParts.push("BEGIN");
statementParts.push(effectStatements.join("; "));
if (effectStatements.length > 1) statementParts.push("END");
statement2 += ` ${statementParts.join(" ")}`;
}
writeStatements.push(statement2);
return queryInstructions;
}
let statement = `${tableAction} TABLE "${tableName}"`;
if (kind === "schemas") {
const providedFields = instructionList?.fields || [];
const fields = [...SYSTEM_FIELDS, ...providedFields];
if (queryType === "create" || queryType === "set") {
queryInstructions.to = prepareSchema(queryInstructions.to);
}
if (queryType === "create") {

@@ -368,11 +749,6 @@ const columns = fields.map(getFieldStatement).filter(Boolean);

}
} 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"]
});
}
writeStatements.push(statement);
return queryInstructions;
}
if (kind === "fields") {
if (queryType === "create") {

@@ -390,220 +766,27 @@ if (!instructionList.type) {

if (newSlug) {
statement += ` RENAME COLUMN "${fieldSlug}" TO "${newSlug}"`;
statement += ` RENAME COLUMN "${slug}" TO "${newSlug}"`;
}
} else if (queryType === "drop") {
statement += ` DROP COLUMN "${fieldSlug}"`;
statement += ` DROP COLUMN "${slug}"`;
}
writeStatements.push(statement);
}
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})`;
var slugToName = (slug) => {
const name = slug.replace(/([a-z])([A-Z])/g, "$1 $2");
return title(name);
};
// 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;
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`;
}
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 (lastLetter === "s" || word.slice(-2).toLowerCase() === "ch" || word.slice(-2).toLowerCase() === "sh" || word.slice(-2).toLowerCase() === "ex") {
return `${word}es`;
}
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(" ")}`;
return `${word}s`;
};
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
};
};

@@ -698,3 +881,3 @@ // src/instructions/before-after.ts

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"

@@ -704,3 +887,3 @@ });

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

@@ -732,3 +915,3 @@ RONIN_SCHEMA_SYMBOLS.VALUE,

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"

@@ -901,4 +1084,4 @@ });

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;

@@ -961,3 +1144,3 @@ const { readStatement } = compileQueryInput(

// src/index.ts
// src/utils/index.ts
var compileQueryInput = (query, defaultSchemas, options) => {

@@ -973,3 +1156,8 @@ const parsedQuery = splitQuery(query);

const writeStatements = [];
addSchemaQueries(parsedQuery, writeStatements);
instructions = addSchemaQueries(
schemas,
statementValues,
{ queryType, querySchema, queryInstructions: instructions },
writeStatements
);
const columns = handleSelecting(schema, statementValues, {

@@ -1117,4 +1305,11 @@ selecting: instructions?.selecting,

};
// src/index.ts
var compileQuery = (query, defaultSchemas, options) => {
return compileQueryInput(query, defaultSchemas, {
inlineValues: options?.inlineValues
});
};
export {
compileQueryInput
compileQuery
};
{
"name": "@ronin/compiler",
"version": "0.1.0",
"version": "0.1.1-leo-ron-1083-experimental.29",
"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 { compileQuery } from '@ronin/compiler';

@@ -18,3 +52,2 @@ const query = {

{
pluralSlug: 'accounts',
slug: 'account',

@@ -24,3 +57,3 @@ },

const { writeStatements, readStatement } = compileQueryInput(query, schemas);
const { writeStatements, readStatement } = compileQuery(query, schemas);

@@ -31,8 +64,37 @@ console.log(readStatement);

## Testing
#### Options
Use the following command to run the test suite:
To fine-tune the behavior of the compiler, you can pass the following options:
```typescript
compileQuery(query, 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
});
```
#### Transpilation
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