@ronin/compiler 0.14.0 → 0.14.1

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
@@ -169,7 +169,7 @@ type ModelFieldBasics = {
169
169
  * The value that should be inserted into the field in the case that no value was
170
170
  * explicitly provided for it when a record is created.
171
171
  */
172
- defaultValue?: unknown;
172
+ defaultValue?: Expression | unknown;
173
173
  /**
174
174
  * An expression that should be evaluated to form the value of the field. The
175
175
  * expression can either be VIRTUAL (evaluated whenever a record is read) or STORED
package/dist/index.js CHANGED
@@ -350,6 +350,16 @@ var handleIncluding = (models, model, statementParams, single, instruction) => {
350
350
  if (single && !subSingle) {
351
351
  tableSubQuery = `SELECT * FROM "${model.table}" LIMIT 1`;
352
352
  }
353
+ if (modifiableQueryInstructions?.including) {
354
+ const subIncluding = handleIncluding(
355
+ models,
356
+ { ...relatedModel, tableAlias },
357
+ statementParams,
358
+ subSingle,
359
+ modifiableQueryInstructions.including
360
+ );
361
+ statement += ` ${subIncluding.statement}`;
362
+ }
353
363
  }
354
364
  return { statement, tableSubQuery };
355
365
  };
@@ -391,93 +401,95 @@ var handleOrderedBy = (model, instruction) => {
391
401
  };
392
402
 
393
403
  // src/instructions/selecting.ts
394
- var handleSelecting = (models, model, statementParams, single, instructions, options) => {
395
- let loadedFields = [];
396
- let expandColumns = false;
397
- let statement = "*";
404
+ var handleSelecting = (models, model, statementParams, single, instructions, options = {}) => {
398
405
  let isJoining = false;
406
+ const selectedFields = (instructions.selecting ? instructions.selecting.map((slug) => {
407
+ const { field } = getFieldFromModel(model, slug, {
408
+ instructionName: "selecting"
409
+ });
410
+ return field;
411
+ }) : model.fields).filter((field) => !(field.type === "link" && field.kind === "many")).map((field) => {
412
+ const newField = { ...field, mountingPath: field.slug };
413
+ if (options.mountingPath) {
414
+ newField.mountingPath = `${options.mountingPath}.${field.slug}`;
415
+ }
416
+ return newField;
417
+ });
418
+ if (instructions.selecting) options.expandColumns = true;
419
+ const joinedSelectedFields = [];
420
+ const joinedColumns = [];
399
421
  if (instructions.including) {
422
+ const symbol = getSymbol(instructions.including);
423
+ if (symbol?.type === "query") {
424
+ instructions.including.ronin_root = { ...instructions.including };
425
+ delete instructions.including[QUERY_SYMBOLS.QUERY];
426
+ }
400
427
  const flatObject = flatten(instructions.including);
401
- instructions.including = {};
402
428
  for (const [key, value] of Object.entries(flatObject)) {
403
- const symbol = getSymbol(value);
404
- if (symbol?.type === "query") {
405
- const { queryModel, queryInstructions } = splitQuery(symbol.value);
429
+ const symbol2 = getSymbol(value);
430
+ if (symbol2?.type === "query") {
431
+ const { queryModel, queryInstructions } = splitQuery(symbol2.value);
406
432
  const subQueryModel = getModelBySlug(models, queryModel);
407
433
  isJoining = true;
408
- expandColumns = Boolean(options?.expandColumns || queryInstructions?.selecting);
434
+ if (queryInstructions?.selecting) options.expandColumns = true;
409
435
  const tableAlias = composeIncludedTableAlias(key);
410
436
  const subSingle = queryModel !== subQueryModel.pluralSlug;
411
- if (single && !subSingle) {
412
- model.tableAlias = `sub_${model.table}`;
413
- }
414
- const queryModelFields = queryInstructions?.selecting ? subQueryModel.fields.filter((field) => {
415
- return queryInstructions.selecting?.includes(field.slug);
416
- }) : (
417
- // Exclude link fields with cardinality "many", since those don't exist as columns.
418
- subQueryModel.fields.filter((field) => {
419
- return !(field.type === "link" && field.kind === "many");
420
- })
437
+ if (!model.tableAlias)
438
+ model.tableAlias = single && !subSingle ? `sub_${model.table}` : model.table;
439
+ const subMountingPath = key === "ronin_root" ? options.mountingPath : `${options?.mountingPath ? `${options?.mountingPath}.` : ""}${subSingle ? key : `${key}[0]`}`;
440
+ const { columns: nestedColumns, selectedFields: nestedSelectedFields } = handleSelecting(
441
+ models,
442
+ { ...subQueryModel, tableAlias },
443
+ statementParams,
444
+ subSingle,
445
+ {
446
+ selecting: queryInstructions?.selecting,
447
+ including: queryInstructions?.including
448
+ },
449
+ { ...options, mountingPath: subMountingPath }
421
450
  );
422
- for (const field of queryModelFields) {
423
- loadedFields.push({
424
- ...field,
425
- parentField: {
426
- slug: key,
427
- single: subSingle
428
- }
429
- });
430
- if (expandColumns) {
431
- const newValue2 = parseFieldExpression(
432
- { ...subQueryModel, tableAlias },
433
- "including",
434
- `${QUERY_SYMBOLS.FIELD}${field.slug}`
435
- );
436
- instructions.including[`${tableAlias}.${field.slug}`] = newValue2;
437
- }
438
- }
451
+ if (nestedColumns !== "*") joinedColumns.push(nestedColumns);
452
+ joinedSelectedFields.push(...nestedSelectedFields);
439
453
  continue;
440
454
  }
441
- let newValue = value;
442
- if (symbol?.type === "expression") {
443
- newValue = `(${parseFieldExpression(model, "including", symbol.value)})`;
455
+ let mountedValue = value;
456
+ if (symbol2?.type === "expression") {
457
+ mountedValue = `(${parseFieldExpression(model, "including", symbol2.value)})`;
444
458
  } else {
445
- newValue = prepareStatementValue(statementParams, value);
459
+ mountedValue = prepareStatementValue(statementParams, value);
446
460
  }
447
- instructions.including[key] = newValue;
448
- loadedFields.push({
461
+ selectedFields.push({
449
462
  slug: key,
450
- type: RAW_FIELD_TYPES.includes(typeof value) ? typeof value : "string"
463
+ mountingPath: key,
464
+ type: RAW_FIELD_TYPES.includes(typeof value) ? typeof value : "string",
465
+ mountedValue
451
466
  });
452
467
  }
453
468
  }
454
- if (expandColumns) {
455
- instructions.selecting = model.fields.filter((field) => !(field.type === "link" && field.kind === "many")).map((field) => field.slug);
456
- }
457
- if (instructions.selecting) {
458
- const usableModel = expandColumns ? { ...model, tableAlias: model.tableAlias || model.table } : model;
459
- const selectedFields = [];
460
- statement = instructions.selecting.map((slug) => {
461
- const { field, fieldSelector } = getFieldFromModel(usableModel, slug, {
462
- instructionName: "selecting"
463
- });
464
- selectedFields.push(field);
465
- return fieldSelector;
466
- }).join(", ");
467
- loadedFields = [...selectedFields, ...loadedFields];
468
- } else {
469
- loadedFields = [
470
- ...model.fields.filter(
471
- (field) => !(field.type === "link" && field.kind === "many")
472
- ),
473
- ...loadedFields
474
- ];
475
- }
476
- if (instructions.including && Object.keys(instructions.including).length > 0) {
477
- statement += ", ";
478
- statement += Object.entries(instructions.including).map(([key, value]) => `${value} as "${key}"`).join(", ");
479
- }
480
- return { columns: statement, isJoining, loadedFields };
469
+ let columns = ["*"];
470
+ const fieldsToExpand = options.expandColumns ? selectedFields : selectedFields.filter(
471
+ (loadedField) => typeof loadedField.mountedValue !== "undefined"
472
+ );
473
+ const extraColumns = fieldsToExpand.map((selectedField) => {
474
+ if (selectedField.mountedValue) {
475
+ return `${selectedField.mountedValue} as "${selectedField.slug}"`;
476
+ }
477
+ const { fieldSelector } = getFieldFromModel(model, selectedField.slug, {
478
+ instructionName: "selecting"
479
+ });
480
+ if (options.mountingPath) {
481
+ return `${fieldSelector} as "${options.mountingPath}.${selectedField.slug}"`;
482
+ }
483
+ return fieldSelector;
484
+ });
485
+ if (options.expandColumns) {
486
+ columns = extraColumns;
487
+ } else if (extraColumns) {
488
+ columns.push(...extraColumns);
489
+ }
490
+ columns.push(...joinedColumns);
491
+ selectedFields.push(...joinedSelectedFields);
492
+ return { columns: columns.join(", "), isJoining, selectedFields };
481
493
  };
482
494
 
483
495
  // src/instructions/to.ts
@@ -599,7 +611,7 @@ var compileQueryInput = (defaultQuery, models, statementParams, options) => {
599
611
  defaultQuery
600
612
  );
601
613
  if (query === null)
602
- return { dependencies: [], main: dependencyStatements[0], loadedFields: [] };
614
+ return { dependencies: [], main: dependencyStatements[0], selectedFields: [] };
603
615
  const parsedQuery = splitQuery(query);
604
616
  const { queryType, queryModel, queryInstructions } = parsedQuery;
605
617
  const model = getModelBySlug(models, queryModel);
@@ -609,7 +621,7 @@ var compileQueryInput = (defaultQuery, models, statementParams, options) => {
609
621
  if (instructions && Object.hasOwn(instructions, "for")) {
610
622
  instructions = handleFor(model, instructions);
611
623
  }
612
- const { columns, isJoining, loadedFields } = handleSelecting(
624
+ const { columns, isJoining, selectedFields } = handleSelecting(
613
625
  models,
614
626
  model,
615
627
  statementParams,
@@ -741,7 +753,7 @@ var compileQueryInput = (defaultQuery, models, statementParams, options) => {
741
753
  return {
742
754
  dependencies: dependencyStatements,
743
755
  main: mainStatement,
744
- loadedFields
756
+ selectedFields
745
757
  };
746
758
  };
747
759
 
@@ -1215,15 +1227,59 @@ var addDefaultModelPresets = (list, model) => {
1215
1227
  const defaultPresets = [];
1216
1228
  for (const field of model.fields || []) {
1217
1229
  if (field.type === "link" && !field.slug.startsWith("ronin.")) {
1218
- const relatedModel = getModelBySlug(list, field.target);
1219
- if (field.kind === "many") continue;
1230
+ const targetModel = getModelBySlug(list, field.target);
1231
+ if (field.kind === "many") {
1232
+ const systemModel = list.find(({ system }) => {
1233
+ return system?.model === model.id && system?.associationSlug === field.slug;
1234
+ });
1235
+ if (!systemModel) continue;
1236
+ const preset = {
1237
+ instructions: {
1238
+ // Perform a LEFT JOIN that adds the associative table.
1239
+ including: {
1240
+ [field.slug]: {
1241
+ [QUERY_SYMBOLS.QUERY]: {
1242
+ get: {
1243
+ [systemModel.pluralSlug]: {
1244
+ // ON associative_table.source = origin_model.id
1245
+ with: {
1246
+ source: {
1247
+ [QUERY_SYMBOLS.EXPRESSION]: `${QUERY_SYMBOLS.FIELD_PARENT}id`
1248
+ }
1249
+ },
1250
+ // Perform a LEFT JOIN that adds the target model table.
1251
+ including: {
1252
+ [QUERY_SYMBOLS.QUERY]: {
1253
+ get: {
1254
+ [targetModel.slug]: {
1255
+ // ON target_model.id = associative_table.target
1256
+ with: {
1257
+ id: {
1258
+ [QUERY_SYMBOLS.EXPRESSION]: `${QUERY_SYMBOLS.FIELD_PARENT}target`
1259
+ }
1260
+ }
1261
+ }
1262
+ }
1263
+ }
1264
+ }
1265
+ }
1266
+ }
1267
+ }
1268
+ }
1269
+ }
1270
+ },
1271
+ slug: field.slug
1272
+ };
1273
+ defaultPresets.push(preset);
1274
+ continue;
1275
+ }
1220
1276
  defaultPresets.push({
1221
1277
  instructions: {
1222
1278
  including: {
1223
1279
  [field.slug]: {
1224
1280
  [QUERY_SYMBOLS.QUERY]: {
1225
1281
  get: {
1226
- [relatedModel.slug]: {
1282
+ [targetModel.slug]: {
1227
1283
  with: {
1228
1284
  // Compare the `id` field of the related model to the link field on
1229
1285
  // the root model (`field.slug`).
@@ -1242,6 +1298,7 @@ var addDefaultModelPresets = (list, model) => {
1242
1298
  }
1243
1299
  }
1244
1300
  const childModels = list.map((subModel) => {
1301
+ if (subModel.system?.associationSlug) return null;
1245
1302
  const field = subModel.fields?.find((field2) => {
1246
1303
  return field2.type === "link" && field2.target === model.slug;
1247
1304
  });
@@ -1299,8 +1356,10 @@ var getFieldSelector = (model, field, fieldPath, writing) => {
1299
1356
  if (field.type === "json" && !writing) {
1300
1357
  const dotParts = fieldPath.split(".");
1301
1358
  const columnName = tablePrefix + dotParts.shift();
1302
- const jsonField = dotParts.join(".");
1303
- return `json_extract(${columnName}, '$.${jsonField}')`;
1359
+ if (dotParts.length > 0) {
1360
+ const jsonField = dotParts.join(".");
1361
+ return `json_extract(${columnName}, '$.${jsonField}')`;
1362
+ }
1304
1363
  }
1305
1364
  return `${tablePrefix}"${fieldPath}"`;
1306
1365
  };
@@ -1901,7 +1960,7 @@ var Transaction = class {
1901
1960
  ...subStatements.map((statement) => ({
1902
1961
  ...statement,
1903
1962
  query,
1904
- fields: result.loadedFields
1963
+ selectedFields: result.selectedFields
1905
1964
  }))
1906
1965
  );
1907
1966
  }
@@ -1912,12 +1971,8 @@ var Transaction = class {
1912
1971
  const records = [];
1913
1972
  for (const row of rows) {
1914
1973
  const record = fields.reduce((acc, field, fieldIndex) => {
1915
- let newSlug = field.slug;
1974
+ const newSlug = field.mountingPath;
1916
1975
  let newValue = row[fieldIndex];
1917
- if (field.parentField) {
1918
- const arrayKey = field.parentField.single ? "" : "[0]";
1919
- newSlug = `${field.parentField.slug}${arrayKey}.${field.slug}`;
1920
- }
1921
1976
  if (field.type === "json") {
1922
1977
  newValue = JSON.parse(newValue);
1923
1978
  } else if (field.type === "boolean") {
@@ -1938,17 +1993,13 @@ var Transaction = class {
1938
1993
  records.push(record);
1939
1994
  continue;
1940
1995
  }
1941
- const joinFields = fields.reduce(
1942
- (acc, field) => {
1943
- if (!field.parentField) return acc;
1944
- const { single: single2, slug } = field.parentField;
1945
- return single2 || acc.includes(slug) ? acc : acc.concat([slug]);
1946
- },
1947
- []
1948
- );
1949
- for (const parentField of joinFields) {
1950
- const currentValue = existingRecord[parentField];
1951
- const newValue = record[parentField];
1996
+ const joinFields = fields.reduce((acc, { mountingPath }) => {
1997
+ if (mountingPath.includes("[0]")) acc.add(mountingPath.split("[0]")[0]);
1998
+ return acc;
1999
+ }, /* @__PURE__ */ new Set());
2000
+ for (const arrayField of joinFields.values()) {
2001
+ const currentValue = existingRecord[arrayField];
2002
+ const newValue = record[arrayField];
1952
2003
  currentValue.push(...newValue);
1953
2004
  }
1954
2005
  }
@@ -1976,11 +2027,7 @@ var Transaction = class {
1976
2027
  });
1977
2028
  const formattedResults = normalizedResults.map(
1978
2029
  (rows, index) => {
1979
- const {
1980
- returning,
1981
- query,
1982
- fields: rawModelFields
1983
- } = this.#internalStatements[index];
2030
+ const { returning, query, selectedFields } = this.#internalStatements[index];
1984
2031
  if (!returning) return null;
1985
2032
  const { queryType, queryModel, queryInstructions } = splitQuery(query);
1986
2033
  const model = getModelBySlug(this.models, queryModel);
@@ -1994,13 +2041,13 @@ var Transaction = class {
1994
2041
  const single = queryModel !== model.pluralSlug;
1995
2042
  if (single) {
1996
2043
  return {
1997
- record: rows[0] ? this.#formatRows(rawModelFields, rows, true, isMeta) : null,
2044
+ record: rows[0] ? this.#formatRows(selectedFields, rows, true, isMeta) : null,
1998
2045
  modelFields
1999
2046
  };
2000
2047
  }
2001
2048
  const pageSize = queryInstructions?.limitedTo;
2002
2049
  const output = {
2003
- records: this.#formatRows(rawModelFields, rows, false, isMeta),
2050
+ records: this.#formatRows(selectedFields, rows, false, isMeta),
2004
2051
  modelFields
2005
2052
  };
2006
2053
  if (pageSize && output.records.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ronin/compiler",
3
- "version": "0.14.0",
3
+ "version": "0.14.1",
4
4
  "type": "module",
5
5
  "description": "Compiles RONIN queries to SQL statements.",
6
6
  "publishConfig": {