@ronin/compiler 0.13.8 → 0.13.9-leo-ron-1071-experimental-302
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +72 -36
- package/package.json +1 -4
package/dist/index.d.ts
CHANGED
@@ -15,7 +15,7 @@ declare const QUERY_SYMBOLS: {
|
|
15
15
|
readonly FIELD_PARENT_NEW: "__RONIN_FIELD_PARENT_NEW_";
|
16
16
|
readonly VALUE: "__RONIN_VALUE";
|
17
17
|
};
|
18
|
-
type RoninErrorCode = 'MODEL_NOT_FOUND' | 'FIELD_NOT_FOUND' | 'INDEX_NOT_FOUND' | 'TRIGGER_NOT_FOUND' | 'PRESET_NOT_FOUND' | 'INVALID_WITH_VALUE' | 'INVALID_TO_VALUE' | 'INVALID_INCLUDING_VALUE' | 'INVALID_FOR_VALUE' | 'INVALID_BEFORE_OR_AFTER_INSTRUCTION' | 'INVALID_MODEL_VALUE' | 'MUTUALLY_EXCLUSIVE_INSTRUCTIONS' | 'MISSING_INSTRUCTION' | 'MISSING_FIELD';
|
18
|
+
type RoninErrorCode = 'MODEL_NOT_FOUND' | 'FIELD_NOT_FOUND' | 'INDEX_NOT_FOUND' | 'TRIGGER_NOT_FOUND' | 'PRESET_NOT_FOUND' | 'INVALID_WITH_VALUE' | 'INVALID_TO_VALUE' | 'INVALID_INCLUDING_VALUE' | 'INVALID_FOR_VALUE' | 'INVALID_BEFORE_OR_AFTER_INSTRUCTION' | 'INVALID_MODEL_VALUE' | 'EXISTING_MODEL_ENTITY' | 'REQUIRED_MODEL_ENTITY' | 'MUTUALLY_EXCLUSIVE_INSTRUCTIONS' | 'MISSING_INSTRUCTION' | 'MISSING_FIELD';
|
19
19
|
interface Issue {
|
20
20
|
message: string;
|
21
21
|
path: Array<string | number>;
|
package/dist/index.js
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
// src/utils/helpers.ts
|
2
|
-
import { init as cuid } from "@paralleldrive/cuid2";
|
3
2
|
var QUERY_SYMBOLS = {
|
4
3
|
// Represents a sub query.
|
5
4
|
QUERY: "__RONIN_QUERY",
|
@@ -54,7 +53,6 @@ var DOUBLE_QUOTE_REGEX = /"/g;
|
|
54
53
|
var AMPERSAND_REGEX = /\s*&+\s*/g;
|
55
54
|
var SPECIAL_CHARACTERS_REGEX = /[^\w\s-]+/g;
|
56
55
|
var SPLIT_REGEX = /(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|[\s.\-_]+/;
|
57
|
-
var generateRecordId = (prefix) => `${prefix}_${cuid({ length: 16 })()}`;
|
58
56
|
var capitalize = (str) => {
|
59
57
|
if (!str || str.length === 0) return "";
|
60
58
|
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
@@ -186,7 +184,7 @@ var parseFieldExpression = (model, instructionName, expression, parentModel) =>
|
|
186
184
|
}
|
187
185
|
}
|
188
186
|
const fieldSlug = match.replace(toReplace, "");
|
189
|
-
const field = getFieldFromModel(rootModel, fieldSlug, instructionName);
|
187
|
+
const field = getFieldFromModel(rootModel, fieldSlug, { instructionName });
|
190
188
|
return field.fieldSelector;
|
191
189
|
});
|
192
190
|
};
|
@@ -194,7 +192,7 @@ var composeFieldValues = (models, model, statementParams, instructionName, value
|
|
194
192
|
const { fieldSelector: conditionSelector } = getFieldFromModel(
|
195
193
|
model,
|
196
194
|
options.fieldSlug,
|
197
|
-
instructionName
|
195
|
+
{ instructionName }
|
198
196
|
);
|
199
197
|
const collectStatementValue = options.type !== "fields";
|
200
198
|
const symbol = getSymbol(value);
|
@@ -235,7 +233,9 @@ var composeConditions = (models, model, statementParams, instructionName, value,
|
|
235
233
|
return slug.includes(".") && slug.split(".")[0] === options.fieldSlug;
|
236
234
|
});
|
237
235
|
if (!childField) {
|
238
|
-
const fieldDetails = getFieldFromModel(model, options.fieldSlug,
|
236
|
+
const fieldDetails = getFieldFromModel(model, options.fieldSlug, {
|
237
|
+
instructionName
|
238
|
+
});
|
239
239
|
const { field: modelField } = fieldDetails || {};
|
240
240
|
const consumeJSON = modelField?.type === "json" && instructionName === "to";
|
241
241
|
if (modelField && !(isObject(value) || Array.isArray(value)) || getSymbol(value) || consumeJSON) {
|
@@ -567,10 +567,10 @@ var getModelBySlug = (models, slug) => {
|
|
567
567
|
return model;
|
568
568
|
};
|
569
569
|
var composeAssociationModelSlug = (model, field) => convertToCamelCase(`ronin_link_${model.slug}_${field.slug}`);
|
570
|
-
var getFieldSelector = (model, field, fieldPath,
|
570
|
+
var getFieldSelector = (model, field, fieldPath, writing) => {
|
571
571
|
const symbol = model.tableAlias?.startsWith(QUERY_SYMBOLS.FIELD_PARENT) ? `${model.tableAlias.replace(QUERY_SYMBOLS.FIELD_PARENT, "").slice(0, -1)}.` : "";
|
572
572
|
const tablePrefix = symbol || (model.tableAlias ? `"${model.tableAlias}".` : "");
|
573
|
-
if (field.type === "json" &&
|
573
|
+
if (field.type === "json" && !writing) {
|
574
574
|
const dotParts = fieldPath.split(".");
|
575
575
|
const columnName = tablePrefix + dotParts.shift();
|
576
576
|
const jsonField = dotParts.join(".");
|
@@ -578,19 +578,16 @@ var getFieldSelector = (model, field, fieldPath, instructionName) => {
|
|
578
578
|
}
|
579
579
|
return `${tablePrefix}"${fieldPath}"`;
|
580
580
|
};
|
581
|
-
function getFieldFromModel(model, fieldPath,
|
582
|
-
const
|
581
|
+
function getFieldFromModel(model, fieldPath, source, shouldThrow = true) {
|
582
|
+
const writingField = "instructionName" in source ? source.instructionName === "to" : true;
|
583
|
+
const errorTarget = "instructionName" in source ? `\`${source.instructionName}\`` : `${source.modelEntityType} "${source.modelEntityName}"`;
|
584
|
+
const errorPrefix = `Field "${fieldPath}" defined for ${errorTarget}`;
|
583
585
|
const modelFields = model.fields || [];
|
584
586
|
let modelField;
|
585
587
|
if (fieldPath.includes(".")) {
|
586
588
|
modelField = modelFields.find((field) => field.slug === fieldPath.split(".")[0]);
|
587
589
|
if (modelField?.type === "json") {
|
588
|
-
const fieldSelector2 = getFieldSelector(
|
589
|
-
model,
|
590
|
-
modelField,
|
591
|
-
fieldPath,
|
592
|
-
instructionName
|
593
|
-
);
|
590
|
+
const fieldSelector2 = getFieldSelector(model, modelField, fieldPath, writingField);
|
594
591
|
return { field: modelField, fieldSelector: fieldSelector2 };
|
595
592
|
}
|
596
593
|
}
|
@@ -606,7 +603,7 @@ function getFieldFromModel(model, fieldPath, instructionName, shouldThrow = true
|
|
606
603
|
}
|
607
604
|
return null;
|
608
605
|
}
|
609
|
-
const fieldSelector = getFieldSelector(model, modelField, fieldPath,
|
606
|
+
const fieldSelector = getFieldSelector(model, modelField, fieldPath, writingField);
|
610
607
|
return { field: modelField, fieldSelector };
|
611
608
|
}
|
612
609
|
var slugToName = (slug) => {
|
@@ -653,16 +650,21 @@ var addDefaultModelFields = (model, isNew) => {
|
|
653
650
|
);
|
654
651
|
copiedModel.identifiers.slug = suitableField?.slug || "id";
|
655
652
|
}
|
656
|
-
copiedModel.fields = [...
|
653
|
+
copiedModel.fields = [...getSystemFields(copiedModel.idPrefix), ...newFields];
|
657
654
|
}
|
658
655
|
return copiedModel;
|
659
656
|
};
|
660
|
-
var
|
657
|
+
var getSystemFields = (idPrefix = "rec") => [
|
661
658
|
{
|
662
659
|
name: "ID",
|
663
660
|
type: "string",
|
664
661
|
slug: "id",
|
665
|
-
|
662
|
+
defaultValue: {
|
663
|
+
// Since default values in SQLite cannot rely on other columns, we unfortunately
|
664
|
+
// cannot rely on the `idPrefix` column here. Instead, we need to inject it directly
|
665
|
+
// into the expression as a static string.
|
666
|
+
[QUERY_SYMBOLS.EXPRESSION]: `'${idPrefix}_' || lower(substr(hex(randomblob(12)), 1, 16))`
|
667
|
+
}
|
666
668
|
},
|
667
669
|
{
|
668
670
|
name: "RONIN - Locked",
|
@@ -1012,6 +1014,13 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
|
|
1012
1014
|
});
|
1013
1015
|
}
|
1014
1016
|
const existingEntity = existingModel[pluralType]?.[targetEntityIndex];
|
1017
|
+
if (action === "create" && existingEntity) {
|
1018
|
+
throw new RoninError({
|
1019
|
+
message: `A ${entity} with the slug "${slug}" already exists.`,
|
1020
|
+
code: "EXISTING_MODEL_ENTITY",
|
1021
|
+
fields: ["slug"]
|
1022
|
+
});
|
1023
|
+
}
|
1015
1024
|
if (entity === "field") {
|
1016
1025
|
const statement = `ALTER TABLE "${existingModel.table}"`;
|
1017
1026
|
const existingField = existingEntity;
|
@@ -1040,6 +1049,14 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
|
|
1040
1049
|
}
|
1041
1050
|
}
|
1042
1051
|
} else if (action === "drop" && !existingLinkField) {
|
1052
|
+
const systemFields = getSystemFields(existingModel.idPrefix);
|
1053
|
+
const isSystemField = systemFields.some((field2) => field2.slug === slug);
|
1054
|
+
if (isSystemField) {
|
1055
|
+
throw new RoninError({
|
1056
|
+
message: `The ${entity} "${slug}" is a system ${entity} and cannot be removed.`,
|
1057
|
+
code: "REQUIRED_MODEL_ENTITY"
|
1058
|
+
});
|
1059
|
+
}
|
1043
1060
|
dependencyStatements.push({
|
1044
1061
|
statement: `${statement} DROP COLUMN "${slug}"`,
|
1045
1062
|
params: []
|
@@ -1052,10 +1069,20 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
|
|
1052
1069
|
const indexName = convertToSnakeCase(slug);
|
1053
1070
|
let statement = `${statementAction}${index?.unique ? " UNIQUE" : ""} INDEX "${indexName}"`;
|
1054
1071
|
if (action === "create") {
|
1072
|
+
if (!Array.isArray(index.fields) || index.fields.length === 0) {
|
1073
|
+
throw new RoninError({
|
1074
|
+
message: `When ${actionReadable} ${PLURAL_MODEL_ENTITIES[entity]}, at least one field must be provided.`,
|
1075
|
+
code: "INVALID_MODEL_VALUE",
|
1076
|
+
fields: ["fields"]
|
1077
|
+
});
|
1078
|
+
}
|
1055
1079
|
const columns = index.fields.map((field2) => {
|
1056
1080
|
let fieldSelector = "";
|
1057
1081
|
if ("slug" in field2) {
|
1058
|
-
({ fieldSelector } = getFieldFromModel(existingModel, field2.slug,
|
1082
|
+
({ fieldSelector } = getFieldFromModel(existingModel, field2.slug, {
|
1083
|
+
modelEntityType: "index",
|
1084
|
+
modelEntityName: indexName
|
1085
|
+
}));
|
1059
1086
|
} else if ("expression" in field2) {
|
1060
1087
|
fieldSelector = parseFieldExpression(existingModel, "to", field2.expression);
|
1061
1088
|
}
|
@@ -1086,7 +1113,10 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
|
|
1086
1113
|
});
|
1087
1114
|
}
|
1088
1115
|
const fieldSelectors = trigger.fields.map((field2) => {
|
1089
|
-
return getFieldFromModel(existingModel, field2.slug,
|
1116
|
+
return getFieldFromModel(existingModel, field2.slug, {
|
1117
|
+
modelEntityType: "trigger",
|
1118
|
+
modelEntityName: triggerName
|
1119
|
+
}).fieldSelector;
|
1090
1120
|
});
|
1091
1121
|
statementParts.push(`OF (${fieldSelectors.join(", ")})`);
|
1092
1122
|
}
|
@@ -1198,7 +1228,9 @@ var generatePaginationCursor = (model, orderedBy, record) => {
|
|
1198
1228
|
const cursors = keys.map((fieldSlug) => {
|
1199
1229
|
const property = getProperty(record, fieldSlug);
|
1200
1230
|
if (property === null || property === void 0) return CURSOR_NULL_PLACEHOLDER;
|
1201
|
-
const { field } = getFieldFromModel(model, fieldSlug,
|
1231
|
+
const { field } = getFieldFromModel(model, fieldSlug, {
|
1232
|
+
instructionName: "orderedBy"
|
1233
|
+
});
|
1202
1234
|
if (field.type === "date") return new Date(property).getTime();
|
1203
1235
|
return property;
|
1204
1236
|
});
|
@@ -1237,7 +1269,9 @@ var handleBeforeOrAfter = (model, statementParams, instructions) => {
|
|
1237
1269
|
if (value === CURSOR_NULL_PLACEHOLDER) {
|
1238
1270
|
return "NULL";
|
1239
1271
|
}
|
1240
|
-
const { field } = getFieldFromModel(model, key,
|
1272
|
+
const { field } = getFieldFromModel(model, key, {
|
1273
|
+
instructionName: "orderedBy"
|
1274
|
+
});
|
1241
1275
|
if (field.type === "boolean") {
|
1242
1276
|
return prepareStatementValue(statementParams, value === "true");
|
1243
1277
|
}
|
@@ -1263,7 +1297,9 @@ var handleBeforeOrAfter = (model, statementParams, instructions) => {
|
|
1263
1297
|
for (let j = 0; j <= i; j++) {
|
1264
1298
|
const key = keys[j];
|
1265
1299
|
const value = values[j];
|
1266
|
-
let { field, fieldSelector } = getFieldFromModel(model, key,
|
1300
|
+
let { field, fieldSelector } = getFieldFromModel(model, key, {
|
1301
|
+
instructionName: "orderedBy"
|
1302
|
+
});
|
1267
1303
|
if (j === i) {
|
1268
1304
|
const closingParentheses = ")".repeat(condition.length);
|
1269
1305
|
const operator = value === "NULL" ? "IS NOT" : compareOperators[j];
|
@@ -1415,7 +1451,7 @@ var handleOrderedBy = (model, instruction) => {
|
|
1415
1451
|
const { field: modelField, fieldSelector } = getFieldFromModel(
|
1416
1452
|
model,
|
1417
1453
|
item.value,
|
1418
|
-
instructionName
|
1454
|
+
{ instructionName }
|
1419
1455
|
);
|
1420
1456
|
const caseInsensitiveStatement = modelField.type === "string" ? " COLLATE NOCASE" : "";
|
1421
1457
|
statement += `${fieldSelector}${caseInsensitiveStatement} ${item.order}`;
|
@@ -1485,11 +1521,9 @@ var handleSelecting = (models, model, statementParams, instructions, options) =>
|
|
1485
1521
|
const usableModel = expandColumns ? { ...model, tableAlias: model.tableAlias || model.table } : model;
|
1486
1522
|
const selectedFields = [];
|
1487
1523
|
statement = instructions.selecting.map((slug) => {
|
1488
|
-
const { field, fieldSelector } = getFieldFromModel(
|
1489
|
-
|
1490
|
-
|
1491
|
-
"selecting"
|
1492
|
-
);
|
1524
|
+
const { field, fieldSelector } = getFieldFromModel(usableModel, slug, {
|
1525
|
+
instructionName: "selecting"
|
1526
|
+
});
|
1493
1527
|
selectedFields.push(field);
|
1494
1528
|
return fieldSelector;
|
1495
1529
|
}).join(", ");
|
@@ -1513,9 +1547,6 @@ var handleSelecting = (models, model, statementParams, instructions, options) =>
|
|
1513
1547
|
var handleTo = (models, model, statementParams, queryType, dependencyStatements, instructions, parentModel) => {
|
1514
1548
|
const { with: withInstruction, to: toInstruction } = instructions;
|
1515
1549
|
const defaultFields = {};
|
1516
|
-
if (queryType === "add") {
|
1517
|
-
defaultFields.id = toInstruction.id || generateRecordId(model.idPrefix);
|
1518
|
-
}
|
1519
1550
|
if (queryType === "set" || toInstruction.ronin) {
|
1520
1551
|
defaultFields.ronin = {
|
1521
1552
|
// If records are being updated, bump their update time.
|
@@ -1542,12 +1573,12 @@ var handleTo = (models, model, statementParams, queryType, dependencyStatements,
|
|
1542
1573
|
) : []
|
1543
1574
|
];
|
1544
1575
|
for (const field of subQueryFields || []) {
|
1545
|
-
getFieldFromModel(model, field, "to");
|
1576
|
+
getFieldFromModel(model, field, { instructionName: "to" });
|
1546
1577
|
}
|
1547
1578
|
let statement2 = "";
|
1548
1579
|
if (subQuerySelectedFields) {
|
1549
1580
|
const columns = subQueryFields.map((field) => {
|
1550
|
-
return getFieldFromModel(model, field, "to").fieldSelector;
|
1581
|
+
return getFieldFromModel(model, field, { instructionName: "to" }).fieldSelector;
|
1551
1582
|
});
|
1552
1583
|
statement2 = `(${columns.join(", ")}) `;
|
1553
1584
|
}
|
@@ -1558,7 +1589,12 @@ var handleTo = (models, model, statementParams, queryType, dependencyStatements,
|
|
1558
1589
|
for (const fieldSlug in toInstruction) {
|
1559
1590
|
if (!Object.hasOwn(toInstruction, fieldSlug)) continue;
|
1560
1591
|
const fieldValue = toInstruction[fieldSlug];
|
1561
|
-
const fieldDetails = getFieldFromModel(
|
1592
|
+
const fieldDetails = getFieldFromModel(
|
1593
|
+
model,
|
1594
|
+
fieldSlug,
|
1595
|
+
{ instructionName: "to" },
|
1596
|
+
false
|
1597
|
+
);
|
1562
1598
|
if (fieldDetails?.field.type === "link" && fieldDetails.field.kind === "many") {
|
1563
1599
|
delete toInstruction[fieldSlug];
|
1564
1600
|
const associativeModelSlug = composeAssociationModelSlug(model, fieldDetails.field);
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@ronin/compiler",
|
3
|
-
"version": "0.13.
|
3
|
+
"version": "0.13.9-leo-ron-1071-experimental-302",
|
4
4
|
"type": "module",
|
5
5
|
"description": "Compiles RONIN queries to SQL statements.",
|
6
6
|
"publishConfig": {
|
@@ -27,9 +27,6 @@
|
|
27
27
|
],
|
28
28
|
"author": "ronin",
|
29
29
|
"license": "Apache-2.0",
|
30
|
-
"dependencies": {
|
31
|
-
"@paralleldrive/cuid2": "2.2.2"
|
32
|
-
},
|
33
30
|
"devDependencies": {
|
34
31
|
"@biomejs/biome": "1.9.4",
|
35
32
|
"@ronin/engine": "0.0.27",
|