@ronin/compiler 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -0
- package/dist/index.d.ts +4706 -0
- package/dist/index.js +1093 -0
- package/package.json +34 -0
package/dist/index.js
ADDED
@@ -0,0 +1,1093 @@
|
|
1
|
+
// src/utils/index.ts
|
2
|
+
import { init as cuid } from "@paralleldrive/cuid2";
|
3
|
+
var RONIN_SCHEMA_SYMBOLS = {
|
4
|
+
QUERY: "__RONIN_QUERY",
|
5
|
+
FIELD: "__RONIN_FIELD_",
|
6
|
+
VALUE: "__RONIN_VALUE"
|
7
|
+
};
|
8
|
+
var RoninError = class extends Error {
|
9
|
+
code;
|
10
|
+
field;
|
11
|
+
fields;
|
12
|
+
issues;
|
13
|
+
queries;
|
14
|
+
constructor(details) {
|
15
|
+
super(details.message);
|
16
|
+
this.name = "RoninError";
|
17
|
+
this.code = details.code;
|
18
|
+
this.field = details.field;
|
19
|
+
this.fields = details.fields;
|
20
|
+
this.issues = details.issues;
|
21
|
+
this.queries = details.queries || null;
|
22
|
+
}
|
23
|
+
};
|
24
|
+
var SINGLE_QUOTE_REGEX = /'/g;
|
25
|
+
var DOUBLE_QUOTE_REGEX = /"/g;
|
26
|
+
var AMPERSAND_REGEX = /\s*&+\s*/g;
|
27
|
+
var SPECIAL_CHARACTERS_REGEX = /[^\w\s-]+/g;
|
28
|
+
var SPLIT_REGEX = /(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|[\s.\-_]+/;
|
29
|
+
var generateRecordId = (prefix) => `${prefix || "rec"}_${cuid({ length: 16 })()}`;
|
30
|
+
var capitalize = (str) => {
|
31
|
+
if (!str || str.length === 0) return "";
|
32
|
+
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
33
|
+
};
|
34
|
+
var sanitize = (str) => {
|
35
|
+
if (!str || str.length === 0) return "";
|
36
|
+
return str.replace(SINGLE_QUOTE_REGEX, "").replace(DOUBLE_QUOTE_REGEX, "").replace(AMPERSAND_REGEX, " and ").replace(SPECIAL_CHARACTERS_REGEX, " ").trim();
|
37
|
+
};
|
38
|
+
var convertToSnakeCase = (str) => {
|
39
|
+
if (!str || str.length === 0) return "";
|
40
|
+
return sanitize(str).split(SPLIT_REGEX).map((part) => part.toLowerCase()).join("_");
|
41
|
+
};
|
42
|
+
var convertToCamelCase = (str) => {
|
43
|
+
if (!str || str.length === 0) return "";
|
44
|
+
return sanitize(str).split(SPLIT_REGEX).map((part, index) => index === 0 ? part.toLowerCase() : capitalize(part)).join("");
|
45
|
+
};
|
46
|
+
var isObject = (value) => value != null && typeof value === "object" && Array.isArray(value) === false;
|
47
|
+
var replaceInObject = (obj, pattern, replacer) => {
|
48
|
+
for (const key in obj) {
|
49
|
+
const value = obj[key];
|
50
|
+
if (isObject(value)) {
|
51
|
+
replaceInObject(value, pattern, replacer);
|
52
|
+
} else if (typeof value === "string" && value.startsWith(pattern)) {
|
53
|
+
obj[key] = value.replace(pattern, replacer);
|
54
|
+
}
|
55
|
+
}
|
56
|
+
};
|
57
|
+
var flatten = (obj, prefix = "", res = {}) => {
|
58
|
+
for (const key in obj) {
|
59
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
60
|
+
if (typeof obj[key] === "object" && obj[key] !== null) {
|
61
|
+
flatten(obj[key], path, res);
|
62
|
+
} else {
|
63
|
+
res[path] = obj[key];
|
64
|
+
}
|
65
|
+
}
|
66
|
+
return res;
|
67
|
+
};
|
68
|
+
var expand = (obj) => {
|
69
|
+
return Object.entries(obj).reduce((res, [key, val]) => {
|
70
|
+
key.split(".").reduce((acc, part, i, arr) => {
|
71
|
+
acc[part] = i === arr.length - 1 ? val : acc[part] || {};
|
72
|
+
return acc[part];
|
73
|
+
}, res);
|
74
|
+
return res;
|
75
|
+
}, {});
|
76
|
+
};
|
77
|
+
var splitQuery = (query) => {
|
78
|
+
const queryType = Object.keys(query)[0];
|
79
|
+
const querySchema = Object.keys(query[queryType])[0];
|
80
|
+
const queryInstructions = query[queryType][querySchema];
|
81
|
+
return { queryType, querySchema, queryInstructions };
|
82
|
+
};
|
83
|
+
|
84
|
+
// src/utils/schema.ts
|
85
|
+
var getSchemaBySlug = (schemas, slug) => {
|
86
|
+
const schema = schemas.find((schema2) => {
|
87
|
+
return schema2.slug === slug || schema2.pluralSlug === slug;
|
88
|
+
});
|
89
|
+
if (!schema) {
|
90
|
+
throw new RoninError({
|
91
|
+
message: `No matching schema with either Slug or Plural Slug of "${slug}" could be found.`,
|
92
|
+
code: "SCHEMA_NOT_FOUND"
|
93
|
+
});
|
94
|
+
}
|
95
|
+
return schema;
|
96
|
+
};
|
97
|
+
var getTableForSchema = (schema) => {
|
98
|
+
return convertToSnakeCase(schema.pluralSlug);
|
99
|
+
};
|
100
|
+
var getSchemaName = (schema) => {
|
101
|
+
return schema.name || schema.slug;
|
102
|
+
};
|
103
|
+
var composeMetaSchemaSlug = (suffix) => convertToCamelCase(`ronin_${suffix}`);
|
104
|
+
var composeAssociationSchemaSlug = (schema, field) => composeMetaSchemaSlug(`${schema.pluralSlug}_${field.slug}`);
|
105
|
+
var getFieldSelector = (field, fieldPath, rootTable) => {
|
106
|
+
const tablePrefix = rootTable ? `"${rootTable}".` : "";
|
107
|
+
if (field.type === "json") {
|
108
|
+
const dotParts = fieldPath.split(".");
|
109
|
+
const columnName = tablePrefix + dotParts.shift();
|
110
|
+
const jsonField = dotParts.join(".");
|
111
|
+
return `json_extract(${columnName}, '$.${jsonField}')`;
|
112
|
+
}
|
113
|
+
return `${tablePrefix}"${fieldPath}"`;
|
114
|
+
};
|
115
|
+
var getFieldFromSchema = (schema, fieldPath, instructionName, rootTable) => {
|
116
|
+
const errorPrefix = `Field "${fieldPath}" defined for \`${instructionName}\``;
|
117
|
+
const schemaFields = schema.fields || [];
|
118
|
+
let schemaField;
|
119
|
+
if (fieldPath.includes(".")) {
|
120
|
+
schemaField = schemaFields.find((field) => field.slug === fieldPath.split(".")[0]);
|
121
|
+
if (schemaField?.type === "json") {
|
122
|
+
const fieldSelector2 = getFieldSelector(schemaField, fieldPath, rootTable);
|
123
|
+
return { field: schemaField, fieldSelector: fieldSelector2 };
|
124
|
+
}
|
125
|
+
}
|
126
|
+
schemaField = schemaFields.find((field) => field.slug === fieldPath);
|
127
|
+
if (!schemaField) {
|
128
|
+
throw new RoninError({
|
129
|
+
message: `${errorPrefix} does not exist in schema "${getSchemaName(schema)}".`,
|
130
|
+
code: "FIELD_NOT_FOUND",
|
131
|
+
field: fieldPath,
|
132
|
+
queries: null
|
133
|
+
});
|
134
|
+
}
|
135
|
+
const fieldSelector = getFieldSelector(schemaField, fieldPath, rootTable);
|
136
|
+
return { field: schemaField, fieldSelector };
|
137
|
+
};
|
138
|
+
var SYSTEM_FIELDS = [
|
139
|
+
{
|
140
|
+
name: "ID",
|
141
|
+
type: "string",
|
142
|
+
slug: "id",
|
143
|
+
displayAs: "single-line"
|
144
|
+
},
|
145
|
+
{
|
146
|
+
name: "RONIN",
|
147
|
+
type: "group",
|
148
|
+
slug: "ronin"
|
149
|
+
},
|
150
|
+
{
|
151
|
+
name: "RONIN - Locked",
|
152
|
+
type: "boolean",
|
153
|
+
slug: "ronin.locked"
|
154
|
+
},
|
155
|
+
{
|
156
|
+
name: "RONIN - Created At",
|
157
|
+
type: "date",
|
158
|
+
slug: "ronin.createdAt"
|
159
|
+
},
|
160
|
+
{
|
161
|
+
name: "RONIN - Created By",
|
162
|
+
type: "reference",
|
163
|
+
schema: "account",
|
164
|
+
slug: "ronin.createdBy"
|
165
|
+
},
|
166
|
+
{
|
167
|
+
name: "RONIN - Updated At",
|
168
|
+
type: "date",
|
169
|
+
slug: "ronin.updatedAt"
|
170
|
+
},
|
171
|
+
{
|
172
|
+
name: "RONIN - Updated By",
|
173
|
+
type: "reference",
|
174
|
+
schema: "account",
|
175
|
+
slug: "ronin.updatedBy"
|
176
|
+
}
|
177
|
+
];
|
178
|
+
var SYSTEM_SCHEMAS = [
|
179
|
+
{
|
180
|
+
name: "Schema",
|
181
|
+
pluralName: "Schemas",
|
182
|
+
slug: "schema",
|
183
|
+
pluralSlug: "schemas",
|
184
|
+
fields: [
|
185
|
+
...SYSTEM_FIELDS,
|
186
|
+
{ slug: "name", type: "string" },
|
187
|
+
{ slug: "pluralName", type: "string" },
|
188
|
+
{ slug: "slug", type: "string" },
|
189
|
+
{ slug: "pluralSlug", type: "string" },
|
190
|
+
{ slug: "idPrefix", type: "string" },
|
191
|
+
{ slug: "identifiers", type: "group" },
|
192
|
+
{ slug: "identifiers.title", type: "string" },
|
193
|
+
{ slug: "identifiers.slug", type: "string" }
|
194
|
+
]
|
195
|
+
},
|
196
|
+
{
|
197
|
+
name: "Field",
|
198
|
+
pluralName: "Fields",
|
199
|
+
slug: "field",
|
200
|
+
pluralSlug: "fields",
|
201
|
+
fields: [
|
202
|
+
...SYSTEM_FIELDS,
|
203
|
+
{ slug: "name", type: "string" },
|
204
|
+
{ slug: "slug", type: "string" },
|
205
|
+
{ slug: "type", type: "string" },
|
206
|
+
{ slug: "schema", type: "reference", schema: "schema" },
|
207
|
+
{ slug: "required", type: "boolean" },
|
208
|
+
{ slug: "defaultValue", type: "string" },
|
209
|
+
{ slug: "unique", type: "boolean" },
|
210
|
+
{ slug: "autoIncrement", type: "boolean" }
|
211
|
+
]
|
212
|
+
}
|
213
|
+
];
|
214
|
+
var addSystemSchemas = (schemas) => {
|
215
|
+
const list = [...SYSTEM_SCHEMAS, ...schemas].map((schema) => ({ ...schema }));
|
216
|
+
for (const schema of list) {
|
217
|
+
const defaultIncluding = {};
|
218
|
+
for (const field of schema.fields || []) {
|
219
|
+
if (field.type === "reference" && !field.slug.startsWith("ronin.")) {
|
220
|
+
const relatedSchema = getSchemaBySlug(list, field.schema);
|
221
|
+
let fieldSlug = relatedSchema.slug;
|
222
|
+
if (field.kind === "many") {
|
223
|
+
fieldSlug = composeAssociationSchemaSlug(schema, field);
|
224
|
+
list.push({
|
225
|
+
pluralSlug: fieldSlug,
|
226
|
+
slug: fieldSlug,
|
227
|
+
fields: [
|
228
|
+
{
|
229
|
+
slug: "origin",
|
230
|
+
type: "reference",
|
231
|
+
schema: schema.slug
|
232
|
+
},
|
233
|
+
{
|
234
|
+
slug: "target",
|
235
|
+
type: "reference",
|
236
|
+
schema: relatedSchema.slug
|
237
|
+
}
|
238
|
+
]
|
239
|
+
});
|
240
|
+
}
|
241
|
+
defaultIncluding[field.slug] = {
|
242
|
+
get: {
|
243
|
+
[fieldSlug]: {
|
244
|
+
with: {
|
245
|
+
// Compare the `id` field of the related schema to the reference field on
|
246
|
+
// the root schema (`field.slug`).
|
247
|
+
id: `${RONIN_SCHEMA_SYMBOLS.FIELD}${field.slug}`
|
248
|
+
}
|
249
|
+
}
|
250
|
+
}
|
251
|
+
};
|
252
|
+
const relatedSchemaToModify = list.find((schema2) => schema2.slug === field.schema);
|
253
|
+
if (!relatedSchemaToModify) throw new Error("Missing related schema");
|
254
|
+
relatedSchemaToModify.including = {
|
255
|
+
[schema.pluralSlug]: {
|
256
|
+
get: {
|
257
|
+
[schema.pluralSlug]: {
|
258
|
+
with: {
|
259
|
+
[field.slug]: `${RONIN_SCHEMA_SYMBOLS.FIELD}id`
|
260
|
+
}
|
261
|
+
}
|
262
|
+
}
|
263
|
+
},
|
264
|
+
...relatedSchemaToModify.including
|
265
|
+
};
|
266
|
+
}
|
267
|
+
}
|
268
|
+
schema.fields = [...SYSTEM_FIELDS, ...schema.fields || []];
|
269
|
+
schema.including = { ...defaultIncluding, ...schema.including };
|
270
|
+
}
|
271
|
+
return list;
|
272
|
+
};
|
273
|
+
var mappedInstructions = {
|
274
|
+
create: "to",
|
275
|
+
set: "with",
|
276
|
+
drop: "with"
|
277
|
+
};
|
278
|
+
var typesInSQLite = {
|
279
|
+
reference: "TEXT",
|
280
|
+
string: "TEXT",
|
281
|
+
date: "DATETIME",
|
282
|
+
blob: "TEXT",
|
283
|
+
boolean: "BOOLEAN",
|
284
|
+
number: "INTEGER",
|
285
|
+
json: "TEXT"
|
286
|
+
};
|
287
|
+
var getFieldStatement = (field) => {
|
288
|
+
if (field.type === "group") return null;
|
289
|
+
let statement = `"${field.slug}" ${typesInSQLite[field.type]}`;
|
290
|
+
if (field.slug === "id") statement += " PRIMARY KEY";
|
291
|
+
if (field.unique === true) statement += " UNIQUE";
|
292
|
+
if (field.required === true) statement += " NOT NULL";
|
293
|
+
if (typeof field.defaultValue !== "undefined")
|
294
|
+
statement += ` DEFAULT ${field.defaultValue}`;
|
295
|
+
return statement;
|
296
|
+
};
|
297
|
+
var addSchemaQueries = (queryDetails, writeStatements) => {
|
298
|
+
const { queryType, querySchema, queryInstructions } = queryDetails;
|
299
|
+
if (!["create", "set", "drop"].includes(queryType)) return;
|
300
|
+
if (!["schema", "schemas", "field", "fields"].includes(querySchema)) return;
|
301
|
+
const instructionName = mappedInstructions[queryType];
|
302
|
+
const instructionList = queryInstructions[instructionName];
|
303
|
+
const kind = ["schema", "schemas"].includes(querySchema) ? "schemas" : "fields";
|
304
|
+
const instructionTarget = kind === "schemas" ? instructionList : instructionList?.schema;
|
305
|
+
let tableAction = "ALTER";
|
306
|
+
let schemaPluralSlug = null;
|
307
|
+
let queryTypeReadable = null;
|
308
|
+
switch (queryType) {
|
309
|
+
case "create": {
|
310
|
+
if (kind === "schemas") tableAction = "CREATE";
|
311
|
+
schemaPluralSlug = instructionTarget?.pluralSlug;
|
312
|
+
queryTypeReadable = "creating";
|
313
|
+
break;
|
314
|
+
}
|
315
|
+
case "set": {
|
316
|
+
if (kind === "schemas") tableAction = "ALTER";
|
317
|
+
schemaPluralSlug = instructionTarget?.pluralSlug?.being || instructionTarget?.pluralSlug;
|
318
|
+
queryTypeReadable = "updating";
|
319
|
+
break;
|
320
|
+
}
|
321
|
+
case "drop": {
|
322
|
+
if (kind === "schemas") tableAction = "DROP";
|
323
|
+
schemaPluralSlug = instructionTarget?.pluralSlug?.being || instructionTarget?.pluralSlug;
|
324
|
+
queryTypeReadable = "deleting";
|
325
|
+
break;
|
326
|
+
}
|
327
|
+
}
|
328
|
+
if (!schemaPluralSlug) {
|
329
|
+
const field = kind === "schemas" ? "pluralSlug" : "schema.pluralSlug";
|
330
|
+
throw new RoninError({
|
331
|
+
message: `When ${queryTypeReadable} ${kind}, a \`${field}\` field must be provided in the \`${instructionName}\` instruction.`,
|
332
|
+
code: "MISSING_FIELD",
|
333
|
+
fields: [field]
|
334
|
+
});
|
335
|
+
}
|
336
|
+
const table = convertToSnakeCase(schemaPluralSlug);
|
337
|
+
const fields = [...SYSTEM_FIELDS];
|
338
|
+
let statement = `${tableAction} TABLE "${table}"`;
|
339
|
+
if (kind === "schemas") {
|
340
|
+
if (queryType === "create") {
|
341
|
+
const columns = fields.map(getFieldStatement).filter(Boolean);
|
342
|
+
statement += ` (${columns.join(", ")})`;
|
343
|
+
} else if (queryType === "set") {
|
344
|
+
const newSlug = queryInstructions.to?.pluralSlug;
|
345
|
+
if (newSlug) {
|
346
|
+
const newTable = convertToSnakeCase(newSlug);
|
347
|
+
statement += ` RENAME TO "${newTable}"`;
|
348
|
+
}
|
349
|
+
}
|
350
|
+
} else if (kind === "fields") {
|
351
|
+
const fieldSlug = instructionTarget?.slug?.being || instructionList?.slug;
|
352
|
+
if (!fieldSlug) {
|
353
|
+
throw new RoninError({
|
354
|
+
message: `When ${queryTypeReadable} fields, a \`slug\` field must be provided in the \`${instructionName}\` instruction.`,
|
355
|
+
code: "MISSING_FIELD",
|
356
|
+
fields: ["slug"]
|
357
|
+
});
|
358
|
+
}
|
359
|
+
if (queryType === "create") {
|
360
|
+
if (!instructionList.type) {
|
361
|
+
throw new RoninError({
|
362
|
+
message: `When ${queryTypeReadable} fields, a \`type\` field must be provided in the \`to\` instruction.`,
|
363
|
+
code: "MISSING_FIELD",
|
364
|
+
fields: ["type"]
|
365
|
+
});
|
366
|
+
}
|
367
|
+
statement += ` ADD COLUMN ${getFieldStatement(instructionList)}`;
|
368
|
+
} else if (queryType === "set") {
|
369
|
+
const newSlug = queryInstructions.to?.slug;
|
370
|
+
if (newSlug) {
|
371
|
+
statement += ` RENAME COLUMN "${fieldSlug}" TO "${newSlug}"`;
|
372
|
+
}
|
373
|
+
} else if (queryType === "drop") {
|
374
|
+
statement += ` DROP COLUMN "${fieldSlug}"`;
|
375
|
+
}
|
376
|
+
}
|
377
|
+
writeStatements.push(statement);
|
378
|
+
};
|
379
|
+
|
380
|
+
// src/instructions/with.ts
|
381
|
+
var WITH_CONDITIONS = [
|
382
|
+
"being",
|
383
|
+
"notBeing",
|
384
|
+
"startingWith",
|
385
|
+
"notStartingWith",
|
386
|
+
"endingWith",
|
387
|
+
"notEndingWith",
|
388
|
+
"containing",
|
389
|
+
"notContaining",
|
390
|
+
"greaterThan",
|
391
|
+
"greaterOrEqual",
|
392
|
+
"lessThan",
|
393
|
+
"lessOrEqual"
|
394
|
+
];
|
395
|
+
var handleWith = (schemas, schema, statementValues, instruction, rootTable) => {
|
396
|
+
const subStatement = composeConditions(
|
397
|
+
schemas,
|
398
|
+
schema,
|
399
|
+
statementValues,
|
400
|
+
"with",
|
401
|
+
instruction,
|
402
|
+
{ rootTable }
|
403
|
+
);
|
404
|
+
return `(${subStatement})`;
|
405
|
+
};
|
406
|
+
|
407
|
+
// src/utils/statement.ts
|
408
|
+
var prepareStatementValue = (statementValues, value, bindNull = false) => {
|
409
|
+
if (!bindNull && value === null) return "NULL";
|
410
|
+
let formattedValue = value;
|
411
|
+
if (Array.isArray(value) || isObject(value)) {
|
412
|
+
formattedValue = JSON.stringify(value);
|
413
|
+
} else if (typeof value === "boolean") {
|
414
|
+
formattedValue = value ? 1 : 0;
|
415
|
+
}
|
416
|
+
const index = statementValues.push(formattedValue);
|
417
|
+
return `?${index}`;
|
418
|
+
};
|
419
|
+
var composeFieldValues = (schemas, schema, statementValues, instructionName, value, options) => {
|
420
|
+
const { field: schemaField, fieldSelector: selector } = getFieldFromSchema(
|
421
|
+
schema,
|
422
|
+
options.fieldSlug,
|
423
|
+
instructionName,
|
424
|
+
options.rootTable
|
425
|
+
);
|
426
|
+
const isSubQuery = isObject(value) && Object.hasOwn(value, RONIN_SCHEMA_SYMBOLS.QUERY);
|
427
|
+
const collectStatementValue = options.type !== "fields";
|
428
|
+
let conditionSelector = selector;
|
429
|
+
let conditionValue = value;
|
430
|
+
if (isSubQuery && collectStatementValue) {
|
431
|
+
conditionValue = `(${compileQueryInput(
|
432
|
+
value[RONIN_SCHEMA_SYMBOLS.QUERY],
|
433
|
+
schemas,
|
434
|
+
{ statementValues }
|
435
|
+
).readStatement})`;
|
436
|
+
} else if (typeof value === "string" && value.startsWith(RONIN_SCHEMA_SYMBOLS.FIELD)) {
|
437
|
+
conditionSelector = `"${options.customTable}"."${schemaField.slug}"`;
|
438
|
+
conditionValue = `"${options.rootTable}"."${value.replace(RONIN_SCHEMA_SYMBOLS.FIELD, "")}"`;
|
439
|
+
} else if (schemaField.type === "json" && instructionName === "to") {
|
440
|
+
conditionSelector = `"${schemaField.slug}"`;
|
441
|
+
if (collectStatementValue) {
|
442
|
+
const preparedValue = prepareStatementValue(statementValues, value, false);
|
443
|
+
conditionValue = `IIF(${conditionSelector} IS NULL, ${preparedValue}, json_patch(${conditionSelector}, ${preparedValue}))`;
|
444
|
+
}
|
445
|
+
} else if (collectStatementValue) {
|
446
|
+
conditionValue = prepareStatementValue(statementValues, value, false);
|
447
|
+
}
|
448
|
+
if (options.type === "fields") return conditionSelector;
|
449
|
+
if (options.type === "values") return conditionValue;
|
450
|
+
const conditionTypes = {
|
451
|
+
being: [getMatcher(value, false), conditionValue],
|
452
|
+
notBeing: [getMatcher(value, true), conditionValue],
|
453
|
+
startingWith: ["LIKE", `${conditionValue}%`],
|
454
|
+
notStartingWith: ["NOT LIKE", `${conditionValue}%`],
|
455
|
+
endingWith: ["LIKE", `%${conditionValue}`],
|
456
|
+
notEndingWith: ["NOT LIKE", `%${conditionValue}`],
|
457
|
+
containing: ["LIKE", `%${conditionValue}%`],
|
458
|
+
notContaining: ["NOT LIKE", `%${conditionValue}%`],
|
459
|
+
greaterThan: [">", conditionValue],
|
460
|
+
greaterOrEqual: [">=", conditionValue],
|
461
|
+
lessThan: ["<", conditionValue],
|
462
|
+
lessOrEqual: ["<=", conditionValue]
|
463
|
+
};
|
464
|
+
return `${conditionSelector} ${conditionTypes[options.condition || "being"].join(" ")}`;
|
465
|
+
};
|
466
|
+
var composeConditions = (schemas, schema, statementValues, instructionName, value, options) => {
|
467
|
+
const isNested = isObject(value) && Object.keys(value).length > 0;
|
468
|
+
if (isNested && Object.keys(value).every(
|
469
|
+
(key) => WITH_CONDITIONS.includes(key)
|
470
|
+
)) {
|
471
|
+
const conditions = Object.entries(value).map(
|
472
|
+
([conditionType, checkValue]) => composeConditions(schemas, schema, statementValues, instructionName, checkValue, {
|
473
|
+
...options,
|
474
|
+
condition: conditionType
|
475
|
+
})
|
476
|
+
);
|
477
|
+
return conditions.join(" AND ");
|
478
|
+
}
|
479
|
+
if (options.fieldSlug) {
|
480
|
+
const fieldDetails = getFieldFromSchema(
|
481
|
+
schema,
|
482
|
+
options.fieldSlug,
|
483
|
+
instructionName,
|
484
|
+
options.rootTable
|
485
|
+
);
|
486
|
+
const { field: schemaField } = fieldDetails;
|
487
|
+
const consumeJSON = schemaField.type === "json" && instructionName === "to";
|
488
|
+
const isSubQuery = isNested && Object.hasOwn(value, RONIN_SCHEMA_SYMBOLS.QUERY);
|
489
|
+
if (!(isObject(value) || Array.isArray(value)) || isSubQuery || consumeJSON) {
|
490
|
+
return composeFieldValues(
|
491
|
+
schemas,
|
492
|
+
schema,
|
493
|
+
statementValues,
|
494
|
+
instructionName,
|
495
|
+
value,
|
496
|
+
{ ...options, fieldSlug: options.fieldSlug }
|
497
|
+
);
|
498
|
+
}
|
499
|
+
if (schemaField.type === "reference" && isNested) {
|
500
|
+
const keys = Object.keys(value);
|
501
|
+
const values = Object.values(value);
|
502
|
+
let recordTarget;
|
503
|
+
if (keys.length === 1 && keys[0] === "id") {
|
504
|
+
recordTarget = values[0];
|
505
|
+
} else {
|
506
|
+
const relatedSchema = getSchemaBySlug(schemas, schemaField.schema);
|
507
|
+
const subQuery = {
|
508
|
+
get: {
|
509
|
+
[relatedSchema.slug]: {
|
510
|
+
with: value,
|
511
|
+
selecting: ["id"]
|
512
|
+
}
|
513
|
+
}
|
514
|
+
};
|
515
|
+
recordTarget = {
|
516
|
+
[RONIN_SCHEMA_SYMBOLS.QUERY]: subQuery
|
517
|
+
};
|
518
|
+
}
|
519
|
+
return composeConditions(
|
520
|
+
schemas,
|
521
|
+
schema,
|
522
|
+
statementValues,
|
523
|
+
instructionName,
|
524
|
+
recordTarget,
|
525
|
+
options
|
526
|
+
);
|
527
|
+
}
|
528
|
+
}
|
529
|
+
if (isNested) {
|
530
|
+
const conditions = Object.entries(value).map(([field, value2]) => {
|
531
|
+
const nestedFieldSlug = options.fieldSlug ? `${options.fieldSlug}.${field}` : field;
|
532
|
+
return composeConditions(schemas, schema, statementValues, instructionName, value2, {
|
533
|
+
...options,
|
534
|
+
fieldSlug: nestedFieldSlug
|
535
|
+
});
|
536
|
+
});
|
537
|
+
const joiner = instructionName === "to" ? ", " : " AND ";
|
538
|
+
if (instructionName === "to") return `${conditions.join(joiner)}`;
|
539
|
+
return conditions.length === 1 ? conditions[0] : options.fieldSlug ? `(${conditions.join(joiner)})` : conditions.join(joiner);
|
540
|
+
}
|
541
|
+
if (Array.isArray(value)) {
|
542
|
+
const conditions = value.map(
|
543
|
+
(filter) => composeConditions(
|
544
|
+
schemas,
|
545
|
+
schema,
|
546
|
+
statementValues,
|
547
|
+
instructionName,
|
548
|
+
filter,
|
549
|
+
options
|
550
|
+
)
|
551
|
+
);
|
552
|
+
return conditions.join(" OR ");
|
553
|
+
}
|
554
|
+
throw new RoninError({
|
555
|
+
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.`,
|
556
|
+
code: "INVALID_WITH_VALUE",
|
557
|
+
queries: null
|
558
|
+
});
|
559
|
+
};
|
560
|
+
var getMatcher = (value, negative) => {
|
561
|
+
if (negative) {
|
562
|
+
if (value === null) return "IS NOT";
|
563
|
+
return "!=";
|
564
|
+
}
|
565
|
+
if (value === null) return "IS";
|
566
|
+
return "=";
|
567
|
+
};
|
568
|
+
var formatIdentifiers = ({ identifiers }, queryInstructions) => {
|
569
|
+
if (!queryInstructions) return queryInstructions;
|
570
|
+
const type = "with" in queryInstructions ? "with" : null;
|
571
|
+
if (!type) return queryInstructions;
|
572
|
+
const nestedInstructions = queryInstructions[type];
|
573
|
+
if (!nestedInstructions || Array.isArray(nestedInstructions))
|
574
|
+
return queryInstructions;
|
575
|
+
const newNestedInstructions = { ...nestedInstructions };
|
576
|
+
for (const oldKey of Object.keys(newNestedInstructions)) {
|
577
|
+
if (oldKey !== "titleIdentifier" && oldKey !== "slugIdentifier") continue;
|
578
|
+
const identifierName = oldKey === "titleIdentifier" ? "title" : "slug";
|
579
|
+
const value = newNestedInstructions[oldKey];
|
580
|
+
const newKey = identifiers?.[identifierName] || "id";
|
581
|
+
newNestedInstructions[newKey] = value;
|
582
|
+
delete newNestedInstructions[oldKey];
|
583
|
+
}
|
584
|
+
return {
|
585
|
+
...queryInstructions,
|
586
|
+
[type]: newNestedInstructions
|
587
|
+
};
|
588
|
+
};
|
589
|
+
|
590
|
+
// src/instructions/before-after.ts
|
591
|
+
var CURSOR_SEPARATOR = ",";
|
592
|
+
var CURSOR_NULL_PLACEHOLDER = "RONIN_NULL";
|
593
|
+
var handleBeforeOrAfter = (schema, statementValues, instructions, rootTable) => {
|
594
|
+
if (!(instructions.before || instructions.after)) {
|
595
|
+
throw new RoninError({
|
596
|
+
message: "The `before` or `after` instruction must not be empty.",
|
597
|
+
code: "MISSING_INSTRUCTION",
|
598
|
+
queries: null
|
599
|
+
});
|
600
|
+
}
|
601
|
+
if (instructions.before && instructions.after) {
|
602
|
+
throw new RoninError({
|
603
|
+
message: "The `before` and `after` instructions cannot co-exist. Choose one.",
|
604
|
+
code: "MUTUALLY_EXCLUSIVE_INSTRUCTIONS",
|
605
|
+
queries: null
|
606
|
+
});
|
607
|
+
}
|
608
|
+
const { ascending = [], descending = [] } = instructions.orderedBy || {};
|
609
|
+
const clause = instructions.with ? "AND " : "";
|
610
|
+
const chunks = (instructions.before || instructions.after).toString().split(CURSOR_SEPARATOR).map(decodeURIComponent);
|
611
|
+
const keys = [...ascending, ...descending];
|
612
|
+
const values = keys.map((key, index) => {
|
613
|
+
const value = chunks[index];
|
614
|
+
if (value === CURSOR_NULL_PLACEHOLDER) {
|
615
|
+
return "NULL";
|
616
|
+
}
|
617
|
+
const { field } = getFieldFromSchema(schema, key, "orderedBy");
|
618
|
+
if (field.type === "boolean") {
|
619
|
+
return prepareStatementValue(statementValues, value === "true");
|
620
|
+
}
|
621
|
+
if (field.type === "number") {
|
622
|
+
return prepareStatementValue(statementValues, Number.parseInt(value));
|
623
|
+
}
|
624
|
+
if (field.type === "date") {
|
625
|
+
return `'${new Date(Number.parseInt(value)).toJSON()}'`;
|
626
|
+
}
|
627
|
+
return prepareStatementValue(statementValues, value);
|
628
|
+
});
|
629
|
+
const compareOperators = [
|
630
|
+
// Reverse the comparison operators if we're querying for records before.
|
631
|
+
...new Array(ascending.length).fill(instructions.before ? "<" : ">"),
|
632
|
+
...new Array(descending.length).fill(instructions.before ? ">" : "<")
|
633
|
+
];
|
634
|
+
const conditions = new Array();
|
635
|
+
for (let i = 0; i < keys.length; i++) {
|
636
|
+
if (values[i] === "NULL" && compareOperators[i] === "<") {
|
637
|
+
continue;
|
638
|
+
}
|
639
|
+
const condition = new Array();
|
640
|
+
for (let j = 0; j <= i; j++) {
|
641
|
+
const key = keys[j];
|
642
|
+
const value = values[j];
|
643
|
+
let { field, fieldSelector } = getFieldFromSchema(
|
644
|
+
schema,
|
645
|
+
key,
|
646
|
+
"orderedBy",
|
647
|
+
rootTable
|
648
|
+
);
|
649
|
+
if (j === i) {
|
650
|
+
const closingParentheses = ")".repeat(condition.length);
|
651
|
+
const operator = value === "NULL" ? "IS NOT" : compareOperators[j];
|
652
|
+
const caseInsensitiveStatement = value !== "NULL" && field.type === "string" ? " COLLATE NOCASE" : "";
|
653
|
+
if (value !== "NULL" && operator === "<" && !["ronin.createdAt", "ronin.updatedAt"].includes(key)) {
|
654
|
+
fieldSelector = `IFNULL(${fieldSelector}, -1e999)`;
|
655
|
+
}
|
656
|
+
condition.push(
|
657
|
+
`(${fieldSelector} ${operator} ${value}${caseInsensitiveStatement})${closingParentheses}`
|
658
|
+
);
|
659
|
+
} else {
|
660
|
+
const operator = value === "NULL" ? "IS" : "=";
|
661
|
+
condition.push(`(${fieldSelector} ${operator} ${value} AND`);
|
662
|
+
}
|
663
|
+
}
|
664
|
+
conditions.push(condition.join(" "));
|
665
|
+
}
|
666
|
+
return `${clause}(${conditions.join(" OR ")})`;
|
667
|
+
};
|
668
|
+
|
669
|
+
// src/instructions/for.ts
|
670
|
+
var handleFor = (schemas, schema, statementValues, instruction, rootTable) => {
|
671
|
+
let statement = "";
|
672
|
+
if (!instruction) return statement;
|
673
|
+
for (const shortcut in instruction) {
|
674
|
+
const args = instruction[shortcut];
|
675
|
+
const forFilter = schema.for?.[shortcut];
|
676
|
+
if (!forFilter) {
|
677
|
+
throw new RoninError({
|
678
|
+
message: `The provided \`for\` shortcut "${shortcut}" does not exist in schema "${getSchemaName(schema)}".`,
|
679
|
+
code: "INVALID_FOR_VALUE"
|
680
|
+
});
|
681
|
+
}
|
682
|
+
const replacedForFilter = structuredClone(forFilter);
|
683
|
+
replaceInObject(
|
684
|
+
replacedForFilter,
|
685
|
+
RONIN_SCHEMA_SYMBOLS.VALUE,
|
686
|
+
(match) => match.replace(RONIN_SCHEMA_SYMBOLS.VALUE, args)
|
687
|
+
);
|
688
|
+
const subStatement = composeConditions(
|
689
|
+
schemas,
|
690
|
+
schema,
|
691
|
+
statementValues,
|
692
|
+
"for",
|
693
|
+
replacedForFilter,
|
694
|
+
{ rootTable }
|
695
|
+
);
|
696
|
+
statement += `(${subStatement})`;
|
697
|
+
}
|
698
|
+
return statement;
|
699
|
+
};
|
700
|
+
|
701
|
+
// src/instructions/including.ts
|
702
|
+
var handleIncluding = (schemas, statementValues, schema, instruction, rootTable) => {
|
703
|
+
let statement = "";
|
704
|
+
let rootTableSubQuery;
|
705
|
+
let rootTableName = rootTable;
|
706
|
+
for (const shortcut of instruction || []) {
|
707
|
+
const includingQuery = schema.including?.[shortcut];
|
708
|
+
if (!includingQuery) {
|
709
|
+
throw new RoninError({
|
710
|
+
message: `The provided \`including\` shortcut "${shortcut}" does not exist in schema "${getSchemaName(schema)}".`,
|
711
|
+
code: "INVALID_INCLUDING_VALUE"
|
712
|
+
});
|
713
|
+
}
|
714
|
+
const { queryType, querySchema, queryInstructions } = splitQuery(includingQuery);
|
715
|
+
let modifiableQueryInstructions = queryInstructions;
|
716
|
+
const relatedSchema = getSchemaBySlug(schemas, querySchema);
|
717
|
+
let joinType = "LEFT";
|
718
|
+
let relatedTableSelector = `"${getTableForSchema(relatedSchema)}"`;
|
719
|
+
const tableAlias = `including_${shortcut}`;
|
720
|
+
const single = querySchema !== relatedSchema.pluralSlug;
|
721
|
+
if (!modifiableQueryInstructions?.with) {
|
722
|
+
joinType = "CROSS";
|
723
|
+
if (single) {
|
724
|
+
if (!modifiableQueryInstructions) modifiableQueryInstructions = {};
|
725
|
+
modifiableQueryInstructions.limitedTo = 1;
|
726
|
+
}
|
727
|
+
}
|
728
|
+
if (modifiableQueryInstructions?.limitedTo || modifiableQueryInstructions?.orderedBy) {
|
729
|
+
const subSelect = compileQueryInput(
|
730
|
+
{
|
731
|
+
[queryType]: {
|
732
|
+
[querySchema]: modifiableQueryInstructions
|
733
|
+
}
|
734
|
+
},
|
735
|
+
schemas,
|
736
|
+
{ statementValues }
|
737
|
+
);
|
738
|
+
relatedTableSelector = `(${subSelect.readStatement})`;
|
739
|
+
}
|
740
|
+
statement += `${joinType} JOIN ${relatedTableSelector} as ${tableAlias}`;
|
741
|
+
if (joinType === "LEFT") {
|
742
|
+
if (!single) {
|
743
|
+
rootTableSubQuery = `SELECT * FROM "${rootTable}" LIMIT 1`;
|
744
|
+
rootTableName = `sub_${rootTable}`;
|
745
|
+
}
|
746
|
+
const subStatement = composeConditions(
|
747
|
+
schemas,
|
748
|
+
relatedSchema,
|
749
|
+
statementValues,
|
750
|
+
"including",
|
751
|
+
queryInstructions?.with,
|
752
|
+
{
|
753
|
+
rootTable: rootTableName,
|
754
|
+
customTable: tableAlias
|
755
|
+
}
|
756
|
+
);
|
757
|
+
statement += ` ON (${subStatement})`;
|
758
|
+
}
|
759
|
+
}
|
760
|
+
return { statement, rootTableSubQuery, rootTableName };
|
761
|
+
};
|
762
|
+
|
763
|
+
// src/instructions/limited-to.ts
|
764
|
+
var handleLimitedTo = (single, instruction) => {
|
765
|
+
const pageSize = instruction || 100;
|
766
|
+
const finalPageSize = pageSize + 1;
|
767
|
+
return `LIMIT ${single ? "1" : finalPageSize} `;
|
768
|
+
};
|
769
|
+
|
770
|
+
// src/instructions/ordered-by.ts
|
771
|
+
var handleOrderedBy = (schema, instruction, rootTable) => {
|
772
|
+
let statement = "";
|
773
|
+
for (const field of instruction.ascending || []) {
|
774
|
+
const { field: schemaField, fieldSelector } = getFieldFromSchema(
|
775
|
+
schema,
|
776
|
+
field,
|
777
|
+
"orderedBy.ascending",
|
778
|
+
rootTable
|
779
|
+
);
|
780
|
+
if (statement.length > 0) {
|
781
|
+
statement += ", ";
|
782
|
+
}
|
783
|
+
const caseInsensitiveStatement = schemaField.type === "string" ? " COLLATE NOCASE" : "";
|
784
|
+
statement += `${fieldSelector}${caseInsensitiveStatement} ASC`;
|
785
|
+
}
|
786
|
+
for (const field of instruction.descending || []) {
|
787
|
+
const { field: schemaField, fieldSelector } = getFieldFromSchema(
|
788
|
+
schema,
|
789
|
+
field,
|
790
|
+
"orderedBy.descending",
|
791
|
+
rootTable
|
792
|
+
);
|
793
|
+
if (statement.length > 0) {
|
794
|
+
statement += ", ";
|
795
|
+
}
|
796
|
+
const caseInsensitiveStatement = schemaField.type === "string" ? " COLLATE NOCASE" : "";
|
797
|
+
statement += `${fieldSelector}${caseInsensitiveStatement} DESC`;
|
798
|
+
}
|
799
|
+
return `ORDER BY ${statement}`;
|
800
|
+
};
|
801
|
+
|
802
|
+
// src/instructions/selecting.ts
|
803
|
+
var handleSelecting = (schema, statementValues, instructions) => {
|
804
|
+
let statement = instructions.selecting ? instructions.selecting.map((slug) => {
|
805
|
+
return getFieldFromSchema(schema, slug, "selecting").fieldSelector;
|
806
|
+
}).join(", ") : "*";
|
807
|
+
if (isObject(instructions.including)) {
|
808
|
+
statement += ", ";
|
809
|
+
statement += Object.entries(
|
810
|
+
flatten(instructions.including)
|
811
|
+
).filter(([_, value]) => {
|
812
|
+
return !(isObject(value) && Object.hasOwn(value, RONIN_SCHEMA_SYMBOLS.QUERY));
|
813
|
+
}).map(([key, value]) => {
|
814
|
+
return `${prepareStatementValue(statementValues, value)} as "${key}"`;
|
815
|
+
}).join(", ");
|
816
|
+
}
|
817
|
+
return statement;
|
818
|
+
};
|
819
|
+
|
820
|
+
// src/instructions/to.ts
|
821
|
+
var handleTo = (schemas, schema, statementValues, queryType, writeStatements, instructions, rootTable) => {
|
822
|
+
const currentTime = (/* @__PURE__ */ new Date()).toISOString();
|
823
|
+
const { with: withInstruction, to: toInstruction } = instructions;
|
824
|
+
const defaultFields = {};
|
825
|
+
if (queryType === "create") {
|
826
|
+
defaultFields.id = toInstruction.id || generateRecordId(schema.idPrefix);
|
827
|
+
}
|
828
|
+
defaultFields.ronin = {
|
829
|
+
// If records are being created, set their creation time.
|
830
|
+
...queryType === "create" ? { createdAt: currentTime } : {},
|
831
|
+
// If records are being created or updated, set their update time.
|
832
|
+
updatedAt: currentTime,
|
833
|
+
// Allow for overwriting the default values provided above.
|
834
|
+
...toInstruction.ronin
|
835
|
+
};
|
836
|
+
const hasSubQuery = Object.hasOwn(toInstruction, RONIN_SCHEMA_SYMBOLS.QUERY);
|
837
|
+
if (hasSubQuery) {
|
838
|
+
const subQuery = toInstruction[RONIN_SCHEMA_SYMBOLS.QUERY];
|
839
|
+
let { querySchema: subQuerySchemaSlug, queryInstructions: subQueryInstructions } = splitQuery(subQuery);
|
840
|
+
const subQuerySchema = getSchemaBySlug(schemas, subQuerySchemaSlug);
|
841
|
+
const subQuerySelectedFields = subQueryInstructions?.selecting;
|
842
|
+
const subQueryIncludedFields = subQueryInstructions?.including;
|
843
|
+
const subQueryFields = [
|
844
|
+
...subQuerySelectedFields || (subQuerySchema.fields || []).map((field) => field.slug),
|
845
|
+
...subQueryIncludedFields ? Object.keys(
|
846
|
+
flatten(subQueryIncludedFields || {})
|
847
|
+
) : []
|
848
|
+
];
|
849
|
+
for (const field of subQueryFields || []) {
|
850
|
+
getFieldFromSchema(schema, field, "to");
|
851
|
+
}
|
852
|
+
const defaultFieldsToAdd = subQuerySelectedFields ? Object.entries(flatten(defaultFields)).filter(([key]) => {
|
853
|
+
return !subQuerySelectedFields.includes(key);
|
854
|
+
}) : [];
|
855
|
+
if (defaultFieldsToAdd.length > 0) {
|
856
|
+
const defaultFieldsObject = expand(Object.fromEntries(defaultFieldsToAdd));
|
857
|
+
if (!subQueryInstructions) subQueryInstructions = {};
|
858
|
+
subQueryInstructions.including = {
|
859
|
+
...defaultFieldsObject,
|
860
|
+
...subQueryInstructions.including
|
861
|
+
};
|
862
|
+
}
|
863
|
+
return compileQueryInput(subQuery, schemas, {
|
864
|
+
statementValues
|
865
|
+
}).readStatement;
|
866
|
+
}
|
867
|
+
Object.assign(toInstruction, defaultFields);
|
868
|
+
for (const fieldSlug in toInstruction) {
|
869
|
+
const fieldValue = toInstruction[fieldSlug];
|
870
|
+
const fieldDetails = getFieldFromSchema(schema, fieldSlug, "to");
|
871
|
+
if (fieldDetails.field.type === "reference" && fieldDetails.field.kind === "many") {
|
872
|
+
delete toInstruction[fieldSlug];
|
873
|
+
const associativeSchemaSlug = composeAssociationSchemaSlug(
|
874
|
+
schema,
|
875
|
+
fieldDetails.field
|
876
|
+
);
|
877
|
+
const composeStatement = (subQueryType, value) => {
|
878
|
+
const origin = queryType === "create" ? { id: toInstruction.id } : withInstruction;
|
879
|
+
const recordDetails = { origin };
|
880
|
+
if (value) recordDetails.target = value;
|
881
|
+
const { readStatement } = compileQueryInput(
|
882
|
+
{
|
883
|
+
[subQueryType]: {
|
884
|
+
[associativeSchemaSlug]: subQueryType === "create" ? { to: recordDetails } : { with: recordDetails }
|
885
|
+
}
|
886
|
+
},
|
887
|
+
schemas,
|
888
|
+
{ statementValues, disableReturning: true }
|
889
|
+
);
|
890
|
+
return readStatement;
|
891
|
+
};
|
892
|
+
if (Array.isArray(fieldValue)) {
|
893
|
+
writeStatements.push(composeStatement("drop"));
|
894
|
+
for (const record of fieldValue) {
|
895
|
+
writeStatements.push(composeStatement("create", record));
|
896
|
+
}
|
897
|
+
} else if (isObject(fieldValue)) {
|
898
|
+
for (const recordToAdd of fieldValue.containing || []) {
|
899
|
+
writeStatements.push(composeStatement("create", recordToAdd));
|
900
|
+
}
|
901
|
+
for (const recordToRemove of fieldValue.notContaining || []) {
|
902
|
+
writeStatements.push(composeStatement("drop", recordToRemove));
|
903
|
+
}
|
904
|
+
}
|
905
|
+
}
|
906
|
+
}
|
907
|
+
let statement = composeConditions(
|
908
|
+
schemas,
|
909
|
+
schema,
|
910
|
+
statementValues,
|
911
|
+
"to",
|
912
|
+
toInstruction,
|
913
|
+
{
|
914
|
+
rootTable,
|
915
|
+
type: queryType === "create" ? "fields" : void 0
|
916
|
+
}
|
917
|
+
);
|
918
|
+
if (queryType === "create") {
|
919
|
+
const deepStatement = composeConditions(
|
920
|
+
schemas,
|
921
|
+
schema,
|
922
|
+
statementValues,
|
923
|
+
"to",
|
924
|
+
toInstruction,
|
925
|
+
{
|
926
|
+
rootTable,
|
927
|
+
type: "values"
|
928
|
+
}
|
929
|
+
);
|
930
|
+
statement = `(${statement}) VALUES (${deepStatement})`;
|
931
|
+
} else if (queryType === "set") {
|
932
|
+
statement = `SET ${statement}`;
|
933
|
+
}
|
934
|
+
return statement;
|
935
|
+
};
|
936
|
+
|
937
|
+
// src/index.ts
|
938
|
+
var compileQueryInput = (query, defaultSchemas, options) => {
|
939
|
+
const parsedQuery = splitQuery(query);
|
940
|
+
const { queryType, querySchema, queryInstructions } = parsedQuery;
|
941
|
+
const schemas = addSystemSchemas(defaultSchemas);
|
942
|
+
const schema = getSchemaBySlug(schemas, querySchema);
|
943
|
+
const single = querySchema !== schema.pluralSlug;
|
944
|
+
let instructions = formatIdentifiers(schema, queryInstructions);
|
945
|
+
let table = getTableForSchema(schema);
|
946
|
+
const statementValues = options?.statementValues || [];
|
947
|
+
const writeStatements = [];
|
948
|
+
addSchemaQueries(parsedQuery, writeStatements);
|
949
|
+
const columns = handleSelecting(schema, statementValues, {
|
950
|
+
selecting: instructions?.selecting,
|
951
|
+
including: instructions?.including
|
952
|
+
});
|
953
|
+
let statement = "";
|
954
|
+
switch (queryType) {
|
955
|
+
case "get":
|
956
|
+
statement += `SELECT ${columns} FROM `;
|
957
|
+
break;
|
958
|
+
case "count":
|
959
|
+
statement += `SELECT COUNT(${columns}) FROM `;
|
960
|
+
break;
|
961
|
+
case "drop":
|
962
|
+
statement += "DELETE FROM ";
|
963
|
+
break;
|
964
|
+
case "create":
|
965
|
+
statement += "INSERT INTO ";
|
966
|
+
break;
|
967
|
+
case "set":
|
968
|
+
statement += "UPDATE ";
|
969
|
+
break;
|
970
|
+
}
|
971
|
+
const isJoining = typeof instructions?.including !== "undefined" && !isObject(instructions.including);
|
972
|
+
let isJoiningMultipleRows = false;
|
973
|
+
if (isJoining) {
|
974
|
+
const {
|
975
|
+
statement: including,
|
976
|
+
rootTableSubQuery,
|
977
|
+
rootTableName
|
978
|
+
} = handleIncluding(schemas, statementValues, schema, instructions?.including, table);
|
979
|
+
if (rootTableSubQuery && rootTableName) {
|
980
|
+
table = rootTableName;
|
981
|
+
statement += `(${rootTableSubQuery}) as ${rootTableName} `;
|
982
|
+
isJoiningMultipleRows = true;
|
983
|
+
} else {
|
984
|
+
statement += `"${table}" `;
|
985
|
+
}
|
986
|
+
statement += `${including} `;
|
987
|
+
} else {
|
988
|
+
statement += `"${table}" `;
|
989
|
+
}
|
990
|
+
if (queryType === "create" || queryType === "set") {
|
991
|
+
if (!isObject(instructions.to) || Object.keys(instructions.to).length === 0) {
|
992
|
+
throw new RoninError({
|
993
|
+
message: `When using a \`${queryType}\` query, the \`to\` instruction must be a non-empty object.`,
|
994
|
+
code: "INVALID_TO_VALUE",
|
995
|
+
queries: [query]
|
996
|
+
});
|
997
|
+
}
|
998
|
+
const toStatement = handleTo(
|
999
|
+
schemas,
|
1000
|
+
schema,
|
1001
|
+
statementValues,
|
1002
|
+
queryType,
|
1003
|
+
writeStatements,
|
1004
|
+
{ with: instructions.with, to: instructions.to },
|
1005
|
+
isJoining ? table : void 0
|
1006
|
+
);
|
1007
|
+
statement += `${toStatement} `;
|
1008
|
+
}
|
1009
|
+
const conditions = [];
|
1010
|
+
if (queryType !== "create" && instructions && Object.hasOwn(instructions, "with")) {
|
1011
|
+
const withStatement = handleWith(
|
1012
|
+
schemas,
|
1013
|
+
schema,
|
1014
|
+
statementValues,
|
1015
|
+
instructions?.with,
|
1016
|
+
isJoining ? table : void 0
|
1017
|
+
);
|
1018
|
+
if (withStatement.length > 0) conditions.push(withStatement);
|
1019
|
+
}
|
1020
|
+
if (instructions && Object.hasOwn(instructions, "for")) {
|
1021
|
+
const forStatement = handleFor(
|
1022
|
+
schemas,
|
1023
|
+
schema,
|
1024
|
+
statementValues,
|
1025
|
+
instructions?.for,
|
1026
|
+
isJoining ? table : void 0
|
1027
|
+
);
|
1028
|
+
if (forStatement.length > 0) conditions.push(forStatement);
|
1029
|
+
}
|
1030
|
+
if ((queryType === "get" || queryType === "count") && !single) {
|
1031
|
+
instructions = instructions || {};
|
1032
|
+
instructions.orderedBy = instructions.orderedBy || {};
|
1033
|
+
instructions.orderedBy.ascending = instructions.orderedBy.ascending || [];
|
1034
|
+
instructions.orderedBy.descending = instructions.orderedBy.descending || [];
|
1035
|
+
if (![
|
1036
|
+
...instructions.orderedBy.ascending,
|
1037
|
+
...instructions.orderedBy.descending
|
1038
|
+
].includes("ronin.createdAt")) {
|
1039
|
+
instructions.orderedBy.descending.push("ronin.createdAt");
|
1040
|
+
}
|
1041
|
+
}
|
1042
|
+
if (instructions && (Object.hasOwn(instructions, "before") || Object.hasOwn(instructions, "after"))) {
|
1043
|
+
if (single) {
|
1044
|
+
throw new RoninError({
|
1045
|
+
message: "The `before` and `after` instructions are not supported when querying for a single record.",
|
1046
|
+
code: "INVALID_BEFORE_OR_AFTER_INSTRUCTION",
|
1047
|
+
queries: [query]
|
1048
|
+
});
|
1049
|
+
}
|
1050
|
+
const beforeAndAfterStatement = handleBeforeOrAfter(
|
1051
|
+
schema,
|
1052
|
+
statementValues,
|
1053
|
+
{
|
1054
|
+
before: instructions.before,
|
1055
|
+
after: instructions.after,
|
1056
|
+
with: instructions.with,
|
1057
|
+
orderedBy: instructions.orderedBy
|
1058
|
+
},
|
1059
|
+
isJoining ? table : void 0
|
1060
|
+
);
|
1061
|
+
conditions.push(beforeAndAfterStatement);
|
1062
|
+
}
|
1063
|
+
if (conditions.length > 0) {
|
1064
|
+
if (conditions.length === 1) {
|
1065
|
+
statement += `WHERE ${conditions[0]} `;
|
1066
|
+
} else {
|
1067
|
+
statement += `WHERE (${conditions.join(" ")}) `;
|
1068
|
+
}
|
1069
|
+
}
|
1070
|
+
if (instructions?.orderedBy) {
|
1071
|
+
const orderedByStatement = handleOrderedBy(
|
1072
|
+
schema,
|
1073
|
+
instructions.orderedBy,
|
1074
|
+
isJoining ? table : void 0
|
1075
|
+
);
|
1076
|
+
statement += `${orderedByStatement} `;
|
1077
|
+
}
|
1078
|
+
if (queryType === "get" && !isJoiningMultipleRows) {
|
1079
|
+
statement += handleLimitedTo(single, instructions?.limitedTo);
|
1080
|
+
}
|
1081
|
+
if (["create", "set", "drop"].includes(queryType) && !options?.disableReturning) {
|
1082
|
+
statement += "RETURNING * ";
|
1083
|
+
}
|
1084
|
+
const finalStatement = statement.trimEnd();
|
1085
|
+
return {
|
1086
|
+
writeStatements,
|
1087
|
+
readStatement: finalStatement,
|
1088
|
+
values: statementValues
|
1089
|
+
};
|
1090
|
+
};
|
1091
|
+
export {
|
1092
|
+
compileQueryInput
|
1093
|
+
};
|