@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 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, instructionName);
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, instructionName) => {
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" && instructionName !== "to") {
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, instructionName, shouldThrow = true) {
582
- const errorPrefix = `Field "${fieldPath}" defined for \`${instructionName}\``;
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, instructionName);
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 = [...SYSTEM_FIELDS, ...newFields];
653
+ copiedModel.fields = [...getSystemFields(copiedModel.idPrefix), ...newFields];
657
654
  }
658
655
  return copiedModel;
659
656
  };
660
- var SYSTEM_FIELDS = [
657
+ var getSystemFields = (idPrefix = "rec") => [
661
658
  {
662
659
  name: "ID",
663
660
  type: "string",
664
661
  slug: "id",
665
- displayAs: "single-line"
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, "to"));
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, "to").fieldSelector;
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, "orderedBy");
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, "orderedBy");
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, "orderedBy");
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
- usableModel,
1490
- slug,
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(model, fieldSlug, "to", false);
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.8",
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",