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
150
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.10

LICENSE

315

dist/index.js
// src/utils/index.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"

@@ -47,11 +54,18 @@ };

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

// src/utils/schema.ts
import title from "title";
var getSchemaBySlug = (schemas, slug) => {

@@ -102,5 +117,2 @@ const schema = schemas.find((schema2) => {

};
var getSchemaName = (schema) => {
return schema.name || schema.slug;
};
var composeMetaSchemaSlug = (suffix) => convertToCamelCase(`ronin_${suffix}`);

@@ -132,3 +144,3 @@ var composeAssociationSchemaSlug = (schema, field) => composeMetaSchemaSlug(`${schema.pluralSlug}_${field.slug}`);

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

@@ -166,4 +178,3 @@ field: fieldPath,

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

@@ -178,4 +189,3 @@ },

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

@@ -210,14 +220,69 @@ }

{ 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",
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",
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: "effect", 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);
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) {

@@ -227,3 +292,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;

@@ -237,5 +302,5 @@ if (field.kind === "many") {

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

@@ -245,3 +310,3 @@ {

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

@@ -262,3 +327,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");

@@ -306,19 +371,28 @@ 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) => {
var addSchemaQueries = (schemas, statementValues, queryDetails, writeStatements) => {
const { queryType, querySchema, queryInstructions } = queryDetails;
if (!["create", "set", "drop"].includes(queryType)) return;
if (!["schema", "schemas", "field", "fields"].includes(querySchema)) return;
if (!SYSTEM_SCHEMA_SLUGS.includes(querySchema)) return;
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";

@@ -329,3 +403,2 @@ break;

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

@@ -335,4 +408,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";

@@ -342,14 +416,49 @@ 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;
let statement2 = `${tableAction}${unique ? " UNIQUE" : ""} INDEX "${indexName}"`;
if (queryType === "create") statement2 += ` ON "${tableName}"`;
writeStatements.push(statement2);
return;
}
if (kind === "triggers") {
const triggerName = convertToSnakeCase(slug);
let statement2 = `${tableAction} TRIGGER "${triggerName}"`;
if (queryType === "create") {
const cause = slugToName(instructionList?.cause).toUpperCase();
const effectQuery = instructionList?.effect[RONIN_SCHEMA_SYMBOLS.QUERY];
const referencesRecord = findInObject(effectQuery, RONIN_SCHEMA_SYMBOLS.FIELD);
const forEach = referencesRecord ? "FOR EACH ROW " : "";
const { readStatement } = compileQueryInput(effectQuery, schemas, {
statementValues,
disableReturning: true
});
instructionList.effect = effectQuery;
statement2 += ` ${cause} ON "${tableName}" ${forEach}BEGIN ${readStatement}`;
}
writeStatements.push(statement2);
return;
}
let statement = `${tableAction} TABLE "${tableName}"`;
if (kind === "schemas") {
const fields = [...SYSTEM_FIELDS];
if (queryType === "create") {

@@ -359,17 +468,12 @@ const columns = fields.map(getFieldStatement).filter(Boolean);

} else if (queryType === "set") {
const newSlug = queryInstructions.to?.pluralSlug;
const newSlug = queryInstructions.to?.slug;
if (newSlug) {
const newTable = convertToSnakeCase(newSlug);
const newTable = convertToSnakeCase(pluralize(newSlug));
statement += ` RENAME TO "${newTable}"`;
}
}
} 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;
}
if (kind === "fields") {
if (queryType === "create") {

@@ -387,26 +491,50 @@ 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);
};
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`;
};
// src/instructions/with.ts
var WITH_CONDITIONS = [
"being",
"notBeing",
"startingWith",
"notStartingWith",
"endingWith",
"notEndingWith",
"containing",
"notContaining",
"greaterThan",
"greaterOrEqual",
"lessThan",
"lessOrEqual"
];
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) => {

@@ -454,4 +582,13 @@ const subStatement = composeConditions(

} 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, "")}"`;
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") {

@@ -468,23 +605,7 @@ conditionSelector = `"${schemaField.slug}"`;

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 `${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) => WITH_CONDITIONS.includes(key)
)) {
if (isNested && Object.keys(value).every((key) => key in WITH_CONDITIONS)) {
const conditions = Object.entries(value).map(

@@ -525,3 +646,3 @@ ([conditionType, checkValue]) => composeConditions(schemas, schema, statementValues, instructionName, checkValue, {

} else {
const relatedSchema = getSchemaBySlug(schemas, schemaField.schema);
const relatedSchema = getSchemaBySlug(schemas, schemaField.target.slug);
const subQuery = {

@@ -580,10 +701,2 @@ get: {

};
var getMatcher = (value, negative) => {
if (negative) {
if (value === null) return "IS NOT";
return "!=";
}
if (value === null) return "IS";
return "=";
};
var formatIdentifiers = ({ identifiers }, queryInstructions) => {

@@ -699,3 +812,3 @@ if (!queryInstructions) return queryInstructions;

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"

@@ -705,3 +818,3 @@ });

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

@@ -733,3 +846,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"

@@ -902,4 +1015,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;

@@ -973,3 +1086,3 @@ const { readStatement } = compileQueryInput(

const writeStatements = [];
addSchemaQueries(parsedQuery, writeStatements);
addSchemaQueries(schemas, statementValues, parsedQuery, writeStatements);
const columns = handleSelecting(schema, statementValues, {

@@ -976,0 +1089,0 @@ selecting: instructions?.selecting,

{
"name": "@ronin/compiler",
"version": "0.1.0",
"version": "0.1.1-leo-ron-1083-experimental.10",
"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

@@ -18,3 +52,2 @@ import { compileQueryInput } from '@ronin/compiler';

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

@@ -30,8 +63,22 @@ },

## Testing
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:
Use the following command to run the test suite:
```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