@ronin/compiler 0.6.0 → 0.7.0
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/README.md +7 -7
- package/dist/index.d.ts +1199 -148
- package/dist/index.js +422 -403
- package/package.json +1 -1
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
|
3
|
+
var RONIN_MODEL_SYMBOLS = {
|
4
4
|
// Represents a sub query.
|
5
5
|
QUERY: "__RONIN_QUERY",
|
6
|
-
// Represents
|
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
|
9
|
-
|
10
|
-
// Represents the
|
11
|
-
|
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
|
16
|
-
`${
|
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
|
98
|
-
const queryInstructions = query[queryType][
|
99
|
-
return { queryType,
|
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
|
116
|
-
|
117
|
-
|
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
|
-
|
144
|
+
const symbol = getSymbol(value);
|
125
145
|
let conditionValue = value;
|
126
|
-
if (
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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
|
-
|
143
|
-
|
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 = (
|
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(
|
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 =
|
170
|
-
|
171
|
-
|
172
|
-
|
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
|
-
|
181
|
-
|
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 (
|
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
|
197
|
+
const relatedModel = getModelBySlug(models, modelField.target.slug);
|
196
198
|
const subQuery = {
|
197
199
|
get: {
|
198
|
-
[
|
200
|
+
[relatedModel.slug]: {
|
199
201
|
with: value,
|
200
202
|
selecting: ["id"]
|
201
203
|
}
|
202
204
|
}
|
203
205
|
};
|
204
206
|
recordTarget = {
|
205
|
-
[
|
207
|
+
[RONIN_MODEL_SYMBOLS.QUERY]: subQuery
|
206
208
|
};
|
207
209
|
}
|
208
210
|
return composeConditions(
|
209
|
-
|
210
|
-
|
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(
|
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 = (
|
306
|
+
var handleWith = (models, model, statementParams, instruction, parentModel) => {
|
295
307
|
const subStatement = composeConditions(
|
296
|
-
|
297
|
-
|
308
|
+
models,
|
309
|
+
model,
|
298
310
|
statementParams,
|
299
311
|
"with",
|
300
312
|
instruction,
|
301
|
-
{
|
313
|
+
{ parentModel }
|
302
314
|
);
|
303
315
|
return `(${subStatement})`;
|
304
316
|
};
|
305
317
|
|
306
|
-
// src/utils/
|
318
|
+
// src/utils/model.ts
|
307
319
|
import title from "title";
|
308
|
-
var
|
309
|
-
const
|
310
|
-
return
|
320
|
+
var getModelBySlug = (models, slug) => {
|
321
|
+
const model = models.find((model2) => {
|
322
|
+
return model2.slug === slug || model2.pluralSlug === slug;
|
311
323
|
});
|
312
|
-
if (!
|
324
|
+
if (!model) {
|
313
325
|
throw new RoninError({
|
314
|
-
message: `No matching
|
315
|
-
code: "
|
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
|
319
|
-
};
|
320
|
-
var getTableForSchema = (schema) => {
|
321
|
-
return convertToSnakeCase(schema.pluralSlug);
|
330
|
+
return model;
|
322
331
|
};
|
323
|
-
var
|
324
|
-
var
|
325
|
-
|
326
|
-
const
|
327
|
-
|
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
|
344
|
+
var getFieldFromModel = (model, fieldPath, instructionName) => {
|
337
345
|
const errorPrefix = `Field "${fieldPath}" defined for \`${instructionName}\``;
|
338
|
-
const
|
339
|
-
let
|
346
|
+
const modelFields = model.fields || [];
|
347
|
+
let modelField;
|
340
348
|
if (fieldPath.includes(".")) {
|
341
|
-
|
342
|
-
if (
|
343
|
-
const fieldSelector2 = getFieldSelector(
|
344
|
-
|
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
|
-
|
348
|
-
if (!
|
360
|
+
modelField = modelFields.find((field) => field.slug === fieldPath);
|
361
|
+
if (!modelField) {
|
349
362
|
throw new RoninError({
|
350
|
-
message: `${errorPrefix} does not exist in
|
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(
|
357
|
-
return { field:
|
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
|
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
|
382
|
-
const
|
383
|
-
for (const [setting, base, generator] of
|
384
|
-
if (
|
385
|
-
|
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 =
|
401
|
+
const newFields = copiedModel.fields || [];
|
388
402
|
if (isNew || newFields.length > 0) {
|
389
|
-
if (!
|
390
|
-
if (!
|
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
|
-
|
408
|
+
copiedModel.identifiers.name = suitableField?.slug || "id";
|
395
409
|
}
|
396
|
-
if (!
|
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
|
-
|
414
|
+
copiedModel.identifiers.slug = suitableField?.slug || "id";
|
401
415
|
}
|
402
|
-
|
416
|
+
copiedModel.fields = [...SYSTEM_FIELDS, ...newFields];
|
403
417
|
}
|
404
|
-
return
|
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
|
458
|
+
var SYSTEM_MODELS = [
|
445
459
|
{
|
446
|
-
slug: "
|
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: "
|
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: "
|
492
|
+
slug: "model",
|
479
493
|
type: "reference",
|
480
|
-
target: { slug: "
|
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: "
|
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: "
|
518
|
+
slug: "model",
|
505
519
|
type: "reference",
|
506
|
-
target: { slug: "
|
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: "
|
537
|
+
slug: "model",
|
524
538
|
type: "reference",
|
525
|
-
target: { slug: "
|
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((
|
536
|
-
var
|
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
|
541
|
-
const
|
542
|
-
const
|
543
|
-
for (const field of
|
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
|
546
|
-
let fieldSlug =
|
572
|
+
const relatedModel = getModelBySlug(models, field.target.slug);
|
573
|
+
let fieldSlug = relatedModel.slug;
|
547
574
|
if (field.kind === "many") {
|
548
|
-
fieldSlug =
|
549
|
-
|
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:
|
584
|
+
target: { slug: model.slug }
|
557
585
|
},
|
558
586
|
{
|
559
587
|
slug: "target",
|
560
588
|
type: "reference",
|
561
|
-
target: { slug:
|
589
|
+
target: { slug: relatedModel.slug }
|
562
590
|
}
|
563
591
|
]
|
564
592
|
});
|
565
593
|
}
|
566
594
|
}
|
567
595
|
}
|
568
|
-
return
|
596
|
+
return addedModels;
|
569
597
|
});
|
570
|
-
return [...
|
598
|
+
return [...SYSTEM_MODELS, ...associativeModels, ...models];
|
571
599
|
};
|
572
|
-
var
|
573
|
-
const
|
574
|
-
for (const field of
|
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
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
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
|
595
|
-
const field =
|
596
|
-
return field2.type === "reference" && field2.target.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 {
|
635
|
+
return { model: subModel, field };
|
600
636
|
}).filter((match) => match !== null);
|
601
|
-
for (const childMatch of
|
602
|
-
const {
|
603
|
-
const pluralSlug =
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
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(
|
615
|
-
|
662
|
+
if (Object.keys(defaultPresets).length > 0) {
|
663
|
+
model.presets = [...defaultPresets, ...model.presets || []];
|
616
664
|
}
|
617
|
-
return
|
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
|
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 (!
|
705
|
+
if (!SYSTEM_MODEL_SLUGS.includes(queryModel)) return queryInstructions;
|
662
706
|
const instructionName = mappedInstructions[queryType];
|
663
707
|
const instructionList = queryInstructions[instructionName];
|
664
|
-
const kind =
|
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 === "
|
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 === "
|
720
|
+
if (kind === "models") tableAction = "ALTER";
|
677
721
|
queryTypeReadable = "updating";
|
678
722
|
break;
|
679
723
|
}
|
680
724
|
case "drop": {
|
681
|
-
if (kind === "
|
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
|
697
|
-
const
|
698
|
-
if (kind !== "
|
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 \`
|
744
|
+
message: `When ${queryTypeReadable} ${kind}, a \`model.slug\` field must be provided in the \`${instructionName}\` instruction.`,
|
701
745
|
code: "MISSING_FIELD",
|
702
|
-
fields: ["
|
746
|
+
fields: ["model.slug"]
|
703
747
|
});
|
704
748
|
}
|
705
|
-
const usableSlug = kind === "
|
749
|
+
const usableSlug = kind === "models" ? slug : modelSlug;
|
706
750
|
const tableName = convertToSnakeCase(pluralize(usableSlug));
|
707
|
-
const
|
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
|
760
|
+
const model = targetModel;
|
717
761
|
const columns = fields.map((field) => {
|
718
762
|
let fieldSelector = "";
|
719
763
|
if ("slug" in field) {
|
720
|
-
({ fieldSelector } =
|
764
|
+
({ fieldSelector } = getFieldFromModel(model, field.slug, "to"));
|
721
765
|
} else if ("expression" in field) {
|
722
|
-
fieldSelector = field.expression
|
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
|
-
|
735
|
-
|
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: "
|
801
|
+
code: "INVALID_MODEL_VALUE",
|
760
802
|
fields: ["action"]
|
761
803
|
});
|
762
804
|
}
|
763
805
|
const fieldSelectors = fields.map((field) => {
|
764
|
-
return
|
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,
|
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
|
815
|
+
const tableAlias = action === "DELETE" ? RONIN_MODEL_SYMBOLS.FIELD_PARENT_OLD : RONIN_MODEL_SYMBOLS.FIELD_PARENT_NEW;
|
774
816
|
const withStatement = handleWith(
|
775
|
-
|
776
|
-
|
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,
|
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 === "
|
839
|
+
if (kind === "models") {
|
798
840
|
if (queryType === "create" || queryType === "set") {
|
799
|
-
const
|
841
|
+
const modelWithFields = addDefaultModelFields(
|
800
842
|
queryInstructions.to,
|
801
843
|
queryType === "create"
|
802
844
|
);
|
803
|
-
const
|
804
|
-
queryInstructions.to =
|
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
|
-
|
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(
|
865
|
+
Object.assign(targetModel, queryInstructions.to);
|
824
866
|
} else if (queryType === "drop") {
|
825
|
-
|
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 = (
|
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 } =
|
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 } =
|
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 = (
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
const
|
953
|
-
|
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: `
|
957
|
-
code: "
|
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
|
1028
|
+
return instructions;
|
977
1029
|
};
|
978
1030
|
|
979
1031
|
// src/instructions/including.ts
|
980
|
-
var handleIncluding = (
|
1032
|
+
var handleIncluding = (models, model, statementParams, instruction) => {
|
981
1033
|
let statement = "";
|
982
|
-
let
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
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
|
1040
|
+
const relatedModel = getModelBySlug(models, queryModel);
|
995
1041
|
let joinType = "LEFT";
|
996
|
-
let relatedTableSelector = `"${
|
997
|
-
const tableAlias = `including_${
|
998
|
-
const single =
|
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
|
-
[
|
1056
|
+
[queryModel]: modifiableQueryInstructions
|
1011
1057
|
}
|
1012
1058
|
},
|
1013
|
-
|
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
|
-
|
1022
|
-
|
1068
|
+
tableSubQuery = `SELECT * FROM "${model.table}" LIMIT 1`;
|
1069
|
+
model.tableAlias = `sub_${model.table}`;
|
1023
1070
|
}
|
1024
1071
|
const subStatement = composeConditions(
|
1025
|
-
|
1026
|
-
|
1072
|
+
models,
|
1073
|
+
{ ...relatedModel, tableAlias },
|
1027
1074
|
statementParams,
|
1028
1075
|
"including",
|
1029
1076
|
queryInstructions?.with,
|
1030
1077
|
{
|
1031
|
-
|
1032
|
-
customTable: tableAlias
|
1078
|
+
parentModel: model
|
1033
1079
|
}
|
1034
1080
|
);
|
1035
1081
|
statement += ` ON (${subStatement})`;
|
1036
1082
|
}
|
1037
1083
|
}
|
1038
|
-
return { statement,
|
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 = (
|
1096
|
+
var handleOrderedBy = (model, instruction) => {
|
1051
1097
|
let statement = "";
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
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
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
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
|
1076
|
-
|
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 = (
|
1124
|
+
var handleSelecting = (model, statementParams, instructions) => {
|
1125
|
+
let isJoining = false;
|
1083
1126
|
let statement = instructions.selecting ? instructions.selecting.map((slug) => {
|
1084
|
-
return
|
1127
|
+
return getFieldFromModel(model, slug, "selecting").fieldSelector;
|
1085
1128
|
}).join(", ") : "*";
|
1086
|
-
if (
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1091
|
-
return !
|
1092
|
-
})
|
1093
|
-
|
1094
|
-
|
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 = (
|
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(
|
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
|
1116
|
-
if (
|
1117
|
-
|
1118
|
-
|
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 || (
|
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
|
-
|
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(
|
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 =
|
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
|
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
|
-
[
|
1205
|
+
[associativeModelSlug]: subQueryType === "create" ? { to: recordDetails } : { with: recordDetails }
|
1162
1206
|
}
|
1163
1207
|
},
|
1164
|
-
|
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
|
-
|
1186
|
-
|
1187
|
-
|
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
|
-
|
1198
|
-
|
1234
|
+
models,
|
1235
|
+
model,
|
1199
1236
|
statementParams,
|
1200
1237
|
"to",
|
1201
1238
|
toInstruction,
|
1202
1239
|
{
|
1203
|
-
|
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,
|
1252
|
+
var compileQueryInput = (query, models, statementParams, options) => {
|
1216
1253
|
const parsedQuery = splitQuery(query);
|
1217
|
-
const { queryType,
|
1218
|
-
const
|
1219
|
-
const single =
|
1220
|
-
let instructions = formatIdentifiers(
|
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 =
|
1225
|
-
|
1226
|
-
{ queryType,
|
1260
|
+
instructions = addModelQueries(
|
1261
|
+
models,
|
1262
|
+
{ queryType, queryModel, queryInstructions: instructions },
|
1227
1263
|
dependencyStatements
|
1228
1264
|
);
|
1229
|
-
|
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
|
-
|
1256
|
-
|
1257
|
-
|
1258
|
-
|
1259
|
-
|
1260
|
-
|
1261
|
-
statement += `(${
|
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
|
-
|
1280
|
-
|
1317
|
+
models,
|
1318
|
+
model,
|
1281
1319
|
statementParams,
|
1282
1320
|
queryType,
|
1283
1321
|
dependencyStatements,
|
1284
1322
|
{ with: instructions.with, to: instructions.to },
|
1285
|
-
|
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
|
-
|
1293
|
-
|
1330
|
+
models,
|
1331
|
+
model,
|
1294
1332
|
statementParams,
|
1295
1333
|
instructions?.with,
|
1296
|
-
|
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
|
-
|
1332
|
-
|
1333
|
-
|
1334
|
-
|
1335
|
-
|
1336
|
-
|
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,
|
1378
|
-
const
|
1379
|
-
return
|
1396
|
+
var compileQueries = (queries, models, options) => {
|
1397
|
+
const modelList = addSystemModels(models).map((model) => {
|
1398
|
+
return addDefaultModelFields(model, true);
|
1380
1399
|
});
|
1381
|
-
const
|
1382
|
-
return
|
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
|
-
|
1390
|
-
options?.
|
1408
|
+
modelListWithPresets,
|
1409
|
+
options?.inlineParams ? null : []
|
1391
1410
|
);
|
1392
1411
|
dependencyStatements.push(...result.dependencies);
|
1393
1412
|
mainStatements.push(result.main);
|