@ronin/compiler 0.10.3 → 0.11.0-leo-ron-1083-experimental-225
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 +50 -5
- package/dist/index.js +263 -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,32 @@ var expand = (obj) => {
|
|
108
131
|
var getProperty = (obj, path) => {
|
109
132
|
return path.split(".").reduce((acc, key) => acc?.[key], obj);
|
110
133
|
};
|
134
|
+
var toInt = (value, defaultValue) => {
|
135
|
+
const def = defaultValue === void 0 ? 0 : defaultValue;
|
136
|
+
if (value === null || value === void 0) return def;
|
137
|
+
const result = Number.parseInt(value);
|
138
|
+
return Number.isNaN(result) ? def : result;
|
139
|
+
};
|
140
|
+
var setProperty = (initial, path, value) => {
|
141
|
+
if (!initial) return setProperty({}, path, value);
|
142
|
+
if (!path || value === void 0) return initial;
|
143
|
+
const segments = path.split(/[.[\]]/g).filter((x) => !!x.trim());
|
144
|
+
const _set = (node) => {
|
145
|
+
if (segments.length > 1) {
|
146
|
+
const key = segments.shift();
|
147
|
+
const nextIsNum = toInt(segments[0], null) !== null;
|
148
|
+
if (typeof node[key] !== "object" || node[key] === null) {
|
149
|
+
node[key] = nextIsNum ? [] : {};
|
150
|
+
}
|
151
|
+
_set(node[key]);
|
152
|
+
} else {
|
153
|
+
node[segments[0]] = value;
|
154
|
+
}
|
155
|
+
};
|
156
|
+
const cloned = structuredClone(initial);
|
157
|
+
_set(cloned);
|
158
|
+
return cloned;
|
159
|
+
};
|
111
160
|
var splitQuery = (query) => {
|
112
161
|
const queryType = Object.keys(query)[0];
|
113
162
|
const queryModel = Object.keys(query[queryType])[0];
|
@@ -133,15 +182,15 @@ var prepareStatementValue = (statementParams, value) => {
|
|
133
182
|
};
|
134
183
|
var parseFieldExpression = (model, instructionName, expression, parentModel) => {
|
135
184
|
return expression.replace(RONIN_MODEL_FIELD_REGEX, (match) => {
|
136
|
-
let toReplace =
|
185
|
+
let toReplace = QUERY_SYMBOLS.FIELD;
|
137
186
|
let rootModel = model;
|
138
|
-
if (match.startsWith(
|
187
|
+
if (match.startsWith(QUERY_SYMBOLS.FIELD_PARENT)) {
|
139
188
|
rootModel = parentModel;
|
140
|
-
toReplace =
|
141
|
-
if (match.startsWith(
|
142
|
-
rootModel.tableAlias = toReplace =
|
143
|
-
} else if (match.startsWith(
|
144
|
-
rootModel.tableAlias = toReplace =
|
189
|
+
toReplace = QUERY_SYMBOLS.FIELD_PARENT;
|
190
|
+
if (match.startsWith(QUERY_SYMBOLS.FIELD_PARENT_OLD)) {
|
191
|
+
rootModel.tableAlias = toReplace = QUERY_SYMBOLS.FIELD_PARENT_OLD;
|
192
|
+
} else if (match.startsWith(QUERY_SYMBOLS.FIELD_PARENT_NEW)) {
|
193
|
+
rootModel.tableAlias = toReplace = QUERY_SYMBOLS.FIELD_PARENT_NEW;
|
145
194
|
}
|
146
195
|
}
|
147
196
|
const fieldSlug = match.replace(toReplace, "");
|
@@ -190,47 +239,52 @@ var composeConditions = (models, model, statementParams, instructionName, value,
|
|
190
239
|
return conditions.join(" AND ");
|
191
240
|
}
|
192
241
|
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
|
-
|
242
|
+
const childField = model.fields.some(({ slug }) => {
|
243
|
+
return slug.includes(".") && slug.split(".")[0] === options.fieldSlug;
|
244
|
+
});
|
245
|
+
if (!childField) {
|
246
|
+
const fieldDetails = getFieldFromModel(model, options.fieldSlug, instructionName);
|
247
|
+
const { field: modelField } = fieldDetails || {};
|
248
|
+
const consumeJSON = modelField?.type === "json" && instructionName === "to";
|
249
|
+
if (modelField && !(isObject(value) || Array.isArray(value)) || getSymbol(value) || consumeJSON) {
|
250
|
+
return composeFieldValues(
|
251
|
+
models,
|
252
|
+
model,
|
253
|
+
statementParams,
|
254
|
+
instructionName,
|
255
|
+
value,
|
256
|
+
{ ...options, fieldSlug: options.fieldSlug }
|
257
|
+
);
|
258
|
+
}
|
259
|
+
if (modelField?.type === "link" && isNested) {
|
260
|
+
const keys = Object.keys(value);
|
261
|
+
const values = Object.values(value);
|
262
|
+
let recordTarget;
|
263
|
+
if (keys.length === 1 && keys[0] === "id") {
|
264
|
+
recordTarget = values[0];
|
265
|
+
} else {
|
266
|
+
const relatedModel = getModelBySlug(models, modelField.target);
|
267
|
+
const subQuery = {
|
268
|
+
get: {
|
269
|
+
[relatedModel.slug]: {
|
270
|
+
with: value,
|
271
|
+
selecting: ["id"]
|
272
|
+
}
|
219
273
|
}
|
220
|
-
}
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
}
|
274
|
+
};
|
275
|
+
recordTarget = {
|
276
|
+
[QUERY_SYMBOLS.QUERY]: subQuery
|
277
|
+
};
|
278
|
+
}
|
279
|
+
return composeConditions(
|
280
|
+
models,
|
281
|
+
model,
|
282
|
+
statementParams,
|
283
|
+
instructionName,
|
284
|
+
recordTarget,
|
285
|
+
options
|
286
|
+
);
|
225
287
|
}
|
226
|
-
return composeConditions(
|
227
|
-
models,
|
228
|
-
model,
|
229
|
-
statementParams,
|
230
|
-
instructionName,
|
231
|
-
recordTarget,
|
232
|
-
options
|
233
|
-
);
|
234
288
|
}
|
235
289
|
}
|
236
290
|
if (isNested) {
|
@@ -278,23 +332,6 @@ var formatIdentifiers = ({ identifiers }, queryInstructions) => {
|
|
278
332
|
[type]: newNestedInstructions
|
279
333
|
};
|
280
334
|
};
|
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
335
|
|
299
336
|
// src/instructions/with.ts
|
300
337
|
var getMatcher = (value, negative) => {
|
@@ -347,7 +384,7 @@ var getModelBySlug = (models, slug) => {
|
|
347
384
|
};
|
348
385
|
var composeAssociationModelSlug = (model, field) => convertToCamelCase(`ronin_link_${model.slug}_${field.slug}`);
|
349
386
|
var getFieldSelector = (model, field, fieldPath, instructionName) => {
|
350
|
-
const symbol = model.tableAlias?.startsWith(
|
387
|
+
const symbol = model.tableAlias?.startsWith(QUERY_SYMBOLS.FIELD_PARENT) ? `${model.tableAlias.replace(QUERY_SYMBOLS.FIELD_PARENT, "").slice(0, -1)}.` : "";
|
351
388
|
const tablePrefix = symbol || (model.tableAlias ? `"${model.tableAlias}".` : "");
|
352
389
|
if (field.type === "json" && instructionName !== "to") {
|
353
390
|
const dotParts = fieldPath.split(".");
|
@@ -357,7 +394,7 @@ var getFieldSelector = (model, field, fieldPath, instructionName) => {
|
|
357
394
|
}
|
358
395
|
return `${tablePrefix}"${fieldPath}"`;
|
359
396
|
};
|
360
|
-
|
397
|
+
function getFieldFromModel(model, fieldPath, instructionName, shouldThrow = true) {
|
361
398
|
const errorPrefix = `Field "${fieldPath}" defined for \`${instructionName}\``;
|
362
399
|
const modelFields = model.fields || [];
|
363
400
|
let modelField;
|
@@ -375,16 +412,19 @@ var getFieldFromModel = (model, fieldPath, instructionName) => {
|
|
375
412
|
}
|
376
413
|
modelField = modelFields.find((field) => field.slug === fieldPath);
|
377
414
|
if (!modelField) {
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
415
|
+
if (shouldThrow) {
|
416
|
+
throw new RoninError({
|
417
|
+
message: `${errorPrefix} does not exist in model "${model.name}".`,
|
418
|
+
code: "FIELD_NOT_FOUND",
|
419
|
+
field: fieldPath,
|
420
|
+
queries: null
|
421
|
+
});
|
422
|
+
}
|
423
|
+
return null;
|
384
424
|
}
|
385
425
|
const fieldSelector = getFieldSelector(model, modelField, fieldPath, instructionName);
|
386
426
|
return { field: modelField, fieldSelector };
|
387
|
-
}
|
427
|
+
}
|
388
428
|
var slugToName = (slug) => {
|
389
429
|
const name = slug.replace(/([a-z])([A-Z])/g, "$1 $2");
|
390
430
|
return title(name);
|
@@ -440,11 +480,6 @@ var SYSTEM_FIELDS = [
|
|
440
480
|
slug: "id",
|
441
481
|
displayAs: "single-line"
|
442
482
|
},
|
443
|
-
{
|
444
|
-
name: "RONIN",
|
445
|
-
type: "group",
|
446
|
-
slug: "ronin"
|
447
|
-
},
|
448
483
|
{
|
449
484
|
name: "RONIN - Locked",
|
450
485
|
type: "boolean",
|
@@ -488,7 +523,6 @@ var ROOT_MODEL = {
|
|
488
523
|
{ slug: "pluralSlug", type: "string" },
|
489
524
|
{ slug: "idPrefix", type: "string" },
|
490
525
|
{ slug: "table", type: "string" },
|
491
|
-
{ slug: "identifiers", type: "group" },
|
492
526
|
{ slug: "identifiers.name", type: "string" },
|
493
527
|
{ slug: "identifiers.slug", type: "string" },
|
494
528
|
// Providing an empty object as a default value allows us to use `json_insert`
|
@@ -543,14 +577,14 @@ var addDefaultModelPresets = (list, model) => {
|
|
543
577
|
instructions: {
|
544
578
|
including: {
|
545
579
|
[field.slug]: {
|
546
|
-
[
|
580
|
+
[QUERY_SYMBOLS.QUERY]: {
|
547
581
|
get: {
|
548
582
|
[relatedModel.slug]: {
|
549
583
|
with: {
|
550
584
|
// Compare the `id` field of the related model to the link field on
|
551
585
|
// the root model (`field.slug`).
|
552
586
|
id: {
|
553
|
-
[
|
587
|
+
[QUERY_SYMBOLS.EXPRESSION]: `${QUERY_SYMBOLS.FIELD_PARENT}${field.slug}`
|
554
588
|
}
|
555
589
|
}
|
556
590
|
}
|
@@ -578,12 +612,12 @@ var addDefaultModelPresets = (list, model) => {
|
|
578
612
|
instructions: {
|
579
613
|
including: {
|
580
614
|
[presetSlug]: {
|
581
|
-
[
|
615
|
+
[QUERY_SYMBOLS.QUERY]: {
|
582
616
|
get: {
|
583
617
|
[pluralSlug]: {
|
584
618
|
with: {
|
585
619
|
[childField.slug]: {
|
586
|
-
[
|
620
|
+
[QUERY_SYMBOLS.EXPRESSION]: `${QUERY_SYMBOLS.FIELD_PARENT}id`
|
587
621
|
}
|
588
622
|
}
|
589
623
|
}
|
@@ -610,7 +644,6 @@ var typesInSQLite = {
|
|
610
644
|
json: "TEXT"
|
611
645
|
};
|
612
646
|
var getFieldStatement = (models, model, field) => {
|
613
|
-
if (field.type === "group") return null;
|
614
647
|
let statement = `"${field.slug}" ${typesInSQLite[field.type]}`;
|
615
648
|
if (field.slug === "id") statement += " PRIMARY KEY";
|
616
649
|
if (field.unique === true) statement += " UNIQUE";
|
@@ -850,11 +883,11 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
|
|
850
883
|
statementParts.push(`OF (${fieldSelectors.join(", ")})`);
|
851
884
|
}
|
852
885
|
statementParts.push("ON", `"${existingModel.table}"`);
|
853
|
-
if (trigger.filter || trigger.effects.some((query2) => findInObject(query2,
|
886
|
+
if (trigger.filter || trigger.effects.some((query2) => findInObject(query2, QUERY_SYMBOLS.FIELD))) {
|
854
887
|
statementParts.push("FOR EACH ROW");
|
855
888
|
}
|
856
889
|
if (trigger.filter) {
|
857
|
-
const tableAlias = trigger.action === "DELETE" ?
|
890
|
+
const tableAlias = trigger.action === "DELETE" ? QUERY_SYMBOLS.FIELD_PARENT_OLD : QUERY_SYMBOLS.FIELD_PARENT_NEW;
|
858
891
|
const withStatement = handleWith(
|
859
892
|
models,
|
860
893
|
{ ...existingModel, tableAlias },
|
@@ -876,7 +909,7 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
|
|
876
909
|
}
|
877
910
|
dependencyStatements.push({ statement, params });
|
878
911
|
}
|
879
|
-
const field = `${
|
912
|
+
const field = `${QUERY_SYMBOLS.FIELD}${pluralType}`;
|
880
913
|
let json;
|
881
914
|
switch (action) {
|
882
915
|
case "create": {
|
@@ -940,7 +973,7 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
|
|
940
973
|
model: {
|
941
974
|
with: { slug: modelSlug },
|
942
975
|
to: {
|
943
|
-
[pluralType]: { [
|
976
|
+
[pluralType]: { [QUERY_SYMBOLS.EXPRESSION]: json }
|
944
977
|
}
|
945
978
|
}
|
946
979
|
}
|
@@ -1059,8 +1092,8 @@ var handleFor = (model, instructions) => {
|
|
1059
1092
|
if (arg !== null) {
|
1060
1093
|
findInObject(
|
1061
1094
|
replacedForFilter,
|
1062
|
-
|
1063
|
-
(match) => match.replace(
|
1095
|
+
QUERY_SYMBOLS.VALUE,
|
1096
|
+
(match) => match.replace(QUERY_SYMBOLS.VALUE, arg)
|
1064
1097
|
);
|
1065
1098
|
}
|
1066
1099
|
for (const subInstruction in replacedForFilter) {
|
@@ -1102,7 +1135,7 @@ var handleIncluding = (models, model, statementParams, instruction) => {
|
|
1102
1135
|
const relatedModel = getModelBySlug(models, queryModel);
|
1103
1136
|
let joinType = "LEFT";
|
1104
1137
|
let relatedTableSelector = `"${relatedModel.table}"`;
|
1105
|
-
const tableAlias =
|
1138
|
+
const tableAlias = composeIncludedTableAlias(ephemeralFieldSlug);
|
1106
1139
|
const single = queryModel !== relatedModel.pluralSlug;
|
1107
1140
|
if (!modifiableQueryInstructions?.with) {
|
1108
1141
|
joinType = "CROSS";
|
@@ -1124,11 +1157,10 @@ var handleIncluding = (models, model, statementParams, instruction) => {
|
|
1124
1157
|
relatedTableSelector = `(${subSelect.main.statement})`;
|
1125
1158
|
}
|
1126
1159
|
statement += `${joinType} JOIN ${relatedTableSelector} as ${tableAlias}`;
|
1127
|
-
model.tableAlias = model.table;
|
1160
|
+
model.tableAlias = model.tableAlias || model.table;
|
1128
1161
|
if (joinType === "LEFT") {
|
1129
1162
|
if (!single) {
|
1130
1163
|
tableSubQuery = `SELECT * FROM "${model.table}" LIMIT 1`;
|
1131
|
-
model.tableAlias = `sub_${model.table}`;
|
1132
1164
|
}
|
1133
1165
|
const subStatement = composeConditions(
|
1134
1166
|
models,
|
@@ -1183,36 +1215,74 @@ var handleOrderedBy = (model, instruction) => {
|
|
1183
1215
|
};
|
1184
1216
|
|
1185
1217
|
// src/instructions/selecting.ts
|
1186
|
-
var handleSelecting = (model, statementParams, instructions) => {
|
1218
|
+
var handleSelecting = (models, model, statementParams, instructions, options) => {
|
1219
|
+
let loadedFields = [];
|
1220
|
+
let statement = "*";
|
1187
1221
|
let isJoining = false;
|
1188
|
-
let statement = instructions.selecting ? instructions.selecting.map((slug) => {
|
1189
|
-
return getFieldFromModel(model, slug, "selecting").fieldSelector;
|
1190
|
-
}).join(", ") : "*";
|
1191
1222
|
if (instructions.including) {
|
1192
|
-
const
|
1223
|
+
const flatObject = flatten(instructions.including);
|
1224
|
+
instructions.including = {};
|
1225
|
+
for (const [key, value] of Object.entries(flatObject)) {
|
1193
1226
|
const symbol = getSymbol(value);
|
1194
|
-
if (symbol) {
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1227
|
+
if (symbol?.type === "query") {
|
1228
|
+
isJoining = true;
|
1229
|
+
const { queryModel, queryInstructions } = splitQuery(symbol.value);
|
1230
|
+
const subQueryModel = getModelBySlug(models, queryModel);
|
1231
|
+
const tableAlias = composeIncludedTableAlias(key);
|
1232
|
+
const single = queryModel !== subQueryModel.pluralSlug;
|
1233
|
+
if (!single) {
|
1234
|
+
model.tableAlias = `sub_${model.table}`;
|
1198
1235
|
}
|
1199
|
-
|
1200
|
-
|
1236
|
+
const queryModelFields = queryInstructions?.selecting ? subQueryModel.fields.filter((field) => {
|
1237
|
+
return queryInstructions.selecting?.includes(field.slug);
|
1238
|
+
}) : subQueryModel.fields;
|
1239
|
+
for (const field of queryModelFields) {
|
1240
|
+
loadedFields.push({ ...field, parentField: key });
|
1241
|
+
if (options?.expandColumns) {
|
1242
|
+
const newValue2 = parseFieldExpression(
|
1243
|
+
{ ...subQueryModel, tableAlias },
|
1244
|
+
"including",
|
1245
|
+
`${QUERY_SYMBOLS.FIELD}${field.slug}`
|
1246
|
+
);
|
1247
|
+
instructions.including[`${tableAlias}.${field.slug}`] = newValue2;
|
1248
|
+
}
|
1201
1249
|
}
|
1250
|
+
continue;
|
1202
1251
|
}
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
return `(${value}) as "${key}"`;
|
1211
|
-
return `${prepareStatementValue(statementParams, value)} as "${key}"`;
|
1212
|
-
}).join(", ");
|
1252
|
+
let newValue = value;
|
1253
|
+
if (symbol?.type === "expression") {
|
1254
|
+
newValue = `(${parseFieldExpression(model, "including", symbol.value)})`;
|
1255
|
+
} else {
|
1256
|
+
newValue = prepareStatementValue(statementParams, value);
|
1257
|
+
}
|
1258
|
+
instructions.including[key] = newValue;
|
1213
1259
|
}
|
1214
1260
|
}
|
1215
|
-
|
1261
|
+
const expandColumns = isJoining && options?.expandColumns;
|
1262
|
+
if (expandColumns) {
|
1263
|
+
instructions.selecting = model.fields.map((field) => field.slug);
|
1264
|
+
}
|
1265
|
+
if (instructions.selecting) {
|
1266
|
+
const usableModel = expandColumns ? { ...model, tableAlias: model.tableAlias || model.table } : model;
|
1267
|
+
const selectedFields = [];
|
1268
|
+
statement = instructions.selecting.map((slug) => {
|
1269
|
+
const { field, fieldSelector } = getFieldFromModel(
|
1270
|
+
usableModel,
|
1271
|
+
slug,
|
1272
|
+
"selecting"
|
1273
|
+
);
|
1274
|
+
selectedFields.push(field);
|
1275
|
+
return fieldSelector;
|
1276
|
+
}).join(", ");
|
1277
|
+
loadedFields = [...selectedFields, ...loadedFields];
|
1278
|
+
} else {
|
1279
|
+
loadedFields = [...model.fields, ...loadedFields];
|
1280
|
+
}
|
1281
|
+
if (instructions.including && Object.keys(instructions.including).length > 0) {
|
1282
|
+
statement += ", ";
|
1283
|
+
statement += Object.entries(instructions.including).map(([key, value]) => `${value} as "${key}"`).join(", ");
|
1284
|
+
}
|
1285
|
+
return { columns: statement, isJoining, loadedFields };
|
1216
1286
|
};
|
1217
1287
|
|
1218
1288
|
// src/instructions/to.ts
|
@@ -1279,8 +1349,8 @@ var handleTo = (models, model, statementParams, queryType, dependencyStatements,
|
|
1279
1349
|
Object.assign(toInstruction, defaultFields);
|
1280
1350
|
for (const fieldSlug in toInstruction) {
|
1281
1351
|
const fieldValue = toInstruction[fieldSlug];
|
1282
|
-
const fieldDetails = getFieldFromModel(model, fieldSlug, "to");
|
1283
|
-
if (fieldDetails
|
1352
|
+
const fieldDetails = getFieldFromModel(model, fieldSlug, "to", false);
|
1353
|
+
if (fieldDetails?.field.type === "link" && fieldDetails.field.kind === "many") {
|
1284
1354
|
delete toInstruction[fieldSlug];
|
1285
1355
|
const associativeModelSlug = composeAssociationModelSlug(model, fieldDetails.field);
|
1286
1356
|
const composeStatement = (subQueryType, value) => {
|
@@ -1345,7 +1415,8 @@ var compileQueryInput = (defaultQuery, models, statementParams, options) => {
|
|
1345
1415
|
statementParams,
|
1346
1416
|
defaultQuery
|
1347
1417
|
);
|
1348
|
-
if (query === null)
|
1418
|
+
if (query === null)
|
1419
|
+
return { dependencies: [], main: dependencyStatements[0], loadedFields: [] };
|
1349
1420
|
const parsedQuery = splitQuery(query);
|
1350
1421
|
const { queryType, queryModel, queryInstructions } = parsedQuery;
|
1351
1422
|
const model = getModelBySlug(models, queryModel);
|
@@ -1355,10 +1426,16 @@ var compileQueryInput = (defaultQuery, models, statementParams, options) => {
|
|
1355
1426
|
if (instructions && Object.hasOwn(instructions, "for")) {
|
1356
1427
|
instructions = handleFor(model, instructions);
|
1357
1428
|
}
|
1358
|
-
const { columns, isJoining } = handleSelecting(
|
1359
|
-
|
1360
|
-
|
1361
|
-
|
1429
|
+
const { columns, isJoining, loadedFields } = handleSelecting(
|
1430
|
+
models,
|
1431
|
+
model,
|
1432
|
+
statementParams,
|
1433
|
+
{
|
1434
|
+
selecting: instructions?.selecting,
|
1435
|
+
including: instructions?.including
|
1436
|
+
},
|
1437
|
+
options
|
1438
|
+
);
|
1362
1439
|
let statement = "";
|
1363
1440
|
switch (queryType) {
|
1364
1441
|
case "get":
|
@@ -1478,7 +1555,8 @@ var compileQueryInput = (defaultQuery, models, statementParams, options) => {
|
|
1478
1555
|
if (returning) mainStatement.returning = true;
|
1479
1556
|
return {
|
1480
1557
|
dependencies: dependencyStatements,
|
1481
|
-
main: mainStatement
|
1558
|
+
main: mainStatement,
|
1559
|
+
loadedFields
|
1482
1560
|
};
|
1483
1561
|
};
|
1484
1562
|
|
@@ -1487,6 +1565,7 @@ var Transaction = class {
|
|
1487
1565
|
statements;
|
1488
1566
|
models = [];
|
1489
1567
|
queries;
|
1568
|
+
fields = [];
|
1490
1569
|
constructor(queries, options) {
|
1491
1570
|
const models = options?.models || [];
|
1492
1571
|
this.statements = this.compileQueries(queries, models, options);
|
@@ -1518,46 +1597,83 @@ var Transaction = class {
|
|
1518
1597
|
const result = compileQueryInput(
|
1519
1598
|
query,
|
1520
1599
|
modelListWithPresets,
|
1521
|
-
options?.inlineParams ? null : []
|
1600
|
+
options?.inlineParams ? null : [],
|
1601
|
+
{ expandColumns: options?.expandColumns }
|
1522
1602
|
);
|
1523
1603
|
dependencyStatements.push(...result.dependencies);
|
1524
1604
|
mainStatements.push(result.main);
|
1605
|
+
this.fields.push(result.loadedFields);
|
1525
1606
|
}
|
1526
1607
|
this.models = modelListWithPresets;
|
1527
1608
|
return [...dependencyStatements, ...mainStatements];
|
1528
1609
|
};
|
1529
|
-
|
1530
|
-
const
|
1531
|
-
for (
|
1532
|
-
const
|
1533
|
-
|
1534
|
-
|
1535
|
-
|
1610
|
+
formatRows(fields, rows, single) {
|
1611
|
+
const records = [];
|
1612
|
+
for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
|
1613
|
+
const row = rows[rowIndex];
|
1614
|
+
for (let valueIndex = 0; valueIndex < row.length; valueIndex++) {
|
1615
|
+
const value = row[valueIndex];
|
1616
|
+
const field = fields[valueIndex];
|
1617
|
+
let newSlug = field.slug;
|
1618
|
+
let newValue = value;
|
1619
|
+
if (field.type === "json") {
|
1620
|
+
newValue = JSON.parse(value);
|
1621
|
+
} else if (field.type === "boolean") {
|
1622
|
+
newValue = Boolean(value);
|
1623
|
+
}
|
1624
|
+
const parentFieldSlug = field.parentField;
|
1625
|
+
if (parentFieldSlug) {
|
1626
|
+
if (rows.length === 1) {
|
1627
|
+
newSlug = `${parentFieldSlug}.${field.slug}`;
|
1628
|
+
} else {
|
1629
|
+
const fieldPath = `${parentFieldSlug}[${rowIndex}].${field.slug}`;
|
1630
|
+
records[0] = setProperty(records[0], fieldPath, newValue);
|
1631
|
+
continue;
|
1632
|
+
}
|
1633
|
+
}
|
1634
|
+
records[rowIndex] = setProperty(records[rowIndex], newSlug, newValue);
|
1536
1635
|
}
|
1537
|
-
formattedRecord[key] = record[key];
|
1538
1636
|
}
|
1539
|
-
return
|
1637
|
+
return single ? records[0] : records;
|
1540
1638
|
}
|
1541
|
-
|
1639
|
+
/**
|
1640
|
+
* Format the results returned from the database into RONIN records.
|
1641
|
+
*
|
1642
|
+
* @param results - A list of results from the database, where each result is an array
|
1643
|
+
* of rows.
|
1644
|
+
* @param raw - By default, rows are expected to be arrays of values, which is how SQL
|
1645
|
+
* databases return rows by default. If the driver being used returns rows as objects
|
1646
|
+
* instead, this option should be set to `false`.
|
1647
|
+
*
|
1648
|
+
* @returns A list of formatted RONIN results, where each result is either a single
|
1649
|
+
* RONIN record, an array of RONIN records, or a RONIN count result.
|
1650
|
+
*/
|
1651
|
+
formatResults(results, raw = true) {
|
1542
1652
|
const relevantResults = results.filter((_, index) => {
|
1543
1653
|
return this.statements[index].returning;
|
1544
1654
|
});
|
1545
|
-
|
1655
|
+
const normalizedResults = raw ? relevantResults : relevantResults.map((rows) => {
|
1656
|
+
return rows.map((row) => {
|
1657
|
+
if (Array.isArray(row)) return row;
|
1658
|
+
if (row["COUNT(*)"]) return [row["COUNT(*)"]];
|
1659
|
+
return Object.values(row);
|
1660
|
+
});
|
1661
|
+
});
|
1662
|
+
return normalizedResults.map((rows, index) => {
|
1546
1663
|
const query = this.queries.at(-index);
|
1664
|
+
const fields = this.fields.at(-index);
|
1547
1665
|
const { queryType, queryModel, queryInstructions } = splitQuery(query);
|
1548
1666
|
const model = getModelBySlug(this.models, queryModel);
|
1549
1667
|
if (queryType === "count") {
|
1550
|
-
return { amount:
|
1668
|
+
return { amount: rows[0][0] };
|
1551
1669
|
}
|
1552
1670
|
const single = queryModel !== model.pluralSlug;
|
1553
1671
|
if (single) {
|
1554
|
-
return { record: this.
|
1672
|
+
return { record: rows[0] ? this.formatRows(fields, rows, single) : null };
|
1555
1673
|
}
|
1556
1674
|
const pageSize = queryInstructions?.limitedTo;
|
1557
1675
|
const output = {
|
1558
|
-
records:
|
1559
|
-
return this.formatRecord(model, resultItem);
|
1560
|
-
})
|
1676
|
+
records: this.formatRows(fields, rows, single)
|
1561
1677
|
};
|
1562
1678
|
if (pageSize && output.records.length > 0) {
|
1563
1679
|
if (queryInstructions?.before || queryInstructions?.after) {
|
@@ -1585,6 +1701,8 @@ var Transaction = class {
|
|
1585
1701
|
};
|
1586
1702
|
var CLEAN_ROOT_MODEL = omit(ROOT_MODEL, ["system"]);
|
1587
1703
|
export {
|
1704
|
+
QUERY_SYMBOLS,
|
1588
1705
|
CLEAN_ROOT_MODEL as ROOT_MODEL,
|
1706
|
+
RoninError,
|
1589
1707
|
Transaction
|
1590
1708
|
};
|