@ronin/compiler 0.10.3 → 0.11.0-leo-ron-1083-experimental-226
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +8 -5
- package/dist/index.d.ts +50 -5
- package/dist/index.js +257 -145
- 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,12 +5984,16 @@ 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: {
|
5992
|
+
locked: boolean;
|
5991
5993
|
createdAt: Date;
|
5994
|
+
createdBy: string | null;
|
5992
5995
|
updatedAt: Date;
|
5996
|
+
updatedBy: string | null;
|
5993
5997
|
};
|
5994
5998
|
};
|
5995
5999
|
type SingleRecordResult = {
|
@@ -6005,6 +6009,45 @@ type AmountResult = {
|
|
6005
6009
|
};
|
6006
6010
|
type Result = SingleRecordResult | MultipleRecordResult | AmountResult;
|
6007
6011
|
|
6012
|
+
/**
|
6013
|
+
* A list of placeholders that can be located inside queries after those queries were
|
6014
|
+
* serialized into JSON objects.
|
6015
|
+
*
|
6016
|
+
* These placeholders are used to represent special keys and values. For example, if a
|
6017
|
+
* query is nested into a query, the nested query will be marked with `__RONIN_QUERY`,
|
6018
|
+
* which allows for distinguishing that nested query from an object of instructions.
|
6019
|
+
*/
|
6020
|
+
declare const QUERY_SYMBOLS: {
|
6021
|
+
readonly QUERY: "__RONIN_QUERY";
|
6022
|
+
readonly EXPRESSION: "__RONIN_EXPRESSION";
|
6023
|
+
readonly FIELD: "__RONIN_FIELD_";
|
6024
|
+
readonly FIELD_PARENT: "__RONIN_FIELD_PARENT_";
|
6025
|
+
readonly FIELD_PARENT_OLD: "__RONIN_FIELD_PARENT_OLD_";
|
6026
|
+
readonly FIELD_PARENT_NEW: "__RONIN_FIELD_PARENT_NEW_";
|
6027
|
+
readonly VALUE: "__RONIN_VALUE";
|
6028
|
+
};
|
6029
|
+
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';
|
6030
|
+
interface Issue {
|
6031
|
+
message: string;
|
6032
|
+
path: Array<string | number>;
|
6033
|
+
}
|
6034
|
+
interface Details {
|
6035
|
+
message: string;
|
6036
|
+
code: RoninErrorCode;
|
6037
|
+
field?: string;
|
6038
|
+
fields?: Array<string>;
|
6039
|
+
issues?: Array<Issue>;
|
6040
|
+
queries?: Array<Query> | null;
|
6041
|
+
}
|
6042
|
+
declare class RoninError extends Error {
|
6043
|
+
code: Details['code'];
|
6044
|
+
field?: Details['field'];
|
6045
|
+
fields?: Details['fields'];
|
6046
|
+
issues?: Details['issues'];
|
6047
|
+
queries?: Details['queries'];
|
6048
|
+
constructor(details: Details);
|
6049
|
+
}
|
6050
|
+
|
6008
6051
|
interface TransactionOptions {
|
6009
6052
|
/** A list of models that already exist in the database. */
|
6010
6053
|
models?: Array<PublicModel>;
|
@@ -6020,6 +6063,7 @@ declare class Transaction {
|
|
6020
6063
|
statements: Array<Statement>;
|
6021
6064
|
models: Array<Model>;
|
6022
6065
|
private queries;
|
6066
|
+
private fields;
|
6023
6067
|
constructor(queries: Array<Query>, options?: TransactionOptions);
|
6024
6068
|
/**
|
6025
6069
|
* Composes SQL statements for the provided RONIN queries.
|
@@ -6031,10 +6075,11 @@ declare class Transaction {
|
|
6031
6075
|
* @returns The composed SQL statements.
|
6032
6076
|
*/
|
6033
6077
|
private compileQueries;
|
6034
|
-
private
|
6035
|
-
|
6078
|
+
private formatRows;
|
6079
|
+
formatResults(results: Array<Array<RawRow>>, raw?: true): Array<Result>;
|
6080
|
+
formatResults(results: Array<Array<ObjectRow>>, raw?: false): Array<Result>;
|
6036
6081
|
}
|
6037
6082
|
|
6038
6083
|
declare const CLEAN_ROOT_MODEL: PublicModel;
|
6039
6084
|
|
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 };
|
6085
|
+
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;
|
@@ -108,6 +131,26 @@ var expand = (obj) => {
|
|
108
131
|
var getProperty = (obj, path) => {
|
109
132
|
return path.split(".").reduce((acc, key) => acc?.[key], obj);
|
110
133
|
};
|
134
|
+
var setProperty = (obj, path, value) => {
|
135
|
+
if (!obj) return setProperty({}, path, value);
|
136
|
+
if (!path || value === void 0) return obj;
|
137
|
+
const segments = path.split(/[.[\]]/g).filter((x) => !!x.trim());
|
138
|
+
const _set = (node) => {
|
139
|
+
if (segments.length > 1) {
|
140
|
+
const key = segments.shift();
|
141
|
+
const nextIsNum = !Number.isNaN(Number.parseInt(segments[0]));
|
142
|
+
if (typeof node[key] !== "object" || node[key] === null) {
|
143
|
+
node[key] = nextIsNum ? [] : {};
|
144
|
+
}
|
145
|
+
_set(node[key]);
|
146
|
+
} else {
|
147
|
+
node[segments[0]] = value;
|
148
|
+
}
|
149
|
+
};
|
150
|
+
const cloned = structuredClone(obj);
|
151
|
+
_set(cloned);
|
152
|
+
return cloned;
|
153
|
+
};
|
111
154
|
var splitQuery = (query) => {
|
112
155
|
const queryType = Object.keys(query)[0];
|
113
156
|
const queryModel = Object.keys(query[queryType])[0];
|
@@ -133,15 +176,15 @@ var prepareStatementValue = (statementParams, value) => {
|
|
133
176
|
};
|
134
177
|
var parseFieldExpression = (model, instructionName, expression, parentModel) => {
|
135
178
|
return expression.replace(RONIN_MODEL_FIELD_REGEX, (match) => {
|
136
|
-
let toReplace =
|
179
|
+
let toReplace = QUERY_SYMBOLS.FIELD;
|
137
180
|
let rootModel = model;
|
138
|
-
if (match.startsWith(
|
181
|
+
if (match.startsWith(QUERY_SYMBOLS.FIELD_PARENT)) {
|
139
182
|
rootModel = parentModel;
|
140
|
-
toReplace =
|
141
|
-
if (match.startsWith(
|
142
|
-
rootModel.tableAlias = toReplace =
|
143
|
-
} else if (match.startsWith(
|
144
|
-
rootModel.tableAlias = toReplace =
|
183
|
+
toReplace = QUERY_SYMBOLS.FIELD_PARENT;
|
184
|
+
if (match.startsWith(QUERY_SYMBOLS.FIELD_PARENT_OLD)) {
|
185
|
+
rootModel.tableAlias = toReplace = QUERY_SYMBOLS.FIELD_PARENT_OLD;
|
186
|
+
} else if (match.startsWith(QUERY_SYMBOLS.FIELD_PARENT_NEW)) {
|
187
|
+
rootModel.tableAlias = toReplace = QUERY_SYMBOLS.FIELD_PARENT_NEW;
|
145
188
|
}
|
146
189
|
}
|
147
190
|
const fieldSlug = match.replace(toReplace, "");
|
@@ -190,47 +233,52 @@ var composeConditions = (models, model, statementParams, instructionName, value,
|
|
190
233
|
return conditions.join(" AND ");
|
191
234
|
}
|
192
235
|
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
|
-
|
236
|
+
const childField = model.fields.some(({ slug }) => {
|
237
|
+
return slug.includes(".") && slug.split(".")[0] === options.fieldSlug;
|
238
|
+
});
|
239
|
+
if (!childField) {
|
240
|
+
const fieldDetails = getFieldFromModel(model, options.fieldSlug, instructionName);
|
241
|
+
const { field: modelField } = fieldDetails || {};
|
242
|
+
const consumeJSON = modelField?.type === "json" && instructionName === "to";
|
243
|
+
if (modelField && !(isObject(value) || Array.isArray(value)) || getSymbol(value) || consumeJSON) {
|
244
|
+
return composeFieldValues(
|
245
|
+
models,
|
246
|
+
model,
|
247
|
+
statementParams,
|
248
|
+
instructionName,
|
249
|
+
value,
|
250
|
+
{ ...options, fieldSlug: options.fieldSlug }
|
251
|
+
);
|
252
|
+
}
|
253
|
+
if (modelField?.type === "link" && isNested) {
|
254
|
+
const keys = Object.keys(value);
|
255
|
+
const values = Object.values(value);
|
256
|
+
let recordTarget;
|
257
|
+
if (keys.length === 1 && keys[0] === "id") {
|
258
|
+
recordTarget = values[0];
|
259
|
+
} else {
|
260
|
+
const relatedModel = getModelBySlug(models, modelField.target);
|
261
|
+
const subQuery = {
|
262
|
+
get: {
|
263
|
+
[relatedModel.slug]: {
|
264
|
+
with: value,
|
265
|
+
selecting: ["id"]
|
266
|
+
}
|
219
267
|
}
|
220
|
-
}
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
}
|
268
|
+
};
|
269
|
+
recordTarget = {
|
270
|
+
[QUERY_SYMBOLS.QUERY]: subQuery
|
271
|
+
};
|
272
|
+
}
|
273
|
+
return composeConditions(
|
274
|
+
models,
|
275
|
+
model,
|
276
|
+
statementParams,
|
277
|
+
instructionName,
|
278
|
+
recordTarget,
|
279
|
+
options
|
280
|
+
);
|
225
281
|
}
|
226
|
-
return composeConditions(
|
227
|
-
models,
|
228
|
-
model,
|
229
|
-
statementParams,
|
230
|
-
instructionName,
|
231
|
-
recordTarget,
|
232
|
-
options
|
233
|
-
);
|
234
282
|
}
|
235
283
|
}
|
236
284
|
if (isNested) {
|
@@ -278,23 +326,6 @@ var formatIdentifiers = ({ identifiers }, queryInstructions) => {
|
|
278
326
|
[type]: newNestedInstructions
|
279
327
|
};
|
280
328
|
};
|
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
329
|
|
299
330
|
// src/instructions/with.ts
|
300
331
|
var getMatcher = (value, negative) => {
|
@@ -347,7 +378,7 @@ var getModelBySlug = (models, slug) => {
|
|
347
378
|
};
|
348
379
|
var composeAssociationModelSlug = (model, field) => convertToCamelCase(`ronin_link_${model.slug}_${field.slug}`);
|
349
380
|
var getFieldSelector = (model, field, fieldPath, instructionName) => {
|
350
|
-
const symbol = model.tableAlias?.startsWith(
|
381
|
+
const symbol = model.tableAlias?.startsWith(QUERY_SYMBOLS.FIELD_PARENT) ? `${model.tableAlias.replace(QUERY_SYMBOLS.FIELD_PARENT, "").slice(0, -1)}.` : "";
|
351
382
|
const tablePrefix = symbol || (model.tableAlias ? `"${model.tableAlias}".` : "");
|
352
383
|
if (field.type === "json" && instructionName !== "to") {
|
353
384
|
const dotParts = fieldPath.split(".");
|
@@ -357,7 +388,7 @@ var getFieldSelector = (model, field, fieldPath, instructionName) => {
|
|
357
388
|
}
|
358
389
|
return `${tablePrefix}"${fieldPath}"`;
|
359
390
|
};
|
360
|
-
|
391
|
+
function getFieldFromModel(model, fieldPath, instructionName, shouldThrow = true) {
|
361
392
|
const errorPrefix = `Field "${fieldPath}" defined for \`${instructionName}\``;
|
362
393
|
const modelFields = model.fields || [];
|
363
394
|
let modelField;
|
@@ -375,16 +406,19 @@ var getFieldFromModel = (model, fieldPath, instructionName) => {
|
|
375
406
|
}
|
376
407
|
modelField = modelFields.find((field) => field.slug === fieldPath);
|
377
408
|
if (!modelField) {
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
409
|
+
if (shouldThrow) {
|
410
|
+
throw new RoninError({
|
411
|
+
message: `${errorPrefix} does not exist in model "${model.name}".`,
|
412
|
+
code: "FIELD_NOT_FOUND",
|
413
|
+
field: fieldPath,
|
414
|
+
queries: null
|
415
|
+
});
|
416
|
+
}
|
417
|
+
return null;
|
384
418
|
}
|
385
419
|
const fieldSelector = getFieldSelector(model, modelField, fieldPath, instructionName);
|
386
420
|
return { field: modelField, fieldSelector };
|
387
|
-
}
|
421
|
+
}
|
388
422
|
var slugToName = (slug) => {
|
389
423
|
const name = slug.replace(/([a-z])([A-Z])/g, "$1 $2");
|
390
424
|
return title(name);
|
@@ -440,11 +474,6 @@ var SYSTEM_FIELDS = [
|
|
440
474
|
slug: "id",
|
441
475
|
displayAs: "single-line"
|
442
476
|
},
|
443
|
-
{
|
444
|
-
name: "RONIN",
|
445
|
-
type: "group",
|
446
|
-
slug: "ronin"
|
447
|
-
},
|
448
477
|
{
|
449
478
|
name: "RONIN - Locked",
|
450
479
|
type: "boolean",
|
@@ -488,7 +517,6 @@ var ROOT_MODEL = {
|
|
488
517
|
{ slug: "pluralSlug", type: "string" },
|
489
518
|
{ slug: "idPrefix", type: "string" },
|
490
519
|
{ slug: "table", type: "string" },
|
491
|
-
{ slug: "identifiers", type: "group" },
|
492
520
|
{ slug: "identifiers.name", type: "string" },
|
493
521
|
{ slug: "identifiers.slug", type: "string" },
|
494
522
|
// Providing an empty object as a default value allows us to use `json_insert`
|
@@ -543,14 +571,14 @@ var addDefaultModelPresets = (list, model) => {
|
|
543
571
|
instructions: {
|
544
572
|
including: {
|
545
573
|
[field.slug]: {
|
546
|
-
[
|
574
|
+
[QUERY_SYMBOLS.QUERY]: {
|
547
575
|
get: {
|
548
576
|
[relatedModel.slug]: {
|
549
577
|
with: {
|
550
578
|
// Compare the `id` field of the related model to the link field on
|
551
579
|
// the root model (`field.slug`).
|
552
580
|
id: {
|
553
|
-
[
|
581
|
+
[QUERY_SYMBOLS.EXPRESSION]: `${QUERY_SYMBOLS.FIELD_PARENT}${field.slug}`
|
554
582
|
}
|
555
583
|
}
|
556
584
|
}
|
@@ -578,12 +606,12 @@ var addDefaultModelPresets = (list, model) => {
|
|
578
606
|
instructions: {
|
579
607
|
including: {
|
580
608
|
[presetSlug]: {
|
581
|
-
[
|
609
|
+
[QUERY_SYMBOLS.QUERY]: {
|
582
610
|
get: {
|
583
611
|
[pluralSlug]: {
|
584
612
|
with: {
|
585
613
|
[childField.slug]: {
|
586
|
-
[
|
614
|
+
[QUERY_SYMBOLS.EXPRESSION]: `${QUERY_SYMBOLS.FIELD_PARENT}id`
|
587
615
|
}
|
588
616
|
}
|
589
617
|
}
|
@@ -610,7 +638,6 @@ var typesInSQLite = {
|
|
610
638
|
json: "TEXT"
|
611
639
|
};
|
612
640
|
var getFieldStatement = (models, model, field) => {
|
613
|
-
if (field.type === "group") return null;
|
614
641
|
let statement = `"${field.slug}" ${typesInSQLite[field.type]}`;
|
615
642
|
if (field.slug === "id") statement += " PRIMARY KEY";
|
616
643
|
if (field.unique === true) statement += " UNIQUE";
|
@@ -850,11 +877,11 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
|
|
850
877
|
statementParts.push(`OF (${fieldSelectors.join(", ")})`);
|
851
878
|
}
|
852
879
|
statementParts.push("ON", `"${existingModel.table}"`);
|
853
|
-
if (trigger.filter || trigger.effects.some((query2) => findInObject(query2,
|
880
|
+
if (trigger.filter || trigger.effects.some((query2) => findInObject(query2, QUERY_SYMBOLS.FIELD))) {
|
854
881
|
statementParts.push("FOR EACH ROW");
|
855
882
|
}
|
856
883
|
if (trigger.filter) {
|
857
|
-
const tableAlias = trigger.action === "DELETE" ?
|
884
|
+
const tableAlias = trigger.action === "DELETE" ? QUERY_SYMBOLS.FIELD_PARENT_OLD : QUERY_SYMBOLS.FIELD_PARENT_NEW;
|
858
885
|
const withStatement = handleWith(
|
859
886
|
models,
|
860
887
|
{ ...existingModel, tableAlias },
|
@@ -876,7 +903,7 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
|
|
876
903
|
}
|
877
904
|
dependencyStatements.push({ statement, params });
|
878
905
|
}
|
879
|
-
const field = `${
|
906
|
+
const field = `${QUERY_SYMBOLS.FIELD}${pluralType}`;
|
880
907
|
let json;
|
881
908
|
switch (action) {
|
882
909
|
case "create": {
|
@@ -940,7 +967,7 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
|
|
940
967
|
model: {
|
941
968
|
with: { slug: modelSlug },
|
942
969
|
to: {
|
943
|
-
[pluralType]: { [
|
970
|
+
[pluralType]: { [QUERY_SYMBOLS.EXPRESSION]: json }
|
944
971
|
}
|
945
972
|
}
|
946
973
|
}
|
@@ -1059,8 +1086,8 @@ var handleFor = (model, instructions) => {
|
|
1059
1086
|
if (arg !== null) {
|
1060
1087
|
findInObject(
|
1061
1088
|
replacedForFilter,
|
1062
|
-
|
1063
|
-
(match) => match.replace(
|
1089
|
+
QUERY_SYMBOLS.VALUE,
|
1090
|
+
(match) => match.replace(QUERY_SYMBOLS.VALUE, arg)
|
1064
1091
|
);
|
1065
1092
|
}
|
1066
1093
|
for (const subInstruction in replacedForFilter) {
|
@@ -1102,7 +1129,7 @@ var handleIncluding = (models, model, statementParams, instruction) => {
|
|
1102
1129
|
const relatedModel = getModelBySlug(models, queryModel);
|
1103
1130
|
let joinType = "LEFT";
|
1104
1131
|
let relatedTableSelector = `"${relatedModel.table}"`;
|
1105
|
-
const tableAlias =
|
1132
|
+
const tableAlias = composeIncludedTableAlias(ephemeralFieldSlug);
|
1106
1133
|
const single = queryModel !== relatedModel.pluralSlug;
|
1107
1134
|
if (!modifiableQueryInstructions?.with) {
|
1108
1135
|
joinType = "CROSS";
|
@@ -1124,11 +1151,10 @@ var handleIncluding = (models, model, statementParams, instruction) => {
|
|
1124
1151
|
relatedTableSelector = `(${subSelect.main.statement})`;
|
1125
1152
|
}
|
1126
1153
|
statement += `${joinType} JOIN ${relatedTableSelector} as ${tableAlias}`;
|
1127
|
-
model.tableAlias = model.table;
|
1154
|
+
model.tableAlias = model.tableAlias || model.table;
|
1128
1155
|
if (joinType === "LEFT") {
|
1129
1156
|
if (!single) {
|
1130
1157
|
tableSubQuery = `SELECT * FROM "${model.table}" LIMIT 1`;
|
1131
|
-
model.tableAlias = `sub_${model.table}`;
|
1132
1158
|
}
|
1133
1159
|
const subStatement = composeConditions(
|
1134
1160
|
models,
|
@@ -1183,36 +1209,74 @@ var handleOrderedBy = (model, instruction) => {
|
|
1183
1209
|
};
|
1184
1210
|
|
1185
1211
|
// src/instructions/selecting.ts
|
1186
|
-
var handleSelecting = (model, statementParams, instructions) => {
|
1212
|
+
var handleSelecting = (models, model, statementParams, instructions, options) => {
|
1213
|
+
let loadedFields = [];
|
1214
|
+
let statement = "*";
|
1187
1215
|
let isJoining = false;
|
1188
|
-
let statement = instructions.selecting ? instructions.selecting.map((slug) => {
|
1189
|
-
return getFieldFromModel(model, slug, "selecting").fieldSelector;
|
1190
|
-
}).join(", ") : "*";
|
1191
1216
|
if (instructions.including) {
|
1192
|
-
const
|
1217
|
+
const flatObject = flatten(instructions.including);
|
1218
|
+
instructions.including = {};
|
1219
|
+
for (const [key, value] of Object.entries(flatObject)) {
|
1193
1220
|
const symbol = getSymbol(value);
|
1194
|
-
if (symbol) {
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1221
|
+
if (symbol?.type === "query") {
|
1222
|
+
isJoining = true;
|
1223
|
+
const { queryModel, queryInstructions } = splitQuery(symbol.value);
|
1224
|
+
const subQueryModel = getModelBySlug(models, queryModel);
|
1225
|
+
const tableAlias = composeIncludedTableAlias(key);
|
1226
|
+
const single = queryModel !== subQueryModel.pluralSlug;
|
1227
|
+
if (!single) {
|
1228
|
+
model.tableAlias = `sub_${model.table}`;
|
1198
1229
|
}
|
1199
|
-
|
1200
|
-
|
1230
|
+
const queryModelFields = queryInstructions?.selecting ? subQueryModel.fields.filter((field) => {
|
1231
|
+
return queryInstructions.selecting?.includes(field.slug);
|
1232
|
+
}) : subQueryModel.fields;
|
1233
|
+
for (const field of queryModelFields) {
|
1234
|
+
loadedFields.push({ ...field, parentField: key });
|
1235
|
+
if (options?.expandColumns) {
|
1236
|
+
const newValue2 = parseFieldExpression(
|
1237
|
+
{ ...subQueryModel, tableAlias },
|
1238
|
+
"including",
|
1239
|
+
`${QUERY_SYMBOLS.FIELD}${field.slug}`
|
1240
|
+
);
|
1241
|
+
instructions.including[`${tableAlias}.${field.slug}`] = newValue2;
|
1242
|
+
}
|
1201
1243
|
}
|
1244
|
+
continue;
|
1202
1245
|
}
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
return `(${value}) as "${key}"`;
|
1211
|
-
return `${prepareStatementValue(statementParams, value)} as "${key}"`;
|
1212
|
-
}).join(", ");
|
1246
|
+
let newValue = value;
|
1247
|
+
if (symbol?.type === "expression") {
|
1248
|
+
newValue = `(${parseFieldExpression(model, "including", symbol.value)})`;
|
1249
|
+
} else {
|
1250
|
+
newValue = prepareStatementValue(statementParams, value);
|
1251
|
+
}
|
1252
|
+
instructions.including[key] = newValue;
|
1213
1253
|
}
|
1214
1254
|
}
|
1215
|
-
|
1255
|
+
const expandColumns = isJoining && options?.expandColumns;
|
1256
|
+
if (expandColumns) {
|
1257
|
+
instructions.selecting = model.fields.map((field) => field.slug);
|
1258
|
+
}
|
1259
|
+
if (instructions.selecting) {
|
1260
|
+
const usableModel = expandColumns ? { ...model, tableAlias: model.tableAlias || model.table } : model;
|
1261
|
+
const selectedFields = [];
|
1262
|
+
statement = instructions.selecting.map((slug) => {
|
1263
|
+
const { field, fieldSelector } = getFieldFromModel(
|
1264
|
+
usableModel,
|
1265
|
+
slug,
|
1266
|
+
"selecting"
|
1267
|
+
);
|
1268
|
+
selectedFields.push(field);
|
1269
|
+
return fieldSelector;
|
1270
|
+
}).join(", ");
|
1271
|
+
loadedFields = [...selectedFields, ...loadedFields];
|
1272
|
+
} else {
|
1273
|
+
loadedFields = [...model.fields, ...loadedFields];
|
1274
|
+
}
|
1275
|
+
if (instructions.including && Object.keys(instructions.including).length > 0) {
|
1276
|
+
statement += ", ";
|
1277
|
+
statement += Object.entries(instructions.including).map(([key, value]) => `${value} as "${key}"`).join(", ");
|
1278
|
+
}
|
1279
|
+
return { columns: statement, isJoining, loadedFields };
|
1216
1280
|
};
|
1217
1281
|
|
1218
1282
|
// src/instructions/to.ts
|
@@ -1279,8 +1343,8 @@ var handleTo = (models, model, statementParams, queryType, dependencyStatements,
|
|
1279
1343
|
Object.assign(toInstruction, defaultFields);
|
1280
1344
|
for (const fieldSlug in toInstruction) {
|
1281
1345
|
const fieldValue = toInstruction[fieldSlug];
|
1282
|
-
const fieldDetails = getFieldFromModel(model, fieldSlug, "to");
|
1283
|
-
if (fieldDetails
|
1346
|
+
const fieldDetails = getFieldFromModel(model, fieldSlug, "to", false);
|
1347
|
+
if (fieldDetails?.field.type === "link" && fieldDetails.field.kind === "many") {
|
1284
1348
|
delete toInstruction[fieldSlug];
|
1285
1349
|
const associativeModelSlug = composeAssociationModelSlug(model, fieldDetails.field);
|
1286
1350
|
const composeStatement = (subQueryType, value) => {
|
@@ -1345,7 +1409,8 @@ var compileQueryInput = (defaultQuery, models, statementParams, options) => {
|
|
1345
1409
|
statementParams,
|
1346
1410
|
defaultQuery
|
1347
1411
|
);
|
1348
|
-
if (query === null)
|
1412
|
+
if (query === null)
|
1413
|
+
return { dependencies: [], main: dependencyStatements[0], loadedFields: [] };
|
1349
1414
|
const parsedQuery = splitQuery(query);
|
1350
1415
|
const { queryType, queryModel, queryInstructions } = parsedQuery;
|
1351
1416
|
const model = getModelBySlug(models, queryModel);
|
@@ -1355,10 +1420,16 @@ var compileQueryInput = (defaultQuery, models, statementParams, options) => {
|
|
1355
1420
|
if (instructions && Object.hasOwn(instructions, "for")) {
|
1356
1421
|
instructions = handleFor(model, instructions);
|
1357
1422
|
}
|
1358
|
-
const { columns, isJoining } = handleSelecting(
|
1359
|
-
|
1360
|
-
|
1361
|
-
|
1423
|
+
const { columns, isJoining, loadedFields } = handleSelecting(
|
1424
|
+
models,
|
1425
|
+
model,
|
1426
|
+
statementParams,
|
1427
|
+
{
|
1428
|
+
selecting: instructions?.selecting,
|
1429
|
+
including: instructions?.including
|
1430
|
+
},
|
1431
|
+
options
|
1432
|
+
);
|
1362
1433
|
let statement = "";
|
1363
1434
|
switch (queryType) {
|
1364
1435
|
case "get":
|
@@ -1478,7 +1549,8 @@ var compileQueryInput = (defaultQuery, models, statementParams, options) => {
|
|
1478
1549
|
if (returning) mainStatement.returning = true;
|
1479
1550
|
return {
|
1480
1551
|
dependencies: dependencyStatements,
|
1481
|
-
main: mainStatement
|
1552
|
+
main: mainStatement,
|
1553
|
+
loadedFields
|
1482
1554
|
};
|
1483
1555
|
};
|
1484
1556
|
|
@@ -1487,6 +1559,7 @@ var Transaction = class {
|
|
1487
1559
|
statements;
|
1488
1560
|
models = [];
|
1489
1561
|
queries;
|
1562
|
+
fields = [];
|
1490
1563
|
constructor(queries, options) {
|
1491
1564
|
const models = options?.models || [];
|
1492
1565
|
this.statements = this.compileQueries(queries, models, options);
|
@@ -1518,46 +1591,83 @@ var Transaction = class {
|
|
1518
1591
|
const result = compileQueryInput(
|
1519
1592
|
query,
|
1520
1593
|
modelListWithPresets,
|
1521
|
-
options?.inlineParams ? null : []
|
1594
|
+
options?.inlineParams ? null : [],
|
1595
|
+
{ expandColumns: options?.expandColumns }
|
1522
1596
|
);
|
1523
1597
|
dependencyStatements.push(...result.dependencies);
|
1524
1598
|
mainStatements.push(result.main);
|
1599
|
+
this.fields.push(result.loadedFields);
|
1525
1600
|
}
|
1526
1601
|
this.models = modelListWithPresets;
|
1527
1602
|
return [...dependencyStatements, ...mainStatements];
|
1528
1603
|
};
|
1529
|
-
|
1530
|
-
const
|
1531
|
-
for (
|
1532
|
-
const
|
1533
|
-
|
1534
|
-
|
1535
|
-
|
1604
|
+
formatRows(fields, rows, single) {
|
1605
|
+
const records = [];
|
1606
|
+
for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
|
1607
|
+
const row = rows[rowIndex];
|
1608
|
+
for (let valueIndex = 0; valueIndex < row.length; valueIndex++) {
|
1609
|
+
const value = row[valueIndex];
|
1610
|
+
const field = fields[valueIndex];
|
1611
|
+
let newSlug = field.slug;
|
1612
|
+
let newValue = value;
|
1613
|
+
if (field.type === "json") {
|
1614
|
+
newValue = JSON.parse(value);
|
1615
|
+
} else if (field.type === "boolean") {
|
1616
|
+
newValue = Boolean(value);
|
1617
|
+
}
|
1618
|
+
const parentFieldSlug = field.parentField;
|
1619
|
+
if (parentFieldSlug) {
|
1620
|
+
if (rows.length === 1) {
|
1621
|
+
newSlug = `${parentFieldSlug}.${field.slug}`;
|
1622
|
+
} else {
|
1623
|
+
const fieldPath = `${parentFieldSlug}[${rowIndex}].${field.slug}`;
|
1624
|
+
records[0] = setProperty(records[0], fieldPath, newValue);
|
1625
|
+
continue;
|
1626
|
+
}
|
1627
|
+
}
|
1628
|
+
records[rowIndex] = setProperty(records[rowIndex], newSlug, newValue);
|
1536
1629
|
}
|
1537
|
-
formattedRecord[key] = record[key];
|
1538
1630
|
}
|
1539
|
-
return
|
1631
|
+
return single ? records[0] : records;
|
1540
1632
|
}
|
1541
|
-
|
1633
|
+
/**
|
1634
|
+
* Format the results returned from the database into RONIN records.
|
1635
|
+
*
|
1636
|
+
* @param results - A list of results from the database, where each result is an array
|
1637
|
+
* of rows.
|
1638
|
+
* @param raw - By default, rows are expected to be arrays of values, which is how SQL
|
1639
|
+
* databases return rows by default. If the driver being used returns rows as objects
|
1640
|
+
* instead, this option should be set to `false`.
|
1641
|
+
*
|
1642
|
+
* @returns A list of formatted RONIN results, where each result is either a single
|
1643
|
+
* RONIN record, an array of RONIN records, or a RONIN count result.
|
1644
|
+
*/
|
1645
|
+
formatResults(results, raw = true) {
|
1542
1646
|
const relevantResults = results.filter((_, index) => {
|
1543
1647
|
return this.statements[index].returning;
|
1544
1648
|
});
|
1545
|
-
|
1649
|
+
const normalizedResults = raw ? relevantResults : relevantResults.map((rows) => {
|
1650
|
+
return rows.map((row) => {
|
1651
|
+
if (Array.isArray(row)) return row;
|
1652
|
+
if (row["COUNT(*)"]) return [row["COUNT(*)"]];
|
1653
|
+
return Object.values(row);
|
1654
|
+
});
|
1655
|
+
});
|
1656
|
+
return normalizedResults.map((rows, index) => {
|
1546
1657
|
const query = this.queries.at(-index);
|
1658
|
+
const fields = this.fields.at(-index);
|
1547
1659
|
const { queryType, queryModel, queryInstructions } = splitQuery(query);
|
1548
1660
|
const model = getModelBySlug(this.models, queryModel);
|
1549
1661
|
if (queryType === "count") {
|
1550
|
-
return { amount:
|
1662
|
+
return { amount: rows[0][0] };
|
1551
1663
|
}
|
1552
1664
|
const single = queryModel !== model.pluralSlug;
|
1553
1665
|
if (single) {
|
1554
|
-
return { record: this.
|
1666
|
+
return { record: rows[0] ? this.formatRows(fields, rows, single) : null };
|
1555
1667
|
}
|
1556
1668
|
const pageSize = queryInstructions?.limitedTo;
|
1557
1669
|
const output = {
|
1558
|
-
records:
|
1559
|
-
return this.formatRecord(model, resultItem);
|
1560
|
-
})
|
1670
|
+
records: this.formatRows(fields, rows, single)
|
1561
1671
|
};
|
1562
1672
|
if (pageSize && output.records.length > 0) {
|
1563
1673
|
if (queryInstructions?.before || queryInstructions?.after) {
|
@@ -1585,6 +1695,8 @@ var Transaction = class {
|
|
1585
1695
|
};
|
1586
1696
|
var CLEAN_ROOT_MODEL = omit(ROOT_MODEL, ["system"]);
|
1587
1697
|
export {
|
1698
|
+
QUERY_SYMBOLS,
|
1588
1699
|
CLEAN_ROOT_MODEL as ROOT_MODEL,
|
1700
|
+
RoninError,
|
1589
1701
|
Transaction
|
1590
1702
|
};
|