@ronin/compiler 0.10.3 → 0.11.0-leo-ron-1083-experimental-226
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 +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
|
};
|