@ronin/compiler 0.10.3 → 0.11.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 +8 -5
- package/dist/index.d.ts +47 -5
- package/dist/index.js +221 -143
- package/package.json +1 -1
package/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# RONIN Compiler
|
2
2
|
|
3
|
-
This package compiles [RONIN queries](https://ronin.co/docs/queries) to
|
3
|
+
This package compiles [RONIN queries](https://ronin.co/docs/queries) to [SQLite](https://www.sqlite.org) statements.
|
4
4
|
|
5
5
|
## Setup
|
6
6
|
|
@@ -65,9 +65,11 @@ Once the RONIN queries have been compiled down to SQL statements, the statements
|
|
65
65
|
executed and their results can be formatted by the compiler as well:
|
66
66
|
|
67
67
|
```typescript
|
68
|
-
// `
|
68
|
+
// Passing `rawResults` (rows being of arrays of values) provided by the database (ideal)
|
69
|
+
const results: Array<Result> = transaction.formatResults(rawResults);
|
69
70
|
|
70
|
-
|
71
|
+
// Passing `objectResults` (rows being of objects) provided by a driver
|
72
|
+
const results: Array<Result> = transaction.formatResults(objectResults, false);
|
71
73
|
```
|
72
74
|
|
73
75
|
#### Root Model
|
@@ -133,12 +135,13 @@ new Transaction(queries, {
|
|
133
135
|
//
|
134
136
|
// If the driver being used instead returns an object for every row, the driver must
|
135
137
|
// ensure the uniqueness of every key in that object, which means prefixing duplicated
|
136
|
-
// column names with the name of the respective table, if multiple tables are joined
|
138
|
+
// column names with the name of the respective table, if multiple tables are joined
|
139
|
+
// (example for an object key: "table_name.column_name").
|
137
140
|
//
|
138
141
|
// Drivers that return objects for rows offer this behavior as an option that is
|
139
142
|
// usually called "expand columns". If the driver being used does not offer such an
|
140
143
|
// option, you can instead activate the option in the compiler, which results in longer
|
141
|
-
// SQL statements because
|
144
|
+
// SQL statements because all column names are aliased.
|
142
145
|
expandColumns: true
|
143
146
|
});
|
144
147
|
```
|
package/dist/index.d.ts
CHANGED
@@ -5867,7 +5867,7 @@ type ModelFieldBasics = {
|
|
5867
5867
|
increment?: boolean;
|
5868
5868
|
};
|
5869
5869
|
type ModelFieldNormal = ModelFieldBasics & {
|
5870
|
-
type: 'string' | 'number' | 'boolean' | 'date' | 'json'
|
5870
|
+
type: 'string' | 'number' | 'boolean' | 'date' | 'json';
|
5871
5871
|
};
|
5872
5872
|
type ModelFieldReferenceAction = 'CASCADE' | 'RESTRICT' | 'SET NULL' | 'SET DEFAULT' | 'NO ACTION';
|
5873
5873
|
type ModelFieldReference = ModelFieldBasics & {
|
@@ -5984,7 +5984,8 @@ type PublicModel<T extends Array<ModelField> = Array<ModelField>> = Omit<Partial
|
|
5984
5984
|
identifiers?: Partial<Model['identifiers']>;
|
5985
5985
|
};
|
5986
5986
|
|
5987
|
-
type
|
5987
|
+
type RawRow = Array<unknown>;
|
5988
|
+
type ObjectRow = Record<string, unknown>;
|
5988
5989
|
type NativeRecord = Record<string, unknown> & {
|
5989
5990
|
id: string;
|
5990
5991
|
ronin: {
|
@@ -6005,6 +6006,45 @@ type AmountResult = {
|
|
6005
6006
|
};
|
6006
6007
|
type Result = SingleRecordResult | MultipleRecordResult | AmountResult;
|
6007
6008
|
|
6009
|
+
/**
|
6010
|
+
* A list of placeholders that can be located inside queries after those queries were
|
6011
|
+
* serialized into JSON objects.
|
6012
|
+
*
|
6013
|
+
* These placeholders are used to represent special keys and values. For example, if a
|
6014
|
+
* query is nested into a query, the nested query will be marked with `__RONIN_QUERY`,
|
6015
|
+
* which allows for distinguishing that nested query from an object of instructions.
|
6016
|
+
*/
|
6017
|
+
declare const QUERY_SYMBOLS: {
|
6018
|
+
readonly QUERY: "__RONIN_QUERY";
|
6019
|
+
readonly EXPRESSION: "__RONIN_EXPRESSION";
|
6020
|
+
readonly FIELD: "__RONIN_FIELD_";
|
6021
|
+
readonly FIELD_PARENT: "__RONIN_FIELD_PARENT_";
|
6022
|
+
readonly FIELD_PARENT_OLD: "__RONIN_FIELD_PARENT_OLD_";
|
6023
|
+
readonly FIELD_PARENT_NEW: "__RONIN_FIELD_PARENT_NEW_";
|
6024
|
+
readonly VALUE: "__RONIN_VALUE";
|
6025
|
+
};
|
6026
|
+
type RoninErrorCode = 'MODEL_NOT_FOUND' | 'FIELD_NOT_FOUND' | 'INDEX_NOT_FOUND' | 'TRIGGER_NOT_FOUND' | 'PRESET_NOT_FOUND' | 'INVALID_WITH_VALUE' | 'INVALID_TO_VALUE' | 'INVALID_INCLUDING_VALUE' | 'INVALID_FOR_VALUE' | 'INVALID_BEFORE_OR_AFTER_INSTRUCTION' | 'INVALID_MODEL_VALUE' | 'MUTUALLY_EXCLUSIVE_INSTRUCTIONS' | 'MISSING_INSTRUCTION' | 'MISSING_FIELD';
|
6027
|
+
interface Issue {
|
6028
|
+
message: string;
|
6029
|
+
path: Array<string | number>;
|
6030
|
+
}
|
6031
|
+
interface Details {
|
6032
|
+
message: string;
|
6033
|
+
code: RoninErrorCode;
|
6034
|
+
field?: string;
|
6035
|
+
fields?: Array<string>;
|
6036
|
+
issues?: Array<Issue>;
|
6037
|
+
queries?: Array<Query> | null;
|
6038
|
+
}
|
6039
|
+
declare class RoninError extends Error {
|
6040
|
+
code: Details['code'];
|
6041
|
+
field?: Details['field'];
|
6042
|
+
fields?: Details['fields'];
|
6043
|
+
issues?: Details['issues'];
|
6044
|
+
queries?: Details['queries'];
|
6045
|
+
constructor(details: Details);
|
6046
|
+
}
|
6047
|
+
|
6008
6048
|
interface TransactionOptions {
|
6009
6049
|
/** A list of models that already exist in the database. */
|
6010
6050
|
models?: Array<PublicModel>;
|
@@ -6020,6 +6060,7 @@ declare class Transaction {
|
|
6020
6060
|
statements: Array<Statement>;
|
6021
6061
|
models: Array<Model>;
|
6022
6062
|
private queries;
|
6063
|
+
private fields;
|
6023
6064
|
constructor(queries: Array<Query>, options?: TransactionOptions);
|
6024
6065
|
/**
|
6025
6066
|
* Composes SQL statements for the provided RONIN queries.
|
@@ -6031,10 +6072,11 @@ declare class Transaction {
|
|
6031
6072
|
* @returns The composed SQL statements.
|
6032
6073
|
*/
|
6033
6074
|
private compileQueries;
|
6034
|
-
private
|
6035
|
-
|
6075
|
+
private formatRow;
|
6076
|
+
formatResults(results: Array<Array<RawRow>>, raw?: true): Array<Result>;
|
6077
|
+
formatResults(results: Array<Array<ObjectRow>>, raw?: false): Array<Result>;
|
6036
6078
|
}
|
6037
6079
|
|
6038
6080
|
declare const CLEAN_ROOT_MODEL: PublicModel;
|
6039
6081
|
|
6040
|
-
export { type PublicModel as Model, type ModelField, type ModelIndex, type ModelPreset, type ModelTrigger, type Query, CLEAN_ROOT_MODEL as ROOT_MODEL, type Result, type Statement, Transaction };
|
6082
|
+
export { type PublicModel as Model, type ModelField, type ModelIndex, type ModelPreset, type ModelTrigger, QUERY_SYMBOLS, type Query, CLEAN_ROOT_MODEL as ROOT_MODEL, type Result, RoninError, type Statement, Transaction };
|
package/dist/index.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
// src/utils/helpers.ts
|
2
2
|
import { init as cuid } from "@paralleldrive/cuid2";
|
3
|
-
var
|
3
|
+
var QUERY_SYMBOLS = {
|
4
4
|
// Represents a sub query.
|
5
5
|
QUERY: "__RONIN_QUERY",
|
6
6
|
// Represents an expression that should be evaluated.
|
@@ -17,9 +17,10 @@ var RONIN_MODEL_SYMBOLS = {
|
|
17
17
|
VALUE: "__RONIN_VALUE"
|
18
18
|
};
|
19
19
|
var RONIN_MODEL_FIELD_REGEX = new RegExp(
|
20
|
-
`${
|
20
|
+
`${QUERY_SYMBOLS.FIELD}[_a-zA-Z0-9.]+`,
|
21
21
|
"g"
|
22
22
|
);
|
23
|
+
var composeIncludedTableAlias = (fieldSlug) => `including_${fieldSlug}`;
|
23
24
|
var MODEL_ENTITY_ERROR_CODES = {
|
24
25
|
field: "FIELD_NOT_FOUND",
|
25
26
|
index: "INDEX_NOT_FOUND",
|
@@ -65,6 +66,23 @@ var convertToCamelCase = (str) => {
|
|
65
66
|
return sanitize(str).split(SPLIT_REGEX).map((part, index) => index === 0 ? part.toLowerCase() : capitalize(part)).join("");
|
66
67
|
};
|
67
68
|
var isObject = (value) => value != null && typeof value === "object" && Array.isArray(value) === false;
|
69
|
+
var getSymbol = (value) => {
|
70
|
+
if (!isObject(value)) return null;
|
71
|
+
const objectValue = value;
|
72
|
+
if (QUERY_SYMBOLS.QUERY in objectValue) {
|
73
|
+
return {
|
74
|
+
type: "query",
|
75
|
+
value: objectValue[QUERY_SYMBOLS.QUERY]
|
76
|
+
};
|
77
|
+
}
|
78
|
+
if (QUERY_SYMBOLS.EXPRESSION in objectValue) {
|
79
|
+
return {
|
80
|
+
type: "expression",
|
81
|
+
value: objectValue[QUERY_SYMBOLS.EXPRESSION]
|
82
|
+
};
|
83
|
+
}
|
84
|
+
return null;
|
85
|
+
};
|
68
86
|
var findInObject = (obj, pattern, replacer) => {
|
69
87
|
let found = false;
|
70
88
|
for (const key in obj) {
|
@@ -85,10 +103,11 @@ var findInObject = (obj, pattern, replacer) => {
|
|
85
103
|
var flatten = (obj, prefix = "", res = {}) => {
|
86
104
|
for (const key in obj) {
|
87
105
|
const path = prefix ? `${prefix}.${key}` : key;
|
88
|
-
|
89
|
-
|
106
|
+
const value = obj[key];
|
107
|
+
if (typeof value === "object" && value !== null && !getSymbol(value)) {
|
108
|
+
flatten(value, path, res);
|
90
109
|
} else {
|
91
|
-
res[path] =
|
110
|
+
res[path] = value;
|
92
111
|
}
|
93
112
|
}
|
94
113
|
return res;
|
@@ -99,7 +118,11 @@ var omit = (obj, properties) => Object.fromEntries(
|
|
99
118
|
var expand = (obj) => {
|
100
119
|
return Object.entries(obj).reduce((res, [key, val]) => {
|
101
120
|
key.split(".").reduce((acc, part, i, arr) => {
|
102
|
-
|
121
|
+
if (i === arr.length - 1) {
|
122
|
+
acc[part] = val;
|
123
|
+
} else {
|
124
|
+
acc[part] = typeof acc[part] === "object" && acc[part] !== null ? acc[part] : {};
|
125
|
+
}
|
103
126
|
return acc[part];
|
104
127
|
}, res);
|
105
128
|
return res;
|
@@ -133,15 +156,15 @@ var prepareStatementValue = (statementParams, value) => {
|
|
133
156
|
};
|
134
157
|
var parseFieldExpression = (model, instructionName, expression, parentModel) => {
|
135
158
|
return expression.replace(RONIN_MODEL_FIELD_REGEX, (match) => {
|
136
|
-
let toReplace =
|
159
|
+
let toReplace = QUERY_SYMBOLS.FIELD;
|
137
160
|
let rootModel = model;
|
138
|
-
if (match.startsWith(
|
161
|
+
if (match.startsWith(QUERY_SYMBOLS.FIELD_PARENT)) {
|
139
162
|
rootModel = parentModel;
|
140
|
-
toReplace =
|
141
|
-
if (match.startsWith(
|
142
|
-
rootModel.tableAlias = toReplace =
|
143
|
-
} else if (match.startsWith(
|
144
|
-
rootModel.tableAlias = toReplace =
|
163
|
+
toReplace = QUERY_SYMBOLS.FIELD_PARENT;
|
164
|
+
if (match.startsWith(QUERY_SYMBOLS.FIELD_PARENT_OLD)) {
|
165
|
+
rootModel.tableAlias = toReplace = QUERY_SYMBOLS.FIELD_PARENT_OLD;
|
166
|
+
} else if (match.startsWith(QUERY_SYMBOLS.FIELD_PARENT_NEW)) {
|
167
|
+
rootModel.tableAlias = toReplace = QUERY_SYMBOLS.FIELD_PARENT_NEW;
|
145
168
|
}
|
146
169
|
}
|
147
170
|
const fieldSlug = match.replace(toReplace, "");
|
@@ -190,47 +213,52 @@ var composeConditions = (models, model, statementParams, instructionName, value,
|
|
190
213
|
return conditions.join(" AND ");
|
191
214
|
}
|
192
215
|
if (options.fieldSlug) {
|
193
|
-
const
|
194
|
-
|
195
|
-
|
196
|
-
if (!
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
if (
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
216
|
+
const childField = model.fields.some(({ slug }) => {
|
217
|
+
return slug.includes(".") && slug.split(".")[0] === options.fieldSlug;
|
218
|
+
});
|
219
|
+
if (!childField) {
|
220
|
+
const fieldDetails = getFieldFromModel(model, options.fieldSlug, instructionName);
|
221
|
+
const { field: modelField } = fieldDetails || {};
|
222
|
+
const consumeJSON = modelField?.type === "json" && instructionName === "to";
|
223
|
+
if (modelField && !(isObject(value) || Array.isArray(value)) || getSymbol(value) || consumeJSON) {
|
224
|
+
return composeFieldValues(
|
225
|
+
models,
|
226
|
+
model,
|
227
|
+
statementParams,
|
228
|
+
instructionName,
|
229
|
+
value,
|
230
|
+
{ ...options, fieldSlug: options.fieldSlug }
|
231
|
+
);
|
232
|
+
}
|
233
|
+
if (modelField?.type === "link" && isNested) {
|
234
|
+
const keys = Object.keys(value);
|
235
|
+
const values = Object.values(value);
|
236
|
+
let recordTarget;
|
237
|
+
if (keys.length === 1 && keys[0] === "id") {
|
238
|
+
recordTarget = values[0];
|
239
|
+
} else {
|
240
|
+
const relatedModel = getModelBySlug(models, modelField.target);
|
241
|
+
const subQuery = {
|
242
|
+
get: {
|
243
|
+
[relatedModel.slug]: {
|
244
|
+
with: value,
|
245
|
+
selecting: ["id"]
|
246
|
+
}
|
219
247
|
}
|
220
|
-
}
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
}
|
248
|
+
};
|
249
|
+
recordTarget = {
|
250
|
+
[QUERY_SYMBOLS.QUERY]: subQuery
|
251
|
+
};
|
252
|
+
}
|
253
|
+
return composeConditions(
|
254
|
+
models,
|
255
|
+
model,
|
256
|
+
statementParams,
|
257
|
+
instructionName,
|
258
|
+
recordTarget,
|
259
|
+
options
|
260
|
+
);
|
225
261
|
}
|
226
|
-
return composeConditions(
|
227
|
-
models,
|
228
|
-
model,
|
229
|
-
statementParams,
|
230
|
-
instructionName,
|
231
|
-
recordTarget,
|
232
|
-
options
|
233
|
-
);
|
234
262
|
}
|
235
263
|
}
|
236
264
|
if (isNested) {
|
@@ -278,23 +306,6 @@ var formatIdentifiers = ({ identifiers }, queryInstructions) => {
|
|
278
306
|
[type]: newNestedInstructions
|
279
307
|
};
|
280
308
|
};
|
281
|
-
var getSymbol = (value) => {
|
282
|
-
if (!isObject(value)) return null;
|
283
|
-
const objectValue = value;
|
284
|
-
if (RONIN_MODEL_SYMBOLS.QUERY in objectValue) {
|
285
|
-
return {
|
286
|
-
type: "query",
|
287
|
-
value: objectValue[RONIN_MODEL_SYMBOLS.QUERY]
|
288
|
-
};
|
289
|
-
}
|
290
|
-
if (RONIN_MODEL_SYMBOLS.EXPRESSION in objectValue) {
|
291
|
-
return {
|
292
|
-
type: "expression",
|
293
|
-
value: objectValue[RONIN_MODEL_SYMBOLS.EXPRESSION]
|
294
|
-
};
|
295
|
-
}
|
296
|
-
return null;
|
297
|
-
};
|
298
309
|
|
299
310
|
// src/instructions/with.ts
|
300
311
|
var getMatcher = (value, negative) => {
|
@@ -347,7 +358,7 @@ var getModelBySlug = (models, slug) => {
|
|
347
358
|
};
|
348
359
|
var composeAssociationModelSlug = (model, field) => convertToCamelCase(`ronin_link_${model.slug}_${field.slug}`);
|
349
360
|
var getFieldSelector = (model, field, fieldPath, instructionName) => {
|
350
|
-
const symbol = model.tableAlias?.startsWith(
|
361
|
+
const symbol = model.tableAlias?.startsWith(QUERY_SYMBOLS.FIELD_PARENT) ? `${model.tableAlias.replace(QUERY_SYMBOLS.FIELD_PARENT, "").slice(0, -1)}.` : "";
|
351
362
|
const tablePrefix = symbol || (model.tableAlias ? `"${model.tableAlias}".` : "");
|
352
363
|
if (field.type === "json" && instructionName !== "to") {
|
353
364
|
const dotParts = fieldPath.split(".");
|
@@ -357,7 +368,7 @@ var getFieldSelector = (model, field, fieldPath, instructionName) => {
|
|
357
368
|
}
|
358
369
|
return `${tablePrefix}"${fieldPath}"`;
|
359
370
|
};
|
360
|
-
|
371
|
+
function getFieldFromModel(model, fieldPath, instructionName, shouldThrow = true) {
|
361
372
|
const errorPrefix = `Field "${fieldPath}" defined for \`${instructionName}\``;
|
362
373
|
const modelFields = model.fields || [];
|
363
374
|
let modelField;
|
@@ -375,16 +386,19 @@ var getFieldFromModel = (model, fieldPath, instructionName) => {
|
|
375
386
|
}
|
376
387
|
modelField = modelFields.find((field) => field.slug === fieldPath);
|
377
388
|
if (!modelField) {
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
389
|
+
if (shouldThrow) {
|
390
|
+
throw new RoninError({
|
391
|
+
message: `${errorPrefix} does not exist in model "${model.name}".`,
|
392
|
+
code: "FIELD_NOT_FOUND",
|
393
|
+
field: fieldPath,
|
394
|
+
queries: null
|
395
|
+
});
|
396
|
+
}
|
397
|
+
return null;
|
384
398
|
}
|
385
399
|
const fieldSelector = getFieldSelector(model, modelField, fieldPath, instructionName);
|
386
400
|
return { field: modelField, fieldSelector };
|
387
|
-
}
|
401
|
+
}
|
388
402
|
var slugToName = (slug) => {
|
389
403
|
const name = slug.replace(/([a-z])([A-Z])/g, "$1 $2");
|
390
404
|
return title(name);
|
@@ -440,11 +454,6 @@ var SYSTEM_FIELDS = [
|
|
440
454
|
slug: "id",
|
441
455
|
displayAs: "single-line"
|
442
456
|
},
|
443
|
-
{
|
444
|
-
name: "RONIN",
|
445
|
-
type: "group",
|
446
|
-
slug: "ronin"
|
447
|
-
},
|
448
457
|
{
|
449
458
|
name: "RONIN - Locked",
|
450
459
|
type: "boolean",
|
@@ -488,7 +497,6 @@ var ROOT_MODEL = {
|
|
488
497
|
{ slug: "pluralSlug", type: "string" },
|
489
498
|
{ slug: "idPrefix", type: "string" },
|
490
499
|
{ slug: "table", type: "string" },
|
491
|
-
{ slug: "identifiers", type: "group" },
|
492
500
|
{ slug: "identifiers.name", type: "string" },
|
493
501
|
{ slug: "identifiers.slug", type: "string" },
|
494
502
|
// Providing an empty object as a default value allows us to use `json_insert`
|
@@ -543,14 +551,14 @@ var addDefaultModelPresets = (list, model) => {
|
|
543
551
|
instructions: {
|
544
552
|
including: {
|
545
553
|
[field.slug]: {
|
546
|
-
[
|
554
|
+
[QUERY_SYMBOLS.QUERY]: {
|
547
555
|
get: {
|
548
556
|
[relatedModel.slug]: {
|
549
557
|
with: {
|
550
558
|
// Compare the `id` field of the related model to the link field on
|
551
559
|
// the root model (`field.slug`).
|
552
560
|
id: {
|
553
|
-
[
|
561
|
+
[QUERY_SYMBOLS.EXPRESSION]: `${QUERY_SYMBOLS.FIELD_PARENT}${field.slug}`
|
554
562
|
}
|
555
563
|
}
|
556
564
|
}
|
@@ -578,12 +586,12 @@ var addDefaultModelPresets = (list, model) => {
|
|
578
586
|
instructions: {
|
579
587
|
including: {
|
580
588
|
[presetSlug]: {
|
581
|
-
[
|
589
|
+
[QUERY_SYMBOLS.QUERY]: {
|
582
590
|
get: {
|
583
591
|
[pluralSlug]: {
|
584
592
|
with: {
|
585
593
|
[childField.slug]: {
|
586
|
-
[
|
594
|
+
[QUERY_SYMBOLS.EXPRESSION]: `${QUERY_SYMBOLS.FIELD_PARENT}id`
|
587
595
|
}
|
588
596
|
}
|
589
597
|
}
|
@@ -610,7 +618,6 @@ var typesInSQLite = {
|
|
610
618
|
json: "TEXT"
|
611
619
|
};
|
612
620
|
var getFieldStatement = (models, model, field) => {
|
613
|
-
if (field.type === "group") return null;
|
614
621
|
let statement = `"${field.slug}" ${typesInSQLite[field.type]}`;
|
615
622
|
if (field.slug === "id") statement += " PRIMARY KEY";
|
616
623
|
if (field.unique === true) statement += " UNIQUE";
|
@@ -850,11 +857,11 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
|
|
850
857
|
statementParts.push(`OF (${fieldSelectors.join(", ")})`);
|
851
858
|
}
|
852
859
|
statementParts.push("ON", `"${existingModel.table}"`);
|
853
|
-
if (trigger.filter || trigger.effects.some((query2) => findInObject(query2,
|
860
|
+
if (trigger.filter || trigger.effects.some((query2) => findInObject(query2, QUERY_SYMBOLS.FIELD))) {
|
854
861
|
statementParts.push("FOR EACH ROW");
|
855
862
|
}
|
856
863
|
if (trigger.filter) {
|
857
|
-
const tableAlias = trigger.action === "DELETE" ?
|
864
|
+
const tableAlias = trigger.action === "DELETE" ? QUERY_SYMBOLS.FIELD_PARENT_OLD : QUERY_SYMBOLS.FIELD_PARENT_NEW;
|
858
865
|
const withStatement = handleWith(
|
859
866
|
models,
|
860
867
|
{ ...existingModel, tableAlias },
|
@@ -876,7 +883,7 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
|
|
876
883
|
}
|
877
884
|
dependencyStatements.push({ statement, params });
|
878
885
|
}
|
879
|
-
const field = `${
|
886
|
+
const field = `${QUERY_SYMBOLS.FIELD}${pluralType}`;
|
880
887
|
let json;
|
881
888
|
switch (action) {
|
882
889
|
case "create": {
|
@@ -940,7 +947,7 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
|
|
940
947
|
model: {
|
941
948
|
with: { slug: modelSlug },
|
942
949
|
to: {
|
943
|
-
[pluralType]: { [
|
950
|
+
[pluralType]: { [QUERY_SYMBOLS.EXPRESSION]: json }
|
944
951
|
}
|
945
952
|
}
|
946
953
|
}
|
@@ -1059,8 +1066,8 @@ var handleFor = (model, instructions) => {
|
|
1059
1066
|
if (arg !== null) {
|
1060
1067
|
findInObject(
|
1061
1068
|
replacedForFilter,
|
1062
|
-
|
1063
|
-
(match) => match.replace(
|
1069
|
+
QUERY_SYMBOLS.VALUE,
|
1070
|
+
(match) => match.replace(QUERY_SYMBOLS.VALUE, arg)
|
1064
1071
|
);
|
1065
1072
|
}
|
1066
1073
|
for (const subInstruction in replacedForFilter) {
|
@@ -1102,7 +1109,7 @@ var handleIncluding = (models, model, statementParams, instruction) => {
|
|
1102
1109
|
const relatedModel = getModelBySlug(models, queryModel);
|
1103
1110
|
let joinType = "LEFT";
|
1104
1111
|
let relatedTableSelector = `"${relatedModel.table}"`;
|
1105
|
-
const tableAlias =
|
1112
|
+
const tableAlias = composeIncludedTableAlias(ephemeralFieldSlug);
|
1106
1113
|
const single = queryModel !== relatedModel.pluralSlug;
|
1107
1114
|
if (!modifiableQueryInstructions?.with) {
|
1108
1115
|
joinType = "CROSS";
|
@@ -1183,36 +1190,70 @@ var handleOrderedBy = (model, instruction) => {
|
|
1183
1190
|
};
|
1184
1191
|
|
1185
1192
|
// src/instructions/selecting.ts
|
1186
|
-
var handleSelecting = (model, statementParams, instructions) => {
|
1193
|
+
var handleSelecting = (models, model, statementParams, instructions, options) => {
|
1194
|
+
let loadedFields = [];
|
1195
|
+
let statement = "*";
|
1187
1196
|
let isJoining = false;
|
1188
|
-
let statement = instructions.selecting ? instructions.selecting.map((slug) => {
|
1189
|
-
return getFieldFromModel(model, slug, "selecting").fieldSelector;
|
1190
|
-
}).join(", ") : "*";
|
1191
1197
|
if (instructions.including) {
|
1192
|
-
const
|
1198
|
+
const flatObject = flatten(instructions.including);
|
1199
|
+
instructions.including = {};
|
1200
|
+
for (const [key, value] of Object.entries(flatObject)) {
|
1193
1201
|
const symbol = getSymbol(value);
|
1194
|
-
if (symbol) {
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1198
|
-
|
1199
|
-
|
1200
|
-
|
1202
|
+
if (symbol?.type === "query") {
|
1203
|
+
isJoining = true;
|
1204
|
+
const { queryModel, queryInstructions } = splitQuery(symbol.value);
|
1205
|
+
const subQueryModel = getModelBySlug(models, queryModel);
|
1206
|
+
const tableName = composeIncludedTableAlias(key);
|
1207
|
+
const queryModelFields = queryInstructions?.selecting ? subQueryModel.fields.filter((field) => {
|
1208
|
+
return queryInstructions.selecting?.includes(field.slug);
|
1209
|
+
}) : subQueryModel.fields;
|
1210
|
+
for (const field of queryModelFields) {
|
1211
|
+
loadedFields.push({ ...field, parentField: key });
|
1212
|
+
if (options?.expandColumns) {
|
1213
|
+
const newValue2 = parseFieldExpression(
|
1214
|
+
{ ...subQueryModel, tableAlias: tableName },
|
1215
|
+
"including",
|
1216
|
+
`${QUERY_SYMBOLS.FIELD}${field.slug}`
|
1217
|
+
);
|
1218
|
+
instructions.including[`${tableName}.${field.slug}`] = newValue2;
|
1219
|
+
}
|
1201
1220
|
}
|
1221
|
+
continue;
|
1202
1222
|
}
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
return `(${value}) as "${key}"`;
|
1211
|
-
return `${prepareStatementValue(statementParams, value)} as "${key}"`;
|
1212
|
-
}).join(", ");
|
1223
|
+
let newValue = value;
|
1224
|
+
if (symbol?.type === "expression") {
|
1225
|
+
newValue = `(${parseFieldExpression(model, "including", symbol.value)})`;
|
1226
|
+
} else {
|
1227
|
+
newValue = prepareStatementValue(statementParams, value);
|
1228
|
+
}
|
1229
|
+
instructions.including[key] = newValue;
|
1213
1230
|
}
|
1214
1231
|
}
|
1215
|
-
|
1232
|
+
const expandColumns = isJoining && options?.expandColumns;
|
1233
|
+
if (expandColumns) {
|
1234
|
+
instructions.selecting = model.fields.map((field) => field.slug);
|
1235
|
+
}
|
1236
|
+
if (instructions.selecting) {
|
1237
|
+
const usableModel = expandColumns ? { ...model, tableAlias: model.tableAlias || model.table } : model;
|
1238
|
+
const selectedFields = [];
|
1239
|
+
statement = instructions.selecting.map((slug) => {
|
1240
|
+
const { field, fieldSelector } = getFieldFromModel(
|
1241
|
+
usableModel,
|
1242
|
+
slug,
|
1243
|
+
"selecting"
|
1244
|
+
);
|
1245
|
+
selectedFields.push(field);
|
1246
|
+
return fieldSelector;
|
1247
|
+
}).join(", ");
|
1248
|
+
loadedFields = [...selectedFields, ...loadedFields];
|
1249
|
+
} else {
|
1250
|
+
loadedFields = [...model.fields, ...loadedFields];
|
1251
|
+
}
|
1252
|
+
if (instructions.including && Object.keys(instructions.including).length > 0) {
|
1253
|
+
statement += ", ";
|
1254
|
+
statement += Object.entries(instructions.including).map(([key, value]) => `${value} as "${key}"`).join(", ");
|
1255
|
+
}
|
1256
|
+
return { columns: statement, isJoining, loadedFields };
|
1216
1257
|
};
|
1217
1258
|
|
1218
1259
|
// src/instructions/to.ts
|
@@ -1279,8 +1320,8 @@ var handleTo = (models, model, statementParams, queryType, dependencyStatements,
|
|
1279
1320
|
Object.assign(toInstruction, defaultFields);
|
1280
1321
|
for (const fieldSlug in toInstruction) {
|
1281
1322
|
const fieldValue = toInstruction[fieldSlug];
|
1282
|
-
const fieldDetails = getFieldFromModel(model, fieldSlug, "to");
|
1283
|
-
if (fieldDetails
|
1323
|
+
const fieldDetails = getFieldFromModel(model, fieldSlug, "to", false);
|
1324
|
+
if (fieldDetails?.field.type === "link" && fieldDetails.field.kind === "many") {
|
1284
1325
|
delete toInstruction[fieldSlug];
|
1285
1326
|
const associativeModelSlug = composeAssociationModelSlug(model, fieldDetails.field);
|
1286
1327
|
const composeStatement = (subQueryType, value) => {
|
@@ -1345,7 +1386,8 @@ var compileQueryInput = (defaultQuery, models, statementParams, options) => {
|
|
1345
1386
|
statementParams,
|
1346
1387
|
defaultQuery
|
1347
1388
|
);
|
1348
|
-
if (query === null)
|
1389
|
+
if (query === null)
|
1390
|
+
return { dependencies: [], main: dependencyStatements[0], loadedFields: [] };
|
1349
1391
|
const parsedQuery = splitQuery(query);
|
1350
1392
|
const { queryType, queryModel, queryInstructions } = parsedQuery;
|
1351
1393
|
const model = getModelBySlug(models, queryModel);
|
@@ -1355,10 +1397,16 @@ var compileQueryInput = (defaultQuery, models, statementParams, options) => {
|
|
1355
1397
|
if (instructions && Object.hasOwn(instructions, "for")) {
|
1356
1398
|
instructions = handleFor(model, instructions);
|
1357
1399
|
}
|
1358
|
-
const { columns, isJoining } = handleSelecting(
|
1359
|
-
|
1360
|
-
|
1361
|
-
|
1400
|
+
const { columns, isJoining, loadedFields } = handleSelecting(
|
1401
|
+
models,
|
1402
|
+
model,
|
1403
|
+
statementParams,
|
1404
|
+
{
|
1405
|
+
selecting: instructions?.selecting,
|
1406
|
+
including: instructions?.including
|
1407
|
+
},
|
1408
|
+
options
|
1409
|
+
);
|
1362
1410
|
let statement = "";
|
1363
1411
|
switch (queryType) {
|
1364
1412
|
case "get":
|
@@ -1478,7 +1526,8 @@ var compileQueryInput = (defaultQuery, models, statementParams, options) => {
|
|
1478
1526
|
if (returning) mainStatement.returning = true;
|
1479
1527
|
return {
|
1480
1528
|
dependencies: dependencyStatements,
|
1481
|
-
main: mainStatement
|
1529
|
+
main: mainStatement,
|
1530
|
+
loadedFields
|
1482
1531
|
};
|
1483
1532
|
};
|
1484
1533
|
|
@@ -1487,6 +1536,7 @@ var Transaction = class {
|
|
1487
1536
|
statements;
|
1488
1537
|
models = [];
|
1489
1538
|
queries;
|
1539
|
+
fields = [];
|
1490
1540
|
constructor(queries, options) {
|
1491
1541
|
const models = options?.models || [];
|
1492
1542
|
this.statements = this.compileQueries(queries, models, options);
|
@@ -1518,46 +1568,72 @@ var Transaction = class {
|
|
1518
1568
|
const result = compileQueryInput(
|
1519
1569
|
query,
|
1520
1570
|
modelListWithPresets,
|
1521
|
-
options?.inlineParams ? null : []
|
1571
|
+
options?.inlineParams ? null : [],
|
1572
|
+
{ expandColumns: options?.expandColumns }
|
1522
1573
|
);
|
1523
1574
|
dependencyStatements.push(...result.dependencies);
|
1524
1575
|
mainStatements.push(result.main);
|
1576
|
+
this.fields.push(result.loadedFields);
|
1525
1577
|
}
|
1526
1578
|
this.models = modelListWithPresets;
|
1527
1579
|
return [...dependencyStatements, ...mainStatements];
|
1528
1580
|
};
|
1529
|
-
|
1530
|
-
const
|
1531
|
-
for (
|
1532
|
-
const
|
1581
|
+
formatRow(fields, row) {
|
1582
|
+
const record = {};
|
1583
|
+
for (let index = 0; index < row.length; index++) {
|
1584
|
+
const value = row[index];
|
1585
|
+
const field = fields[index];
|
1586
|
+
let newSlug = field.slug;
|
1587
|
+
let newValue = value;
|
1588
|
+
const parentFieldSlug = field.parentField;
|
1589
|
+
if (parentFieldSlug) {
|
1590
|
+
newSlug = `${parentFieldSlug}.${field.slug}`;
|
1591
|
+
}
|
1533
1592
|
if (field.type === "json") {
|
1534
|
-
|
1535
|
-
continue;
|
1593
|
+
newValue = JSON.parse(value);
|
1536
1594
|
}
|
1537
|
-
|
1595
|
+
record[newSlug] = newValue;
|
1538
1596
|
}
|
1539
|
-
return expand(
|
1597
|
+
return expand(record);
|
1540
1598
|
}
|
1541
|
-
|
1599
|
+
/**
|
1600
|
+
* Format the results returned from the database into RONIN records.
|
1601
|
+
*
|
1602
|
+
* @param results - A list of results from the database, where each result is an array
|
1603
|
+
* of rows.
|
1604
|
+
* @param raw - By default, rows are expected to be arrays of values, which is how SQL
|
1605
|
+
* databases return rows by default. If the driver being used returns rows as objects
|
1606
|
+
* instead, this option should be set to `false`.
|
1607
|
+
*
|
1608
|
+
* @returns A list of formatted RONIN results, where each result is either a single
|
1609
|
+
* RONIN record, an array of RONIN records, or a RONIN count result.
|
1610
|
+
*/
|
1611
|
+
formatResults(results, raw = true) {
|
1542
1612
|
const relevantResults = results.filter((_, index) => {
|
1543
1613
|
return this.statements[index].returning;
|
1544
1614
|
});
|
1545
|
-
|
1615
|
+
const normalizedResults = raw ? relevantResults : relevantResults.map((rows) => {
|
1616
|
+
return rows.map((row) => {
|
1617
|
+
if (Array.isArray(row)) return row;
|
1618
|
+
if (row["COUNT(*)"]) return [row["COUNT(*)"]];
|
1619
|
+
return Object.values(row);
|
1620
|
+
});
|
1621
|
+
});
|
1622
|
+
return normalizedResults.map((rows, index) => {
|
1546
1623
|
const query = this.queries.at(-index);
|
1624
|
+
const fields = this.fields.at(-index);
|
1547
1625
|
const { queryType, queryModel, queryInstructions } = splitQuery(query);
|
1548
1626
|
const model = getModelBySlug(this.models, queryModel);
|
1549
1627
|
if (queryType === "count") {
|
1550
|
-
return { amount:
|
1628
|
+
return { amount: rows[0][0] };
|
1551
1629
|
}
|
1552
1630
|
const single = queryModel !== model.pluralSlug;
|
1553
1631
|
if (single) {
|
1554
|
-
return { record: this.
|
1632
|
+
return { record: this.formatRow(fields, rows[0]) };
|
1555
1633
|
}
|
1556
1634
|
const pageSize = queryInstructions?.limitedTo;
|
1557
1635
|
const output = {
|
1558
|
-
records:
|
1559
|
-
return this.formatRecord(model, resultItem);
|
1560
|
-
})
|
1636
|
+
records: rows.map((row) => this.formatRow(fields, row))
|
1561
1637
|
};
|
1562
1638
|
if (pageSize && output.records.length > 0) {
|
1563
1639
|
if (queryInstructions?.before || queryInstructions?.after) {
|
@@ -1585,6 +1661,8 @@ var Transaction = class {
|
|
1585
1661
|
};
|
1586
1662
|
var CLEAN_ROOT_MODEL = omit(ROOT_MODEL, ["system"]);
|
1587
1663
|
export {
|
1664
|
+
QUERY_SYMBOLS,
|
1588
1665
|
CLEAN_ROOT_MODEL as ROOT_MODEL,
|
1666
|
+
RoninError,
|
1589
1667
|
Transaction
|
1590
1668
|
};
|