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