@ronin/compiler 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
package/dist/index.js CHANGED
@@ -1,19 +1,23 @@
1
1
  // src/utils/helpers.ts
2
2
  import { init as cuid } from "@paralleldrive/cuid2";
3
- var RONIN_SCHEMA_SYMBOLS = {
3
+ var RONIN_MODEL_SYMBOLS = {
4
4
  // Represents a sub query.
5
5
  QUERY: "__RONIN_QUERY",
6
- // Represents the value of a field in a schema.
6
+ // Represents an expression that should be evaluated.
7
+ EXPRESSION: "__RONIN_EXPRESSION",
8
+ // Represents the value of a field in the model.
7
9
  FIELD: "__RONIN_FIELD_",
8
- // Represents the old value of a field in a schema. Used for triggers.
9
- FIELD_OLD: "__RONIN_FIELD_OLD_",
10
- // Represents the new value of a field in a schema. Used for triggers.
11
- FIELD_NEW: "__RONIN_FIELD_NEW_",
10
+ // Represents the value of a field in the model of a parent query.
11
+ FIELD_PARENT: "__RONIN_FIELD_PARENT_",
12
+ // Represents the old value of a field in the parent model. Used for triggers.
13
+ FIELD_PARENT_OLD: "__RONIN_FIELD_PARENT_OLD_",
14
+ // Represents the new value of a field in the parent model. Used for triggers.
15
+ FIELD_PARENT_NEW: "__RONIN_FIELD_PARENT_NEW_",
12
16
  // Represents a value provided to a query preset.
13
17
  VALUE: "__RONIN_VALUE"
14
18
  };
15
- var RONIN_SCHEMA_FIELD_REGEX = new RegExp(
16
- `${RONIN_SCHEMA_SYMBOLS.FIELD}[a-zA-Z0-9]+`,
19
+ var RONIN_MODEL_FIELD_REGEX = new RegExp(
20
+ `${RONIN_MODEL_SYMBOLS.FIELD}[_a-zA-Z0-9]+`,
17
21
  "g"
18
22
  );
19
23
  var RoninError = class extends Error {
@@ -94,9 +98,9 @@ var expand = (obj) => {
94
98
  };
95
99
  var splitQuery = (query) => {
96
100
  const queryType = Object.keys(query)[0];
97
- const querySchema = Object.keys(query[queryType])[0];
98
- const queryInstructions = query[queryType][querySchema];
99
- return { queryType, querySchema, queryInstructions };
101
+ const queryModel = Object.keys(query[queryType])[0];
102
+ const queryInstructions = query[queryType][queryModel];
103
+ return { queryType, queryModel, queryInstructions };
100
104
  };
101
105
 
102
106
  // src/utils/statement.ts
@@ -112,40 +116,44 @@ var prepareStatementValue = (statementParams, value) => {
112
116
  const index = statementParams.push(formattedValue);
113
117
  return `?${index}`;
114
118
  };
115
- var composeFieldValues = (schemas, schema, statementParams, instructionName, value, options) => {
116
- const { field: schemaField, fieldSelector: selector } = getFieldFromSchema(
117
- schema,
119
+ var parseFieldExpression = (model, instructionName, expression, parentModel) => {
120
+ return expression.replace(RONIN_MODEL_FIELD_REGEX, (match) => {
121
+ let toReplace = RONIN_MODEL_SYMBOLS.FIELD;
122
+ let rootModel = model;
123
+ if (match.startsWith(RONIN_MODEL_SYMBOLS.FIELD_PARENT)) {
124
+ rootModel = parentModel;
125
+ toReplace = RONIN_MODEL_SYMBOLS.FIELD_PARENT;
126
+ if (match.startsWith(RONIN_MODEL_SYMBOLS.FIELD_PARENT_OLD)) {
127
+ rootModel.tableAlias = toReplace = RONIN_MODEL_SYMBOLS.FIELD_PARENT_OLD;
128
+ } else if (match.startsWith(RONIN_MODEL_SYMBOLS.FIELD_PARENT_NEW)) {
129
+ rootModel.tableAlias = toReplace = RONIN_MODEL_SYMBOLS.FIELD_PARENT_NEW;
130
+ }
131
+ }
132
+ const fieldSlug = match.replace(toReplace, "");
133
+ const field = getFieldFromModel(rootModel, fieldSlug, instructionName);
134
+ return field.fieldSelector;
135
+ });
136
+ };
137
+ var composeFieldValues = (models, model, statementParams, instructionName, value, options) => {
138
+ const { fieldSelector: conditionSelector } = getFieldFromModel(
139
+ model,
118
140
  options.fieldSlug,
119
- instructionName,
120
- options.rootTable
141
+ instructionName
121
142
  );
122
- const isSubQuery = isObject(value) && Object.hasOwn(value, RONIN_SCHEMA_SYMBOLS.QUERY);
123
143
  const collectStatementValue = options.type !== "fields";
124
- let conditionSelector = selector;
144
+ const symbol = getSymbol(value);
125
145
  let conditionValue = value;
126
- if (isSubQuery && collectStatementValue) {
127
- conditionValue = `(${compileQueryInput(
128
- value[RONIN_SCHEMA_SYMBOLS.QUERY],
129
- schemas,
130
- statementParams
131
- ).main.statement})`;
132
- } else if (typeof value === "string" && value.startsWith(RONIN_SCHEMA_SYMBOLS.FIELD)) {
133
- let targetTable = `"${options.rootTable}"`;
134
- let toReplace = RONIN_SCHEMA_SYMBOLS.FIELD;
135
- if (value.startsWith(RONIN_SCHEMA_SYMBOLS.FIELD_OLD)) {
136
- targetTable = "OLD";
137
- toReplace = RONIN_SCHEMA_SYMBOLS.FIELD_OLD;
138
- } else if (value.startsWith(RONIN_SCHEMA_SYMBOLS.FIELD_NEW)) {
139
- targetTable = "NEW";
140
- toReplace = RONIN_SCHEMA_SYMBOLS.FIELD_NEW;
146
+ if (symbol) {
147
+ if (symbol?.type === "expression") {
148
+ conditionValue = parseFieldExpression(
149
+ model,
150
+ instructionName,
151
+ symbol.value,
152
+ options.parentModel
153
+ );
141
154
  }
142
- conditionSelector = `${options.customTable ? `"${options.customTable}".` : ""}"${schemaField.slug}"`;
143
- conditionValue = `${targetTable}."${value.replace(toReplace, "")}"`;
144
- } else if (schemaField.type === "json" && instructionName === "to") {
145
- conditionSelector = `"${schemaField.slug}"`;
146
- if (collectStatementValue) {
147
- const preparedValue = prepareStatementValue(statementParams, value);
148
- conditionValue = `IIF(${conditionSelector} IS NULL, ${preparedValue}, json_patch(${conditionSelector}, ${preparedValue}))`;
155
+ if (symbol.type === "query" && collectStatementValue) {
156
+ conditionValue = `(${compileQueryInput(symbol.value, models, statementParams).main.statement})`;
149
157
  }
150
158
  } else if (collectStatementValue) {
151
159
  conditionValue = prepareStatementValue(statementParams, value);
@@ -154,11 +162,11 @@ var composeFieldValues = (schemas, schema, statementParams, instructionName, val
154
162
  if (options.type === "values") return conditionValue;
155
163
  return `${conditionSelector} ${WITH_CONDITIONS[options.condition || "being"](conditionValue, value)}`;
156
164
  };
157
- var composeConditions = (schemas, schema, statementParams, instructionName, value, options) => {
165
+ var composeConditions = (models, model, statementParams, instructionName, value, options) => {
158
166
  const isNested = isObject(value) && Object.keys(value).length > 0;
159
167
  if (isNested && Object.keys(value).every((key) => key in WITH_CONDITIONS)) {
160
168
  const conditions = Object.entries(value).map(
161
- ([conditionType, checkValue]) => composeConditions(schemas, schema, statementParams, instructionName, checkValue, {
169
+ ([conditionType, checkValue]) => composeConditions(models, model, statementParams, instructionName, checkValue, {
162
170
  ...options,
163
171
  condition: conditionType
164
172
  })
@@ -166,48 +174,42 @@ var composeConditions = (schemas, schema, statementParams, instructionName, valu
166
174
  return conditions.join(" AND ");
167
175
  }
168
176
  if (options.fieldSlug) {
169
- const fieldDetails = getFieldFromSchema(
170
- schema,
171
- options.fieldSlug,
172
- instructionName,
173
- options.rootTable
174
- );
175
- const { field: schemaField } = fieldDetails;
176
- const consumeJSON = schemaField.type === "json" && instructionName === "to";
177
- const isSubQuery = isNested && Object.hasOwn(value, RONIN_SCHEMA_SYMBOLS.QUERY);
178
- if (!(isObject(value) || Array.isArray(value)) || isSubQuery || consumeJSON) {
177
+ const fieldDetails = getFieldFromModel(model, options.fieldSlug, instructionName);
178
+ const { field: modelField } = fieldDetails;
179
+ const consumeJSON = modelField.type === "json" && instructionName === "to";
180
+ if (!(isObject(value) || Array.isArray(value)) || getSymbol(value) || consumeJSON) {
179
181
  return composeFieldValues(
180
- schemas,
181
- schema,
182
+ models,
183
+ model,
182
184
  statementParams,
183
185
  instructionName,
184
186
  value,
185
187
  { ...options, fieldSlug: options.fieldSlug }
186
188
  );
187
189
  }
188
- if (schemaField.type === "reference" && isNested) {
190
+ if (modelField.type === "reference" && isNested) {
189
191
  const keys = Object.keys(value);
190
192
  const values = Object.values(value);
191
193
  let recordTarget;
192
194
  if (keys.length === 1 && keys[0] === "id") {
193
195
  recordTarget = values[0];
194
196
  } else {
195
- const relatedSchema = getSchemaBySlug(schemas, schemaField.target.slug);
197
+ const relatedModel = getModelBySlug(models, modelField.target.slug);
196
198
  const subQuery = {
197
199
  get: {
198
- [relatedSchema.slug]: {
200
+ [relatedModel.slug]: {
199
201
  with: value,
200
202
  selecting: ["id"]
201
203
  }
202
204
  }
203
205
  };
204
206
  recordTarget = {
205
- [RONIN_SCHEMA_SYMBOLS.QUERY]: subQuery
207
+ [RONIN_MODEL_SYMBOLS.QUERY]: subQuery
206
208
  };
207
209
  }
208
210
  return composeConditions(
209
- schemas,
210
- schema,
211
+ models,
212
+ model,
211
213
  statementParams,
212
214
  instructionName,
213
215
  recordTarget,
@@ -218,7 +220,7 @@ var composeConditions = (schemas, schema, statementParams, instructionName, valu
218
220
  if (isNested) {
219
221
  const conditions = Object.entries(value).map(([field, value2]) => {
220
222
  const nestedFieldSlug = options.fieldSlug ? `${options.fieldSlug}.${field}` : field;
221
- return composeConditions(schemas, schema, statementParams, instructionName, value2, {
223
+ return composeConditions(models, model, statementParams, instructionName, value2, {
222
224
  ...options,
223
225
  fieldSlug: nestedFieldSlug
224
226
  });
@@ -229,14 +231,7 @@ var composeConditions = (schemas, schema, statementParams, instructionName, valu
229
231
  }
230
232
  if (Array.isArray(value)) {
231
233
  const conditions = value.map(
232
- (filter) => composeConditions(
233
- schemas,
234
- schema,
235
- statementParams,
236
- instructionName,
237
- filter,
238
- options
239
- )
234
+ (filter) => composeConditions(models, model, statementParams, instructionName, filter, options)
240
235
  );
241
236
  return conditions.join(" OR ");
242
237
  }
@@ -267,6 +262,23 @@ var formatIdentifiers = ({ identifiers }, queryInstructions) => {
267
262
  [type]: newNestedInstructions
268
263
  };
269
264
  };
265
+ var getSymbol = (value) => {
266
+ if (!isObject(value)) return null;
267
+ const objectValue = value;
268
+ if (RONIN_MODEL_SYMBOLS.QUERY in objectValue) {
269
+ return {
270
+ type: "query",
271
+ value: objectValue[RONIN_MODEL_SYMBOLS.QUERY]
272
+ };
273
+ }
274
+ if (RONIN_MODEL_SYMBOLS.EXPRESSION in objectValue) {
275
+ return {
276
+ type: "expression",
277
+ value: objectValue[RONIN_MODEL_SYMBOLS.EXPRESSION]
278
+ };
279
+ }
280
+ return null;
281
+ };
270
282
 
271
283
  // src/instructions/with.ts
272
284
  var getMatcher = (value, negative) => {
@@ -291,41 +303,37 @@ var WITH_CONDITIONS = {
291
303
  lessThan: (value) => `< ${value}`,
292
304
  lessOrEqual: (value) => `<= ${value}`
293
305
  };
294
- var handleWith = (schemas, schema, statementParams, instruction, rootTable) => {
306
+ var handleWith = (models, model, statementParams, instruction, parentModel) => {
295
307
  const subStatement = composeConditions(
296
- schemas,
297
- schema,
308
+ models,
309
+ model,
298
310
  statementParams,
299
311
  "with",
300
312
  instruction,
301
- { rootTable }
313
+ { parentModel }
302
314
  );
303
315
  return `(${subStatement})`;
304
316
  };
305
317
 
306
- // src/utils/schema.ts
318
+ // src/utils/model.ts
307
319
  import title from "title";
308
- var getSchemaBySlug = (schemas, slug) => {
309
- const schema = schemas.find((schema2) => {
310
- return schema2.slug === slug || schema2.pluralSlug === slug;
320
+ var getModelBySlug = (models, slug) => {
321
+ const model = models.find((model2) => {
322
+ return model2.slug === slug || model2.pluralSlug === slug;
311
323
  });
312
- if (!schema) {
324
+ if (!model) {
313
325
  throw new RoninError({
314
- message: `No matching schema with either Slug or Plural Slug of "${slug}" could be found.`,
315
- code: "SCHEMA_NOT_FOUND"
326
+ message: `No matching model with either Slug or Plural Slug of "${slug}" could be found.`,
327
+ code: "MODEL_NOT_FOUND"
316
328
  });
317
329
  }
318
- return schema;
319
- };
320
- var getTableForSchema = (schema) => {
321
- return convertToSnakeCase(schema.pluralSlug);
330
+ return model;
322
331
  };
323
- var composeMetaSchemaSlug = (suffix) => convertToCamelCase(`ronin_${suffix}`);
324
- var composeAssociationSchemaSlug = (schema, field) => composeMetaSchemaSlug(`${schema.slug}_${field.slug}`);
325
- var getFieldSelector = (field, fieldPath, rootTable) => {
326
- const symbol = rootTable?.startsWith(RONIN_SCHEMA_SYMBOLS.FIELD) ? `${rootTable.replace(RONIN_SCHEMA_SYMBOLS.FIELD, "").slice(0, -1)}.` : "";
327
- const tablePrefix = symbol || (rootTable ? `"${rootTable}".` : "");
328
- if (field.type === "json") {
332
+ var composeAssociationModelSlug = (model, field) => convertToCamelCase(`ronin_link_${model.slug}_${field.slug}`);
333
+ var getFieldSelector = (model, field, fieldPath, instructionName) => {
334
+ const symbol = model.tableAlias?.startsWith(RONIN_MODEL_SYMBOLS.FIELD_PARENT) ? `${model.tableAlias.replace(RONIN_MODEL_SYMBOLS.FIELD_PARENT, "").slice(0, -1)}.` : "";
335
+ const tablePrefix = symbol || (model.tableAlias ? `"${model.tableAlias}".` : "");
336
+ if (field.type === "json" && instructionName !== "to") {
329
337
  const dotParts = fieldPath.split(".");
330
338
  const columnName = tablePrefix + dotParts.shift();
331
339
  const jsonField = dotParts.join(".");
@@ -333,28 +341,33 @@ var getFieldSelector = (field, fieldPath, rootTable) => {
333
341
  }
334
342
  return `${tablePrefix}"${fieldPath}"`;
335
343
  };
336
- var getFieldFromSchema = (schema, fieldPath, instructionName, rootTable) => {
344
+ var getFieldFromModel = (model, fieldPath, instructionName) => {
337
345
  const errorPrefix = `Field "${fieldPath}" defined for \`${instructionName}\``;
338
- const schemaFields = schema.fields || [];
339
- let schemaField;
346
+ const modelFields = model.fields || [];
347
+ let modelField;
340
348
  if (fieldPath.includes(".")) {
341
- schemaField = schemaFields.find((field) => field.slug === fieldPath.split(".")[0]);
342
- if (schemaField?.type === "json") {
343
- const fieldSelector2 = getFieldSelector(schemaField, fieldPath, rootTable);
344
- return { field: schemaField, fieldSelector: fieldSelector2 };
349
+ modelField = modelFields.find((field) => field.slug === fieldPath.split(".")[0]);
350
+ if (modelField?.type === "json") {
351
+ const fieldSelector2 = getFieldSelector(
352
+ model,
353
+ modelField,
354
+ fieldPath,
355
+ instructionName
356
+ );
357
+ return { field: modelField, fieldSelector: fieldSelector2 };
345
358
  }
346
359
  }
347
- schemaField = schemaFields.find((field) => field.slug === fieldPath);
348
- if (!schemaField) {
360
+ modelField = modelFields.find((field) => field.slug === fieldPath);
361
+ if (!modelField) {
349
362
  throw new RoninError({
350
- message: `${errorPrefix} does not exist in schema "${schema.name}".`,
363
+ message: `${errorPrefix} does not exist in model "${model.name}".`,
351
364
  code: "FIELD_NOT_FOUND",
352
365
  field: fieldPath,
353
366
  queries: null
354
367
  });
355
368
  }
356
- const fieldSelector = getFieldSelector(schemaField, fieldPath, rootTable);
357
- return { field: schemaField, fieldSelector };
369
+ const fieldSelector = getFieldSelector(model, modelField, fieldPath, instructionName);
370
+ return { field: modelField, fieldSelector };
358
371
  };
359
372
  var slugToName = (slug) => {
360
373
  const name = slug.replace(/([a-z])([A-Z])/g, "$1 $2");
@@ -372,36 +385,37 @@ var pluralize = (word) => {
372
385
  }
373
386
  return `${word}s`;
374
387
  };
375
- var schemaSettings = [
388
+ var modelSettings = [
376
389
  ["pluralSlug", "slug", pluralize],
377
390
  ["name", "slug", slugToName],
378
391
  ["pluralName", "pluralSlug", slugToName],
379
- ["idPrefix", "slug", (slug) => slug.slice(0, 3)]
392
+ ["idPrefix", "slug", (slug) => slug.slice(0, 3)],
393
+ ["table", "pluralSlug", convertToSnakeCase]
380
394
  ];
381
- var addDefaultSchemaFields = (schema, isNew) => {
382
- const copiedSchema = { ...schema };
383
- for (const [setting, base, generator] of schemaSettings) {
384
- if (copiedSchema[setting] || !copiedSchema[base]) continue;
385
- copiedSchema[setting] = generator(copiedSchema[base]);
395
+ var addDefaultModelFields = (model, isNew) => {
396
+ const copiedModel = { ...model };
397
+ for (const [setting, base, generator] of modelSettings) {
398
+ if (copiedModel[setting] || !copiedModel[base]) continue;
399
+ copiedModel[setting] = generator(copiedModel[base]);
386
400
  }
387
- const newFields = copiedSchema.fields || [];
401
+ const newFields = copiedModel.fields || [];
388
402
  if (isNew || newFields.length > 0) {
389
- if (!copiedSchema.identifiers) copiedSchema.identifiers = {};
390
- if (!copiedSchema.identifiers.name) {
403
+ if (!copiedModel.identifiers) copiedModel.identifiers = {};
404
+ if (!copiedModel.identifiers.name) {
391
405
  const suitableField = newFields.find(
392
406
  (field) => field.type === "string" && field.required === true && ["name"].includes(field.slug)
393
407
  );
394
- copiedSchema.identifiers.name = suitableField?.slug || "id";
408
+ copiedModel.identifiers.name = suitableField?.slug || "id";
395
409
  }
396
- if (!copiedSchema.identifiers.slug) {
410
+ if (!copiedModel.identifiers.slug) {
397
411
  const suitableField = newFields.find(
398
412
  (field) => field.type === "string" && field.unique === true && field.required === true && ["slug", "handle"].includes(field.slug)
399
413
  );
400
- copiedSchema.identifiers.slug = suitableField?.slug || "id";
414
+ copiedModel.identifiers.slug = suitableField?.slug || "id";
401
415
  }
402
- copiedSchema.fields = [...SYSTEM_FIELDS, ...newFields];
416
+ copiedModel.fields = [...SYSTEM_FIELDS, ...newFields];
403
417
  }
404
- return copiedSchema;
418
+ return copiedModel;
405
419
  };
406
420
  var SYSTEM_FIELDS = [
407
421
  {
@@ -441,9 +455,9 @@ var SYSTEM_FIELDS = [
441
455
  slug: "ronin.updatedBy"
442
456
  }
443
457
  ];
444
- var SYSTEM_SCHEMAS = [
458
+ var SYSTEM_MODELS = [
445
459
  {
446
- slug: "schema",
460
+ slug: "model",
447
461
  identifiers: {
448
462
  name: "name",
449
463
  slug: "slug"
@@ -454,14 +468,14 @@ var SYSTEM_SCHEMAS = [
454
468
  { slug: "slug", type: "string" },
455
469
  { slug: "pluralSlug", type: "string" },
456
470
  { slug: "idPrefix", type: "string" },
471
+ { slug: "table", type: "string" },
457
472
  { slug: "identifiers", type: "group" },
458
473
  { slug: "identifiers.name", type: "string" },
459
474
  { slug: "identifiers.slug", type: "string" },
460
475
  { slug: "fields", type: "json" },
461
476
  { slug: "indexes", type: "json" },
462
477
  { slug: "triggers", type: "json" },
463
- { slug: "including", type: "json" },
464
- { slug: "for", type: "json" }
478
+ { slug: "presets", type: "json" }
465
479
  ]
466
480
  },
467
481
  {
@@ -475,9 +489,9 @@ var SYSTEM_SCHEMAS = [
475
489
  { slug: "slug", type: "string", required: true },
476
490
  { slug: "type", type: "string", required: true },
477
491
  {
478
- slug: "schema",
492
+ slug: "model",
479
493
  type: "reference",
480
- target: { slug: "schema" },
494
+ target: { slug: "model" },
481
495
  required: true
482
496
  },
483
497
  { slug: "required", type: "boolean" },
@@ -485,7 +499,7 @@ var SYSTEM_SCHEMAS = [
485
499
  { slug: "unique", type: "boolean" },
486
500
  { slug: "autoIncrement", type: "boolean" },
487
501
  // Only allowed for fields of type "reference".
488
- { slug: "target", type: "reference", target: { slug: "schema" } },
502
+ { slug: "target", type: "reference", target: { slug: "model" } },
489
503
  { slug: "kind", type: "string" },
490
504
  { slug: "actions", type: "group" },
491
505
  { slug: "actions.onDelete", type: "string" },
@@ -501,9 +515,9 @@ var SYSTEM_SCHEMAS = [
501
515
  fields: [
502
516
  { slug: "slug", type: "string", required: true },
503
517
  {
504
- slug: "schema",
518
+ slug: "model",
505
519
  type: "reference",
506
- target: { slug: "schema" },
520
+ target: { slug: "model" },
507
521
  required: true
508
522
  },
509
523
  { slug: "unique", type: "boolean" },
@@ -520,9 +534,9 @@ var SYSTEM_SCHEMAS = [
520
534
  fields: [
521
535
  { slug: "slug", type: "string", required: true },
522
536
  {
523
- slug: "schema",
537
+ slug: "model",
524
538
  type: "reference",
525
- target: { slug: "schema" },
539
+ target: { slug: "model" },
526
540
  required: true
527
541
  },
528
542
  { slug: "when", type: "string", required: true },
@@ -531,90 +545,124 @@ var SYSTEM_SCHEMAS = [
531
545
  { slug: "effects", type: "json", required: true },
532
546
  { slug: "fields", type: "json" }
533
547
  ]
548
+ },
549
+ {
550
+ slug: "preset",
551
+ fields: [
552
+ { slug: "slug", type: "string", required: true },
553
+ {
554
+ slug: "model",
555
+ type: "reference",
556
+ target: { slug: "model" },
557
+ required: true
558
+ },
559
+ { slug: "instructions", type: "json", required: true }
560
+ ]
534
561
  }
535
- ].map((schema) => addDefaultSchemaFields(schema, true));
536
- var SYSTEM_SCHEMA_SLUGS = SYSTEM_SCHEMAS.flatMap(({ slug, pluralSlug }) => [
562
+ ].map((model) => addDefaultModelFields(model, true));
563
+ var SYSTEM_MODEL_SLUGS = SYSTEM_MODELS.flatMap(({ slug, pluralSlug }) => [
537
564
  slug,
538
565
  pluralSlug
539
566
  ]);
540
- var addSystemSchemas = (schemas) => {
541
- const associativeSchemas = schemas.flatMap((schema) => {
542
- const addedSchemas = [];
543
- for (const field of schema.fields || []) {
567
+ var addSystemModels = (models) => {
568
+ const associativeModels = models.flatMap((model) => {
569
+ const addedModels = [];
570
+ for (const field of model.fields || []) {
544
571
  if (field.type === "reference" && !field.slug.startsWith("ronin.")) {
545
- const relatedSchema = getSchemaBySlug(schemas, field.target.slug);
546
- let fieldSlug = relatedSchema.slug;
572
+ const relatedModel = getModelBySlug(models, field.target.slug);
573
+ let fieldSlug = relatedModel.slug;
547
574
  if (field.kind === "many") {
548
- fieldSlug = composeAssociationSchemaSlug(schema, field);
549
- addedSchemas.push({
575
+ fieldSlug = composeAssociationModelSlug(model, field);
576
+ addedModels.push({
550
577
  pluralSlug: fieldSlug,
551
578
  slug: fieldSlug,
579
+ associationSlug: field.slug,
552
580
  fields: [
553
581
  {
554
582
  slug: "source",
555
583
  type: "reference",
556
- target: { slug: schema.slug }
584
+ target: { slug: model.slug }
557
585
  },
558
586
  {
559
587
  slug: "target",
560
588
  type: "reference",
561
- target: { slug: relatedSchema.slug }
589
+ target: { slug: relatedModel.slug }
562
590
  }
563
591
  ]
564
592
  });
565
593
  }
566
594
  }
567
595
  }
568
- return addedSchemas;
596
+ return addedModels;
569
597
  });
570
- return [...SYSTEM_SCHEMAS, ...associativeSchemas, ...schemas];
598
+ return [...SYSTEM_MODELS, ...associativeModels, ...models];
571
599
  };
572
- var addDefaultSchemaShortcuts = (list, schema) => {
573
- const defaultIncluding = {};
574
- for (const field of schema.fields || []) {
600
+ var addDefaultModelPresets = (list, model) => {
601
+ const defaultPresets = [];
602
+ for (const field of model.fields || []) {
575
603
  if (field.type === "reference" && !field.slug.startsWith("ronin.")) {
576
- const relatedSchema = getSchemaBySlug(list, field.target.slug);
577
- let fieldSlug = relatedSchema.slug;
578
- if (field.kind === "many") {
579
- fieldSlug = composeAssociationSchemaSlug(schema, field);
580
- }
581
- defaultIncluding[field.slug] = {
582
- get: {
583
- [fieldSlug]: {
584
- with: {
585
- // Compare the `id` field of the related schema to the reference field on
586
- // the root schema (`field.slug`).
587
- id: `${RONIN_SCHEMA_SYMBOLS.FIELD}${field.slug}`
604
+ const relatedModel = getModelBySlug(list, field.target.slug);
605
+ if (field.kind === "many") continue;
606
+ defaultPresets.push({
607
+ instructions: {
608
+ including: {
609
+ [field.slug]: {
610
+ [RONIN_MODEL_SYMBOLS.QUERY]: {
611
+ get: {
612
+ [relatedModel.slug]: {
613
+ with: {
614
+ // Compare the `id` field of the related model to the reference field on
615
+ // the root model (`field.slug`).
616
+ id: {
617
+ [RONIN_MODEL_SYMBOLS.EXPRESSION]: `${RONIN_MODEL_SYMBOLS.FIELD_PARENT}${field.slug}`
618
+ }
619
+ }
620
+ }
621
+ }
622
+ }
588
623
  }
589
624
  }
590
- }
591
- };
625
+ },
626
+ slug: field.slug
627
+ });
592
628
  }
593
629
  }
594
- const childSchemas = list.map((subSchema) => {
595
- const field = subSchema.fields?.find((field2) => {
596
- return field2.type === "reference" && field2.target.slug === schema.slug;
630
+ const childModels = list.map((subModel) => {
631
+ const field = subModel.fields?.find((field2) => {
632
+ return field2.type === "reference" && field2.target.slug === model.slug;
597
633
  });
598
634
  if (!field) return null;
599
- return { schema: subSchema, field };
635
+ return { model: subModel, field };
600
636
  }).filter((match) => match !== null);
601
- for (const childMatch of childSchemas) {
602
- const { schema: childSchema, field: childField } = childMatch;
603
- const pluralSlug = childSchema.pluralSlug;
604
- defaultIncluding[pluralSlug] = {
605
- get: {
606
- [pluralSlug]: {
607
- with: {
608
- [childField.slug]: `${RONIN_SCHEMA_SYMBOLS.FIELD}id`
637
+ for (const childMatch of childModels) {
638
+ const { model: childModel, field: childField } = childMatch;
639
+ const pluralSlug = childModel.pluralSlug;
640
+ const presetSlug = childModel.associationSlug || pluralSlug;
641
+ defaultPresets.push({
642
+ instructions: {
643
+ including: {
644
+ [presetSlug]: {
645
+ [RONIN_MODEL_SYMBOLS.QUERY]: {
646
+ get: {
647
+ [pluralSlug]: {
648
+ with: {
649
+ [childField.slug]: {
650
+ [RONIN_MODEL_SYMBOLS.EXPRESSION]: `${RONIN_MODEL_SYMBOLS.FIELD_PARENT}id`
651
+ }
652
+ }
653
+ }
654
+ }
655
+ }
609
656
  }
610
657
  }
611
- }
612
- };
658
+ },
659
+ slug: presetSlug
660
+ });
613
661
  }
614
- if (Object.keys(defaultIncluding).length > 0) {
615
- schema.including = { ...defaultIncluding, ...schema.including };
662
+ if (Object.keys(defaultPresets).length > 0) {
663
+ model.presets = [...defaultPresets, ...model.presets || []];
616
664
  }
617
- return schema;
665
+ return model;
618
666
  };
619
667
  var mappedInstructions = {
620
668
  create: "to",
@@ -650,35 +698,31 @@ var getFieldStatement = (field) => {
650
698
  }
651
699
  return statement;
652
700
  };
653
- var addSchemaQueries = (schemas, queryDetails, dependencyStatements) => {
654
- const {
655
- queryType,
656
- querySchema,
657
- queryInstructions: queryInstructionsRaw
658
- } = queryDetails;
701
+ var addModelQueries = (models, queryDetails, dependencyStatements) => {
702
+ const { queryType, queryModel, queryInstructions: queryInstructionsRaw } = queryDetails;
659
703
  const queryInstructions = queryInstructionsRaw;
660
704
  if (!["create", "set", "drop"].includes(queryType)) return queryInstructions;
661
- if (!SYSTEM_SCHEMA_SLUGS.includes(querySchema)) return queryInstructions;
705
+ if (!SYSTEM_MODEL_SLUGS.includes(queryModel)) return queryInstructions;
662
706
  const instructionName = mappedInstructions[queryType];
663
707
  const instructionList = queryInstructions[instructionName];
664
- const kind = getSchemaBySlug(SYSTEM_SCHEMAS, querySchema).pluralSlug;
708
+ const kind = getModelBySlug(SYSTEM_MODELS, queryModel).pluralSlug;
665
709
  let tableAction = "ALTER";
666
710
  let queryTypeReadable = null;
667
711
  switch (queryType) {
668
712
  case "create": {
669
- if (kind === "schemas" || kind === "indexes" || kind === "triggers") {
713
+ if (kind === "models" || kind === "indexes" || kind === "triggers") {
670
714
  tableAction = "CREATE";
671
715
  }
672
716
  queryTypeReadable = "creating";
673
717
  break;
674
718
  }
675
719
  case "set": {
676
- if (kind === "schemas") tableAction = "ALTER";
720
+ if (kind === "models") tableAction = "ALTER";
677
721
  queryTypeReadable = "updating";
678
722
  break;
679
723
  }
680
724
  case "drop": {
681
- if (kind === "schemas" || kind === "indexes" || kind === "triggers") {
725
+ if (kind === "models" || kind === "indexes" || kind === "triggers") {
682
726
  tableAction = "DROP";
683
727
  }
684
728
  queryTypeReadable = "deleting";
@@ -693,18 +737,18 @@ var addSchemaQueries = (schemas, queryDetails, dependencyStatements) => {
693
737
  fields: ["slug"]
694
738
  });
695
739
  }
696
- const schemaInstruction = instructionList?.schema;
697
- const schemaSlug = schemaInstruction?.slug?.being || schemaInstruction?.slug;
698
- if (kind !== "schemas" && !schemaSlug) {
740
+ const modelInstruction = instructionList?.model;
741
+ const modelSlug = modelInstruction?.slug?.being || modelInstruction?.slug;
742
+ if (kind !== "models" && !modelSlug) {
699
743
  throw new RoninError({
700
- message: `When ${queryTypeReadable} ${kind}, a \`schema.slug\` field must be provided in the \`${instructionName}\` instruction.`,
744
+ message: `When ${queryTypeReadable} ${kind}, a \`model.slug\` field must be provided in the \`${instructionName}\` instruction.`,
701
745
  code: "MISSING_FIELD",
702
- fields: ["schema.slug"]
746
+ fields: ["model.slug"]
703
747
  });
704
748
  }
705
- const usableSlug = kind === "schemas" ? slug : schemaSlug;
749
+ const usableSlug = kind === "models" ? slug : modelSlug;
706
750
  const tableName = convertToSnakeCase(pluralize(usableSlug));
707
- const targetSchema = kind === "schemas" && queryType === "create" ? null : getSchemaBySlug(schemas, usableSlug);
751
+ const targetModel = kind === "models" && queryType === "create" ? null : getModelBySlug(models, usableSlug);
708
752
  if (kind === "indexes") {
709
753
  const indexName = convertToSnakeCase(slug);
710
754
  const unique = instructionList?.unique;
@@ -713,16 +757,13 @@ var addSchemaQueries = (schemas, queryDetails, dependencyStatements) => {
713
757
  const params = [];
714
758
  let statement2 = `${tableAction}${unique ? " UNIQUE" : ""} INDEX "${indexName}"`;
715
759
  if (queryType === "create") {
716
- const schema = targetSchema;
760
+ const model = targetModel;
717
761
  const columns = fields.map((field) => {
718
762
  let fieldSelector = "";
719
763
  if ("slug" in field) {
720
- ({ fieldSelector } = getFieldFromSchema(schema, field.slug, "to"));
764
+ ({ fieldSelector } = getFieldFromModel(model, field.slug, "to"));
721
765
  } else if ("expression" in field) {
722
- fieldSelector = field.expression.replace(RONIN_SCHEMA_FIELD_REGEX, (match) => {
723
- const fieldSlug = match.replace(RONIN_SCHEMA_SYMBOLS.FIELD, "");
724
- return getFieldFromSchema(schema, fieldSlug, "to").fieldSelector;
725
- });
766
+ fieldSelector = parseFieldExpression(model, "to", field.expression, model);
726
767
  }
727
768
  if (field.collation) fieldSelector += ` COLLATE ${field.collation}`;
728
769
  if (field.order) fieldSelector += ` ${field.order}`;
@@ -731,8 +772,8 @@ var addSchemaQueries = (schemas, queryDetails, dependencyStatements) => {
731
772
  statement2 += ` ON "${tableName}" (${columns.join(", ")})`;
732
773
  if (filterQuery) {
733
774
  const withStatement = handleWith(
734
- schemas,
735
- targetSchema,
775
+ models,
776
+ targetModel,
736
777
  params,
737
778
  filterQuery
738
779
  );
@@ -747,6 +788,7 @@ var addSchemaQueries = (schemas, queryDetails, dependencyStatements) => {
747
788
  const params = [];
748
789
  let statement2 = `${tableAction} TRIGGER "${triggerName}"`;
749
790
  if (queryType === "create") {
791
+ const currentModel = targetModel;
750
792
  const { when, action } = instructionList;
751
793
  const statementParts = [`${when} ${action}`];
752
794
  const effectQueries = instructionList?.effects;
@@ -756,33 +798,33 @@ var addSchemaQueries = (schemas, queryDetails, dependencyStatements) => {
756
798
  if (action !== "UPDATE") {
757
799
  throw new RoninError({
758
800
  message: `When ${queryTypeReadable} ${kind}, targeting specific fields requires the \`UPDATE\` action.`,
759
- code: "INVALID_SCHEMA_VALUE",
801
+ code: "INVALID_MODEL_VALUE",
760
802
  fields: ["action"]
761
803
  });
762
804
  }
763
805
  const fieldSelectors = fields.map((field) => {
764
- return getFieldFromSchema(targetSchema, field.slug, "to").fieldSelector;
806
+ return getFieldFromModel(currentModel, field.slug, "to").fieldSelector;
765
807
  });
766
808
  statementParts.push(`OF (${fieldSelectors.join(", ")})`);
767
809
  }
768
810
  statementParts.push("ON", `"${tableName}"`);
769
- if (filterQuery || effectQueries.some((query) => findInObject(query, RONIN_SCHEMA_SYMBOLS.FIELD))) {
811
+ if (filterQuery || effectQueries.some((query) => findInObject(query, RONIN_MODEL_SYMBOLS.FIELD))) {
770
812
  statementParts.push("FOR EACH ROW");
771
813
  }
772
814
  if (filterQuery) {
773
- const tablePlaceholder = action === "DELETE" ? RONIN_SCHEMA_SYMBOLS.FIELD_OLD : RONIN_SCHEMA_SYMBOLS.FIELD_NEW;
815
+ const tableAlias = action === "DELETE" ? RONIN_MODEL_SYMBOLS.FIELD_PARENT_OLD : RONIN_MODEL_SYMBOLS.FIELD_PARENT_NEW;
774
816
  const withStatement = handleWith(
775
- schemas,
776
- targetSchema,
817
+ models,
818
+ { ...currentModel, tableAlias },
777
819
  params,
778
- filterQuery,
779
- tablePlaceholder
820
+ filterQuery
780
821
  );
781
822
  statementParts.push("WHEN", `(${withStatement})`);
782
823
  }
783
824
  const effectStatements = effectQueries.map((effectQuery) => {
784
- return compileQueryInput(effectQuery, schemas, params, {
785
- returning: false
825
+ return compileQueryInput(effectQuery, models, params, {
826
+ returning: false,
827
+ parentModel: currentModel
786
828
  }).main.statement;
787
829
  });
788
830
  if (effectStatements.length > 1) statementParts.push("BEGIN");
@@ -794,14 +836,14 @@ var addSchemaQueries = (schemas, queryDetails, dependencyStatements) => {
794
836
  return queryInstructions;
795
837
  }
796
838
  const statement = `${tableAction} TABLE "${tableName}"`;
797
- if (kind === "schemas") {
839
+ if (kind === "models") {
798
840
  if (queryType === "create" || queryType === "set") {
799
- const schemaWithFields = addDefaultSchemaFields(
841
+ const modelWithFields = addDefaultModelFields(
800
842
  queryInstructions.to,
801
843
  queryType === "create"
802
844
  );
803
- const schemaWithShortcuts = addDefaultSchemaShortcuts(schemas, schemaWithFields);
804
- queryInstructions.to = schemaWithShortcuts;
845
+ const modelWithPresets = addDefaultModelPresets(models, modelWithFields);
846
+ queryInstructions.to = modelWithPresets;
805
847
  }
806
848
  if (queryType === "create") {
807
849
  const { fields } = queryInstructions.to;
@@ -810,7 +852,7 @@ var addSchemaQueries = (schemas, queryDetails, dependencyStatements) => {
810
852
  statement: `${statement} (${columns.join(", ")})`,
811
853
  params: []
812
854
  });
813
- schemas.push(queryInstructions.to);
855
+ models.push(queryInstructions.to);
814
856
  } else if (queryType === "set") {
815
857
  const newSlug = queryInstructions.to?.pluralSlug;
816
858
  if (newSlug) {
@@ -820,9 +862,9 @@ var addSchemaQueries = (schemas, queryDetails, dependencyStatements) => {
820
862
  params: []
821
863
  });
822
864
  }
823
- Object.assign(targetSchema, queryInstructions.to);
865
+ Object.assign(targetModel, queryInstructions.to);
824
866
  } else if (queryType === "drop") {
825
- schemas.splice(schemas.indexOf(targetSchema), 1);
867
+ models.splice(models.indexOf(targetModel), 1);
826
868
  dependencyStatements.push({ statement, params: [] });
827
869
  }
828
870
  return queryInstructions;
@@ -861,7 +903,7 @@ var addSchemaQueries = (schemas, queryDetails, dependencyStatements) => {
861
903
  // src/instructions/before-after.ts
862
904
  var CURSOR_SEPARATOR = ",";
863
905
  var CURSOR_NULL_PLACEHOLDER = "RONIN_NULL";
864
- var handleBeforeOrAfter = (schema, statementParams, instructions, rootTable) => {
906
+ var handleBeforeOrAfter = (model, statementParams, instructions) => {
865
907
  if (!(instructions.before || instructions.after)) {
866
908
  throw new RoninError({
867
909
  message: "The `before` or `after` instruction must not be empty.",
@@ -892,7 +934,7 @@ var handleBeforeOrAfter = (schema, statementParams, instructions, rootTable) =>
892
934
  if (value === CURSOR_NULL_PLACEHOLDER) {
893
935
  return "NULL";
894
936
  }
895
- const { field } = getFieldFromSchema(schema, key, "orderedBy");
937
+ const { field } = getFieldFromModel(model, key, "orderedBy");
896
938
  if (field.type === "boolean") {
897
939
  return prepareStatementValue(statementParams, value === "true");
898
940
  }
@@ -918,12 +960,7 @@ var handleBeforeOrAfter = (schema, statementParams, instructions, rootTable) =>
918
960
  for (let j = 0; j <= i; j++) {
919
961
  const key = keys[j];
920
962
  const value = values[j];
921
- let { field, fieldSelector } = getFieldFromSchema(
922
- schema,
923
- key,
924
- "orderedBy",
925
- rootTable
926
- );
963
+ let { field, fieldSelector } = getFieldFromModel(model, key, "orderedBy");
927
964
  if (j === i) {
928
965
  const closingParentheses = ")".repeat(condition.length);
929
966
  const operator = value === "NULL" ? "IS NOT" : compareOperators[j];
@@ -945,57 +982,66 @@ var handleBeforeOrAfter = (schema, statementParams, instructions, rootTable) =>
945
982
  };
946
983
 
947
984
  // src/instructions/for.ts
948
- var handleFor = (schemas, schema, statementParams, instruction, rootTable) => {
949
- let statement = "";
950
- if (!instruction) return statement;
951
- for (const shortcut in instruction) {
952
- const args = instruction[shortcut];
953
- const forFilter = schema.for?.[shortcut];
954
- if (!forFilter) {
985
+ var handleFor = (model, instructions) => {
986
+ const normalizedFor = Array.isArray(instructions.for) ? Object.fromEntries(instructions.for.map((presetSlug) => [presetSlug, null])) : instructions.for;
987
+ for (const presetSlug in normalizedFor) {
988
+ const arg = normalizedFor[presetSlug];
989
+ const preset = model.presets?.find((preset2) => preset2.slug === presetSlug);
990
+ if (!preset) {
955
991
  throw new RoninError({
956
- message: `The provided \`for\` shortcut "${shortcut}" does not exist in schema "${schema.name}".`,
957
- code: "INVALID_FOR_VALUE"
992
+ message: `Preset "${presetSlug}" does not exist in model "${model.name}".`,
993
+ code: "PRESET_NOT_FOUND"
994
+ });
995
+ }
996
+ const replacedForFilter = structuredClone(preset.instructions);
997
+ if (arg !== null) {
998
+ findInObject(
999
+ replacedForFilter,
1000
+ RONIN_MODEL_SYMBOLS.VALUE,
1001
+ (match) => match.replace(RONIN_MODEL_SYMBOLS.VALUE, arg)
1002
+ );
1003
+ }
1004
+ for (const subInstruction in replacedForFilter) {
1005
+ const instructionName = subInstruction;
1006
+ const currentValue = instructions[instructionName];
1007
+ if (currentValue) {
1008
+ let newValue;
1009
+ if (Array.isArray(currentValue)) {
1010
+ newValue = [
1011
+ ...replacedForFilter[instructionName],
1012
+ ...currentValue
1013
+ ];
1014
+ } else if (isObject(currentValue)) {
1015
+ newValue = {
1016
+ ...replacedForFilter[instructionName],
1017
+ ...currentValue
1018
+ };
1019
+ }
1020
+ Object.assign(instructions, { [instructionName]: newValue });
1021
+ continue;
1022
+ }
1023
+ Object.assign(instructions, {
1024
+ [instructionName]: replacedForFilter[instructionName]
958
1025
  });
959
1026
  }
960
- const replacedForFilter = structuredClone(forFilter);
961
- findInObject(
962
- replacedForFilter,
963
- RONIN_SCHEMA_SYMBOLS.VALUE,
964
- (match) => match.replace(RONIN_SCHEMA_SYMBOLS.VALUE, args)
965
- );
966
- const subStatement = composeConditions(
967
- schemas,
968
- schema,
969
- statementParams,
970
- "for",
971
- replacedForFilter,
972
- { rootTable }
973
- );
974
- statement += `(${subStatement})`;
975
1027
  }
976
- return statement;
1028
+ return instructions;
977
1029
  };
978
1030
 
979
1031
  // src/instructions/including.ts
980
- var handleIncluding = (schemas, statementParams, schema, instruction, rootTable) => {
1032
+ var handleIncluding = (models, model, statementParams, instruction) => {
981
1033
  let statement = "";
982
- let rootTableSubQuery;
983
- let rootTableName = rootTable;
984
- for (const shortcut of instruction || []) {
985
- const includingQuery = schema.including?.[shortcut];
986
- if (!includingQuery) {
987
- throw new RoninError({
988
- message: `The provided \`including\` shortcut "${shortcut}" does not exist in schema "${schema.name}".`,
989
- code: "INVALID_INCLUDING_VALUE"
990
- });
991
- }
992
- const { queryType, querySchema, queryInstructions } = splitQuery(includingQuery);
1034
+ let tableSubQuery;
1035
+ for (const ephemeralFieldSlug in instruction) {
1036
+ const symbol = getSymbol(instruction[ephemeralFieldSlug]);
1037
+ if (symbol?.type !== "query") continue;
1038
+ const { queryType, queryModel, queryInstructions } = splitQuery(symbol.value);
993
1039
  let modifiableQueryInstructions = queryInstructions;
994
- const relatedSchema = getSchemaBySlug(schemas, querySchema);
1040
+ const relatedModel = getModelBySlug(models, queryModel);
995
1041
  let joinType = "LEFT";
996
- let relatedTableSelector = `"${getTableForSchema(relatedSchema)}"`;
997
- const tableAlias = `including_${shortcut}`;
998
- const single = querySchema !== relatedSchema.pluralSlug;
1042
+ let relatedTableSelector = `"${relatedModel.table}"`;
1043
+ const tableAlias = `including_${ephemeralFieldSlug}`;
1044
+ const single = queryModel !== relatedModel.pluralSlug;
999
1045
  if (!modifiableQueryInstructions?.with) {
1000
1046
  joinType = "CROSS";
1001
1047
  if (single) {
@@ -1007,35 +1053,35 @@ var handleIncluding = (schemas, statementParams, schema, instruction, rootTable)
1007
1053
  const subSelect = compileQueryInput(
1008
1054
  {
1009
1055
  [queryType]: {
1010
- [querySchema]: modifiableQueryInstructions
1056
+ [queryModel]: modifiableQueryInstructions
1011
1057
  }
1012
1058
  },
1013
- schemas,
1059
+ models,
1014
1060
  statementParams
1015
1061
  );
1016
1062
  relatedTableSelector = `(${subSelect.main.statement})`;
1017
1063
  }
1018
1064
  statement += `${joinType} JOIN ${relatedTableSelector} as ${tableAlias}`;
1065
+ model.tableAlias = model.table;
1019
1066
  if (joinType === "LEFT") {
1020
1067
  if (!single) {
1021
- rootTableSubQuery = `SELECT * FROM "${rootTable}" LIMIT 1`;
1022
- rootTableName = `sub_${rootTable}`;
1068
+ tableSubQuery = `SELECT * FROM "${model.table}" LIMIT 1`;
1069
+ model.tableAlias = `sub_${model.table}`;
1023
1070
  }
1024
1071
  const subStatement = composeConditions(
1025
- schemas,
1026
- relatedSchema,
1072
+ models,
1073
+ { ...relatedModel, tableAlias },
1027
1074
  statementParams,
1028
1075
  "including",
1029
1076
  queryInstructions?.with,
1030
1077
  {
1031
- rootTable: rootTableName,
1032
- customTable: tableAlias
1078
+ parentModel: model
1033
1079
  }
1034
1080
  );
1035
1081
  statement += ` ON (${subStatement})`;
1036
1082
  }
1037
1083
  }
1038
- return { statement, rootTableSubQuery, rootTableName };
1084
+ return { statement, tableSubQuery };
1039
1085
  };
1040
1086
 
1041
1087
  // src/instructions/limited-to.ts
@@ -1047,62 +1093,64 @@ var handleLimitedTo = (single, instruction) => {
1047
1093
  };
1048
1094
 
1049
1095
  // src/instructions/ordered-by.ts
1050
- var handleOrderedBy = (schema, instruction, rootTable) => {
1096
+ var handleOrderedBy = (model, instruction) => {
1051
1097
  let statement = "";
1052
- for (const field of instruction.ascending || []) {
1053
- const { field: schemaField, fieldSelector } = getFieldFromSchema(
1054
- schema,
1055
- field,
1056
- "orderedBy.ascending",
1057
- rootTable
1058
- );
1098
+ const items = [
1099
+ ...(instruction.ascending || []).map((value) => ({ value, order: "ASC" })),
1100
+ ...(instruction.descending || []).map((value) => ({ value, order: "DESC" }))
1101
+ ];
1102
+ for (const item of items) {
1059
1103
  if (statement.length > 0) {
1060
1104
  statement += ", ";
1061
1105
  }
1062
- const caseInsensitiveStatement = schemaField.type === "string" ? " COLLATE NOCASE" : "";
1063
- statement += `${fieldSelector}${caseInsensitiveStatement} ASC`;
1064
- }
1065
- for (const field of instruction.descending || []) {
1066
- const { field: schemaField, fieldSelector } = getFieldFromSchema(
1067
- schema,
1068
- field,
1069
- "orderedBy.descending",
1070
- rootTable
1071
- );
1072
- if (statement.length > 0) {
1073
- statement += ", ";
1106
+ const symbol = getSymbol(item.value);
1107
+ const instructionName = item.order === "ASC" ? "orderedBy.ascending" : "orderedBy.descending";
1108
+ if (symbol?.type === "expression") {
1109
+ statement += `(${parseFieldExpression(model, instructionName, symbol.value)}) ${item.order}`;
1110
+ continue;
1074
1111
  }
1075
- const caseInsensitiveStatement = schemaField.type === "string" ? " COLLATE NOCASE" : "";
1076
- statement += `${fieldSelector}${caseInsensitiveStatement} DESC`;
1112
+ const { field: modelField, fieldSelector } = getFieldFromModel(
1113
+ model,
1114
+ item.value,
1115
+ instructionName
1116
+ );
1117
+ const caseInsensitiveStatement = modelField.type === "string" ? " COLLATE NOCASE" : "";
1118
+ statement += `${fieldSelector}${caseInsensitiveStatement} ${item.order}`;
1077
1119
  }
1078
1120
  return `ORDER BY ${statement}`;
1079
1121
  };
1080
1122
 
1081
1123
  // src/instructions/selecting.ts
1082
- var handleSelecting = (schema, statementParams, instructions) => {
1124
+ var handleSelecting = (model, statementParams, instructions) => {
1125
+ let isJoining = false;
1083
1126
  let statement = instructions.selecting ? instructions.selecting.map((slug) => {
1084
- return getFieldFromSchema(schema, slug, "selecting").fieldSelector;
1127
+ return getFieldFromModel(model, slug, "selecting").fieldSelector;
1085
1128
  }).join(", ") : "*";
1086
- if (isObject(instructions.including)) {
1087
- statement += ", ";
1088
- statement += Object.entries(
1089
- flatten(instructions.including)
1090
- ).filter(([_, value]) => {
1091
- return !(isObject(value) && Object.hasOwn(value, RONIN_SCHEMA_SYMBOLS.QUERY));
1092
- }).map(([key, value]) => {
1093
- return `${prepareStatementValue(statementParams, value)} as "${key}"`;
1094
- }).join(", ");
1129
+ if (instructions.including) {
1130
+ const filteredObject = Object.entries(instructions.including).filter(([_, value]) => {
1131
+ const symbol = getSymbol(value);
1132
+ const hasQuery = symbol?.type === "query";
1133
+ if (hasQuery) isJoining = true;
1134
+ return !hasQuery;
1135
+ });
1136
+ const newObjectEntries = Object.entries(flatten(Object.fromEntries(filteredObject)));
1137
+ if (newObjectEntries.length > 0) {
1138
+ statement += ", ";
1139
+ statement += newObjectEntries.map(([key, value]) => {
1140
+ return `${prepareStatementValue(statementParams, value)} as "${key}"`;
1141
+ }).join(", ");
1142
+ }
1095
1143
  }
1096
- return statement;
1144
+ return { columns: statement, isJoining };
1097
1145
  };
1098
1146
 
1099
1147
  // src/instructions/to.ts
1100
- var handleTo = (schemas, schema, statementParams, queryType, dependencyStatements, instructions, rootTable) => {
1148
+ var handleTo = (models, model, statementParams, queryType, dependencyStatements, instructions, parentModel) => {
1101
1149
  const currentTime = (/* @__PURE__ */ new Date()).toISOString();
1102
1150
  const { with: withInstruction, to: toInstruction } = instructions;
1103
1151
  const defaultFields = {};
1104
1152
  if (queryType === "create") {
1105
- defaultFields.id = toInstruction.id || generateRecordId(schema.idPrefix);
1153
+ defaultFields.id = toInstruction.id || generateRecordId(model.idPrefix);
1106
1154
  }
1107
1155
  defaultFields.ronin = {
1108
1156
  // If records are being created, set their creation time.
@@ -1112,21 +1160,20 @@ var handleTo = (schemas, schema, statementParams, queryType, dependencyStatement
1112
1160
  // Allow for overwriting the default values provided above.
1113
1161
  ...toInstruction.ronin
1114
1162
  };
1115
- const hasSubQuery = Object.hasOwn(toInstruction, RONIN_SCHEMA_SYMBOLS.QUERY);
1116
- if (hasSubQuery) {
1117
- const subQuery = toInstruction[RONIN_SCHEMA_SYMBOLS.QUERY];
1118
- let { querySchema: subQuerySchemaSlug, queryInstructions: subQueryInstructions } = splitQuery(subQuery);
1119
- const subQuerySchema = getSchemaBySlug(schemas, subQuerySchemaSlug);
1163
+ const symbol = getSymbol(toInstruction);
1164
+ if (symbol?.type === "query") {
1165
+ let { queryModel: subQueryModelSlug, queryInstructions: subQueryInstructions } = splitQuery(symbol.value);
1166
+ const subQueryModel = getModelBySlug(models, subQueryModelSlug);
1120
1167
  const subQuerySelectedFields = subQueryInstructions?.selecting;
1121
1168
  const subQueryIncludedFields = subQueryInstructions?.including;
1122
1169
  const subQueryFields = [
1123
- ...subQuerySelectedFields || (subQuerySchema.fields || []).map((field) => field.slug),
1170
+ ...subQuerySelectedFields || (subQueryModel.fields || []).map((field) => field.slug),
1124
1171
  ...subQueryIncludedFields ? Object.keys(
1125
1172
  flatten(subQueryIncludedFields || {})
1126
1173
  ) : []
1127
1174
  ];
1128
1175
  for (const field of subQueryFields || []) {
1129
- getFieldFromSchema(schema, field, "to");
1176
+ getFieldFromModel(model, field, "to");
1130
1177
  }
1131
1178
  const defaultFieldsToAdd = subQuerySelectedFields ? Object.entries(flatten(defaultFields)).filter(([key]) => {
1132
1179
  return !subQuerySelectedFields.includes(key);
@@ -1139,18 +1186,15 @@ var handleTo = (schemas, schema, statementParams, queryType, dependencyStatement
1139
1186
  ...subQueryInstructions.including
1140
1187
  };
1141
1188
  }
1142
- return compileQueryInput(subQuery, schemas, statementParams).main.statement;
1189
+ return compileQueryInput(symbol.value, models, statementParams).main.statement;
1143
1190
  }
1144
1191
  Object.assign(toInstruction, defaultFields);
1145
1192
  for (const fieldSlug in toInstruction) {
1146
1193
  const fieldValue = toInstruction[fieldSlug];
1147
- const fieldDetails = getFieldFromSchema(schema, fieldSlug, "to");
1194
+ const fieldDetails = getFieldFromModel(model, fieldSlug, "to");
1148
1195
  if (fieldDetails.field.type === "reference" && fieldDetails.field.kind === "many") {
1149
1196
  delete toInstruction[fieldSlug];
1150
- const associativeSchemaSlug = composeAssociationSchemaSlug(
1151
- schema,
1152
- fieldDetails.field
1153
- );
1197
+ const associativeModelSlug = composeAssociationModelSlug(model, fieldDetails.field);
1154
1198
  const composeStatement = (subQueryType, value) => {
1155
1199
  const source = queryType === "create" ? { id: toInstruction.id } : withInstruction;
1156
1200
  const recordDetails = { source };
@@ -1158,10 +1202,10 @@ var handleTo = (schemas, schema, statementParams, queryType, dependencyStatement
1158
1202
  return compileQueryInput(
1159
1203
  {
1160
1204
  [subQueryType]: {
1161
- [associativeSchemaSlug]: subQueryType === "create" ? { to: recordDetails } : { with: recordDetails }
1205
+ [associativeModelSlug]: subQueryType === "create" ? { to: recordDetails } : { with: recordDetails }
1162
1206
  }
1163
1207
  },
1164
- schemas,
1208
+ models,
1165
1209
  [],
1166
1210
  { returning: false }
1167
1211
  ).main;
@@ -1181,26 +1225,19 @@ var handleTo = (schemas, schema, statementParams, queryType, dependencyStatement
1181
1225
  }
1182
1226
  }
1183
1227
  }
1184
- let statement = composeConditions(
1185
- schemas,
1186
- schema,
1187
- statementParams,
1188
- "to",
1189
- toInstruction,
1190
- {
1191
- rootTable,
1192
- type: queryType === "create" ? "fields" : void 0
1193
- }
1194
- );
1228
+ let statement = composeConditions(models, model, statementParams, "to", toInstruction, {
1229
+ parentModel,
1230
+ type: queryType === "create" ? "fields" : void 0
1231
+ });
1195
1232
  if (queryType === "create") {
1196
1233
  const deepStatement = composeConditions(
1197
- schemas,
1198
- schema,
1234
+ models,
1235
+ model,
1199
1236
  statementParams,
1200
1237
  "to",
1201
1238
  toInstruction,
1202
1239
  {
1203
- rootTable,
1240
+ parentModel,
1204
1241
  type: "values"
1205
1242
  }
1206
1243
  );
@@ -1212,21 +1249,23 @@ var handleTo = (schemas, schema, statementParams, queryType, dependencyStatement
1212
1249
  };
1213
1250
 
1214
1251
  // src/utils/index.ts
1215
- var compileQueryInput = (query, schemas, statementParams, options) => {
1252
+ var compileQueryInput = (query, models, statementParams, options) => {
1216
1253
  const parsedQuery = splitQuery(query);
1217
- const { queryType, querySchema, queryInstructions } = parsedQuery;
1218
- const schema = getSchemaBySlug(schemas, querySchema);
1219
- const single = querySchema !== schema.pluralSlug;
1220
- let instructions = formatIdentifiers(schema, queryInstructions);
1221
- let table = getTableForSchema(schema);
1254
+ const { queryType, queryModel, queryInstructions } = parsedQuery;
1255
+ const model = getModelBySlug(models, queryModel);
1256
+ const single = queryModel !== model.pluralSlug;
1257
+ let instructions = formatIdentifiers(model, queryInstructions);
1222
1258
  const dependencyStatements = [];
1223
1259
  const returning = options?.returning ?? true;
1224
- instructions = addSchemaQueries(
1225
- schemas,
1226
- { queryType, querySchema, queryInstructions: instructions },
1260
+ instructions = addModelQueries(
1261
+ models,
1262
+ { queryType, queryModel, queryInstructions: instructions },
1227
1263
  dependencyStatements
1228
1264
  );
1229
- const columns = handleSelecting(schema, statementParams, {
1265
+ if (instructions && Object.hasOwn(instructions, "for")) {
1266
+ instructions = handleFor(model, instructions);
1267
+ }
1268
+ const { columns, isJoining } = handleSelecting(model, statementParams, {
1230
1269
  selecting: instructions?.selecting,
1231
1270
  including: instructions?.including
1232
1271
  });
@@ -1248,24 +1287,23 @@ var compileQueryInput = (query, schemas, statementParams, options) => {
1248
1287
  statement += "UPDATE ";
1249
1288
  break;
1250
1289
  }
1251
- const isJoining = typeof instructions?.including !== "undefined" && !isObject(instructions.including);
1252
1290
  let isJoiningMultipleRows = false;
1253
1291
  if (isJoining) {
1254
- const {
1255
- statement: including,
1256
- rootTableSubQuery,
1257
- rootTableName
1258
- } = handleIncluding(schemas, statementParams, schema, instructions?.including, table);
1259
- if (rootTableSubQuery && rootTableName) {
1260
- table = rootTableName;
1261
- statement += `(${rootTableSubQuery}) as ${rootTableName} `;
1292
+ const { statement: including, tableSubQuery } = handleIncluding(
1293
+ models,
1294
+ model,
1295
+ statementParams,
1296
+ instructions?.including
1297
+ );
1298
+ if (tableSubQuery) {
1299
+ statement += `(${tableSubQuery}) as ${model.tableAlias} `;
1262
1300
  isJoiningMultipleRows = true;
1263
1301
  } else {
1264
- statement += `"${table}" `;
1302
+ statement += `"${model.table}" `;
1265
1303
  }
1266
1304
  statement += `${including} `;
1267
1305
  } else {
1268
- statement += `"${table}" `;
1306
+ statement += `"${model.table}" `;
1269
1307
  }
1270
1308
  if (queryType === "create" || queryType === "set") {
1271
1309
  if (!isObject(instructions.to) || Object.keys(instructions.to).length === 0) {
@@ -1276,37 +1314,27 @@ var compileQueryInput = (query, schemas, statementParams, options) => {
1276
1314
  });
1277
1315
  }
1278
1316
  const toStatement = handleTo(
1279
- schemas,
1280
- schema,
1317
+ models,
1318
+ model,
1281
1319
  statementParams,
1282
1320
  queryType,
1283
1321
  dependencyStatements,
1284
1322
  { with: instructions.with, to: instructions.to },
1285
- isJoining ? table : void 0
1323
+ options?.parentModel
1286
1324
  );
1287
1325
  statement += `${toStatement} `;
1288
1326
  }
1289
1327
  const conditions = [];
1290
1328
  if (queryType !== "create" && instructions && Object.hasOwn(instructions, "with")) {
1291
1329
  const withStatement = handleWith(
1292
- schemas,
1293
- schema,
1330
+ models,
1331
+ model,
1294
1332
  statementParams,
1295
1333
  instructions?.with,
1296
- isJoining ? table : void 0
1334
+ options?.parentModel
1297
1335
  );
1298
1336
  if (withStatement.length > 0) conditions.push(withStatement);
1299
1337
  }
1300
- if (instructions && Object.hasOwn(instructions, "for")) {
1301
- const forStatement = handleFor(
1302
- schemas,
1303
- schema,
1304
- statementParams,
1305
- instructions?.for,
1306
- isJoining ? table : void 0
1307
- );
1308
- if (forStatement.length > 0) conditions.push(forStatement);
1309
- }
1310
1338
  if ((queryType === "get" || queryType === "count") && !single && instructions?.limitedTo) {
1311
1339
  instructions = instructions || {};
1312
1340
  instructions.orderedBy = instructions.orderedBy || {};
@@ -1327,18 +1355,13 @@ var compileQueryInput = (query, schemas, statementParams, options) => {
1327
1355
  queries: [query]
1328
1356
  });
1329
1357
  }
1330
- const beforeAndAfterStatement = handleBeforeOrAfter(
1331
- schema,
1332
- statementParams,
1333
- {
1334
- before: instructions.before,
1335
- after: instructions.after,
1336
- with: instructions.with,
1337
- orderedBy: instructions.orderedBy,
1338
- limitedTo: instructions.limitedTo
1339
- },
1340
- isJoining ? table : void 0
1341
- );
1358
+ const beforeAndAfterStatement = handleBeforeOrAfter(model, statementParams, {
1359
+ before: instructions.before,
1360
+ after: instructions.after,
1361
+ with: instructions.with,
1362
+ orderedBy: instructions.orderedBy,
1363
+ limitedTo: instructions.limitedTo
1364
+ });
1342
1365
  conditions.push(beforeAndAfterStatement);
1343
1366
  }
1344
1367
  if (conditions.length > 0) {
@@ -1349,11 +1372,7 @@ var compileQueryInput = (query, schemas, statementParams, options) => {
1349
1372
  }
1350
1373
  }
1351
1374
  if (instructions?.orderedBy) {
1352
- const orderedByStatement = handleOrderedBy(
1353
- schema,
1354
- instructions.orderedBy,
1355
- isJoining ? table : void 0
1356
- );
1375
+ const orderedByStatement = handleOrderedBy(model, instructions.orderedBy);
1357
1376
  statement += `${orderedByStatement} `;
1358
1377
  }
1359
1378
  if (queryType === "get" && !isJoiningMultipleRows && (single || instructions?.limitedTo)) {
@@ -1374,20 +1393,20 @@ var compileQueryInput = (query, schemas, statementParams, options) => {
1374
1393
  };
1375
1394
 
1376
1395
  // src/index.ts
1377
- var compileQueries = (queries, schemas, options) => {
1378
- const schemaList = addSystemSchemas(schemas).map((schema) => {
1379
- return addDefaultSchemaFields(schema, true);
1396
+ var compileQueries = (queries, models, options) => {
1397
+ const modelList = addSystemModels(models).map((model) => {
1398
+ return addDefaultModelFields(model, true);
1380
1399
  });
1381
- const schemaListWithShortcuts = schemaList.map((schema) => {
1382
- return addDefaultSchemaShortcuts(schemaList, schema);
1400
+ const modelListWithPresets = modelList.map((model) => {
1401
+ return addDefaultModelPresets(modelList, model);
1383
1402
  });
1384
1403
  const dependencyStatements = [];
1385
1404
  const mainStatements = [];
1386
1405
  for (const query of queries) {
1387
1406
  const result = compileQueryInput(
1388
1407
  query,
1389
- schemaListWithShortcuts,
1390
- options?.inlineValues ? null : []
1408
+ modelListWithPresets,
1409
+ options?.inlineParams ? null : []
1391
1410
  );
1392
1411
  dependencyStatements.push(...result.dependencies);
1393
1412
  mainStatements.push(result.main);