@ronin/compiler 0.13.9 → 0.13.10

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
@@ -184,7 +184,7 @@ var parseFieldExpression = (model, instructionName, expression, parentModel) =>
184
184
  }
185
185
  }
186
186
  const fieldSlug = match.replace(toReplace, "");
187
- const field = getFieldFromModel(rootModel, fieldSlug, instructionName);
187
+ const field = getFieldFromModel(rootModel, fieldSlug, { instructionName });
188
188
  return field.fieldSelector;
189
189
  });
190
190
  };
@@ -192,7 +192,7 @@ var composeFieldValues = (models, model, statementParams, instructionName, value
192
192
  const { fieldSelector: conditionSelector } = getFieldFromModel(
193
193
  model,
194
194
  options.fieldSlug,
195
- instructionName
195
+ { instructionName }
196
196
  );
197
197
  const collectStatementValue = options.type !== "fields";
198
198
  const symbol = getSymbol(value);
@@ -233,7 +233,9 @@ var composeConditions = (models, model, statementParams, instructionName, value,
233
233
  return slug.includes(".") && slug.split(".")[0] === options.fieldSlug;
234
234
  });
235
235
  if (!childField) {
236
- const fieldDetails = getFieldFromModel(model, options.fieldSlug, instructionName);
236
+ const fieldDetails = getFieldFromModel(model, options.fieldSlug, {
237
+ instructionName
238
+ });
237
239
  const { field: modelField } = fieldDetails || {};
238
240
  const consumeJSON = modelField?.type === "json" && instructionName === "to";
239
241
  if (modelField && !(isObject(value) || Array.isArray(value)) || getSymbol(value) || consumeJSON) {
@@ -565,10 +567,10 @@ var getModelBySlug = (models, slug) => {
565
567
  return model;
566
568
  };
567
569
  var composeAssociationModelSlug = (model, field) => convertToCamelCase(`ronin_link_${model.slug}_${field.slug}`);
568
- var getFieldSelector = (model, field, fieldPath, instructionName) => {
570
+ var getFieldSelector = (model, field, fieldPath, writing) => {
569
571
  const symbol = model.tableAlias?.startsWith(QUERY_SYMBOLS.FIELD_PARENT) ? `${model.tableAlias.replace(QUERY_SYMBOLS.FIELD_PARENT, "").slice(0, -1)}.` : "";
570
572
  const tablePrefix = symbol || (model.tableAlias ? `"${model.tableAlias}".` : "");
571
- if (field.type === "json" && instructionName !== "to") {
573
+ if (field.type === "json" && !writing) {
572
574
  const dotParts = fieldPath.split(".");
573
575
  const columnName = tablePrefix + dotParts.shift();
574
576
  const jsonField = dotParts.join(".");
@@ -576,19 +578,16 @@ var getFieldSelector = (model, field, fieldPath, instructionName) => {
576
578
  }
577
579
  return `${tablePrefix}"${fieldPath}"`;
578
580
  };
579
- function getFieldFromModel(model, fieldPath, instructionName, shouldThrow = true) {
580
- 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}`;
581
585
  const modelFields = model.fields || [];
582
586
  let modelField;
583
587
  if (fieldPath.includes(".")) {
584
588
  modelField = modelFields.find((field) => field.slug === fieldPath.split(".")[0]);
585
589
  if (modelField?.type === "json") {
586
- const fieldSelector2 = getFieldSelector(
587
- model,
588
- modelField,
589
- fieldPath,
590
- instructionName
591
- );
590
+ const fieldSelector2 = getFieldSelector(model, modelField, fieldPath, writingField);
592
591
  return { field: modelField, fieldSelector: fieldSelector2 };
593
592
  }
594
593
  }
@@ -604,7 +603,7 @@ function getFieldFromModel(model, fieldPath, instructionName, shouldThrow = true
604
603
  }
605
604
  return null;
606
605
  }
607
- const fieldSelector = getFieldSelector(model, modelField, fieldPath, instructionName);
606
+ const fieldSelector = getFieldSelector(model, modelField, fieldPath, writingField);
608
607
  return { field: modelField, fieldSelector };
609
608
  }
610
609
  var slugToName = (slug) => {
@@ -1015,6 +1014,13 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
1015
1014
  });
1016
1015
  }
1017
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
+ }
1018
1024
  if (entity === "field") {
1019
1025
  const statement = `ALTER TABLE "${existingModel.table}"`;
1020
1026
  const existingField = existingEntity;
@@ -1043,6 +1049,14 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
1043
1049
  }
1044
1050
  }
1045
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
+ }
1046
1060
  dependencyStatements.push({
1047
1061
  statement: `${statement} DROP COLUMN "${slug}"`,
1048
1062
  params: []
@@ -1055,10 +1069,20 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
1055
1069
  const indexName = convertToSnakeCase(slug);
1056
1070
  let statement = `${statementAction}${index?.unique ? " UNIQUE" : ""} INDEX "${indexName}"`;
1057
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
+ }
1058
1079
  const columns = index.fields.map((field2) => {
1059
1080
  let fieldSelector = "";
1060
1081
  if ("slug" in field2) {
1061
- ({ fieldSelector } = getFieldFromModel(existingModel, field2.slug, "to"));
1082
+ ({ fieldSelector } = getFieldFromModel(existingModel, field2.slug, {
1083
+ modelEntityType: "index",
1084
+ modelEntityName: indexName
1085
+ }));
1062
1086
  } else if ("expression" in field2) {
1063
1087
  fieldSelector = parseFieldExpression(existingModel, "to", field2.expression);
1064
1088
  }
@@ -1089,7 +1113,10 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
1089
1113
  });
1090
1114
  }
1091
1115
  const fieldSelectors = trigger.fields.map((field2) => {
1092
- return getFieldFromModel(existingModel, field2.slug, "to").fieldSelector;
1116
+ return getFieldFromModel(existingModel, field2.slug, {
1117
+ modelEntityType: "trigger",
1118
+ modelEntityName: triggerName
1119
+ }).fieldSelector;
1093
1120
  });
1094
1121
  statementParts.push(`OF (${fieldSelectors.join(", ")})`);
1095
1122
  }
@@ -1201,7 +1228,9 @@ var generatePaginationCursor = (model, orderedBy, record) => {
1201
1228
  const cursors = keys.map((fieldSlug) => {
1202
1229
  const property = getProperty(record, fieldSlug);
1203
1230
  if (property === null || property === void 0) return CURSOR_NULL_PLACEHOLDER;
1204
- const { field } = getFieldFromModel(model, fieldSlug, "orderedBy");
1231
+ const { field } = getFieldFromModel(model, fieldSlug, {
1232
+ instructionName: "orderedBy"
1233
+ });
1205
1234
  if (field.type === "date") return new Date(property).getTime();
1206
1235
  return property;
1207
1236
  });
@@ -1240,7 +1269,9 @@ var handleBeforeOrAfter = (model, statementParams, instructions) => {
1240
1269
  if (value === CURSOR_NULL_PLACEHOLDER) {
1241
1270
  return "NULL";
1242
1271
  }
1243
- const { field } = getFieldFromModel(model, key, "orderedBy");
1272
+ const { field } = getFieldFromModel(model, key, {
1273
+ instructionName: "orderedBy"
1274
+ });
1244
1275
  if (field.type === "boolean") {
1245
1276
  return prepareStatementValue(statementParams, value === "true");
1246
1277
  }
@@ -1266,7 +1297,9 @@ var handleBeforeOrAfter = (model, statementParams, instructions) => {
1266
1297
  for (let j = 0; j <= i; j++) {
1267
1298
  const key = keys[j];
1268
1299
  const value = values[j];
1269
- let { field, fieldSelector } = getFieldFromModel(model, key, "orderedBy");
1300
+ let { field, fieldSelector } = getFieldFromModel(model, key, {
1301
+ instructionName: "orderedBy"
1302
+ });
1270
1303
  if (j === i) {
1271
1304
  const closingParentheses = ")".repeat(condition.length);
1272
1305
  const operator = value === "NULL" ? "IS NOT" : compareOperators[j];
@@ -1418,7 +1451,7 @@ var handleOrderedBy = (model, instruction) => {
1418
1451
  const { field: modelField, fieldSelector } = getFieldFromModel(
1419
1452
  model,
1420
1453
  item.value,
1421
- instructionName
1454
+ { instructionName }
1422
1455
  );
1423
1456
  const caseInsensitiveStatement = modelField.type === "string" ? " COLLATE NOCASE" : "";
1424
1457
  statement += `${fieldSelector}${caseInsensitiveStatement} ${item.order}`;
@@ -1488,11 +1521,9 @@ var handleSelecting = (models, model, statementParams, instructions, options) =>
1488
1521
  const usableModel = expandColumns ? { ...model, tableAlias: model.tableAlias || model.table } : model;
1489
1522
  const selectedFields = [];
1490
1523
  statement = instructions.selecting.map((slug) => {
1491
- const { field, fieldSelector } = getFieldFromModel(
1492
- usableModel,
1493
- slug,
1494
- "selecting"
1495
- );
1524
+ const { field, fieldSelector } = getFieldFromModel(usableModel, slug, {
1525
+ instructionName: "selecting"
1526
+ });
1496
1527
  selectedFields.push(field);
1497
1528
  return fieldSelector;
1498
1529
  }).join(", ");
@@ -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.9",
3
+ "version": "0.13.10",
4
4
  "type": "module",
5
5
  "description": "Compiles RONIN queries to SQL statements.",
6
6
  "publishConfig": {