@ronin/compiler 0.13.10 → 0.13.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1560 -1536
- package/package.json +1 -1
package/dist/index.js
CHANGED
@@ -150,1659 +150,1679 @@ var splitQuery = (query) => {
|
|
150
150
|
return { queryType, queryModel, queryInstructions };
|
151
151
|
};
|
152
152
|
|
153
|
-
// src/utils/
|
154
|
-
var
|
155
|
-
|
156
|
-
|
153
|
+
// src/utils/pagination.ts
|
154
|
+
var CURSOR_SEPARATOR = ",";
|
155
|
+
var CURSOR_NULL_PLACEHOLDER = "RONIN_NULL";
|
156
|
+
var generatePaginationCursor = (model, orderedBy, record) => {
|
157
|
+
const { ascending = [], descending = [] } = orderedBy || {};
|
158
|
+
const keys = [...ascending, ...descending];
|
159
|
+
if (keys.length === 0) keys.push("ronin.createdAt");
|
160
|
+
const cursors = keys.map((fieldSlug) => {
|
161
|
+
const property = getProperty(record, fieldSlug);
|
162
|
+
if (property === null || property === void 0) return CURSOR_NULL_PLACEHOLDER;
|
163
|
+
const { field } = getFieldFromModel(model, fieldSlug, {
|
164
|
+
instructionName: "orderedBy"
|
165
|
+
});
|
166
|
+
if (field.type === "date") return new Date(property).getTime();
|
167
|
+
return property;
|
168
|
+
});
|
169
|
+
return cursors.map((cursor) => encodeURIComponent(String(cursor))).join(CURSOR_SEPARATOR);
|
157
170
|
};
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
171
|
+
|
172
|
+
// src/instructions/before-after.ts
|
173
|
+
var handleBeforeOrAfter = (model, statementParams, instructions) => {
|
174
|
+
if (!(instructions.before || instructions.after)) {
|
175
|
+
throw new RoninError({
|
176
|
+
message: "The `before` or `after` instruction must not be empty.",
|
177
|
+
code: "MISSING_INSTRUCTION"
|
178
|
+
});
|
163
179
|
}
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
180
|
+
if (instructions.before && instructions.after) {
|
181
|
+
throw new RoninError({
|
182
|
+
message: "The `before` and `after` instructions cannot co-exist. Choose one.",
|
183
|
+
code: "MUTUALLY_EXCLUSIVE_INSTRUCTIONS"
|
184
|
+
});
|
169
185
|
}
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
186
|
+
if (!instructions.limitedTo) {
|
187
|
+
let message = "When providing a pagination cursor in the `before` or `after`";
|
188
|
+
message += " instruction, a `limitedTo` instruction must be provided as well, to";
|
189
|
+
message += " define the page size.";
|
190
|
+
throw new RoninError({
|
191
|
+
message,
|
192
|
+
code: "MISSING_INSTRUCTION"
|
193
|
+
});
|
194
|
+
}
|
195
|
+
const { ascending = [], descending = [] } = instructions.orderedBy || {};
|
196
|
+
const clause = instructions.with ? "AND " : "";
|
197
|
+
const chunks = (instructions.before || instructions.after).toString().split(CURSOR_SEPARATOR).map(decodeURIComponent);
|
198
|
+
const keys = [...ascending, ...descending];
|
199
|
+
const values = keys.map((key, index) => {
|
200
|
+
const value = chunks[index];
|
201
|
+
if (value === CURSOR_NULL_PLACEHOLDER) {
|
202
|
+
return "NULL";
|
185
203
|
}
|
186
|
-
const
|
187
|
-
|
188
|
-
|
204
|
+
const { field } = getFieldFromModel(model, key, {
|
205
|
+
instructionName: "orderedBy"
|
206
|
+
});
|
207
|
+
if (field.type === "boolean") {
|
208
|
+
return prepareStatementValue(statementParams, value === "true");
|
209
|
+
}
|
210
|
+
if (field.type === "number") {
|
211
|
+
return prepareStatementValue(statementParams, Number.parseInt(value));
|
212
|
+
}
|
213
|
+
if (field.type === "date") {
|
214
|
+
return `'${new Date(Number.parseInt(value)).toJSON()}'`;
|
215
|
+
}
|
216
|
+
return prepareStatementValue(statementParams, value);
|
189
217
|
});
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
)
|
197
|
-
|
198
|
-
|
199
|
-
const syntax = WITH_CONDITIONS[options.condition || "being"](value);
|
200
|
-
let conditionValue = syntax[1];
|
201
|
-
if (symbol) {
|
202
|
-
if (symbol?.type === "expression") {
|
203
|
-
conditionValue = parseFieldExpression(
|
204
|
-
model,
|
205
|
-
instructionName,
|
206
|
-
symbol.value,
|
207
|
-
options.parentModel
|
208
|
-
);
|
218
|
+
const compareOperators = [
|
219
|
+
// Reverse the comparison operators if we're querying for records before.
|
220
|
+
...new Array(ascending.length).fill(instructions.before ? "<" : ">"),
|
221
|
+
...new Array(descending.length).fill(instructions.before ? ">" : "<")
|
222
|
+
];
|
223
|
+
const conditions = new Array();
|
224
|
+
for (let i = 0; i < keys.length; i++) {
|
225
|
+
if (values[i] === "NULL" && compareOperators[i] === "<") {
|
226
|
+
continue;
|
209
227
|
}
|
210
|
-
|
211
|
-
|
228
|
+
const condition = new Array();
|
229
|
+
for (let j = 0; j <= i; j++) {
|
230
|
+
const key = keys[j];
|
231
|
+
const value = values[j];
|
232
|
+
let { field, fieldSelector } = getFieldFromModel(model, key, {
|
233
|
+
instructionName: "orderedBy"
|
234
|
+
});
|
235
|
+
if (j === i) {
|
236
|
+
const closingParentheses = ")".repeat(condition.length);
|
237
|
+
const operator = value === "NULL" ? "IS NOT" : compareOperators[j];
|
238
|
+
const caseInsensitiveStatement = value !== "NULL" && field.type === "string" ? " COLLATE NOCASE" : "";
|
239
|
+
if (value !== "NULL" && operator === "<" && !["ronin.createdAt", "ronin.updatedAt"].includes(key)) {
|
240
|
+
fieldSelector = `IFNULL(${fieldSelector}, -1e999)`;
|
241
|
+
}
|
242
|
+
condition.push(
|
243
|
+
`(${fieldSelector} ${operator} ${value}${caseInsensitiveStatement})${closingParentheses}`
|
244
|
+
);
|
245
|
+
} else {
|
246
|
+
const operator = value === "NULL" ? "IS" : "=";
|
247
|
+
condition.push(`(${fieldSelector} ${operator} ${value} AND`);
|
248
|
+
}
|
212
249
|
}
|
213
|
-
|
214
|
-
conditionValue = prepareStatementValue(statementParams, conditionValue);
|
250
|
+
conditions.push(condition.join(" "));
|
215
251
|
}
|
216
|
-
|
217
|
-
if (options.type === "values") return conditionValue;
|
218
|
-
return `${conditionSelector} ${syntax[0]} ${conditionValue}`;
|
252
|
+
return `${clause}(${conditions.join(" OR ")})`;
|
219
253
|
};
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
)
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
const childField = model.fields.some(({ slug }) => {
|
233
|
-
return slug.includes(".") && slug.split(".")[0] === options.fieldSlug;
|
234
|
-
});
|
235
|
-
if (!childField) {
|
236
|
-
const fieldDetails = getFieldFromModel(model, options.fieldSlug, {
|
237
|
-
instructionName
|
254
|
+
|
255
|
+
// src/instructions/for.ts
|
256
|
+
var handleFor = (model, instructions) => {
|
257
|
+
const normalizedFor = Array.isArray(instructions.for) ? Object.fromEntries(instructions.for.map((presetSlug) => [presetSlug, null])) : instructions.for;
|
258
|
+
for (const presetSlug in normalizedFor) {
|
259
|
+
if (!Object.hasOwn(normalizedFor, presetSlug)) continue;
|
260
|
+
const arg = normalizedFor[presetSlug];
|
261
|
+
const preset = model.presets?.find((preset2) => preset2.slug === presetSlug);
|
262
|
+
if (!preset) {
|
263
|
+
throw new RoninError({
|
264
|
+
message: `Preset "${presetSlug}" does not exist in model "${model.name}".`,
|
265
|
+
code: "PRESET_NOT_FOUND"
|
238
266
|
});
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
selecting: ["id"]
|
264
|
-
}
|
265
|
-
}
|
266
|
-
};
|
267
|
-
recordTarget = {
|
268
|
-
[QUERY_SYMBOLS.QUERY]: subQuery
|
267
|
+
}
|
268
|
+
const replacedForFilter = structuredClone(preset.instructions);
|
269
|
+
if (arg !== null) {
|
270
|
+
findInObject(
|
271
|
+
replacedForFilter,
|
272
|
+
QUERY_SYMBOLS.VALUE,
|
273
|
+
(match) => match.replace(QUERY_SYMBOLS.VALUE, arg)
|
274
|
+
);
|
275
|
+
}
|
276
|
+
for (const subInstruction in replacedForFilter) {
|
277
|
+
if (!Object.hasOwn(replacedForFilter, subInstruction)) continue;
|
278
|
+
const instructionName = subInstruction;
|
279
|
+
const currentValue = instructions[instructionName];
|
280
|
+
if (currentValue) {
|
281
|
+
let newValue;
|
282
|
+
if (Array.isArray(currentValue)) {
|
283
|
+
newValue = [
|
284
|
+
...replacedForFilter[instructionName],
|
285
|
+
...currentValue
|
286
|
+
];
|
287
|
+
} else if (isObject(currentValue)) {
|
288
|
+
newValue = {
|
289
|
+
...replacedForFilter[instructionName],
|
290
|
+
...currentValue
|
269
291
|
};
|
270
292
|
}
|
271
|
-
|
272
|
-
|
273
|
-
model,
|
274
|
-
statementParams,
|
275
|
-
instructionName,
|
276
|
-
recordTarget,
|
277
|
-
options
|
278
|
-
);
|
293
|
+
Object.assign(instructions, { [instructionName]: newValue });
|
294
|
+
continue;
|
279
295
|
}
|
296
|
+
Object.assign(instructions, {
|
297
|
+
[instructionName]: replacedForFilter[instructionName]
|
298
|
+
});
|
280
299
|
}
|
281
300
|
}
|
282
|
-
|
283
|
-
const conditions = Object.entries(value).map(([field, value2]) => {
|
284
|
-
const nestedFieldSlug = options.fieldSlug ? `${options.fieldSlug}.${field}` : field;
|
285
|
-
return composeConditions(models, model, statementParams, instructionName, value2, {
|
286
|
-
...options,
|
287
|
-
fieldSlug: nestedFieldSlug
|
288
|
-
});
|
289
|
-
});
|
290
|
-
const joiner = instructionName === "to" ? ", " : " AND ";
|
291
|
-
if (instructionName === "to") return `${conditions.join(joiner)}`;
|
292
|
-
return conditions.length === 1 ? conditions[0] : options.fieldSlug ? `(${conditions.join(joiner)})` : conditions.join(joiner);
|
293
|
-
}
|
294
|
-
if (Array.isArray(value)) {
|
295
|
-
const conditions = value.map(
|
296
|
-
(filter) => composeConditions(models, model, statementParams, instructionName, filter, options)
|
297
|
-
);
|
298
|
-
return conditions.join(" OR ");
|
299
|
-
}
|
300
|
-
throw new RoninError({
|
301
|
-
message: `The \`with\` instruction must not contain an empty field. The following fields are empty: \`${options.fieldSlug}\`. If you meant to query by an empty field, try using \`null\` instead.`,
|
302
|
-
code: "INVALID_WITH_VALUE",
|
303
|
-
queries: null
|
304
|
-
});
|
301
|
+
return instructions;
|
305
302
|
};
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
const
|
318
|
-
|
319
|
-
|
320
|
-
|
303
|
+
|
304
|
+
// src/instructions/including.ts
|
305
|
+
var handleIncluding = (models, model, statementParams, instruction) => {
|
306
|
+
let statement = "";
|
307
|
+
let tableSubQuery;
|
308
|
+
for (const ephemeralFieldSlug in instruction) {
|
309
|
+
if (!Object.hasOwn(instruction, ephemeralFieldSlug)) continue;
|
310
|
+
const symbol = getSymbol(instruction[ephemeralFieldSlug]);
|
311
|
+
if (symbol?.type !== "query") continue;
|
312
|
+
const { queryType, queryModel, queryInstructions } = splitQuery(symbol.value);
|
313
|
+
let modifiableQueryInstructions = queryInstructions;
|
314
|
+
const relatedModel = getModelBySlug(models, queryModel);
|
315
|
+
let joinType = "LEFT";
|
316
|
+
let relatedTableSelector = `"${relatedModel.table}"`;
|
317
|
+
const tableAlias = composeIncludedTableAlias(ephemeralFieldSlug);
|
318
|
+
const single = queryModel !== relatedModel.pluralSlug;
|
319
|
+
if (!modifiableQueryInstructions?.with) {
|
320
|
+
joinType = "CROSS";
|
321
|
+
if (single) {
|
322
|
+
if (!modifiableQueryInstructions) modifiableQueryInstructions = {};
|
323
|
+
modifiableQueryInstructions.limitedTo = 1;
|
324
|
+
}
|
325
|
+
}
|
326
|
+
if (modifiableQueryInstructions?.limitedTo || modifiableQueryInstructions?.orderedBy) {
|
327
|
+
const subSelect = compileQueryInput(
|
328
|
+
{
|
329
|
+
[queryType]: {
|
330
|
+
[queryModel]: modifiableQueryInstructions
|
331
|
+
}
|
332
|
+
},
|
333
|
+
models,
|
334
|
+
statementParams
|
335
|
+
);
|
336
|
+
relatedTableSelector = `(${subSelect.main.statement})`;
|
337
|
+
}
|
338
|
+
statement += `${joinType} JOIN ${relatedTableSelector} as ${tableAlias}`;
|
339
|
+
model.tableAlias = model.tableAlias || model.table;
|
340
|
+
if (joinType === "LEFT") {
|
341
|
+
const subStatement = composeConditions(
|
342
|
+
models,
|
343
|
+
{ ...relatedModel, tableAlias },
|
344
|
+
statementParams,
|
345
|
+
"including",
|
346
|
+
queryInstructions?.with,
|
347
|
+
{
|
348
|
+
parentModel: model
|
349
|
+
}
|
350
|
+
);
|
351
|
+
statement += ` ON (${subStatement})`;
|
352
|
+
}
|
353
|
+
if (!single) tableSubQuery = `SELECT * FROM "${model.table}" LIMIT 1`;
|
321
354
|
}
|
322
|
-
return {
|
323
|
-
...queryInstructions,
|
324
|
-
[type]: newNestedInstructions
|
325
|
-
};
|
355
|
+
return { statement, tableSubQuery };
|
326
356
|
};
|
327
357
|
|
328
|
-
// src/instructions/
|
329
|
-
var
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
}
|
334
|
-
if (value === null) return "IS";
|
335
|
-
return "=";
|
358
|
+
// src/instructions/limited-to.ts
|
359
|
+
var handleLimitedTo = (single, instruction) => {
|
360
|
+
let amount;
|
361
|
+
if (instruction) amount = instruction + 1;
|
362
|
+
if (single) amount = 1;
|
363
|
+
return `LIMIT ${amount} `;
|
336
364
|
};
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
365
|
+
|
366
|
+
// src/instructions/ordered-by.ts
|
367
|
+
var handleOrderedBy = (model, instruction) => {
|
368
|
+
let statement = "";
|
369
|
+
const items = [
|
370
|
+
...(instruction.ascending || []).map((value) => ({ value, order: "ASC" })),
|
371
|
+
...(instruction.descending || []).map((value) => ({ value, order: "DESC" }))
|
372
|
+
];
|
373
|
+
for (const item of items) {
|
374
|
+
if (statement.length > 0) {
|
375
|
+
statement += ", ";
|
376
|
+
}
|
377
|
+
const symbol = getSymbol(item.value);
|
378
|
+
const instructionName = item.order === "ASC" ? "orderedBy.ascending" : "orderedBy.descending";
|
379
|
+
if (symbol?.type === "expression") {
|
380
|
+
statement += `(${parseFieldExpression(model, instructionName, symbol.value)}) ${item.order}`;
|
381
|
+
continue;
|
382
|
+
}
|
383
|
+
const { field: modelField, fieldSelector } = getFieldFromModel(
|
384
|
+
model,
|
385
|
+
item.value,
|
386
|
+
{ instructionName }
|
387
|
+
);
|
388
|
+
const caseInsensitiveStatement = modelField.type === "string" ? " COLLATE NOCASE" : "";
|
389
|
+
statement += `${fieldSelector}${caseInsensitiveStatement} ${item.order}`;
|
390
|
+
}
|
391
|
+
return `ORDER BY ${statement}`;
|
350
392
|
};
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
393
|
+
|
394
|
+
// src/instructions/selecting.ts
|
395
|
+
var handleSelecting = (models, model, statementParams, instructions, options) => {
|
396
|
+
let loadedFields = [];
|
397
|
+
let expandColumns = false;
|
398
|
+
let statement = "*";
|
399
|
+
let isJoining = false;
|
400
|
+
if (instructions.including) {
|
401
|
+
const flatObject = flatten(instructions.including);
|
402
|
+
instructions.including = {};
|
403
|
+
for (const [key, value] of Object.entries(flatObject)) {
|
404
|
+
const symbol = getSymbol(value);
|
405
|
+
if (symbol?.type === "query") {
|
406
|
+
const { queryModel, queryInstructions } = splitQuery(symbol.value);
|
407
|
+
const subQueryModel = getModelBySlug(models, queryModel);
|
408
|
+
isJoining = true;
|
409
|
+
expandColumns = Boolean(options?.expandColumns || queryInstructions?.selecting);
|
410
|
+
const tableAlias = composeIncludedTableAlias(key);
|
411
|
+
const single = queryModel !== subQueryModel.pluralSlug;
|
412
|
+
if (!single) {
|
413
|
+
model.tableAlias = `sub_${model.table}`;
|
414
|
+
}
|
415
|
+
const queryModelFields = queryInstructions?.selecting ? subQueryModel.fields.filter((field) => {
|
416
|
+
return queryInstructions.selecting?.includes(field.slug);
|
417
|
+
}) : (
|
418
|
+
// Exclude link fields with cardinality "many", since those don't exist as columns.
|
419
|
+
subQueryModel.fields.filter((field) => {
|
420
|
+
return !(field.type === "link" && field.kind === "many");
|
421
|
+
})
|
422
|
+
);
|
423
|
+
for (const field of queryModelFields) {
|
424
|
+
loadedFields.push({ ...field, parentField: key });
|
425
|
+
if (expandColumns) {
|
426
|
+
const newValue2 = parseFieldExpression(
|
427
|
+
{ ...subQueryModel, tableAlias },
|
428
|
+
"including",
|
429
|
+
`${QUERY_SYMBOLS.FIELD}${field.slug}`
|
430
|
+
);
|
431
|
+
instructions.including[`${tableAlias}.${field.slug}`] = newValue2;
|
432
|
+
}
|
433
|
+
}
|
434
|
+
continue;
|
435
|
+
}
|
436
|
+
let newValue = value;
|
437
|
+
if (symbol?.type === "expression") {
|
438
|
+
newValue = `(${parseFieldExpression(model, "including", symbol.value)})`;
|
439
|
+
} else {
|
440
|
+
newValue = prepareStatementValue(statementParams, value);
|
441
|
+
}
|
442
|
+
instructions.including[key] = newValue;
|
443
|
+
loadedFields.push({
|
444
|
+
slug: key,
|
445
|
+
type: RAW_FIELD_TYPES.includes(typeof value) ? typeof value : "string"
|
446
|
+
});
|
447
|
+
}
|
448
|
+
}
|
449
|
+
if (expandColumns) {
|
450
|
+
instructions.selecting = model.fields.filter((field) => !(field.type === "link" && field.kind === "many")).map((field) => field.slug);
|
451
|
+
}
|
452
|
+
if (instructions.selecting) {
|
453
|
+
const usableModel = expandColumns ? { ...model, tableAlias: model.tableAlias || model.table } : model;
|
454
|
+
const selectedFields = [];
|
455
|
+
statement = instructions.selecting.map((slug) => {
|
456
|
+
const { field, fieldSelector } = getFieldFromModel(usableModel, slug, {
|
457
|
+
instructionName: "selecting"
|
458
|
+
});
|
459
|
+
selectedFields.push(field);
|
460
|
+
return fieldSelector;
|
461
|
+
}).join(", ");
|
462
|
+
loadedFields = [...selectedFields, ...loadedFields];
|
463
|
+
} else {
|
464
|
+
loadedFields = [
|
465
|
+
...model.fields.filter(
|
466
|
+
(field) => !(field.type === "link" && field.kind === "many")
|
467
|
+
),
|
468
|
+
...loadedFields
|
469
|
+
];
|
470
|
+
}
|
471
|
+
if (instructions.including && Object.keys(instructions.including).length > 0) {
|
472
|
+
statement += ", ";
|
473
|
+
statement += Object.entries(instructions.including).map(([key, value]) => `${value} as "${key}"`).join(", ");
|
474
|
+
}
|
475
|
+
return { columns: statement, isJoining, loadedFields };
|
361
476
|
};
|
362
477
|
|
363
|
-
//
|
364
|
-
var
|
365
|
-
|
366
|
-
|
367
|
-
"
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
"a",
|
375
|
-
"an",
|
376
|
-
"the"
|
377
|
-
];
|
378
|
-
var prepositions = [
|
379
|
-
"aboard",
|
380
|
-
"about",
|
381
|
-
"above",
|
382
|
-
"across",
|
383
|
-
"after",
|
384
|
-
"against",
|
385
|
-
"along",
|
386
|
-
"amid",
|
387
|
-
"among",
|
388
|
-
"anti",
|
389
|
-
"around",
|
390
|
-
"as",
|
391
|
-
"at",
|
392
|
-
"before",
|
393
|
-
"behind",
|
394
|
-
"below",
|
395
|
-
"beneath",
|
396
|
-
"beside",
|
397
|
-
"besides",
|
398
|
-
"between",
|
399
|
-
"beyond",
|
400
|
-
"but",
|
401
|
-
"by",
|
402
|
-
"concerning",
|
403
|
-
"considering",
|
404
|
-
"despite",
|
405
|
-
"down",
|
406
|
-
"during",
|
407
|
-
"except",
|
408
|
-
"excepting",
|
409
|
-
"excluding",
|
410
|
-
"following",
|
411
|
-
"for",
|
412
|
-
"from",
|
413
|
-
"in",
|
414
|
-
"inside",
|
415
|
-
"into",
|
416
|
-
"like",
|
417
|
-
"minus",
|
418
|
-
"near",
|
419
|
-
"of",
|
420
|
-
"off",
|
421
|
-
"on",
|
422
|
-
"onto",
|
423
|
-
"opposite",
|
424
|
-
"over",
|
425
|
-
"past",
|
426
|
-
"per",
|
427
|
-
"plus",
|
428
|
-
"regarding",
|
429
|
-
"round",
|
430
|
-
"save",
|
431
|
-
"since",
|
432
|
-
"than",
|
433
|
-
"through",
|
434
|
-
"to",
|
435
|
-
"toward",
|
436
|
-
"towards",
|
437
|
-
"under",
|
438
|
-
"underneath",
|
439
|
-
"unlike",
|
440
|
-
"until",
|
441
|
-
"up",
|
442
|
-
"upon",
|
443
|
-
"versus",
|
444
|
-
"via",
|
445
|
-
"with",
|
446
|
-
"within",
|
447
|
-
"without"
|
448
|
-
];
|
449
|
-
var lowerCase = /* @__PURE__ */ new Set([
|
450
|
-
...conjunctions,
|
451
|
-
...articles,
|
452
|
-
...prepositions
|
453
|
-
]);
|
454
|
-
|
455
|
-
// node_modules/title/dist/esm/specials.js
|
456
|
-
var specials = [
|
457
|
-
"ZEIT",
|
458
|
-
"ZEIT Inc.",
|
459
|
-
"Vercel",
|
460
|
-
"Vercel Inc.",
|
461
|
-
"CLI",
|
462
|
-
"API",
|
463
|
-
"HTTP",
|
464
|
-
"HTTPS",
|
465
|
-
"JSX",
|
466
|
-
"DNS",
|
467
|
-
"URL",
|
468
|
-
"now.sh",
|
469
|
-
"now.json",
|
470
|
-
"vercel.app",
|
471
|
-
"vercel.json",
|
472
|
-
"CI",
|
473
|
-
"CD",
|
474
|
-
"CDN",
|
475
|
-
"package.json",
|
476
|
-
"package.lock",
|
477
|
-
"yarn.lock",
|
478
|
-
"GitHub",
|
479
|
-
"GitLab",
|
480
|
-
"CSS",
|
481
|
-
"Sass",
|
482
|
-
"JS",
|
483
|
-
"JavaScript",
|
484
|
-
"TypeScript",
|
485
|
-
"HTML",
|
486
|
-
"WordPress",
|
487
|
-
"Next.js",
|
488
|
-
"Node.js",
|
489
|
-
"Webpack",
|
490
|
-
"Docker",
|
491
|
-
"Bash",
|
492
|
-
"Kubernetes",
|
493
|
-
"SWR",
|
494
|
-
"TinaCMS",
|
495
|
-
"UI",
|
496
|
-
"UX",
|
497
|
-
"TS",
|
498
|
-
"TSX",
|
499
|
-
"iPhone",
|
500
|
-
"iPad",
|
501
|
-
"watchOS",
|
502
|
-
"iOS",
|
503
|
-
"iPadOS",
|
504
|
-
"macOS",
|
505
|
-
"PHP",
|
506
|
-
"composer.json",
|
507
|
-
"composer.lock",
|
508
|
-
"CMS",
|
509
|
-
"SQL",
|
510
|
-
"C",
|
511
|
-
"C#",
|
512
|
-
"GraphQL",
|
513
|
-
"GraphiQL",
|
514
|
-
"JWT",
|
515
|
-
"JWTs"
|
516
|
-
];
|
517
|
-
|
518
|
-
// node_modules/title/dist/esm/index.js
|
519
|
-
var word = `[^\\s'\u2019\\(\\)!?;:"-]`;
|
520
|
-
var regex = new RegExp(`(?:(?:(\\s?(?:^|[.\\(\\)!?;:"-])\\s*)(${word}))|(${word}))(${word}*[\u2019']*${word}*)`, "g");
|
521
|
-
var convertToRegExp = (specials2) => specials2.map((s) => [new RegExp(`\\b${s}\\b`, "gi"), s]);
|
522
|
-
function parseMatch(match) {
|
523
|
-
const firstCharacter = match[0];
|
524
|
-
if (/\s/.test(firstCharacter)) {
|
525
|
-
return match.slice(1);
|
526
|
-
}
|
527
|
-
if (/[\(\)]/.test(firstCharacter)) {
|
528
|
-
return null;
|
478
|
+
// src/instructions/to.ts
|
479
|
+
var handleTo = (models, model, statementParams, queryType, dependencyStatements, instructions, parentModel) => {
|
480
|
+
const { with: withInstruction, to: toInstruction } = instructions;
|
481
|
+
const defaultFields = {};
|
482
|
+
if (queryType === "set" || toInstruction.ronin) {
|
483
|
+
defaultFields.ronin = {
|
484
|
+
// If records are being updated, bump their update time.
|
485
|
+
...queryType === "set" ? { updatedAt: CURRENT_TIME_EXPRESSION } : {},
|
486
|
+
// Allow for overwriting the default values provided above.
|
487
|
+
...toInstruction.ronin
|
488
|
+
};
|
529
489
|
}
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
}
|
539
|
-
if (!forced) {
|
540
|
-
const fullLower = lower + rest;
|
541
|
-
if (lowerCase.has(fullLower) && !isLastWord) {
|
542
|
-
return parsedMatch;
|
543
|
-
}
|
490
|
+
const symbol = getSymbol(toInstruction);
|
491
|
+
if (symbol?.type === "query") {
|
492
|
+
const { queryModel: subQueryModelSlug, queryInstructions: subQueryInstructions } = splitQuery(symbol.value);
|
493
|
+
const subQueryModel = getModelBySlug(models, subQueryModelSlug);
|
494
|
+
if (subQueryInstructions?.selecting) {
|
495
|
+
const currentFields = new Set(subQueryInstructions.selecting);
|
496
|
+
currentFields.add("id");
|
497
|
+
subQueryInstructions.selecting = Array.from(currentFields);
|
544
498
|
}
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
};
|
555
|
-
|
556
|
-
// src/utils/model.ts
|
557
|
-
var getModelBySlug = (models, slug) => {
|
558
|
-
const model = models.find((model2) => {
|
559
|
-
return model2.slug === slug || model2.pluralSlug === slug;
|
560
|
-
});
|
561
|
-
if (!model) {
|
562
|
-
throw new RoninError({
|
563
|
-
message: `No matching model with either Slug or Plural Slug of "${slug}" could be found.`,
|
564
|
-
code: "MODEL_NOT_FOUND"
|
565
|
-
});
|
566
|
-
}
|
567
|
-
return model;
|
568
|
-
};
|
569
|
-
var composeAssociationModelSlug = (model, field) => convertToCamelCase(`ronin_link_${model.slug}_${field.slug}`);
|
570
|
-
var getFieldSelector = (model, field, fieldPath, writing) => {
|
571
|
-
const symbol = model.tableAlias?.startsWith(QUERY_SYMBOLS.FIELD_PARENT) ? `${model.tableAlias.replace(QUERY_SYMBOLS.FIELD_PARENT, "").slice(0, -1)}.` : "";
|
572
|
-
const tablePrefix = symbol || (model.tableAlias ? `"${model.tableAlias}".` : "");
|
573
|
-
if (field.type === "json" && !writing) {
|
574
|
-
const dotParts = fieldPath.split(".");
|
575
|
-
const columnName = tablePrefix + dotParts.shift();
|
576
|
-
const jsonField = dotParts.join(".");
|
577
|
-
return `json_extract(${columnName}, '$.${jsonField}')`;
|
578
|
-
}
|
579
|
-
return `${tablePrefix}"${fieldPath}"`;
|
580
|
-
};
|
581
|
-
function getFieldFromModel(model, fieldPath, source, shouldThrow = true) {
|
582
|
-
const writingField = "instructionName" in source ? source.instructionName === "to" : true;
|
583
|
-
const errorTarget = "instructionName" in source ? `\`${source.instructionName}\`` : `${source.modelEntityType} "${source.modelEntityName}"`;
|
584
|
-
const errorPrefix = `Field "${fieldPath}" defined for ${errorTarget}`;
|
585
|
-
const modelFields = model.fields || [];
|
586
|
-
let modelField;
|
587
|
-
if (fieldPath.includes(".")) {
|
588
|
-
modelField = modelFields.find((field) => field.slug === fieldPath.split(".")[0]);
|
589
|
-
if (modelField?.type === "json") {
|
590
|
-
const fieldSelector2 = getFieldSelector(model, modelField, fieldPath, writingField);
|
591
|
-
return { field: modelField, fieldSelector: fieldSelector2 };
|
499
|
+
const subQuerySelectedFields = subQueryInstructions?.selecting;
|
500
|
+
const subQueryIncludedFields = subQueryInstructions?.including;
|
501
|
+
const subQueryFields = [
|
502
|
+
...subQuerySelectedFields || (subQueryModel.fields || []).map((field) => field.slug),
|
503
|
+
...subQueryIncludedFields ? Object.keys(
|
504
|
+
flatten(subQueryIncludedFields || {})
|
505
|
+
) : []
|
506
|
+
];
|
507
|
+
for (const field of subQueryFields || []) {
|
508
|
+
getFieldFromModel(model, field, { instructionName: "to" });
|
592
509
|
}
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
throw new RoninError({
|
598
|
-
message: `${errorPrefix} does not exist in model "${model.name}".`,
|
599
|
-
code: "FIELD_NOT_FOUND",
|
600
|
-
field: fieldPath,
|
601
|
-
queries: null
|
510
|
+
let statement2 = "";
|
511
|
+
if (subQuerySelectedFields) {
|
512
|
+
const columns = subQueryFields.map((field) => {
|
513
|
+
return getFieldFromModel(model, field, { instructionName: "to" }).fieldSelector;
|
602
514
|
});
|
515
|
+
statement2 = `(${columns.join(", ")}) `;
|
603
516
|
}
|
604
|
-
|
517
|
+
statement2 += compileQueryInput(symbol.value, models, statementParams).main.statement;
|
518
|
+
return statement2;
|
605
519
|
}
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
520
|
+
Object.assign(toInstruction, defaultFields);
|
521
|
+
for (const fieldSlug in toInstruction) {
|
522
|
+
if (!Object.hasOwn(toInstruction, fieldSlug)) continue;
|
523
|
+
const fieldValue = toInstruction[fieldSlug];
|
524
|
+
const fieldDetails = getFieldFromModel(
|
525
|
+
model,
|
526
|
+
fieldSlug,
|
527
|
+
{ instructionName: "to" },
|
528
|
+
false
|
529
|
+
);
|
530
|
+
if (fieldDetails?.field.type === "link" && fieldDetails.field.kind === "many") {
|
531
|
+
delete toInstruction[fieldSlug];
|
532
|
+
const associativeModelSlug = composeAssociationModelSlug(model, fieldDetails.field);
|
533
|
+
const composeStatement = (subQueryType, value) => {
|
534
|
+
const source = queryType === "add" ? { id: toInstruction.id } : withInstruction;
|
535
|
+
const recordDetails = { source };
|
536
|
+
if (value) recordDetails.target = value;
|
537
|
+
return compileQueryInput(
|
538
|
+
{
|
539
|
+
[subQueryType]: {
|
540
|
+
[associativeModelSlug]: subQueryType === "add" ? { to: recordDetails } : { with: recordDetails }
|
541
|
+
}
|
542
|
+
},
|
543
|
+
models,
|
544
|
+
[],
|
545
|
+
{ returning: false }
|
546
|
+
).main;
|
547
|
+
};
|
548
|
+
if (Array.isArray(fieldValue)) {
|
549
|
+
dependencyStatements.push(composeStatement("remove"));
|
550
|
+
for (const record of fieldValue) {
|
551
|
+
dependencyStatements.push(composeStatement("add", record));
|
552
|
+
}
|
553
|
+
} else if (isObject(fieldValue)) {
|
554
|
+
const value = fieldValue;
|
555
|
+
for (const recordToAdd of value.containing || []) {
|
556
|
+
dependencyStatements.push(composeStatement("add", recordToAdd));
|
557
|
+
}
|
558
|
+
for (const recordToRemove of value.notContaining || []) {
|
559
|
+
dependencyStatements.push(composeStatement("remove", recordToRemove));
|
560
|
+
}
|
561
|
+
}
|
562
|
+
}
|
619
563
|
}
|
620
|
-
|
621
|
-
|
564
|
+
let statement = composeConditions(models, model, statementParams, "to", toInstruction, {
|
565
|
+
parentModel,
|
566
|
+
type: queryType === "add" ? "fields" : void 0
|
567
|
+
});
|
568
|
+
if (queryType === "add") {
|
569
|
+
const deepStatement = composeConditions(
|
570
|
+
models,
|
571
|
+
model,
|
572
|
+
statementParams,
|
573
|
+
"to",
|
574
|
+
toInstruction,
|
575
|
+
{
|
576
|
+
parentModel,
|
577
|
+
type: "values"
|
578
|
+
}
|
579
|
+
);
|
580
|
+
statement = `(${statement}) VALUES (${deepStatement})`;
|
581
|
+
} else if (queryType === "set") {
|
582
|
+
statement = `SET ${statement}`;
|
622
583
|
}
|
623
|
-
return
|
584
|
+
return statement;
|
624
585
|
};
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
586
|
+
|
587
|
+
// src/utils/index.ts
|
588
|
+
var compileQueryInput = (defaultQuery, models, statementParams, options) => {
|
589
|
+
const dependencyStatements = [];
|
590
|
+
const query = transformMetaQuery(
|
591
|
+
models,
|
592
|
+
dependencyStatements,
|
593
|
+
statementParams,
|
594
|
+
defaultQuery
|
595
|
+
);
|
596
|
+
if (query === null)
|
597
|
+
return { dependencies: [], main: dependencyStatements[0], loadedFields: [] };
|
598
|
+
const parsedQuery = splitQuery(query);
|
599
|
+
const { queryType, queryModel, queryInstructions } = parsedQuery;
|
600
|
+
const model = getModelBySlug(models, queryModel);
|
601
|
+
const single = queryModel !== model.pluralSlug;
|
602
|
+
let instructions = formatIdentifiers(model, queryInstructions);
|
603
|
+
const returning = options?.returning ?? true;
|
604
|
+
if (instructions && Object.hasOwn(instructions, "for")) {
|
605
|
+
instructions = handleFor(model, instructions);
|
637
606
|
}
|
638
|
-
const
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
607
|
+
const { columns, isJoining, loadedFields } = handleSelecting(
|
608
|
+
models,
|
609
|
+
model,
|
610
|
+
statementParams,
|
611
|
+
{
|
612
|
+
selecting: instructions?.selecting,
|
613
|
+
including: instructions?.including
|
614
|
+
},
|
615
|
+
options
|
616
|
+
);
|
617
|
+
let statement = "";
|
618
|
+
switch (queryType) {
|
619
|
+
case "get":
|
620
|
+
statement += `SELECT ${columns} FROM `;
|
621
|
+
break;
|
622
|
+
case "set":
|
623
|
+
statement += "UPDATE ";
|
624
|
+
break;
|
625
|
+
case "add":
|
626
|
+
statement += "INSERT INTO ";
|
627
|
+
break;
|
628
|
+
case "remove":
|
629
|
+
statement += "DELETE FROM ";
|
630
|
+
break;
|
631
|
+
case "count":
|
632
|
+
statement += `SELECT COUNT(${columns}) FROM `;
|
633
|
+
break;
|
634
|
+
}
|
635
|
+
let isJoiningMultipleRows = false;
|
636
|
+
if (isJoining) {
|
637
|
+
const { statement: including, tableSubQuery } = handleIncluding(
|
638
|
+
models,
|
639
|
+
model,
|
640
|
+
statementParams,
|
641
|
+
instructions?.including
|
642
|
+
);
|
643
|
+
if (tableSubQuery) {
|
644
|
+
statement += `(${tableSubQuery}) as ${model.tableAlias} `;
|
645
|
+
isJoiningMultipleRows = true;
|
646
|
+
} else {
|
647
|
+
statement += `"${model.table}" `;
|
646
648
|
}
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
649
|
+
statement += `${including} `;
|
650
|
+
} else {
|
651
|
+
statement += `"${model.table}" `;
|
652
|
+
}
|
653
|
+
if (queryType === "add" || queryType === "set") {
|
654
|
+
if (!isObject(instructions.to) || Object.keys(instructions.to).length === 0) {
|
655
|
+
throw new RoninError({
|
656
|
+
message: `When using a \`${queryType}\` query, the \`to\` instruction must be a non-empty object.`,
|
657
|
+
code: "INVALID_TO_VALUE",
|
658
|
+
queries: [query]
|
659
|
+
});
|
652
660
|
}
|
653
|
-
|
661
|
+
const toStatement = handleTo(
|
662
|
+
models,
|
663
|
+
model,
|
664
|
+
statementParams,
|
665
|
+
queryType,
|
666
|
+
dependencyStatements,
|
667
|
+
{ with: instructions.with, to: instructions.to },
|
668
|
+
options?.parentModel
|
669
|
+
);
|
670
|
+
statement += `${toStatement} `;
|
654
671
|
}
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
672
|
+
const conditions = [];
|
673
|
+
if (queryType !== "add" && instructions && Object.hasOwn(instructions, "with")) {
|
674
|
+
const withStatement = handleWith(
|
675
|
+
models,
|
676
|
+
model,
|
677
|
+
statementParams,
|
678
|
+
instructions.with,
|
679
|
+
options?.parentModel
|
680
|
+
);
|
681
|
+
if (withStatement.length > 0) conditions.push(withStatement);
|
682
|
+
}
|
683
|
+
if ((queryType === "get" || queryType === "count") && !single && instructions?.limitedTo) {
|
684
|
+
instructions = instructions || {};
|
685
|
+
instructions.orderedBy = instructions.orderedBy || {};
|
686
|
+
instructions.orderedBy.ascending = instructions.orderedBy.ascending || [];
|
687
|
+
instructions.orderedBy.descending = instructions.orderedBy.descending || [];
|
688
|
+
if (![
|
689
|
+
...instructions.orderedBy.ascending,
|
690
|
+
...instructions.orderedBy.descending
|
691
|
+
].includes("ronin.createdAt")) {
|
692
|
+
instructions.orderedBy.descending.push("ronin.createdAt");
|
667
693
|
}
|
668
|
-
},
|
669
|
-
{
|
670
|
-
name: "RONIN - Locked",
|
671
|
-
type: "boolean",
|
672
|
-
slug: "ronin.locked"
|
673
|
-
},
|
674
|
-
{
|
675
|
-
name: "RONIN - Created At",
|
676
|
-
type: "date",
|
677
|
-
slug: "ronin.createdAt",
|
678
|
-
defaultValue: CURRENT_TIME_EXPRESSION
|
679
|
-
},
|
680
|
-
{
|
681
|
-
name: "RONIN - Created By",
|
682
|
-
type: "string",
|
683
|
-
slug: "ronin.createdBy"
|
684
|
-
},
|
685
|
-
{
|
686
|
-
name: "RONIN - Updated At",
|
687
|
-
type: "date",
|
688
|
-
slug: "ronin.updatedAt",
|
689
|
-
defaultValue: CURRENT_TIME_EXPRESSION
|
690
|
-
},
|
691
|
-
{
|
692
|
-
name: "RONIN - Updated By",
|
693
|
-
type: "string",
|
694
|
-
slug: "ronin.updatedBy"
|
695
694
|
}
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
},
|
703
|
-
// This name mimics the `sqlite_schema` table in SQLite.
|
704
|
-
table: "ronin_schema",
|
705
|
-
// Indicates that the model was automatically generated by RONIN.
|
706
|
-
system: { model: "root" },
|
707
|
-
fields: [
|
708
|
-
{ slug: "name", type: "string" },
|
709
|
-
{ slug: "pluralName", type: "string" },
|
710
|
-
{ slug: "slug", type: "string" },
|
711
|
-
{ slug: "pluralSlug", type: "string" },
|
712
|
-
{ slug: "idPrefix", type: "string" },
|
713
|
-
{ slug: "table", type: "string" },
|
714
|
-
{ slug: "identifiers.name", type: "string" },
|
715
|
-
{ slug: "identifiers.slug", type: "string" },
|
716
|
-
// Providing an empty object as a default value allows us to use `json_insert`
|
717
|
-
// without needing to fall back to an empty object in the insertion statement,
|
718
|
-
// which makes the statement shorter.
|
719
|
-
{ slug: "fields", type: "json", defaultValue: "{}" },
|
720
|
-
{ slug: "indexes", type: "json", defaultValue: "{}" },
|
721
|
-
{ slug: "triggers", type: "json", defaultValue: "{}" },
|
722
|
-
{ slug: "presets", type: "json", defaultValue: "{}" }
|
723
|
-
]
|
724
|
-
};
|
725
|
-
var getSystemModels = (models, model) => {
|
726
|
-
const addedModels = [];
|
727
|
-
for (const field of model.fields || []) {
|
728
|
-
if (field.type === "link" && !field.slug.startsWith("ronin.")) {
|
729
|
-
const relatedModel = getModelBySlug(models, field.target);
|
730
|
-
let fieldSlug = relatedModel.slug;
|
731
|
-
if (field.kind === "many") {
|
732
|
-
fieldSlug = composeAssociationModelSlug(model, field);
|
733
|
-
addedModels.push({
|
734
|
-
pluralSlug: fieldSlug,
|
735
|
-
slug: fieldSlug,
|
736
|
-
system: {
|
737
|
-
model: model.slug,
|
738
|
-
associationSlug: field.slug
|
739
|
-
},
|
740
|
-
fields: [
|
741
|
-
{
|
742
|
-
slug: "source",
|
743
|
-
type: "link",
|
744
|
-
target: model.slug
|
745
|
-
},
|
746
|
-
{
|
747
|
-
slug: "target",
|
748
|
-
type: "link",
|
749
|
-
target: relatedModel.slug
|
750
|
-
}
|
751
|
-
]
|
752
|
-
});
|
753
|
-
}
|
754
|
-
}
|
755
|
-
}
|
756
|
-
return addedModels;
|
757
|
-
};
|
758
|
-
var addDefaultModelPresets = (list, model) => {
|
759
|
-
const defaultPresets = [];
|
760
|
-
for (const field of model.fields || []) {
|
761
|
-
if (field.type === "link" && !field.slug.startsWith("ronin.")) {
|
762
|
-
const relatedModel = getModelBySlug(list, field.target);
|
763
|
-
if (field.kind === "many") continue;
|
764
|
-
defaultPresets.push({
|
765
|
-
instructions: {
|
766
|
-
including: {
|
767
|
-
[field.slug]: {
|
768
|
-
[QUERY_SYMBOLS.QUERY]: {
|
769
|
-
get: {
|
770
|
-
[relatedModel.slug]: {
|
771
|
-
with: {
|
772
|
-
// Compare the `id` field of the related model to the link field on
|
773
|
-
// the root model (`field.slug`).
|
774
|
-
id: {
|
775
|
-
[QUERY_SYMBOLS.EXPRESSION]: `${QUERY_SYMBOLS.FIELD_PARENT}${field.slug}`
|
776
|
-
}
|
777
|
-
}
|
778
|
-
}
|
779
|
-
}
|
780
|
-
}
|
781
|
-
}
|
782
|
-
}
|
783
|
-
},
|
784
|
-
slug: field.slug
|
695
|
+
if (instructions && (Object.hasOwn(instructions, "before") || Object.hasOwn(instructions, "after"))) {
|
696
|
+
if (single) {
|
697
|
+
throw new RoninError({
|
698
|
+
message: "The `before` and `after` instructions are not supported when querying for a single record.",
|
699
|
+
code: "INVALID_BEFORE_OR_AFTER_INSTRUCTION",
|
700
|
+
queries: [query]
|
785
701
|
});
|
786
702
|
}
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
return { model: subModel, field };
|
794
|
-
}).filter((match) => match !== null);
|
795
|
-
for (const childMatch of childModels) {
|
796
|
-
const { model: childModel, field: childField } = childMatch;
|
797
|
-
const pluralSlug = childModel.pluralSlug;
|
798
|
-
const presetSlug = childModel.system?.associationSlug || pluralSlug;
|
799
|
-
defaultPresets.push({
|
800
|
-
instructions: {
|
801
|
-
including: {
|
802
|
-
[presetSlug]: {
|
803
|
-
[QUERY_SYMBOLS.QUERY]: {
|
804
|
-
get: {
|
805
|
-
[pluralSlug]: {
|
806
|
-
with: {
|
807
|
-
[childField.slug]: {
|
808
|
-
[QUERY_SYMBOLS.EXPRESSION]: `${QUERY_SYMBOLS.FIELD_PARENT}id`
|
809
|
-
}
|
810
|
-
}
|
811
|
-
}
|
812
|
-
}
|
813
|
-
}
|
814
|
-
}
|
815
|
-
}
|
816
|
-
},
|
817
|
-
slug: presetSlug
|
703
|
+
const beforeAndAfterStatement = handleBeforeOrAfter(model, statementParams, {
|
704
|
+
before: instructions.before,
|
705
|
+
after: instructions.after,
|
706
|
+
with: instructions.with,
|
707
|
+
orderedBy: instructions.orderedBy,
|
708
|
+
limitedTo: instructions.limitedTo
|
818
709
|
});
|
710
|
+
conditions.push(beforeAndAfterStatement);
|
819
711
|
}
|
820
|
-
if (
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
}
|
825
|
-
|
826
|
-
link: "TEXT",
|
827
|
-
string: "TEXT",
|
828
|
-
date: "DATETIME",
|
829
|
-
blob: "TEXT",
|
830
|
-
boolean: "BOOLEAN",
|
831
|
-
number: "INTEGER",
|
832
|
-
json: "TEXT"
|
833
|
-
};
|
834
|
-
var getFieldStatement = (models, model, field) => {
|
835
|
-
let statement = `"${field.slug}" ${typesInSQLite[field.type || "string"]}`;
|
836
|
-
if (field.slug === "id") statement += " PRIMARY KEY";
|
837
|
-
if (field.unique === true) statement += " UNIQUE";
|
838
|
-
if (field.required === true) statement += " NOT NULL";
|
839
|
-
if (typeof field.defaultValue !== "undefined") {
|
840
|
-
const symbol = getSymbol(field.defaultValue);
|
841
|
-
let value = typeof field.defaultValue === "string" ? `'${field.defaultValue}'` : field.defaultValue;
|
842
|
-
if (symbol) value = `(${parseFieldExpression(model, "to", symbol.value)})`;
|
843
|
-
statement += ` DEFAULT ${value}`;
|
712
|
+
if (conditions.length > 0) {
|
713
|
+
if (conditions.length === 1) {
|
714
|
+
statement += `WHERE ${conditions[0]} `;
|
715
|
+
} else {
|
716
|
+
statement += `WHERE (${conditions.join(" ")}) `;
|
717
|
+
}
|
844
718
|
}
|
845
|
-
if (
|
846
|
-
|
719
|
+
if (instructions?.orderedBy) {
|
720
|
+
const orderedByStatement = handleOrderedBy(model, instructions.orderedBy);
|
721
|
+
statement += `${orderedByStatement} `;
|
847
722
|
}
|
848
|
-
if (
|
849
|
-
statement +=
|
723
|
+
if (queryType === "get" && !isJoiningMultipleRows && (single || instructions?.limitedTo)) {
|
724
|
+
statement += handleLimitedTo(single, instructions?.limitedTo);
|
850
725
|
}
|
851
|
-
if (
|
852
|
-
|
853
|
-
statement += ` CHECK (${parseFieldExpression(model, "to", symbol?.value)})`;
|
726
|
+
if (["add", "set", "remove"].includes(queryType) && returning) {
|
727
|
+
statement += "RETURNING * ";
|
854
728
|
}
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
729
|
+
const mainStatement = {
|
730
|
+
statement: statement.trimEnd(),
|
731
|
+
params: statementParams || []
|
732
|
+
};
|
733
|
+
if (returning) mainStatement.returning = true;
|
734
|
+
return {
|
735
|
+
dependencies: dependencyStatements,
|
736
|
+
main: mainStatement,
|
737
|
+
loadedFields
|
738
|
+
};
|
739
|
+
};
|
740
|
+
|
741
|
+
// src/utils/statement.ts
|
742
|
+
var replaceJSON = (key, value) => {
|
743
|
+
if (key === QUERY_SYMBOLS.EXPRESSION) return value.replaceAll(`'`, `''`);
|
744
|
+
return value;
|
745
|
+
};
|
746
|
+
var prepareStatementValue = (statementParams, value) => {
|
747
|
+
if (value === null) return "NULL";
|
748
|
+
if (!statementParams) {
|
749
|
+
const valueString = typeof value === "object" ? `json('${JSON.stringify(value, replaceJSON)}')` : `'${value.toString()}'`;
|
750
|
+
return valueString;
|
859
751
|
}
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
statement += ` REFERENCES ${targetTable}("id")`;
|
866
|
-
for (const trigger in actions) {
|
867
|
-
if (!Object.hasOwn(actions, trigger)) continue;
|
868
|
-
const triggerName = trigger.toUpperCase().slice(2);
|
869
|
-
const action = actions[trigger];
|
870
|
-
statement += ` ON ${triggerName} ${action}`;
|
871
|
-
}
|
752
|
+
let formattedValue = value;
|
753
|
+
if (Array.isArray(value) || isObject(value)) {
|
754
|
+
formattedValue = JSON.stringify(value);
|
755
|
+
} else if (typeof value === "boolean") {
|
756
|
+
formattedValue = value ? 1 : 0;
|
872
757
|
}
|
873
|
-
|
874
|
-
}
|
875
|
-
var PLURAL_MODEL_ENTITIES = {
|
876
|
-
field: "fields",
|
877
|
-
index: "indexes",
|
878
|
-
trigger: "triggers",
|
879
|
-
preset: "presets"
|
758
|
+
const index = statementParams.push(formattedValue);
|
759
|
+
return `?${index}`;
|
880
760
|
};
|
881
|
-
var
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
761
|
+
var parseFieldExpression = (model, instructionName, expression, parentModel) => {
|
762
|
+
return expression.replace(RONIN_MODEL_FIELD_REGEX, (match) => {
|
763
|
+
let toReplace = QUERY_SYMBOLS.FIELD;
|
764
|
+
let rootModel = model;
|
765
|
+
if (match.startsWith(QUERY_SYMBOLS.FIELD_PARENT)) {
|
766
|
+
rootModel = parentModel;
|
767
|
+
toReplace = QUERY_SYMBOLS.FIELD_PARENT;
|
768
|
+
if (match.startsWith(QUERY_SYMBOLS.FIELD_PARENT_OLD)) {
|
769
|
+
rootModel.tableAlias = toReplace = QUERY_SYMBOLS.FIELD_PARENT_OLD;
|
770
|
+
} else if (match.startsWith(QUERY_SYMBOLS.FIELD_PARENT_NEW)) {
|
771
|
+
rootModel.tableAlias = toReplace = QUERY_SYMBOLS.FIELD_PARENT_NEW;
|
772
|
+
}
|
773
|
+
}
|
774
|
+
const fieldSlug = match.replace(toReplace, "");
|
775
|
+
const field = getFieldFromModel(rootModel, fieldSlug, { instructionName });
|
776
|
+
return field.fieldSelector;
|
886
777
|
});
|
887
|
-
return entries ? Object.fromEntries(entries) : void 0;
|
888
|
-
};
|
889
|
-
var handleSystemModel = (models, dependencyStatements, action, systemModel, newModel) => {
|
890
|
-
const { system: _, ...systemModelClean } = systemModel;
|
891
|
-
const query = {
|
892
|
-
[action]: { model: action === "create" ? systemModelClean : systemModelClean.slug }
|
893
|
-
};
|
894
|
-
if (action === "alter" && newModel && "alter" in query && query.alter) {
|
895
|
-
const { system: _2, ...newModelClean } = newModel;
|
896
|
-
query.alter.to = newModelClean;
|
897
|
-
}
|
898
|
-
const statement = compileQueryInput(query, models, []);
|
899
|
-
dependencyStatements.push(...statement.dependencies);
|
900
778
|
};
|
901
|
-
var
|
902
|
-
const {
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
} else {
|
919
|
-
slug = query.alter[action][entity];
|
920
|
-
if ("create" in query.alter) {
|
921
|
-
const item = query.alter.create[entity];
|
922
|
-
slug = item.slug || `${entity}Slug`;
|
923
|
-
jsonValue = { slug, ...item };
|
924
|
-
}
|
925
|
-
if ("alter" in query.alter && query.alter.alter) jsonValue = query.alter.alter.to;
|
926
|
-
}
|
927
|
-
}
|
928
|
-
if (!(modelSlug && slug)) return query;
|
929
|
-
const model = action === "create" && entity === "model" ? null : getModelBySlug(models, modelSlug);
|
930
|
-
if (entity === "model") {
|
931
|
-
let queryTypeDetails = {};
|
932
|
-
if (action === "create") {
|
933
|
-
const newModel = jsonValue;
|
934
|
-
const modelWithFields = addDefaultModelFields(newModel, true);
|
935
|
-
const modelWithPresets = addDefaultModelPresets(
|
936
|
-
[...models, modelWithFields],
|
937
|
-
modelWithFields
|
938
|
-
);
|
939
|
-
modelWithPresets.fields = modelWithPresets.fields.map((field2) => ({
|
940
|
-
...field2,
|
941
|
-
// Default field type.
|
942
|
-
type: field2.type || "string",
|
943
|
-
// Default field name.
|
944
|
-
name: field2.name || slugToName(field2.slug)
|
945
|
-
}));
|
946
|
-
const columns = modelWithPresets.fields.map((field2) => getFieldStatement(models, modelWithPresets, field2)).filter(Boolean);
|
947
|
-
const entities = Object.fromEntries(
|
948
|
-
Object.entries(PLURAL_MODEL_ENTITIES).map(([type, pluralType2]) => {
|
949
|
-
const list = modelWithPresets[pluralType2];
|
950
|
-
return [pluralType2, formatModelEntity(type, list)];
|
951
|
-
})
|
779
|
+
var composeFieldValues = (models, model, statementParams, instructionName, value, options) => {
|
780
|
+
const { fieldSelector: conditionSelector } = getFieldFromModel(
|
781
|
+
model,
|
782
|
+
options.fieldSlug,
|
783
|
+
{ instructionName }
|
784
|
+
);
|
785
|
+
const collectStatementValue = options.type !== "fields";
|
786
|
+
const symbol = getSymbol(value);
|
787
|
+
const syntax = WITH_CONDITIONS[options.condition || "being"](value);
|
788
|
+
let conditionValue = syntax[1];
|
789
|
+
if (symbol) {
|
790
|
+
if (symbol?.type === "expression") {
|
791
|
+
conditionValue = parseFieldExpression(
|
792
|
+
model,
|
793
|
+
instructionName,
|
794
|
+
symbol.value,
|
795
|
+
options.parentModel
|
952
796
|
);
|
953
|
-
dependencyStatements.push({
|
954
|
-
statement: `CREATE TABLE "${modelWithPresets.table}" (${columns.join(", ")})`,
|
955
|
-
params: []
|
956
|
-
});
|
957
|
-
models.push(modelWithPresets);
|
958
|
-
const modelWithObjects = Object.assign({}, modelWithPresets);
|
959
|
-
for (const entity2 in entities) {
|
960
|
-
if (!Object.hasOwn(entities, entity2)) continue;
|
961
|
-
Object.defineProperty(modelWithObjects, entity2, { value: entities[entity2] });
|
962
|
-
}
|
963
|
-
queryTypeDetails = { to: modelWithObjects };
|
964
|
-
getSystemModels(models, modelWithPresets).map((systemModel) => {
|
965
|
-
return handleSystemModel(models, dependencyStatements, "create", systemModel);
|
966
|
-
});
|
967
797
|
}
|
968
|
-
if (
|
969
|
-
|
970
|
-
const modelWithFields = addDefaultModelFields(newModel, false);
|
971
|
-
const modelWithPresets = addDefaultModelPresets(models, modelWithFields);
|
972
|
-
const newTableName = modelWithPresets.table;
|
973
|
-
if (newTableName) {
|
974
|
-
dependencyStatements.push({
|
975
|
-
statement: `ALTER TABLE "${model.table}" RENAME TO "${newTableName}"`,
|
976
|
-
params: []
|
977
|
-
});
|
978
|
-
}
|
979
|
-
Object.assign(model, modelWithPresets);
|
980
|
-
queryTypeDetails = {
|
981
|
-
with: {
|
982
|
-
slug
|
983
|
-
},
|
984
|
-
to: modelWithPresets
|
985
|
-
};
|
986
|
-
}
|
987
|
-
if (action === "drop" && model) {
|
988
|
-
models.splice(models.indexOf(model), 1);
|
989
|
-
dependencyStatements.push({ statement: `DROP TABLE "${model.table}"`, params: [] });
|
990
|
-
queryTypeDetails = { with: { slug } };
|
991
|
-
models.filter(({ system }) => system?.model === model.slug).map((systemModel) => {
|
992
|
-
return handleSystemModel(models, dependencyStatements, "drop", systemModel);
|
993
|
-
});
|
798
|
+
if (symbol.type === "query" && collectStatementValue) {
|
799
|
+
conditionValue = `(${compileQueryInput(symbol.value, models, statementParams).main.statement})`;
|
994
800
|
}
|
995
|
-
|
996
|
-
|
997
|
-
const queryTypeAction = action === "create" ? "add" : action === "alter" ? "set" : "remove";
|
998
|
-
return {
|
999
|
-
[queryTypeAction]: {
|
1000
|
-
model: queryTypeDetails
|
1001
|
-
}
|
1002
|
-
};
|
801
|
+
} else if (collectStatementValue) {
|
802
|
+
conditionValue = prepareStatementValue(statementParams, conditionValue);
|
1003
803
|
}
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
);
|
1010
|
-
if (
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
804
|
+
if (options.type === "fields") return conditionSelector;
|
805
|
+
if (options.type === "values") return conditionValue;
|
806
|
+
return `${conditionSelector} ${syntax[0]} ${conditionValue}`;
|
807
|
+
};
|
808
|
+
var composeConditions = (models, model, statementParams, instructionName, value, options) => {
|
809
|
+
const isNested = isObject(value) && Object.keys(value).length > 0;
|
810
|
+
if (isNested && Object.keys(value).every((key) => key in WITH_CONDITIONS)) {
|
811
|
+
const conditions = Object.entries(value).map(
|
812
|
+
([conditionType, checkValue]) => composeConditions(models, model, statementParams, instructionName, checkValue, {
|
813
|
+
...options,
|
814
|
+
condition: conditionType
|
815
|
+
})
|
816
|
+
);
|
817
|
+
return conditions.join(" AND ");
|
1015
818
|
}
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
message: `A ${entity} with the slug "${slug}" already exists.`,
|
1020
|
-
code: "EXISTING_MODEL_ENTITY",
|
1021
|
-
fields: ["slug"]
|
819
|
+
if (options.fieldSlug) {
|
820
|
+
const childField = model.fields.some(({ slug }) => {
|
821
|
+
return slug.includes(".") && slug.split(".")[0] === options.fieldSlug;
|
1022
822
|
});
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
const existingField = existingEntity;
|
1027
|
-
const existingLinkField = existingField?.type === "link" && existingField.kind === "many";
|
1028
|
-
if (action === "create") {
|
1029
|
-
const field2 = jsonValue;
|
1030
|
-
field2.type = field2.type || "string";
|
1031
|
-
field2.name = field2.name || slugToName(field2.slug);
|
1032
|
-
const fieldStatement = getFieldStatement(models, existingModel, field2);
|
1033
|
-
if (fieldStatement) {
|
1034
|
-
dependencyStatements.push({
|
1035
|
-
statement: `${statement} ADD COLUMN ${fieldStatement}`,
|
1036
|
-
params: []
|
1037
|
-
});
|
1038
|
-
}
|
1039
|
-
} else if (action === "alter") {
|
1040
|
-
const field2 = jsonValue;
|
1041
|
-
const newSlug = field2.slug;
|
1042
|
-
if (newSlug) {
|
1043
|
-
field2.name = field2.name || slugToName(field2.slug);
|
1044
|
-
if (!existingLinkField) {
|
1045
|
-
dependencyStatements.push({
|
1046
|
-
statement: `${statement} RENAME COLUMN "${slug}" TO "${newSlug}"`,
|
1047
|
-
params: []
|
1048
|
-
});
|
1049
|
-
}
|
1050
|
-
}
|
1051
|
-
} else if (action === "drop" && !existingLinkField) {
|
1052
|
-
const systemFields = getSystemFields(existingModel.idPrefix);
|
1053
|
-
const isSystemField = systemFields.some((field2) => field2.slug === slug);
|
1054
|
-
if (isSystemField) {
|
1055
|
-
throw new RoninError({
|
1056
|
-
message: `The ${entity} "${slug}" is a system ${entity} and cannot be removed.`,
|
1057
|
-
code: "REQUIRED_MODEL_ENTITY"
|
1058
|
-
});
|
1059
|
-
}
|
1060
|
-
dependencyStatements.push({
|
1061
|
-
statement: `${statement} DROP COLUMN "${slug}"`,
|
1062
|
-
params: []
|
1063
|
-
});
|
1064
|
-
}
|
1065
|
-
}
|
1066
|
-
const statementAction = action.toUpperCase();
|
1067
|
-
if (entity === "index") {
|
1068
|
-
const index = jsonValue;
|
1069
|
-
const indexName = convertToSnakeCase(slug);
|
1070
|
-
let statement = `${statementAction}${index?.unique ? " UNIQUE" : ""} INDEX "${indexName}"`;
|
1071
|
-
if (action === "create") {
|
1072
|
-
if (!Array.isArray(index.fields) || index.fields.length === 0) {
|
1073
|
-
throw new RoninError({
|
1074
|
-
message: `When ${actionReadable} ${PLURAL_MODEL_ENTITIES[entity]}, at least one field must be provided.`,
|
1075
|
-
code: "INVALID_MODEL_VALUE",
|
1076
|
-
fields: ["fields"]
|
1077
|
-
});
|
1078
|
-
}
|
1079
|
-
const columns = index.fields.map((field2) => {
|
1080
|
-
let fieldSelector = "";
|
1081
|
-
if ("slug" in field2) {
|
1082
|
-
({ fieldSelector } = getFieldFromModel(existingModel, field2.slug, {
|
1083
|
-
modelEntityType: "index",
|
1084
|
-
modelEntityName: indexName
|
1085
|
-
}));
|
1086
|
-
} else if ("expression" in field2) {
|
1087
|
-
fieldSelector = parseFieldExpression(existingModel, "to", field2.expression);
|
1088
|
-
}
|
1089
|
-
if (field2.collation) fieldSelector += ` COLLATE ${field2.collation}`;
|
1090
|
-
if (field2.order) fieldSelector += ` ${field2.order}`;
|
1091
|
-
return fieldSelector;
|
823
|
+
if (!childField) {
|
824
|
+
const fieldDetails = getFieldFromModel(model, options.fieldSlug, {
|
825
|
+
instructionName
|
1092
826
|
});
|
1093
|
-
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
827
|
+
const { field: modelField } = fieldDetails || {};
|
828
|
+
const consumeJSON = modelField?.type === "json" && instructionName === "to";
|
829
|
+
if (modelField && !(isObject(value) || Array.isArray(value)) || getSymbol(value) || consumeJSON) {
|
830
|
+
return composeFieldValues(
|
831
|
+
models,
|
832
|
+
model,
|
833
|
+
statementParams,
|
834
|
+
instructionName,
|
835
|
+
value,
|
836
|
+
{ ...options, fieldSlug: options.fieldSlug }
|
837
|
+
);
|
1097
838
|
}
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
}
|
839
|
+
if (modelField?.type === "link" && isNested) {
|
840
|
+
const keys = Object.keys(value);
|
841
|
+
const values = Object.values(value);
|
842
|
+
let recordTarget;
|
843
|
+
if (keys.length === 1 && keys[0] === "id") {
|
844
|
+
recordTarget = values[0];
|
845
|
+
} else {
|
846
|
+
const relatedModel = getModelBySlug(models, modelField.target);
|
847
|
+
const subQuery = {
|
848
|
+
get: {
|
849
|
+
[relatedModel.slug]: {
|
850
|
+
with: value,
|
851
|
+
selecting: ["id"]
|
852
|
+
}
|
853
|
+
}
|
854
|
+
};
|
855
|
+
recordTarget = {
|
856
|
+
[QUERY_SYMBOLS.QUERY]: subQuery
|
857
|
+
};
|
1114
858
|
}
|
1115
|
-
|
1116
|
-
return getFieldFromModel(existingModel, field2.slug, {
|
1117
|
-
modelEntityType: "trigger",
|
1118
|
-
modelEntityName: triggerName
|
1119
|
-
}).fieldSelector;
|
1120
|
-
});
|
1121
|
-
statementParts.push(`OF (${fieldSelectors.join(", ")})`);
|
1122
|
-
}
|
1123
|
-
statementParts.push("ON", `"${existingModel.table}"`);
|
1124
|
-
if (trigger.filter || trigger.effects.some((query2) => findInObject(query2, QUERY_SYMBOLS.FIELD))) {
|
1125
|
-
statementParts.push("FOR EACH ROW");
|
1126
|
-
}
|
1127
|
-
if (trigger.filter) {
|
1128
|
-
const tableAlias = trigger.action === "DELETE" ? QUERY_SYMBOLS.FIELD_PARENT_OLD : QUERY_SYMBOLS.FIELD_PARENT_NEW;
|
1129
|
-
const withStatement = handleWith(
|
859
|
+
return composeConditions(
|
1130
860
|
models,
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
861
|
+
model,
|
862
|
+
statementParams,
|
863
|
+
instructionName,
|
864
|
+
recordTarget,
|
865
|
+
options
|
1134
866
|
);
|
1135
|
-
statementParts.push("WHEN", `(${withStatement})`);
|
1136
867
|
}
|
1137
|
-
const effectStatements = trigger.effects.map((effectQuery) => {
|
1138
|
-
return compileQueryInput(effectQuery, models, null, {
|
1139
|
-
returning: false,
|
1140
|
-
parentModel: existingModel
|
1141
|
-
}).main.statement;
|
1142
|
-
});
|
1143
|
-
statementParts.push("BEGIN");
|
1144
|
-
statementParts.push(`${effectStatements.join("; ")};`);
|
1145
|
-
statementParts.push("END");
|
1146
|
-
statement += ` ${statementParts.join(" ")}`;
|
1147
868
|
}
|
1148
|
-
dependencyStatements.push({ statement, params: [] });
|
1149
869
|
}
|
1150
|
-
|
1151
|
-
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
870
|
+
if (isNested) {
|
871
|
+
const conditions = Object.entries(value).map(([field, value2]) => {
|
872
|
+
const nestedFieldSlug = options.fieldSlug ? `${options.fieldSlug}.${field}` : field;
|
873
|
+
return composeConditions(models, model, statementParams, instructionName, value2, {
|
874
|
+
...options,
|
875
|
+
fieldSlug: nestedFieldSlug
|
876
|
+
});
|
877
|
+
});
|
878
|
+
const joiner = instructionName === "to" ? ", " : " AND ";
|
879
|
+
if (instructionName === "to") return `${conditions.join(joiner)}`;
|
880
|
+
return conditions.length === 1 ? conditions[0] : options.fieldSlug ? `(${conditions.join(joiner)})` : conditions.join(joiner);
|
881
|
+
}
|
882
|
+
if (Array.isArray(value)) {
|
883
|
+
const conditions = value.map(
|
884
|
+
(filter) => composeConditions(models, model, statementParams, instructionName, filter, options)
|
885
|
+
);
|
886
|
+
return conditions.join(" OR ");
|
887
|
+
}
|
888
|
+
throw new RoninError({
|
889
|
+
message: `The \`with\` instruction must not contain an empty field. The following fields are empty: \`${options.fieldSlug}\`. If you meant to query by an empty field, try using \`null\` instead.`,
|
890
|
+
code: "INVALID_WITH_VALUE",
|
891
|
+
queries: null
|
892
|
+
});
|
893
|
+
};
|
894
|
+
var formatIdentifiers = ({ identifiers }, queryInstructions) => {
|
895
|
+
if (!queryInstructions) return queryInstructions;
|
896
|
+
const type = "with" in queryInstructions ? "with" : null;
|
897
|
+
if (!type) return queryInstructions;
|
898
|
+
const nestedInstructions = queryInstructions[type];
|
899
|
+
if (!nestedInstructions || Array.isArray(nestedInstructions))
|
900
|
+
return queryInstructions;
|
901
|
+
const newNestedInstructions = { ...nestedInstructions };
|
902
|
+
for (const oldKey of Object.keys(newNestedInstructions)) {
|
903
|
+
if (oldKey !== "nameIdentifier" && oldKey !== "slugIdentifier") continue;
|
904
|
+
const identifierName = oldKey === "nameIdentifier" ? "name" : "slug";
|
905
|
+
const value = newNestedInstructions[oldKey];
|
906
|
+
const newKey = identifiers[identifierName];
|
907
|
+
newNestedInstructions[newKey] = value;
|
908
|
+
delete newNestedInstructions[oldKey];
|
909
|
+
}
|
910
|
+
return {
|
911
|
+
...queryInstructions,
|
912
|
+
[type]: newNestedInstructions
|
913
|
+
};
|
914
|
+
};
|
915
|
+
|
916
|
+
// src/instructions/with.ts
|
917
|
+
var getMatcher = (value, negative) => {
|
918
|
+
if (negative) {
|
919
|
+
if (value === null) return "IS NOT";
|
920
|
+
return "!=";
|
921
|
+
}
|
922
|
+
if (value === null) return "IS";
|
923
|
+
return "=";
|
924
|
+
};
|
925
|
+
var WITH_CONDITIONS = {
|
926
|
+
being: (value) => [getMatcher(value, false), value],
|
927
|
+
notBeing: (value) => [getMatcher(value, true), value],
|
928
|
+
startingWith: (value) => ["LIKE", `${value}%`],
|
929
|
+
notStartingWith: (value) => ["NOT LIKE", `${value}%`],
|
930
|
+
endingWith: (value) => ["LIKE", `%${value}`],
|
931
|
+
notEndingWith: (value) => ["NOT LIKE", `%${value}`],
|
932
|
+
containing: (value) => ["LIKE", `%${value}%`],
|
933
|
+
notContaining: (value) => ["NOT LIKE", `%${value}%`],
|
934
|
+
greaterThan: (value) => [">", value],
|
935
|
+
greaterOrEqual: (value) => [">=", value],
|
936
|
+
lessThan: (value) => ["<", value],
|
937
|
+
lessOrEqual: (value) => ["<=", value]
|
938
|
+
};
|
939
|
+
var handleWith = (models, model, statementParams, instruction, parentModel) => {
|
940
|
+
const subStatement = composeConditions(
|
941
|
+
models,
|
942
|
+
model,
|
943
|
+
statementParams,
|
944
|
+
"with",
|
945
|
+
instruction,
|
946
|
+
{ parentModel }
|
947
|
+
);
|
948
|
+
return `(${subStatement})`;
|
949
|
+
};
|
950
|
+
|
951
|
+
// node_modules/title/dist/esm/lower-case.js
|
952
|
+
var conjunctions = [
|
953
|
+
"for",
|
954
|
+
"and",
|
955
|
+
"nor",
|
956
|
+
"but",
|
957
|
+
"or",
|
958
|
+
"yet",
|
959
|
+
"so"
|
960
|
+
];
|
961
|
+
var articles = [
|
962
|
+
"a",
|
963
|
+
"an",
|
964
|
+
"the"
|
965
|
+
];
|
966
|
+
var prepositions = [
|
967
|
+
"aboard",
|
968
|
+
"about",
|
969
|
+
"above",
|
970
|
+
"across",
|
971
|
+
"after",
|
972
|
+
"against",
|
973
|
+
"along",
|
974
|
+
"amid",
|
975
|
+
"among",
|
976
|
+
"anti",
|
977
|
+
"around",
|
978
|
+
"as",
|
979
|
+
"at",
|
980
|
+
"before",
|
981
|
+
"behind",
|
982
|
+
"below",
|
983
|
+
"beneath",
|
984
|
+
"beside",
|
985
|
+
"besides",
|
986
|
+
"between",
|
987
|
+
"beyond",
|
988
|
+
"but",
|
989
|
+
"by",
|
990
|
+
"concerning",
|
991
|
+
"considering",
|
992
|
+
"despite",
|
993
|
+
"down",
|
994
|
+
"during",
|
995
|
+
"except",
|
996
|
+
"excepting",
|
997
|
+
"excluding",
|
998
|
+
"following",
|
999
|
+
"for",
|
1000
|
+
"from",
|
1001
|
+
"in",
|
1002
|
+
"inside",
|
1003
|
+
"into",
|
1004
|
+
"like",
|
1005
|
+
"minus",
|
1006
|
+
"near",
|
1007
|
+
"of",
|
1008
|
+
"off",
|
1009
|
+
"on",
|
1010
|
+
"onto",
|
1011
|
+
"opposite",
|
1012
|
+
"over",
|
1013
|
+
"past",
|
1014
|
+
"per",
|
1015
|
+
"plus",
|
1016
|
+
"regarding",
|
1017
|
+
"round",
|
1018
|
+
"save",
|
1019
|
+
"since",
|
1020
|
+
"than",
|
1021
|
+
"through",
|
1022
|
+
"to",
|
1023
|
+
"toward",
|
1024
|
+
"towards",
|
1025
|
+
"under",
|
1026
|
+
"underneath",
|
1027
|
+
"unlike",
|
1028
|
+
"until",
|
1029
|
+
"up",
|
1030
|
+
"upon",
|
1031
|
+
"versus",
|
1032
|
+
"via",
|
1033
|
+
"with",
|
1034
|
+
"within",
|
1035
|
+
"without"
|
1036
|
+
];
|
1037
|
+
var lowerCase = /* @__PURE__ */ new Set([
|
1038
|
+
...conjunctions,
|
1039
|
+
...articles,
|
1040
|
+
...prepositions
|
1041
|
+
]);
|
1042
|
+
|
1043
|
+
// node_modules/title/dist/esm/specials.js
|
1044
|
+
var specials = [
|
1045
|
+
"ZEIT",
|
1046
|
+
"ZEIT Inc.",
|
1047
|
+
"Vercel",
|
1048
|
+
"Vercel Inc.",
|
1049
|
+
"CLI",
|
1050
|
+
"API",
|
1051
|
+
"HTTP",
|
1052
|
+
"HTTPS",
|
1053
|
+
"JSX",
|
1054
|
+
"DNS",
|
1055
|
+
"URL",
|
1056
|
+
"now.sh",
|
1057
|
+
"now.json",
|
1058
|
+
"vercel.app",
|
1059
|
+
"vercel.json",
|
1060
|
+
"CI",
|
1061
|
+
"CD",
|
1062
|
+
"CDN",
|
1063
|
+
"package.json",
|
1064
|
+
"package.lock",
|
1065
|
+
"yarn.lock",
|
1066
|
+
"GitHub",
|
1067
|
+
"GitLab",
|
1068
|
+
"CSS",
|
1069
|
+
"Sass",
|
1070
|
+
"JS",
|
1071
|
+
"JavaScript",
|
1072
|
+
"TypeScript",
|
1073
|
+
"HTML",
|
1074
|
+
"WordPress",
|
1075
|
+
"Next.js",
|
1076
|
+
"Node.js",
|
1077
|
+
"Webpack",
|
1078
|
+
"Docker",
|
1079
|
+
"Bash",
|
1080
|
+
"Kubernetes",
|
1081
|
+
"SWR",
|
1082
|
+
"TinaCMS",
|
1083
|
+
"UI",
|
1084
|
+
"UX",
|
1085
|
+
"TS",
|
1086
|
+
"TSX",
|
1087
|
+
"iPhone",
|
1088
|
+
"iPad",
|
1089
|
+
"watchOS",
|
1090
|
+
"iOS",
|
1091
|
+
"iPadOS",
|
1092
|
+
"macOS",
|
1093
|
+
"PHP",
|
1094
|
+
"composer.json",
|
1095
|
+
"composer.lock",
|
1096
|
+
"CMS",
|
1097
|
+
"SQL",
|
1098
|
+
"C",
|
1099
|
+
"C#",
|
1100
|
+
"GraphQL",
|
1101
|
+
"GraphiQL",
|
1102
|
+
"JWT",
|
1103
|
+
"JWTs"
|
1104
|
+
];
|
1105
|
+
|
1106
|
+
// node_modules/title/dist/esm/index.js
|
1107
|
+
var word = `[^\\s'\u2019\\(\\)!?;:"-]`;
|
1108
|
+
var regex = new RegExp(`(?:(?:(\\s?(?:^|[.\\(\\)!?;:"-])\\s*)(${word}))|(${word}))(${word}*[\u2019']*${word}*)`, "g");
|
1109
|
+
var convertToRegExp = (specials2) => specials2.map((s) => [new RegExp(`\\b${s}\\b`, "gi"), s]);
|
1110
|
+
function parseMatch(match) {
|
1111
|
+
const firstCharacter = match[0];
|
1112
|
+
if (/\s/.test(firstCharacter)) {
|
1113
|
+
return match.slice(1);
|
1114
|
+
}
|
1115
|
+
if (/[\(\)]/.test(firstCharacter)) {
|
1116
|
+
return null;
|
1174
1117
|
}
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1178
|
-
|
1179
|
-
|
1180
|
-
const
|
1181
|
-
|
1182
|
-
|
1183
|
-
if (oldSystemModel.system?.associationSlug) {
|
1184
|
-
const oldFieldIndex = modelBeforeUpdate?.fields.findIndex((item) => {
|
1185
|
-
return item.slug === newSystemModel.system?.associationSlug;
|
1186
|
-
});
|
1187
|
-
const newFieldIndex = existingModel.fields.findIndex((item) => {
|
1188
|
-
return item.slug === oldSystemModel.system?.associationSlug;
|
1189
|
-
});
|
1190
|
-
conditions.push(oldFieldIndex === newFieldIndex);
|
1191
|
-
}
|
1192
|
-
return conditions.every((condition) => condition === true);
|
1193
|
-
};
|
1194
|
-
for (const systemModel of currentSystemModels) {
|
1195
|
-
const exists = newSystemModels.find(matchSystemModels.bind(null, systemModel));
|
1196
|
-
if (exists) {
|
1197
|
-
if (exists.slug !== systemModel.slug) {
|
1198
|
-
handleSystemModel(models, dependencyStatements, "alter", systemModel, exists);
|
1199
|
-
}
|
1200
|
-
continue;
|
1118
|
+
return match;
|
1119
|
+
}
|
1120
|
+
var src_default = (str, options = {}) => {
|
1121
|
+
str = str.toLowerCase().replace(regex, (m, lead = "", forced, lower, rest, offset, string) => {
|
1122
|
+
const isLastWord = m.length + offset >= string.length;
|
1123
|
+
const parsedMatch = parseMatch(m);
|
1124
|
+
if (!parsedMatch) {
|
1125
|
+
return m;
|
1201
1126
|
}
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
if (exists) continue;
|
1207
|
-
handleSystemModel(models, dependencyStatements, "create", systemModel);
|
1208
|
-
}
|
1209
|
-
return {
|
1210
|
-
set: {
|
1211
|
-
model: {
|
1212
|
-
with: { slug: modelSlug },
|
1213
|
-
to: {
|
1214
|
-
[pluralType]: { [QUERY_SYMBOLS.EXPRESSION]: json }
|
1215
|
-
}
|
1127
|
+
if (!forced) {
|
1128
|
+
const fullLower = lower + rest;
|
1129
|
+
if (lowerCase.has(fullLower) && !isLastWord) {
|
1130
|
+
return parsedMatch;
|
1216
1131
|
}
|
1217
1132
|
}
|
1218
|
-
|
1219
|
-
};
|
1220
|
-
|
1221
|
-
// src/utils/pagination.ts
|
1222
|
-
var CURSOR_SEPARATOR = ",";
|
1223
|
-
var CURSOR_NULL_PLACEHOLDER = "RONIN_NULL";
|
1224
|
-
var generatePaginationCursor = (model, orderedBy, record) => {
|
1225
|
-
const { ascending = [], descending = [] } = orderedBy || {};
|
1226
|
-
const keys = [...ascending, ...descending];
|
1227
|
-
if (keys.length === 0) keys.push("ronin.createdAt");
|
1228
|
-
const cursors = keys.map((fieldSlug) => {
|
1229
|
-
const property = getProperty(record, fieldSlug);
|
1230
|
-
if (property === null || property === void 0) return CURSOR_NULL_PLACEHOLDER;
|
1231
|
-
const { field } = getFieldFromModel(model, fieldSlug, {
|
1232
|
-
instructionName: "orderedBy"
|
1233
|
-
});
|
1234
|
-
if (field.type === "date") return new Date(property).getTime();
|
1235
|
-
return property;
|
1133
|
+
return lead + (lower || forced).toUpperCase() + rest;
|
1236
1134
|
});
|
1237
|
-
|
1135
|
+
const customSpecials = options.special || [];
|
1136
|
+
const replace = [...specials, ...customSpecials];
|
1137
|
+
const replaceRegExp = convertToRegExp(replace);
|
1138
|
+
replaceRegExp.forEach(([pattern, s]) => {
|
1139
|
+
str = str.replace(pattern, s);
|
1140
|
+
});
|
1141
|
+
return str;
|
1238
1142
|
};
|
1239
1143
|
|
1240
|
-
// src/
|
1241
|
-
var
|
1242
|
-
|
1243
|
-
|
1244
|
-
|
1245
|
-
|
1246
|
-
|
1247
|
-
|
1248
|
-
|
1249
|
-
|
1250
|
-
|
1251
|
-
code: "MUTUALLY_EXCLUSIVE_INSTRUCTIONS"
|
1252
|
-
});
|
1253
|
-
}
|
1254
|
-
if (!instructions.limitedTo) {
|
1255
|
-
let message = "When providing a pagination cursor in the `before` or `after`";
|
1256
|
-
message += " instruction, a `limitedTo` instruction must be provided as well, to";
|
1257
|
-
message += " define the page size.";
|
1258
|
-
throw new RoninError({
|
1259
|
-
message,
|
1260
|
-
code: "MISSING_INSTRUCTION"
|
1261
|
-
});
|
1144
|
+
// src/model/defaults.ts
|
1145
|
+
var slugToName = (slug) => {
|
1146
|
+
const name = slug.replace(/([a-z])([A-Z])/g, "$1 $2");
|
1147
|
+
return src_default(name);
|
1148
|
+
};
|
1149
|
+
var VOWELS = ["a", "e", "i", "o", "u"];
|
1150
|
+
var pluralize = (word2) => {
|
1151
|
+
const lastLetter = word2.slice(-1).toLowerCase();
|
1152
|
+
const secondLastLetter = word2.slice(-2, -1).toLowerCase();
|
1153
|
+
if (lastLetter === "y" && !VOWELS.includes(secondLastLetter)) {
|
1154
|
+
return `${word2.slice(0, -1)}ies`;
|
1262
1155
|
}
|
1263
|
-
|
1264
|
-
|
1265
|
-
const chunks = (instructions.before || instructions.after).toString().split(CURSOR_SEPARATOR).map(decodeURIComponent);
|
1266
|
-
const keys = [...ascending, ...descending];
|
1267
|
-
const values = keys.map((key, index) => {
|
1268
|
-
const value = chunks[index];
|
1269
|
-
if (value === CURSOR_NULL_PLACEHOLDER) {
|
1270
|
-
return "NULL";
|
1271
|
-
}
|
1272
|
-
const { field } = getFieldFromModel(model, key, {
|
1273
|
-
instructionName: "orderedBy"
|
1274
|
-
});
|
1275
|
-
if (field.type === "boolean") {
|
1276
|
-
return prepareStatementValue(statementParams, value === "true");
|
1277
|
-
}
|
1278
|
-
if (field.type === "number") {
|
1279
|
-
return prepareStatementValue(statementParams, Number.parseInt(value));
|
1280
|
-
}
|
1281
|
-
if (field.type === "date") {
|
1282
|
-
return `'${new Date(Number.parseInt(value)).toJSON()}'`;
|
1283
|
-
}
|
1284
|
-
return prepareStatementValue(statementParams, value);
|
1285
|
-
});
|
1286
|
-
const compareOperators = [
|
1287
|
-
// Reverse the comparison operators if we're querying for records before.
|
1288
|
-
...new Array(ascending.length).fill(instructions.before ? "<" : ">"),
|
1289
|
-
...new Array(descending.length).fill(instructions.before ? ">" : "<")
|
1290
|
-
];
|
1291
|
-
const conditions = new Array();
|
1292
|
-
for (let i = 0; i < keys.length; i++) {
|
1293
|
-
if (values[i] === "NULL" && compareOperators[i] === "<") {
|
1294
|
-
continue;
|
1295
|
-
}
|
1296
|
-
const condition = new Array();
|
1297
|
-
for (let j = 0; j <= i; j++) {
|
1298
|
-
const key = keys[j];
|
1299
|
-
const value = values[j];
|
1300
|
-
let { field, fieldSelector } = getFieldFromModel(model, key, {
|
1301
|
-
instructionName: "orderedBy"
|
1302
|
-
});
|
1303
|
-
if (j === i) {
|
1304
|
-
const closingParentheses = ")".repeat(condition.length);
|
1305
|
-
const operator = value === "NULL" ? "IS NOT" : compareOperators[j];
|
1306
|
-
const caseInsensitiveStatement = value !== "NULL" && field.type === "string" ? " COLLATE NOCASE" : "";
|
1307
|
-
if (value !== "NULL" && operator === "<" && !["ronin.createdAt", "ronin.updatedAt"].includes(key)) {
|
1308
|
-
fieldSelector = `IFNULL(${fieldSelector}, -1e999)`;
|
1309
|
-
}
|
1310
|
-
condition.push(
|
1311
|
-
`(${fieldSelector} ${operator} ${value}${caseInsensitiveStatement})${closingParentheses}`
|
1312
|
-
);
|
1313
|
-
} else {
|
1314
|
-
const operator = value === "NULL" ? "IS" : "=";
|
1315
|
-
condition.push(`(${fieldSelector} ${operator} ${value} AND`);
|
1316
|
-
}
|
1317
|
-
}
|
1318
|
-
conditions.push(condition.join(" "));
|
1156
|
+
if (lastLetter === "s" || word2.slice(-2).toLowerCase() === "ch" || word2.slice(-2).toLowerCase() === "sh" || word2.slice(-2).toLowerCase() === "ex") {
|
1157
|
+
return `${word2}es`;
|
1319
1158
|
}
|
1320
|
-
return `${
|
1159
|
+
return `${word2}s`;
|
1160
|
+
};
|
1161
|
+
var modelAttributes = [
|
1162
|
+
["pluralSlug", "slug", pluralize],
|
1163
|
+
["name", "slug", slugToName],
|
1164
|
+
["pluralName", "pluralSlug", slugToName],
|
1165
|
+
["idPrefix", "slug", (slug) => slug.slice(0, 3)],
|
1166
|
+
["table", "pluralSlug", convertToSnakeCase]
|
1167
|
+
];
|
1168
|
+
var getModelIdentifier = () => {
|
1169
|
+
return `mod_${Array.from(crypto.getRandomValues(new Uint8Array(12))).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 16).toLowerCase()}`;
|
1321
1170
|
};
|
1322
|
-
|
1323
|
-
|
1324
|
-
|
1325
|
-
|
1326
|
-
|
1327
|
-
|
1328
|
-
|
1329
|
-
|
1330
|
-
|
1331
|
-
|
1332
|
-
|
1333
|
-
|
1334
|
-
|
1335
|
-
}
|
1336
|
-
const replacedForFilter = structuredClone(preset.instructions);
|
1337
|
-
if (arg !== null) {
|
1338
|
-
findInObject(
|
1339
|
-
replacedForFilter,
|
1340
|
-
QUERY_SYMBOLS.VALUE,
|
1341
|
-
(match) => match.replace(QUERY_SYMBOLS.VALUE, arg)
|
1171
|
+
var addDefaultModelAttributes = (model, isNew) => {
|
1172
|
+
const copiedModel = { ...model };
|
1173
|
+
if (isNew && !copiedModel.id) copiedModel.id = getModelIdentifier();
|
1174
|
+
for (const [setting, base, generator] of modelAttributes) {
|
1175
|
+
if (copiedModel[setting] || !copiedModel[base]) continue;
|
1176
|
+
copiedModel[setting] = generator(copiedModel[base]);
|
1177
|
+
}
|
1178
|
+
const newFields = copiedModel.fields || [];
|
1179
|
+
if (isNew || newFields.length > 0) {
|
1180
|
+
if (!copiedModel.identifiers) copiedModel.identifiers = {};
|
1181
|
+
if (!copiedModel.identifiers.name) {
|
1182
|
+
const suitableField = newFields.find(
|
1183
|
+
(field) => field.type === "string" && field.required === true && ["name"].includes(field.slug)
|
1342
1184
|
);
|
1185
|
+
copiedModel.identifiers.name = suitableField?.slug || "id";
|
1343
1186
|
}
|
1344
|
-
|
1345
|
-
|
1346
|
-
|
1347
|
-
|
1348
|
-
|
1349
|
-
let newValue;
|
1350
|
-
if (Array.isArray(currentValue)) {
|
1351
|
-
newValue = [
|
1352
|
-
...replacedForFilter[instructionName],
|
1353
|
-
...currentValue
|
1354
|
-
];
|
1355
|
-
} else if (isObject(currentValue)) {
|
1356
|
-
newValue = {
|
1357
|
-
...replacedForFilter[instructionName],
|
1358
|
-
...currentValue
|
1359
|
-
};
|
1360
|
-
}
|
1361
|
-
Object.assign(instructions, { [instructionName]: newValue });
|
1362
|
-
continue;
|
1363
|
-
}
|
1364
|
-
Object.assign(instructions, {
|
1365
|
-
[instructionName]: replacedForFilter[instructionName]
|
1366
|
-
});
|
1187
|
+
if (!copiedModel.identifiers.slug) {
|
1188
|
+
const suitableField = newFields.find(
|
1189
|
+
(field) => field.type === "string" && field.unique === true && field.required === true && ["slug", "handle"].includes(field.slug)
|
1190
|
+
);
|
1191
|
+
copiedModel.identifiers.slug = suitableField?.slug || "id";
|
1367
1192
|
}
|
1368
1193
|
}
|
1369
|
-
return
|
1194
|
+
return copiedModel;
|
1370
1195
|
};
|
1371
|
-
|
1372
|
-
|
1373
|
-
|
1374
|
-
|
1375
|
-
|
1376
|
-
|
1377
|
-
|
1378
|
-
|
1379
|
-
|
1380
|
-
|
1381
|
-
|
1382
|
-
|
1383
|
-
|
1384
|
-
|
1385
|
-
|
1386
|
-
|
1387
|
-
|
1388
|
-
|
1389
|
-
|
1390
|
-
|
1391
|
-
|
1392
|
-
|
1393
|
-
|
1394
|
-
|
1395
|
-
|
1396
|
-
|
1397
|
-
|
1398
|
-
|
1196
|
+
var addDefaultModelFields = (model, isNew) => {
|
1197
|
+
const copiedModel = { ...model };
|
1198
|
+
const newFields = copiedModel.fields || [];
|
1199
|
+
if (isNew || newFields.length > 0) {
|
1200
|
+
copiedModel.fields = [...getSystemFields(copiedModel.idPrefix), ...newFields];
|
1201
|
+
}
|
1202
|
+
return copiedModel;
|
1203
|
+
};
|
1204
|
+
var addDefaultModelPresets = (list, model) => {
|
1205
|
+
const defaultPresets = [];
|
1206
|
+
for (const field of model.fields || []) {
|
1207
|
+
if (field.type === "link" && !field.slug.startsWith("ronin.")) {
|
1208
|
+
const relatedModel = getModelBySlug(list, field.target);
|
1209
|
+
if (field.kind === "many") continue;
|
1210
|
+
defaultPresets.push({
|
1211
|
+
instructions: {
|
1212
|
+
including: {
|
1213
|
+
[field.slug]: {
|
1214
|
+
[QUERY_SYMBOLS.QUERY]: {
|
1215
|
+
get: {
|
1216
|
+
[relatedModel.slug]: {
|
1217
|
+
with: {
|
1218
|
+
// Compare the `id` field of the related model to the link field on
|
1219
|
+
// the root model (`field.slug`).
|
1220
|
+
id: {
|
1221
|
+
[QUERY_SYMBOLS.EXPRESSION]: `${QUERY_SYMBOLS.FIELD_PARENT}${field.slug}`
|
1222
|
+
}
|
1223
|
+
}
|
1224
|
+
}
|
1225
|
+
}
|
1226
|
+
}
|
1227
|
+
}
|
1399
1228
|
}
|
1400
1229
|
},
|
1401
|
-
|
1402
|
-
|
1403
|
-
);
|
1404
|
-
relatedTableSelector = `(${subSelect.main.statement})`;
|
1230
|
+
slug: field.slug
|
1231
|
+
});
|
1405
1232
|
}
|
1406
|
-
|
1407
|
-
|
1408
|
-
|
1409
|
-
|
1410
|
-
|
1411
|
-
|
1412
|
-
|
1413
|
-
|
1414
|
-
|
1415
|
-
|
1416
|
-
|
1233
|
+
}
|
1234
|
+
const childModels = list.map((subModel) => {
|
1235
|
+
const field = subModel.fields?.find((field2) => {
|
1236
|
+
return field2.type === "link" && field2.target === model.slug;
|
1237
|
+
});
|
1238
|
+
if (!field) return null;
|
1239
|
+
return { model: subModel, field };
|
1240
|
+
}).filter((match) => match !== null);
|
1241
|
+
for (const childMatch of childModels) {
|
1242
|
+
const { model: childModel, field: childField } = childMatch;
|
1243
|
+
const pluralSlug = childModel.pluralSlug;
|
1244
|
+
const presetSlug = childModel.system?.associationSlug || pluralSlug;
|
1245
|
+
defaultPresets.push({
|
1246
|
+
instructions: {
|
1247
|
+
including: {
|
1248
|
+
[presetSlug]: {
|
1249
|
+
[QUERY_SYMBOLS.QUERY]: {
|
1250
|
+
get: {
|
1251
|
+
[pluralSlug]: {
|
1252
|
+
with: {
|
1253
|
+
[childField.slug]: {
|
1254
|
+
[QUERY_SYMBOLS.EXPRESSION]: `${QUERY_SYMBOLS.FIELD_PARENT}id`
|
1255
|
+
}
|
1256
|
+
}
|
1257
|
+
}
|
1258
|
+
}
|
1259
|
+
}
|
1260
|
+
}
|
1417
1261
|
}
|
1418
|
-
|
1419
|
-
|
1420
|
-
}
|
1421
|
-
if (!single) tableSubQuery = `SELECT * FROM "${model.table}" LIMIT 1`;
|
1262
|
+
},
|
1263
|
+
slug: presetSlug
|
1264
|
+
});
|
1422
1265
|
}
|
1423
|
-
|
1266
|
+
if (Object.keys(defaultPresets).length > 0) {
|
1267
|
+
model.presets = [...defaultPresets, ...model.presets || []];
|
1268
|
+
}
|
1269
|
+
return model;
|
1424
1270
|
};
|
1425
1271
|
|
1426
|
-
// src/
|
1427
|
-
var
|
1428
|
-
|
1429
|
-
|
1430
|
-
|
1431
|
-
|
1272
|
+
// src/model/index.ts
|
1273
|
+
var getModelBySlug = (models, slug) => {
|
1274
|
+
const model = models.find((model2) => {
|
1275
|
+
return model2.slug === slug || model2.pluralSlug === slug;
|
1276
|
+
});
|
1277
|
+
if (!model) {
|
1278
|
+
throw new RoninError({
|
1279
|
+
message: `No matching model with either Slug or Plural Slug of "${slug}" could be found.`,
|
1280
|
+
code: "MODEL_NOT_FOUND"
|
1281
|
+
});
|
1282
|
+
}
|
1283
|
+
return model;
|
1432
1284
|
};
|
1433
|
-
|
1434
|
-
|
1435
|
-
|
1436
|
-
|
1437
|
-
|
1438
|
-
|
1439
|
-
|
1440
|
-
|
1441
|
-
|
1442
|
-
|
1443
|
-
|
1285
|
+
var composeAssociationModelSlug = (model, field) => convertToCamelCase(`ronin_link_${model.slug}_${field.slug}`);
|
1286
|
+
var getFieldSelector = (model, field, fieldPath, writing) => {
|
1287
|
+
const symbol = model.tableAlias?.startsWith(QUERY_SYMBOLS.FIELD_PARENT) ? `${model.tableAlias.replace(QUERY_SYMBOLS.FIELD_PARENT, "").slice(0, -1)}.` : "";
|
1288
|
+
const tablePrefix = symbol || (model.tableAlias ? `"${model.tableAlias}".` : "");
|
1289
|
+
if (field.type === "json" && !writing) {
|
1290
|
+
const dotParts = fieldPath.split(".");
|
1291
|
+
const columnName = tablePrefix + dotParts.shift();
|
1292
|
+
const jsonField = dotParts.join(".");
|
1293
|
+
return `json_extract(${columnName}, '$.${jsonField}')`;
|
1294
|
+
}
|
1295
|
+
return `${tablePrefix}"${fieldPath}"`;
|
1296
|
+
};
|
1297
|
+
function getFieldFromModel(model, fieldPath, source, shouldThrow = true) {
|
1298
|
+
const writingField = "instructionName" in source ? source.instructionName === "to" : true;
|
1299
|
+
const errorTarget = "instructionName" in source ? `\`${source.instructionName}\`` : `${source.modelEntityType} "${source.modelEntityName}"`;
|
1300
|
+
const errorPrefix = `Field "${fieldPath}" defined for ${errorTarget}`;
|
1301
|
+
const modelFields = model.fields || [];
|
1302
|
+
let modelField;
|
1303
|
+
if (fieldPath.includes(".")) {
|
1304
|
+
modelField = modelFields.find((field) => field.slug === fieldPath.split(".")[0]);
|
1305
|
+
if (modelField?.type === "json") {
|
1306
|
+
const fieldSelector2 = getFieldSelector(model, modelField, fieldPath, writingField);
|
1307
|
+
return { field: modelField, fieldSelector: fieldSelector2 };
|
1444
1308
|
}
|
1445
|
-
|
1446
|
-
|
1447
|
-
|
1448
|
-
|
1449
|
-
|
1309
|
+
}
|
1310
|
+
modelField = modelFields.find((field) => field.slug === fieldPath);
|
1311
|
+
if (!modelField) {
|
1312
|
+
if (shouldThrow) {
|
1313
|
+
throw new RoninError({
|
1314
|
+
message: `${errorPrefix} does not exist in model "${model.name}".`,
|
1315
|
+
code: "FIELD_NOT_FOUND",
|
1316
|
+
field: fieldPath,
|
1317
|
+
queries: null
|
1318
|
+
});
|
1319
|
+
}
|
1320
|
+
return null;
|
1321
|
+
}
|
1322
|
+
const fieldSelector = getFieldSelector(model, modelField, fieldPath, writingField);
|
1323
|
+
return { field: modelField, fieldSelector };
|
1324
|
+
}
|
1325
|
+
var getSystemFields = (idPrefix = "rec") => [
|
1326
|
+
{
|
1327
|
+
name: "ID",
|
1328
|
+
type: "string",
|
1329
|
+
slug: "id",
|
1330
|
+
defaultValue: {
|
1331
|
+
// Since default values in SQLite cannot rely on other columns, we unfortunately
|
1332
|
+
// cannot rely on the `idPrefix` column here. Instead, we need to inject it directly
|
1333
|
+
// into the expression as a static string.
|
1334
|
+
[QUERY_SYMBOLS.EXPRESSION]: `'${idPrefix}_' || lower(substr(hex(randomblob(12)), 1, 16))`
|
1450
1335
|
}
|
1451
|
-
|
1452
|
-
|
1453
|
-
|
1454
|
-
|
1455
|
-
|
1456
|
-
|
1457
|
-
|
1336
|
+
},
|
1337
|
+
{
|
1338
|
+
name: "RONIN - Locked",
|
1339
|
+
type: "boolean",
|
1340
|
+
slug: "ronin.locked"
|
1341
|
+
},
|
1342
|
+
{
|
1343
|
+
name: "RONIN - Created At",
|
1344
|
+
type: "date",
|
1345
|
+
slug: "ronin.createdAt",
|
1346
|
+
defaultValue: CURRENT_TIME_EXPRESSION
|
1347
|
+
},
|
1348
|
+
{
|
1349
|
+
name: "RONIN - Created By",
|
1350
|
+
type: "string",
|
1351
|
+
slug: "ronin.createdBy"
|
1352
|
+
},
|
1353
|
+
{
|
1354
|
+
name: "RONIN - Updated At",
|
1355
|
+
type: "date",
|
1356
|
+
slug: "ronin.updatedAt",
|
1357
|
+
defaultValue: CURRENT_TIME_EXPRESSION
|
1358
|
+
},
|
1359
|
+
{
|
1360
|
+
name: "RONIN - Updated By",
|
1361
|
+
type: "string",
|
1362
|
+
slug: "ronin.updatedBy"
|
1458
1363
|
}
|
1459
|
-
|
1364
|
+
];
|
1365
|
+
var ROOT_MODEL = {
|
1366
|
+
slug: "model",
|
1367
|
+
identifiers: {
|
1368
|
+
name: "name",
|
1369
|
+
slug: "slug"
|
1370
|
+
},
|
1371
|
+
// This name mimics the `sqlite_schema` table in SQLite.
|
1372
|
+
table: "ronin_schema",
|
1373
|
+
// Indicates that the model was automatically generated by RONIN.
|
1374
|
+
system: { model: "root" },
|
1375
|
+
fields: [
|
1376
|
+
{ slug: "name", type: "string" },
|
1377
|
+
{ slug: "pluralName", type: "string" },
|
1378
|
+
{ slug: "slug", type: "string" },
|
1379
|
+
{ slug: "pluralSlug", type: "string" },
|
1380
|
+
{ slug: "idPrefix", type: "string" },
|
1381
|
+
{ slug: "table", type: "string" },
|
1382
|
+
{ slug: "identifiers.name", type: "string" },
|
1383
|
+
{ slug: "identifiers.slug", type: "string" },
|
1384
|
+
// Providing an empty object as a default value allows us to use `json_insert`
|
1385
|
+
// without needing to fall back to an empty object in the insertion statement,
|
1386
|
+
// which makes the statement shorter.
|
1387
|
+
{ slug: "fields", type: "json", defaultValue: "{}" },
|
1388
|
+
{ slug: "indexes", type: "json", defaultValue: "{}" },
|
1389
|
+
{ slug: "triggers", type: "json", defaultValue: "{}" },
|
1390
|
+
{ slug: "presets", type: "json", defaultValue: "{}" }
|
1391
|
+
]
|
1460
1392
|
};
|
1461
|
-
|
1462
|
-
|
1463
|
-
|
1464
|
-
|
1465
|
-
|
1466
|
-
|
1467
|
-
|
1468
|
-
|
1469
|
-
|
1470
|
-
|
1471
|
-
|
1472
|
-
|
1473
|
-
|
1474
|
-
|
1475
|
-
|
1476
|
-
|
1477
|
-
|
1478
|
-
|
1479
|
-
|
1480
|
-
|
1481
|
-
|
1482
|
-
|
1483
|
-
|
1484
|
-
|
1485
|
-
|
1486
|
-
|
1487
|
-
|
1488
|
-
|
1489
|
-
})
|
1490
|
-
);
|
1491
|
-
for (const field of queryModelFields) {
|
1492
|
-
loadedFields.push({ ...field, parentField: key });
|
1493
|
-
if (expandColumns) {
|
1494
|
-
const newValue2 = parseFieldExpression(
|
1495
|
-
{ ...subQueryModel, tableAlias },
|
1496
|
-
"including",
|
1497
|
-
`${QUERY_SYMBOLS.FIELD}${field.slug}`
|
1498
|
-
);
|
1499
|
-
instructions.including[`${tableAlias}.${field.slug}`] = newValue2;
|
1500
|
-
}
|
1501
|
-
}
|
1502
|
-
continue;
|
1503
|
-
}
|
1504
|
-
let newValue = value;
|
1505
|
-
if (symbol?.type === "expression") {
|
1506
|
-
newValue = `(${parseFieldExpression(model, "including", symbol.value)})`;
|
1507
|
-
} else {
|
1508
|
-
newValue = prepareStatementValue(statementParams, value);
|
1393
|
+
var getSystemModels = (models, model) => {
|
1394
|
+
const addedModels = [];
|
1395
|
+
for (const field of model.fields || []) {
|
1396
|
+
if (field.type === "link" && !field.slug.startsWith("ronin.")) {
|
1397
|
+
const relatedModel = getModelBySlug(models, field.target);
|
1398
|
+
let fieldSlug = relatedModel.slug;
|
1399
|
+
if (field.kind === "many") {
|
1400
|
+
fieldSlug = composeAssociationModelSlug(model, field);
|
1401
|
+
addedModels.push({
|
1402
|
+
pluralSlug: fieldSlug,
|
1403
|
+
slug: fieldSlug,
|
1404
|
+
system: {
|
1405
|
+
model: model.id,
|
1406
|
+
associationSlug: field.slug
|
1407
|
+
},
|
1408
|
+
fields: [
|
1409
|
+
{
|
1410
|
+
slug: "source",
|
1411
|
+
type: "link",
|
1412
|
+
target: model.slug
|
1413
|
+
},
|
1414
|
+
{
|
1415
|
+
slug: "target",
|
1416
|
+
type: "link",
|
1417
|
+
target: relatedModel.slug
|
1418
|
+
}
|
1419
|
+
]
|
1420
|
+
});
|
1509
1421
|
}
|
1510
|
-
instructions.including[key] = newValue;
|
1511
|
-
loadedFields.push({
|
1512
|
-
slug: key,
|
1513
|
-
type: RAW_FIELD_TYPES.includes(typeof value) ? typeof value : "string"
|
1514
|
-
});
|
1515
1422
|
}
|
1516
1423
|
}
|
1517
|
-
|
1518
|
-
|
1424
|
+
return addedModels.map((model2) => addDefaultModelAttributes(model2, true));
|
1425
|
+
};
|
1426
|
+
var typesInSQLite = {
|
1427
|
+
link: "TEXT",
|
1428
|
+
string: "TEXT",
|
1429
|
+
date: "DATETIME",
|
1430
|
+
blob: "TEXT",
|
1431
|
+
boolean: "BOOLEAN",
|
1432
|
+
number: "INTEGER",
|
1433
|
+
json: "TEXT"
|
1434
|
+
};
|
1435
|
+
var getFieldStatement = (models, model, field) => {
|
1436
|
+
let statement = `"${field.slug}" ${typesInSQLite[field.type || "string"]}`;
|
1437
|
+
if (field.slug === "id") statement += " PRIMARY KEY";
|
1438
|
+
if (field.unique === true) statement += " UNIQUE";
|
1439
|
+
if (field.required === true) statement += " NOT NULL";
|
1440
|
+
if (typeof field.defaultValue !== "undefined") {
|
1441
|
+
const symbol = getSymbol(field.defaultValue);
|
1442
|
+
let value = typeof field.defaultValue === "string" ? `'${field.defaultValue}'` : field.defaultValue;
|
1443
|
+
if (symbol) value = `(${parseFieldExpression(model, "to", symbol.value)})`;
|
1444
|
+
statement += ` DEFAULT ${value}`;
|
1519
1445
|
}
|
1520
|
-
if (
|
1521
|
-
|
1522
|
-
const selectedFields = [];
|
1523
|
-
statement = instructions.selecting.map((slug) => {
|
1524
|
-
const { field, fieldSelector } = getFieldFromModel(usableModel, slug, {
|
1525
|
-
instructionName: "selecting"
|
1526
|
-
});
|
1527
|
-
selectedFields.push(field);
|
1528
|
-
return fieldSelector;
|
1529
|
-
}).join(", ");
|
1530
|
-
loadedFields = [...selectedFields, ...loadedFields];
|
1531
|
-
} else {
|
1532
|
-
loadedFields = [
|
1533
|
-
...model.fields.filter(
|
1534
|
-
(field) => !(field.type === "link" && field.kind === "many")
|
1535
|
-
),
|
1536
|
-
...loadedFields
|
1537
|
-
];
|
1446
|
+
if (field.type === "string" && field.collation) {
|
1447
|
+
statement += ` COLLATE ${field.collation}`;
|
1538
1448
|
}
|
1539
|
-
if (
|
1540
|
-
statement += "
|
1541
|
-
statement += Object.entries(instructions.including).map(([key, value]) => `${value} as "${key}"`).join(", ");
|
1449
|
+
if (field.type === "number" && field.increment === true) {
|
1450
|
+
statement += " AUTOINCREMENT";
|
1542
1451
|
}
|
1543
|
-
|
1544
|
-
|
1545
|
-
|
1546
|
-
// src/instructions/to.ts
|
1547
|
-
var handleTo = (models, model, statementParams, queryType, dependencyStatements, instructions, parentModel) => {
|
1548
|
-
const { with: withInstruction, to: toInstruction } = instructions;
|
1549
|
-
const defaultFields = {};
|
1550
|
-
if (queryType === "set" || toInstruction.ronin) {
|
1551
|
-
defaultFields.ronin = {
|
1552
|
-
// If records are being updated, bump their update time.
|
1553
|
-
...queryType === "set" ? { updatedAt: CURRENT_TIME_EXPRESSION } : {},
|
1554
|
-
// Allow for overwriting the default values provided above.
|
1555
|
-
...toInstruction.ronin
|
1556
|
-
};
|
1452
|
+
if (typeof field.check !== "undefined") {
|
1453
|
+
const symbol = getSymbol(field.check);
|
1454
|
+
statement += ` CHECK (${parseFieldExpression(model, "to", symbol?.value)})`;
|
1557
1455
|
}
|
1558
|
-
|
1559
|
-
|
1560
|
-
const
|
1561
|
-
|
1562
|
-
|
1563
|
-
|
1564
|
-
|
1565
|
-
|
1456
|
+
if (typeof field.computedAs !== "undefined") {
|
1457
|
+
const { kind, value } = field.computedAs;
|
1458
|
+
const symbol = getSymbol(value);
|
1459
|
+
statement += ` GENERATED ALWAYS AS (${parseFieldExpression(model, "to", symbol?.value)}) ${kind}`;
|
1460
|
+
}
|
1461
|
+
if (field.type === "link") {
|
1462
|
+
if (field.kind === "many") return null;
|
1463
|
+
const actions = field.actions || {};
|
1464
|
+
const modelList = models.some((item) => item.slug === model.slug) ? models : [...models, model];
|
1465
|
+
const targetTable = getModelBySlug(modelList, field.target).table;
|
1466
|
+
statement += ` REFERENCES ${targetTable}("id")`;
|
1467
|
+
for (const trigger in actions) {
|
1468
|
+
if (!Object.hasOwn(actions, trigger)) continue;
|
1469
|
+
const triggerName = trigger.toUpperCase().slice(2);
|
1470
|
+
const action = actions[trigger];
|
1471
|
+
statement += ` ON ${triggerName} ${action}`;
|
1566
1472
|
}
|
1567
|
-
|
1568
|
-
|
1569
|
-
|
1570
|
-
|
1571
|
-
|
1572
|
-
|
1573
|
-
|
1473
|
+
}
|
1474
|
+
return statement;
|
1475
|
+
};
|
1476
|
+
var PLURAL_MODEL_ENTITIES = {
|
1477
|
+
field: "fields",
|
1478
|
+
index: "indexes",
|
1479
|
+
trigger: "triggers",
|
1480
|
+
preset: "presets"
|
1481
|
+
};
|
1482
|
+
var PLURAL_MODEL_ENTITIES_VALUES = Object.values(PLURAL_MODEL_ENTITIES);
|
1483
|
+
var formatModelEntity = (type, entities) => {
|
1484
|
+
const entries = entities?.map((entity) => {
|
1485
|
+
const { slug, ...rest } = "slug" in entity ? entity : { slug: `${type}Slug`, ...entity };
|
1486
|
+
return [slug, rest];
|
1487
|
+
});
|
1488
|
+
return entries ? Object.fromEntries(entries) : void 0;
|
1489
|
+
};
|
1490
|
+
var handleSystemModel = (models, dependencyStatements, action, systemModel, newModel) => {
|
1491
|
+
const { system: _, ...systemModelClean } = systemModel;
|
1492
|
+
const query = {
|
1493
|
+
[action]: { model: action === "create" ? systemModelClean : systemModelClean.slug }
|
1494
|
+
};
|
1495
|
+
if (action === "alter" && newModel && "alter" in query && query.alter) {
|
1496
|
+
const { system: _2, ...newModelClean } = newModel;
|
1497
|
+
query.alter.to = newModelClean;
|
1498
|
+
}
|
1499
|
+
const statement = compileQueryInput(query, models, []);
|
1500
|
+
dependencyStatements.push(...statement.dependencies);
|
1501
|
+
};
|
1502
|
+
var handleSystemModels = (models, dependencyStatements, previousModel, newModel) => {
|
1503
|
+
const currentSystemModels = models.filter(({ system }) => {
|
1504
|
+
return system?.model === newModel.id;
|
1505
|
+
});
|
1506
|
+
const newSystemModels = getSystemModels(models, newModel);
|
1507
|
+
const matchSystemModels = (oldSystemModel, newSystemModel) => {
|
1508
|
+
const conditions = [
|
1509
|
+
oldSystemModel.system?.model === newSystemModel.system?.model
|
1574
1510
|
];
|
1575
|
-
|
1576
|
-
|
1577
|
-
|
1578
|
-
let statement2 = "";
|
1579
|
-
if (subQuerySelectedFields) {
|
1580
|
-
const columns = subQueryFields.map((field) => {
|
1581
|
-
return getFieldFromModel(model, field, { instructionName: "to" }).fieldSelector;
|
1511
|
+
if (oldSystemModel.system?.associationSlug) {
|
1512
|
+
const oldFieldIndex = previousModel.fields.findIndex((item) => {
|
1513
|
+
return item.slug === newSystemModel.system?.associationSlug;
|
1582
1514
|
});
|
1583
|
-
|
1515
|
+
const newFieldIndex = newModel.fields.findIndex((item) => {
|
1516
|
+
return item.slug === oldSystemModel.system?.associationSlug;
|
1517
|
+
});
|
1518
|
+
conditions.push(oldFieldIndex === newFieldIndex);
|
1584
1519
|
}
|
1585
|
-
|
1586
|
-
|
1587
|
-
|
1588
|
-
|
1589
|
-
|
1590
|
-
|
1591
|
-
|
1592
|
-
const fieldDetails = getFieldFromModel(
|
1593
|
-
model,
|
1594
|
-
fieldSlug,
|
1595
|
-
{ instructionName: "to" },
|
1596
|
-
false
|
1597
|
-
);
|
1598
|
-
if (fieldDetails?.field.type === "link" && fieldDetails.field.kind === "many") {
|
1599
|
-
delete toInstruction[fieldSlug];
|
1600
|
-
const associativeModelSlug = composeAssociationModelSlug(model, fieldDetails.field);
|
1601
|
-
const composeStatement = (subQueryType, value) => {
|
1602
|
-
const source = queryType === "add" ? { id: toInstruction.id } : withInstruction;
|
1603
|
-
const recordDetails = { source };
|
1604
|
-
if (value) recordDetails.target = value;
|
1605
|
-
return compileQueryInput(
|
1606
|
-
{
|
1607
|
-
[subQueryType]: {
|
1608
|
-
[associativeModelSlug]: subQueryType === "add" ? { to: recordDetails } : { with: recordDetails }
|
1609
|
-
}
|
1610
|
-
},
|
1611
|
-
models,
|
1612
|
-
[],
|
1613
|
-
{ returning: false }
|
1614
|
-
).main;
|
1615
|
-
};
|
1616
|
-
if (Array.isArray(fieldValue)) {
|
1617
|
-
dependencyStatements.push(composeStatement("remove"));
|
1618
|
-
for (const record of fieldValue) {
|
1619
|
-
dependencyStatements.push(composeStatement("add", record));
|
1620
|
-
}
|
1621
|
-
} else if (isObject(fieldValue)) {
|
1622
|
-
const value = fieldValue;
|
1623
|
-
for (const recordToAdd of value.containing || []) {
|
1624
|
-
dependencyStatements.push(composeStatement("add", recordToAdd));
|
1625
|
-
}
|
1626
|
-
for (const recordToRemove of value.notContaining || []) {
|
1627
|
-
dependencyStatements.push(composeStatement("remove", recordToRemove));
|
1628
|
-
}
|
1520
|
+
return conditions.every((condition) => condition === true);
|
1521
|
+
};
|
1522
|
+
for (const systemModel of currentSystemModels) {
|
1523
|
+
const exists = newSystemModels.find(matchSystemModels.bind(null, systemModel));
|
1524
|
+
if (exists) {
|
1525
|
+
if (exists.slug !== systemModel.slug) {
|
1526
|
+
handleSystemModel(models, dependencyStatements, "alter", systemModel, exists);
|
1629
1527
|
}
|
1528
|
+
continue;
|
1630
1529
|
}
|
1530
|
+
handleSystemModel(models, dependencyStatements, "drop", systemModel);
|
1631
1531
|
}
|
1632
|
-
|
1633
|
-
|
1634
|
-
|
1635
|
-
|
1636
|
-
if (queryType === "add") {
|
1637
|
-
const deepStatement = composeConditions(
|
1638
|
-
models,
|
1639
|
-
model,
|
1640
|
-
statementParams,
|
1641
|
-
"to",
|
1642
|
-
toInstruction,
|
1643
|
-
{
|
1644
|
-
parentModel,
|
1645
|
-
type: "values"
|
1646
|
-
}
|
1647
|
-
);
|
1648
|
-
statement = `(${statement}) VALUES (${deepStatement})`;
|
1649
|
-
} else if (queryType === "set") {
|
1650
|
-
statement = `SET ${statement}`;
|
1532
|
+
for (const systemModel of newSystemModels) {
|
1533
|
+
const exists = currentSystemModels.find(matchSystemModels.bind(null, systemModel));
|
1534
|
+
if (exists) continue;
|
1535
|
+
handleSystemModel(models, dependencyStatements, "create", systemModel);
|
1651
1536
|
}
|
1652
|
-
return statement;
|
1653
1537
|
};
|
1654
|
-
|
1655
|
-
|
1656
|
-
|
1657
|
-
const
|
1658
|
-
const
|
1659
|
-
|
1660
|
-
|
1661
|
-
|
1662
|
-
|
1663
|
-
)
|
1664
|
-
|
1665
|
-
|
1666
|
-
|
1667
|
-
const { queryType, queryModel, queryInstructions } = parsedQuery;
|
1668
|
-
const model = getModelBySlug(models, queryModel);
|
1669
|
-
const single = queryModel !== model.pluralSlug;
|
1670
|
-
let instructions = formatIdentifiers(model, queryInstructions);
|
1671
|
-
const returning = options?.returning ?? true;
|
1672
|
-
if (instructions && Object.hasOwn(instructions, "for")) {
|
1673
|
-
instructions = handleFor(model, instructions);
|
1674
|
-
}
|
1675
|
-
const { columns, isJoining, loadedFields } = handleSelecting(
|
1676
|
-
models,
|
1677
|
-
model,
|
1678
|
-
statementParams,
|
1679
|
-
{
|
1680
|
-
selecting: instructions?.selecting,
|
1681
|
-
including: instructions?.including
|
1682
|
-
},
|
1683
|
-
options
|
1684
|
-
);
|
1685
|
-
let statement = "";
|
1686
|
-
switch (queryType) {
|
1687
|
-
case "get":
|
1688
|
-
statement += `SELECT ${columns} FROM `;
|
1689
|
-
break;
|
1690
|
-
case "set":
|
1691
|
-
statement += "UPDATE ";
|
1692
|
-
break;
|
1693
|
-
case "add":
|
1694
|
-
statement += "INSERT INTO ";
|
1695
|
-
break;
|
1696
|
-
case "remove":
|
1697
|
-
statement += "DELETE FROM ";
|
1698
|
-
break;
|
1699
|
-
case "count":
|
1700
|
-
statement += `SELECT COUNT(${columns}) FROM `;
|
1701
|
-
break;
|
1538
|
+
var transformMetaQuery = (models, dependencyStatements, statementParams, query) => {
|
1539
|
+
const { queryType } = splitQuery(query);
|
1540
|
+
const subAltering = "alter" in query && query.alter && !("to" in query.alter);
|
1541
|
+
const action = subAltering && query.alter ? Object.keys(query.alter).filter((key) => key !== "model")[0] : queryType;
|
1542
|
+
const actionReadable = action === "create" ? "creating" : action === "alter" ? "altering" : "dropping";
|
1543
|
+
const entity = subAltering && query.alter ? Object.keys(query.alter[action])[0] : "model";
|
1544
|
+
let slug = entity === "model" && action === "create" ? null : query[queryType].model;
|
1545
|
+
let modelSlug = slug;
|
1546
|
+
let jsonValue;
|
1547
|
+
if ("create" in query && query.create) {
|
1548
|
+
const init = query.create.model;
|
1549
|
+
jsonValue = "to" in query.create ? { slug: init, ...query.create.to } : init;
|
1550
|
+
slug = modelSlug = jsonValue.slug;
|
1702
1551
|
}
|
1703
|
-
|
1704
|
-
|
1705
|
-
|
1706
|
-
models,
|
1707
|
-
model,
|
1708
|
-
statementParams,
|
1709
|
-
instructions?.including
|
1710
|
-
);
|
1711
|
-
if (tableSubQuery) {
|
1712
|
-
statement += `(${tableSubQuery}) as ${model.tableAlias} `;
|
1713
|
-
isJoiningMultipleRows = true;
|
1552
|
+
if ("alter" in query && query.alter) {
|
1553
|
+
if ("to" in query.alter) {
|
1554
|
+
jsonValue = query.alter.to;
|
1714
1555
|
} else {
|
1715
|
-
|
1556
|
+
slug = query.alter[action][entity];
|
1557
|
+
if ("create" in query.alter) {
|
1558
|
+
const item = query.alter.create[entity];
|
1559
|
+
slug = item.slug || `${entity}Slug`;
|
1560
|
+
jsonValue = { slug, ...item };
|
1561
|
+
}
|
1562
|
+
if ("alter" in query.alter && query.alter.alter) jsonValue = query.alter.alter.to;
|
1716
1563
|
}
|
1717
|
-
statement += `${including} `;
|
1718
|
-
} else {
|
1719
|
-
statement += `"${model.table}" `;
|
1720
1564
|
}
|
1721
|
-
if (
|
1722
|
-
|
1723
|
-
|
1724
|
-
|
1725
|
-
|
1726
|
-
|
1565
|
+
if (!(modelSlug && slug)) return query;
|
1566
|
+
const model = action === "create" && entity === "model" ? null : getModelBySlug(models, modelSlug);
|
1567
|
+
if (entity === "model") {
|
1568
|
+
let queryTypeDetails = {};
|
1569
|
+
if (action === "create") {
|
1570
|
+
const newModel = jsonValue;
|
1571
|
+
const modelWithAttributes = addDefaultModelAttributes(newModel, true);
|
1572
|
+
const modelWithFields = addDefaultModelFields(modelWithAttributes, true);
|
1573
|
+
const modelWithPresets = addDefaultModelPresets(
|
1574
|
+
[...models, modelWithFields],
|
1575
|
+
modelWithFields
|
1576
|
+
);
|
1577
|
+
modelWithPresets.fields = modelWithPresets.fields.map((field2) => ({
|
1578
|
+
...field2,
|
1579
|
+
// Default field type.
|
1580
|
+
type: field2.type || "string",
|
1581
|
+
// Default field name.
|
1582
|
+
name: field2.name || slugToName(field2.slug)
|
1583
|
+
}));
|
1584
|
+
const columns = modelWithPresets.fields.map((field2) => getFieldStatement(models, modelWithPresets, field2)).filter(Boolean);
|
1585
|
+
const entities = Object.fromEntries(
|
1586
|
+
Object.entries(PLURAL_MODEL_ENTITIES).map(([type, pluralType2]) => {
|
1587
|
+
const list = modelWithPresets[pluralType2];
|
1588
|
+
return [pluralType2, formatModelEntity(type, list)];
|
1589
|
+
})
|
1590
|
+
);
|
1591
|
+
dependencyStatements.push({
|
1592
|
+
statement: `CREATE TABLE "${modelWithPresets.table}" (${columns.join(", ")})`,
|
1593
|
+
params: []
|
1594
|
+
});
|
1595
|
+
models.push(modelWithPresets);
|
1596
|
+
const modelWithObjects = Object.assign({}, modelWithPresets);
|
1597
|
+
for (const entity2 in entities) {
|
1598
|
+
if (!Object.hasOwn(entities, entity2)) continue;
|
1599
|
+
Object.defineProperty(modelWithObjects, entity2, { value: entities[entity2] });
|
1600
|
+
}
|
1601
|
+
queryTypeDetails = { to: modelWithObjects };
|
1602
|
+
getSystemModels(models, modelWithPresets).map((systemModel) => {
|
1603
|
+
return handleSystemModel(models, dependencyStatements, "create", systemModel);
|
1604
|
+
});
|
1605
|
+
}
|
1606
|
+
if (action === "alter" && model) {
|
1607
|
+
const modelBeforeUpdate2 = structuredClone(model);
|
1608
|
+
const newModel = jsonValue;
|
1609
|
+
const modelWithAttributes = addDefaultModelAttributes(newModel, false);
|
1610
|
+
const modelWithFields = addDefaultModelFields(modelWithAttributes, false);
|
1611
|
+
const modelWithPresets = addDefaultModelPresets(models, modelWithFields);
|
1612
|
+
const newTableName = modelWithPresets.table;
|
1613
|
+
if (newTableName) {
|
1614
|
+
dependencyStatements.push({
|
1615
|
+
statement: `ALTER TABLE "${model.table}" RENAME TO "${newTableName}"`,
|
1616
|
+
params: []
|
1617
|
+
});
|
1618
|
+
}
|
1619
|
+
Object.assign(model, modelWithPresets);
|
1620
|
+
queryTypeDetails = {
|
1621
|
+
with: {
|
1622
|
+
slug
|
1623
|
+
},
|
1624
|
+
to: modelWithPresets
|
1625
|
+
};
|
1626
|
+
handleSystemModels(models, dependencyStatements, modelBeforeUpdate2, model);
|
1627
|
+
}
|
1628
|
+
if (action === "drop" && model) {
|
1629
|
+
models.splice(models.indexOf(model), 1);
|
1630
|
+
dependencyStatements.push({ statement: `DROP TABLE "${model.table}"`, params: [] });
|
1631
|
+
queryTypeDetails = { with: { slug } };
|
1632
|
+
models.filter(({ system }) => system?.model === model.id).map((systemModel) => {
|
1633
|
+
return handleSystemModel(models, dependencyStatements, "drop", systemModel);
|
1727
1634
|
});
|
1728
1635
|
}
|
1729
|
-
const
|
1730
|
-
|
1731
|
-
|
1732
|
-
|
1733
|
-
|
1734
|
-
|
1735
|
-
|
1736
|
-
|
1737
|
-
);
|
1738
|
-
statement += `${toStatement} `;
|
1636
|
+
const modelSlug2 = "to" in queryTypeDetails ? queryTypeDetails?.to?.slug : "with" in queryTypeDetails ? queryTypeDetails?.with?.slug : void 0;
|
1637
|
+
if (modelSlug2 === "model") return null;
|
1638
|
+
const queryTypeAction = action === "create" ? "add" : action === "alter" ? "set" : "remove";
|
1639
|
+
return {
|
1640
|
+
[queryTypeAction]: {
|
1641
|
+
model: queryTypeDetails
|
1642
|
+
}
|
1643
|
+
};
|
1739
1644
|
}
|
1740
|
-
const
|
1741
|
-
|
1742
|
-
|
1743
|
-
|
1744
|
-
|
1745
|
-
|
1746
|
-
|
1747
|
-
|
1748
|
-
|
1749
|
-
|
1645
|
+
const modelBeforeUpdate = structuredClone(model);
|
1646
|
+
const existingModel = model;
|
1647
|
+
const pluralType = PLURAL_MODEL_ENTITIES[entity];
|
1648
|
+
const targetEntityIndex = existingModel[pluralType]?.findIndex(
|
1649
|
+
(entity2) => entity2.slug === slug
|
1650
|
+
);
|
1651
|
+
if ((action === "alter" || action === "drop") && (typeof targetEntityIndex === "undefined" || targetEntityIndex === -1)) {
|
1652
|
+
throw new RoninError({
|
1653
|
+
message: `No ${entity} with slug "${slug}" defined in model "${existingModel.name}".`,
|
1654
|
+
code: MODEL_ENTITY_ERROR_CODES[entity]
|
1655
|
+
});
|
1750
1656
|
}
|
1751
|
-
|
1752
|
-
|
1753
|
-
|
1754
|
-
|
1755
|
-
|
1756
|
-
|
1757
|
-
|
1758
|
-
...instructions.orderedBy.descending
|
1759
|
-
].includes("ronin.createdAt")) {
|
1760
|
-
instructions.orderedBy.descending.push("ronin.createdAt");
|
1761
|
-
}
|
1657
|
+
const existingEntity = existingModel[pluralType]?.[targetEntityIndex];
|
1658
|
+
if (action === "create" && existingEntity) {
|
1659
|
+
throw new RoninError({
|
1660
|
+
message: `A ${entity} with the slug "${slug}" already exists.`,
|
1661
|
+
code: "EXISTING_MODEL_ENTITY",
|
1662
|
+
fields: ["slug"]
|
1663
|
+
});
|
1762
1664
|
}
|
1763
|
-
if (
|
1764
|
-
|
1765
|
-
|
1766
|
-
|
1767
|
-
|
1768
|
-
|
1665
|
+
if (entity === "field") {
|
1666
|
+
const statement = `ALTER TABLE "${existingModel.table}"`;
|
1667
|
+
const existingField = existingEntity;
|
1668
|
+
const existingLinkField = existingField?.type === "link" && existingField.kind === "many";
|
1669
|
+
if (action === "create") {
|
1670
|
+
const field2 = jsonValue;
|
1671
|
+
field2.type = field2.type || "string";
|
1672
|
+
field2.name = field2.name || slugToName(field2.slug);
|
1673
|
+
const fieldStatement = getFieldStatement(models, existingModel, field2);
|
1674
|
+
if (fieldStatement) {
|
1675
|
+
dependencyStatements.push({
|
1676
|
+
statement: `${statement} ADD COLUMN ${fieldStatement}`,
|
1677
|
+
params: []
|
1678
|
+
});
|
1679
|
+
}
|
1680
|
+
} else if (action === "alter") {
|
1681
|
+
const field2 = jsonValue;
|
1682
|
+
const newSlug = field2.slug;
|
1683
|
+
if (newSlug) {
|
1684
|
+
field2.name = field2.name || slugToName(field2.slug);
|
1685
|
+
if (!existingLinkField) {
|
1686
|
+
dependencyStatements.push({
|
1687
|
+
statement: `${statement} RENAME COLUMN "${slug}" TO "${newSlug}"`,
|
1688
|
+
params: []
|
1689
|
+
});
|
1690
|
+
}
|
1691
|
+
}
|
1692
|
+
} else if (action === "drop" && !existingLinkField) {
|
1693
|
+
const systemFields = getSystemFields(existingModel.idPrefix);
|
1694
|
+
const isSystemField = systemFields.some((field2) => field2.slug === slug);
|
1695
|
+
if (isSystemField) {
|
1696
|
+
throw new RoninError({
|
1697
|
+
message: `The ${entity} "${slug}" is a system ${entity} and cannot be removed.`,
|
1698
|
+
code: "REQUIRED_MODEL_ENTITY"
|
1699
|
+
});
|
1700
|
+
}
|
1701
|
+
dependencyStatements.push({
|
1702
|
+
statement: `${statement} DROP COLUMN "${slug}"`,
|
1703
|
+
params: []
|
1769
1704
|
});
|
1770
1705
|
}
|
1771
|
-
const beforeAndAfterStatement = handleBeforeOrAfter(model, statementParams, {
|
1772
|
-
before: instructions.before,
|
1773
|
-
after: instructions.after,
|
1774
|
-
with: instructions.with,
|
1775
|
-
orderedBy: instructions.orderedBy,
|
1776
|
-
limitedTo: instructions.limitedTo
|
1777
|
-
});
|
1778
|
-
conditions.push(beforeAndAfterStatement);
|
1779
1706
|
}
|
1780
|
-
|
1781
|
-
|
1782
|
-
|
1783
|
-
|
1784
|
-
|
1707
|
+
const statementAction = action.toUpperCase();
|
1708
|
+
if (entity === "index") {
|
1709
|
+
const index = jsonValue;
|
1710
|
+
const indexName = convertToSnakeCase(slug);
|
1711
|
+
let statement = `${statementAction}${index?.unique ? " UNIQUE" : ""} INDEX "${indexName}"`;
|
1712
|
+
if (action === "create") {
|
1713
|
+
if (!Array.isArray(index.fields) || index.fields.length === 0) {
|
1714
|
+
throw new RoninError({
|
1715
|
+
message: `When ${actionReadable} ${PLURAL_MODEL_ENTITIES[entity]}, at least one field must be provided.`,
|
1716
|
+
code: "INVALID_MODEL_VALUE",
|
1717
|
+
fields: ["fields"]
|
1718
|
+
});
|
1719
|
+
}
|
1720
|
+
const columns = index.fields.map((field2) => {
|
1721
|
+
let fieldSelector = "";
|
1722
|
+
if ("slug" in field2) {
|
1723
|
+
({ fieldSelector } = getFieldFromModel(existingModel, field2.slug, {
|
1724
|
+
modelEntityType: "index",
|
1725
|
+
modelEntityName: indexName
|
1726
|
+
}));
|
1727
|
+
} else if ("expression" in field2) {
|
1728
|
+
fieldSelector = parseFieldExpression(existingModel, "to", field2.expression);
|
1729
|
+
}
|
1730
|
+
if (field2.collation) fieldSelector += ` COLLATE ${field2.collation}`;
|
1731
|
+
if (field2.order) fieldSelector += ` ${field2.order}`;
|
1732
|
+
return fieldSelector;
|
1733
|
+
});
|
1734
|
+
statement += ` ON "${existingModel.table}" (${columns.join(", ")})`;
|
1735
|
+
if (index.filter) {
|
1736
|
+
const withStatement = handleWith(models, existingModel, null, index.filter);
|
1737
|
+
statement += ` WHERE (${withStatement})`;
|
1738
|
+
}
|
1785
1739
|
}
|
1740
|
+
dependencyStatements.push({ statement, params: [] });
|
1786
1741
|
}
|
1787
|
-
if (
|
1788
|
-
const
|
1789
|
-
statement
|
1790
|
-
|
1791
|
-
|
1792
|
-
|
1742
|
+
if (entity === "trigger") {
|
1743
|
+
const triggerName = convertToSnakeCase(slug);
|
1744
|
+
let statement = `${statementAction} TRIGGER "${triggerName}"`;
|
1745
|
+
if (action === "create") {
|
1746
|
+
const trigger = jsonValue;
|
1747
|
+
const statementParts = [`${trigger.when} ${trigger.action}`];
|
1748
|
+
if (trigger.fields) {
|
1749
|
+
if (trigger.action !== "UPDATE") {
|
1750
|
+
throw new RoninError({
|
1751
|
+
message: `When ${actionReadable} ${PLURAL_MODEL_ENTITIES[entity]}, targeting specific fields requires the \`UPDATE\` action.`,
|
1752
|
+
code: "INVALID_MODEL_VALUE",
|
1753
|
+
fields: ["action"]
|
1754
|
+
});
|
1755
|
+
}
|
1756
|
+
const fieldSelectors = trigger.fields.map((field2) => {
|
1757
|
+
return getFieldFromModel(existingModel, field2.slug, {
|
1758
|
+
modelEntityType: "trigger",
|
1759
|
+
modelEntityName: triggerName
|
1760
|
+
}).fieldSelector;
|
1761
|
+
});
|
1762
|
+
statementParts.push(`OF (${fieldSelectors.join(", ")})`);
|
1763
|
+
}
|
1764
|
+
statementParts.push("ON", `"${existingModel.table}"`);
|
1765
|
+
if (trigger.filter || trigger.effects.some((query2) => findInObject(query2, QUERY_SYMBOLS.FIELD))) {
|
1766
|
+
statementParts.push("FOR EACH ROW");
|
1767
|
+
}
|
1768
|
+
if (trigger.filter) {
|
1769
|
+
const tableAlias = trigger.action === "DELETE" ? QUERY_SYMBOLS.FIELD_PARENT_OLD : QUERY_SYMBOLS.FIELD_PARENT_NEW;
|
1770
|
+
const withStatement = handleWith(
|
1771
|
+
models,
|
1772
|
+
{ ...existingModel, tableAlias },
|
1773
|
+
null,
|
1774
|
+
trigger.filter
|
1775
|
+
);
|
1776
|
+
statementParts.push("WHEN", `(${withStatement})`);
|
1777
|
+
}
|
1778
|
+
const effectStatements = trigger.effects.map((effectQuery) => {
|
1779
|
+
return compileQueryInput(effectQuery, models, null, {
|
1780
|
+
returning: false,
|
1781
|
+
parentModel: existingModel
|
1782
|
+
}).main.statement;
|
1783
|
+
});
|
1784
|
+
statementParts.push("BEGIN");
|
1785
|
+
statementParts.push(`${effectStatements.join("; ")};`);
|
1786
|
+
statementParts.push("END");
|
1787
|
+
statement += ` ${statementParts.join(" ")}`;
|
1788
|
+
}
|
1789
|
+
dependencyStatements.push({ statement, params: [] });
|
1793
1790
|
}
|
1794
|
-
|
1795
|
-
|
1791
|
+
const field = `${QUERY_SYMBOLS.FIELD}${pluralType}`;
|
1792
|
+
let json;
|
1793
|
+
switch (action) {
|
1794
|
+
case "create": {
|
1795
|
+
const value = prepareStatementValue(statementParams, jsonValue);
|
1796
|
+
json = `json_insert(${field}, '$.${slug}', ${value})`;
|
1797
|
+
existingModel[pluralType] = [
|
1798
|
+
...existingModel[pluralType] || [],
|
1799
|
+
jsonValue
|
1800
|
+
];
|
1801
|
+
break;
|
1802
|
+
}
|
1803
|
+
case "alter": {
|
1804
|
+
const value = prepareStatementValue(statementParams, jsonValue);
|
1805
|
+
json = `json_set(${field}, '$.${slug}', json_patch(json_extract(${field}, '$.${slug}'), ${value}))`;
|
1806
|
+
const targetEntity = existingModel[pluralType];
|
1807
|
+
Object.assign(targetEntity[targetEntityIndex], jsonValue);
|
1808
|
+
break;
|
1809
|
+
}
|
1810
|
+
case "drop": {
|
1811
|
+
json = `json_remove(${field}, '$.${slug}')`;
|
1812
|
+
const targetEntity = existingModel[pluralType];
|
1813
|
+
targetEntity.splice(targetEntityIndex, 1);
|
1814
|
+
}
|
1796
1815
|
}
|
1797
|
-
|
1798
|
-
statement: statement.trimEnd(),
|
1799
|
-
params: statementParams || []
|
1800
|
-
};
|
1801
|
-
if (returning) mainStatement.returning = true;
|
1816
|
+
handleSystemModels(models, dependencyStatements, modelBeforeUpdate, existingModel);
|
1802
1817
|
return {
|
1803
|
-
|
1804
|
-
|
1805
|
-
|
1818
|
+
set: {
|
1819
|
+
model: {
|
1820
|
+
with: { slug: modelSlug },
|
1821
|
+
to: {
|
1822
|
+
[pluralType]: { [QUERY_SYMBOLS.EXPRESSION]: json }
|
1823
|
+
}
|
1824
|
+
}
|
1825
|
+
}
|
1806
1826
|
};
|
1807
1827
|
};
|
1808
1828
|
|
@@ -1825,21 +1845,25 @@ var Transaction = class {
|
|
1825
1845
|
* @returns The composed SQL statements.
|
1826
1846
|
*/
|
1827
1847
|
compileQueries = (queries, models, options) => {
|
1828
|
-
const
|
1829
|
-
|
1830
|
-
|
1831
|
-
|
1848
|
+
const modelsWithAttributes = [ROOT_MODEL, ...models].map((model) => {
|
1849
|
+
return addDefaultModelAttributes(model, true);
|
1850
|
+
});
|
1851
|
+
const modelsWithFields = [
|
1852
|
+
...modelsWithAttributes.flatMap((model) => {
|
1853
|
+
return getSystemModels(modelsWithAttributes, model);
|
1854
|
+
}),
|
1855
|
+
...modelsWithAttributes
|
1832
1856
|
].map((model) => {
|
1833
1857
|
return addDefaultModelFields(model, true);
|
1834
1858
|
});
|
1835
|
-
const
|
1836
|
-
return addDefaultModelPresets(
|
1859
|
+
const modelsWithPresets = modelsWithFields.map((model) => {
|
1860
|
+
return addDefaultModelPresets(modelsWithFields, model);
|
1837
1861
|
});
|
1838
1862
|
const statements = [];
|
1839
1863
|
for (const query of queries) {
|
1840
1864
|
const result = compileQueryInput(
|
1841
1865
|
query,
|
1842
|
-
|
1866
|
+
modelsWithPresets,
|
1843
1867
|
options?.inlineParams ? null : [],
|
1844
1868
|
{ expandColumns: options?.expandColumns }
|
1845
1869
|
);
|
@@ -1853,7 +1877,7 @@ var Transaction = class {
|
|
1853
1877
|
}))
|
1854
1878
|
);
|
1855
1879
|
}
|
1856
|
-
this.models =
|
1880
|
+
this.models = modelsWithPresets;
|
1857
1881
|
return statements;
|
1858
1882
|
};
|
1859
1883
|
formatRows(fields, rows, single, isMeta) {
|