@ronin/compiler 0.8.7 → 0.8.8-leo-ron-1083-experimental-191

Sign up to get free protection for your applications and to get access to all the features.
package/README.md CHANGED
@@ -39,30 +39,49 @@ You will just need to make sure that, once you [create a pull request](https://d
39
39
  The programmatic API of the RONIN compiler looks like this:
40
40
 
41
41
  ```typescript
42
- import {
43
- compileQueries,
44
-
45
- type Query,
46
- type Model,
47
- type Statement
48
- } from '@ronin/compiler';
49
-
50
- const queries: Array<Query> = [{
51
- get: {
52
- accounts: null
42
+ import { Transaction } from '@ronin/compiler';
43
+
44
+ const transaction = new Transaction([
45
+ {
46
+ create: { model: { slug: 'account' } }
47
+ },
48
+ {
49
+ get: { accounts: null }
53
50
  }
54
- }];
51
+ ]);
55
52
 
56
- const models: Array<Model> = [{
57
- slug: 'account'
58
- }];
59
-
60
- const statements: Array<Statement> = compileQueries(queries, models);
53
+ transaction.statements;
61
54
  // [{
62
55
  // statement: 'SELECT * FROM "accounts"',
63
56
  // params: [],
64
57
  // returning: true,
65
58
  // }]
59
+
60
+ transaction.models;
61
+ // [{
62
+ // name: 'Account',
63
+ // slug: 'account'
64
+ // ...
65
+ // }]
66
+ ```
67
+
68
+ #### Types
69
+
70
+ In total, the following types are being exported:
71
+
72
+ ```typescript
73
+ import type {
74
+ Query,
75
+
76
+ Model,
77
+ ModelField,
78
+ ModelIndex,
79
+ ModelTrigger,
80
+ ModelPreset,
81
+
82
+ Statement,
83
+ Result
84
+ } from '@ronin/compiler';
66
85
  ```
67
86
 
68
87
  #### Options
@@ -70,7 +89,12 @@ const statements: Array<Statement> = compileQueries(queries, models);
70
89
  To fine-tune the behavior of the compiler, you can pass the following options:
71
90
 
72
91
  ```typescript
73
- compileQueries(queries, models, {
92
+ new Transaction(queries, {
93
+ // A list of models that already existing inside the database.
94
+ models: [
95
+ { slug: 'account' }
96
+ ],
97
+
74
98
  // Instead of returning an array of parameters for every statement (which allows for
75
99
  // preventing SQL injections), all parameters are inlined directly into the SQL strings.
76
100
  // This option should only be used if the generated SQL will be manually verified.
@@ -88,6 +112,16 @@ bun run dev
88
112
 
89
113
  Whenever you make a change to the source code, it will then automatically be transpiled again.
90
114
 
115
+ ### Mental Model
116
+
117
+ The interface of creating new `Transaction` instances (thereby creating new transactions) was chosen in order to define the smallest workload unit that the compiler can operate on.
118
+
119
+ Just like for the database, a transaction for the compiler defines an [atomic operation](https://www.sqlite.org/lang_transaction.html) in which a list of queries can be executed serially, and where each query can rely on the changes made by a previous one. In order to facilitate this, a programmatic interface that clarifies the accumulation of in-memory state is required (class instances).
120
+
121
+ For example, if one query creates a new model, every query after it within the same transaction must be able to interact with the records of that model, or update the model itself, without roundtrips to the database, thereby requiring the accumulation of state while the transaction is being compiled.
122
+
123
+ Furthermore, since every database transaction causes a [lock](https://www.sqlite.org/lockingv3.html), the database is inherently not locked between the execution of multiple transactions, which could cause the compilation inputs (e.g. models) of a `Transaction` to no longer be up-to-date. If the inputs have changed, a `new Transaction` should therefore be created.
124
+
91
125
  ### Running Tests
92
126
 
93
127
  The RONIN compiler has 100% test coverage, which means that every single line of code is tested automatically, to ensure that any change to the source code doesn't cause a regression.
package/dist/index.d.ts CHANGED
@@ -5972,17 +5972,45 @@ type PublicModel<T extends Array<ModelField> = Array<ModelField>> = Omit<Partial
5972
5972
  identifiers?: Partial<Model['identifiers']>;
5973
5973
  };
5974
5974
 
5975
- /**
5976
- * Composes SQL statements for the provided RONIN queries.
5977
- *
5978
- * @param queries - The RONIN queries for which SQL statements should be composed.
5979
- * @param models - A list of models.
5980
- * @param options - Additional options to adjust the behavior of the statement generation.
5981
- *
5982
- * @returns The composed SQL statements.
5983
- */
5984
- declare const compileQueries: (queries: Array<Query>, models: Array<PublicModel>, options?: {
5985
- inlineParams?: boolean;
5986
- }) => Array<Statement>;
5975
+ type Row = Record<string, unknown>;
5976
+ type NativeRecord = Record<string, unknown> & {
5977
+ id: string;
5978
+ ronin: {
5979
+ createdAt: Date;
5980
+ updatedAt: Date;
5981
+ };
5982
+ };
5983
+ type SingleRecordResult = {
5984
+ record: NativeRecord | null;
5985
+ };
5986
+ type MultipleRecordResult = {
5987
+ records: Array<NativeRecord>;
5988
+ moreAfter?: string;
5989
+ moreBefore?: string;
5990
+ };
5991
+ type AmountResult = {
5992
+ amount: number;
5993
+ };
5994
+ type Result = SingleRecordResult | MultipleRecordResult | AmountResult;
5995
+
5996
+ declare class Transaction {
5997
+ statements: Array<Statement>;
5998
+ models: Array<PublicModel>;
5999
+ private queries;
6000
+ constructor(queries: Array<Query>, options?: Parameters<typeof this.compileQueries>[2] & {
6001
+ models?: Array<PublicModel>;
6002
+ });
6003
+ /**
6004
+ * Composes SQL statements for the provided RONIN queries.
6005
+ *
6006
+ * @param queries - The RONIN queries for which SQL statements should be composed.
6007
+ * @param models - A list of models.
6008
+ * @param options - Additional options to adjust the behavior of the statement generation.
6009
+ *
6010
+ * @returns The composed SQL statements.
6011
+ */
6012
+ private compileQueries;
6013
+ formatOutput(results: Array<Array<Row>>): Array<Result>;
6014
+ }
5987
6015
 
5988
- export { type PublicModel as Model, type ModelField, type ModelIndex, type ModelPreset, type ModelTrigger, type Query, type Statement, compileQueries };
6016
+ export { type PublicModel as Model, type ModelField, type ModelIndex, type ModelPreset, type ModelTrigger, type Query, type Result, type Statement, Transaction };
package/dist/index.js CHANGED
@@ -145,7 +145,8 @@ var composeFieldValues = (models, model, statementParams, instructionName, value
145
145
  );
146
146
  const collectStatementValue = options.type !== "fields";
147
147
  const symbol = getSymbol(value);
148
- let conditionValue = value;
148
+ const syntax = WITH_CONDITIONS[options.condition || "being"](value);
149
+ let conditionValue = syntax[1];
149
150
  if (symbol) {
150
151
  if (symbol?.type === "expression") {
151
152
  conditionValue = parseFieldExpression(
@@ -159,11 +160,11 @@ var composeFieldValues = (models, model, statementParams, instructionName, value
159
160
  conditionValue = `(${compileQueryInput(symbol.value, models, statementParams).main.statement})`;
160
161
  }
161
162
  } else if (collectStatementValue) {
162
- conditionValue = prepareStatementValue(statementParams, value);
163
+ conditionValue = prepareStatementValue(statementParams, conditionValue);
163
164
  }
164
165
  if (options.type === "fields") return conditionSelector;
165
166
  if (options.type === "values") return conditionValue;
166
- return `${conditionSelector} ${WITH_CONDITIONS[options.condition || "being"](conditionValue, value)}`;
167
+ return `${conditionSelector} ${syntax[0]} ${conditionValue}`;
167
168
  };
168
169
  var composeConditions = (models, model, statementParams, instructionName, value, options) => {
169
170
  const isNested = isObject(value) && Object.keys(value).length > 0;
@@ -293,18 +294,18 @@ var getMatcher = (value, negative) => {
293
294
  return "=";
294
295
  };
295
296
  var WITH_CONDITIONS = {
296
- being: (value, baseValue) => `${getMatcher(baseValue, false)} ${value}`,
297
- notBeing: (value, baseValue) => `${getMatcher(baseValue, true)} ${value}`,
298
- startingWith: (value) => `LIKE ${value}%`,
299
- notStartingWith: (value) => `NOT LIKE ${value}%`,
300
- endingWith: (value) => `LIKE %${value}`,
301
- notEndingWith: (value) => `NOT LIKE %${value}`,
302
- containing: (value) => `LIKE %${value}%`,
303
- notContaining: (value) => `NOT LIKE %${value}%`,
304
- greaterThan: (value) => `> ${value}`,
305
- greaterOrEqual: (value) => `>= ${value}`,
306
- lessThan: (value) => `< ${value}`,
307
- lessOrEqual: (value) => `<= ${value}`
297
+ being: (value) => [getMatcher(value, false), value],
298
+ notBeing: (value) => [getMatcher(value, true), value],
299
+ startingWith: (value) => ["LIKE", `${value}%`],
300
+ notStartingWith: (value) => ["NOT LIKE", `${value}%`],
301
+ endingWith: (value) => ["LIKE", `%${value}`],
302
+ notEndingWith: (value) => ["NOT LIKE", `%${value}`],
303
+ containing: (value) => ["LIKE", `%${value}%`],
304
+ notContaining: (value) => ["NOT LIKE", `%${value}%`],
305
+ greaterThan: (value) => [">", value],
306
+ greaterOrEqual: (value) => [">=", value],
307
+ lessThan: (value) => ["<", value],
308
+ lessOrEqual: (value) => ["<=", value]
308
309
  };
309
310
  var handleWith = (models, model, statementParams, instruction, parentModel) => {
310
311
  const subStatement = composeConditions(
@@ -684,7 +685,7 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
684
685
  return [pluralType2, formatModelEntity(type, list)];
685
686
  })
686
687
  );
687
- const columns = modelWithPresets.fields.map((field) => getFieldStatement(models, modelWithPresets, field)).filter(Boolean);
688
+ const columns = modelWithPresets.fields.map((field2) => getFieldStatement(models, modelWithPresets, field2)).filter(Boolean);
688
689
  dependencyStatements.push({
689
690
  statement: `${statement} (${columns.join(", ")})`,
690
691
  params: []
@@ -730,10 +731,10 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
730
731
  }
731
732
  if (entity === "field" && model) {
732
733
  if (action === "create") {
733
- const field = jsonValue;
734
- field.type = field.type || "string";
734
+ const field2 = jsonValue;
735
+ field2.type = field2.type || "string";
735
736
  dependencyStatements.push({
736
- statement: `${statement} ADD COLUMN ${getFieldStatement(models, model, field)}`,
737
+ statement: `${statement} ADD COLUMN ${getFieldStatement(models, model, field2)}`,
737
738
  params: []
738
739
  });
739
740
  } else if (action === "alter") {
@@ -757,15 +758,15 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
757
758
  const params = [];
758
759
  let statement2 = `${tableAction}${index?.unique ? " UNIQUE" : ""} INDEX "${indexName}"`;
759
760
  if (action === "create") {
760
- const columns = index.fields.map((field) => {
761
+ const columns = index.fields.map((field2) => {
761
762
  let fieldSelector = "";
762
- if ("slug" in field) {
763
- ({ fieldSelector } = getFieldFromModel(model, field.slug, "to"));
764
- } else if ("expression" in field) {
765
- fieldSelector = parseFieldExpression(model, "to", field.expression);
763
+ if ("slug" in field2) {
764
+ ({ fieldSelector } = getFieldFromModel(model, field2.slug, "to"));
765
+ } else if ("expression" in field2) {
766
+ fieldSelector = parseFieldExpression(model, "to", field2.expression);
766
767
  }
767
- if (field.collation) fieldSelector += ` COLLATE ${field.collation}`;
768
- if (field.order) fieldSelector += ` ${field.order}`;
768
+ if (field2.collation) fieldSelector += ` COLLATE ${field2.collation}`;
769
+ if (field2.order) fieldSelector += ` ${field2.order}`;
769
770
  return fieldSelector;
770
771
  });
771
772
  statement2 += ` ON "${tableName}" (${columns.join(", ")})`;
@@ -791,8 +792,8 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
791
792
  fields: ["action"]
792
793
  });
793
794
  }
794
- const fieldSelectors = trigger.fields.map((field) => {
795
- return getFieldFromModel(model, field.slug, "to").fieldSelector;
795
+ const fieldSelectors = trigger.fields.map((field2) => {
796
+ return getFieldFromModel(model, field2.slug, "to").fieldSelector;
796
797
  });
797
798
  statementParts.push(`OF (${fieldSelectors.join(", ")})`);
798
799
  }
@@ -824,10 +825,23 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
824
825
  dependencyStatements.push({ statement: statement2, params });
825
826
  }
826
827
  const pluralType = PLURAL_MODEL_ENTITIES[entity];
827
- const jsonAction = action === "create" ? "insert" : action === "alter" ? "patch" : "remove";
828
- let json = `json_${jsonAction}(${RONIN_MODEL_SYMBOLS.FIELD}${pluralType}, '$.${slug}'`;
829
- if (jsonValue) json += `, ${prepareStatementValue(statementParams, jsonValue)}`;
830
- json += ")";
828
+ const field = `${RONIN_MODEL_SYMBOLS.FIELD}${pluralType}`;
829
+ let json;
830
+ switch (action) {
831
+ case "create": {
832
+ const value = prepareStatementValue(statementParams, jsonValue);
833
+ json = `json_insert(${field}, '$.${slug}', ${value})`;
834
+ break;
835
+ }
836
+ case "alter": {
837
+ const value = prepareStatementValue(statementParams, jsonValue);
838
+ json = `json_set(${field}, '$.${slug}', json_patch(json_extract(${field}, '$.${slug}'), ${value}))`;
839
+ break;
840
+ }
841
+ case "drop": {
842
+ json = `json_remove(${field}, '$.${slug}')`;
843
+ }
844
+ }
831
845
  return {
832
846
  set: {
833
847
  model: {
@@ -1337,33 +1351,64 @@ var compileQueryInput = (query, models, statementParams, options) => {
1337
1351
  };
1338
1352
 
1339
1353
  // src/index.ts
1340
- var compileQueries = (queries, models, options) => {
1341
- const modelList = addSystemModels(models).map((model) => {
1342
- return addDefaultModelFields(model, true);
1343
- });
1344
- const modelListWithPresets = modelList.map((model) => {
1345
- return addDefaultModelPresets(modelList, model);
1346
- });
1347
- const dependencyStatements = [];
1348
- const mainStatements = [];
1349
- for (const query of queries) {
1350
- const statementValues = options?.inlineParams ? null : [];
1351
- const transformedQuery = transformMetaQuery(
1352
- modelListWithPresets,
1353
- dependencyStatements,
1354
- statementValues,
1355
- query
1356
- );
1357
- const result = compileQueryInput(
1358
- transformedQuery,
1359
- modelListWithPresets,
1360
- statementValues
1361
- );
1362
- dependencyStatements.push(...result.dependencies);
1363
- mainStatements.push(result.main);
1354
+ var Transaction = class {
1355
+ statements;
1356
+ models;
1357
+ queries;
1358
+ constructor(queries, options) {
1359
+ const models = options?.models || [];
1360
+ this.statements = this.compileQueries(queries, models, options);
1361
+ this.models = models;
1362
+ this.queries = queries;
1363
+ }
1364
+ /**
1365
+ * Composes SQL statements for the provided RONIN queries.
1366
+ *
1367
+ * @param queries - The RONIN queries for which SQL statements should be composed.
1368
+ * @param models - A list of models.
1369
+ * @param options - Additional options to adjust the behavior of the statement generation.
1370
+ *
1371
+ * @returns The composed SQL statements.
1372
+ */
1373
+ compileQueries = (queries, models, options) => {
1374
+ const modelList = addSystemModels(models).map((model) => {
1375
+ return addDefaultModelFields(model, true);
1376
+ });
1377
+ const modelListWithPresets = modelList.map((model) => {
1378
+ return addDefaultModelPresets(modelList, model);
1379
+ });
1380
+ const dependencyStatements = [];
1381
+ const mainStatements = [];
1382
+ for (const query of queries) {
1383
+ const statementValues = options?.inlineParams ? null : [];
1384
+ const transformedQuery = transformMetaQuery(
1385
+ modelListWithPresets,
1386
+ dependencyStatements,
1387
+ statementValues,
1388
+ query
1389
+ );
1390
+ const result = compileQueryInput(
1391
+ transformedQuery,
1392
+ modelListWithPresets,
1393
+ statementValues
1394
+ );
1395
+ dependencyStatements.push(...result.dependencies);
1396
+ mainStatements.push(result.main);
1397
+ }
1398
+ return [...dependencyStatements, ...mainStatements];
1399
+ };
1400
+ formatOutput(results) {
1401
+ return results.map((result, index) => {
1402
+ const query = this.queries.at(-index);
1403
+ const { queryType, queryModel } = splitQuery(query);
1404
+ const model = getModelBySlug(this.models, queryModel);
1405
+ const single = queryModel !== model.pluralSlug;
1406
+ if (single) return { record: result[0] };
1407
+ if (queryType === "count") return { amount: result[0] };
1408
+ return { records: result };
1409
+ });
1364
1410
  }
1365
- return [...dependencyStatements, ...mainStatements];
1366
1411
  };
1367
1412
  export {
1368
- compileQueries
1413
+ Transaction
1369
1414
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ronin/compiler",
3
- "version": "0.8.7",
3
+ "version": "0.8.8-leo-ron-1083-experimental-191",
4
4
  "type": "module",
5
5
  "description": "Compiles RONIN queries to SQL statements.",
6
6
  "publishConfig": {
@@ -33,6 +33,7 @@
33
33
  },
34
34
  "devDependencies": {
35
35
  "@biomejs/biome": "1.9.2",
36
+ "@ronin/engine": "0.0.2",
36
37
  "@types/bun": "1.1.10",
37
38
  "@types/title": "3.4.3",
38
39
  "tsup": "8.3.0",