@ronin/compiler 0.10.2 → 0.10.3-leo-ron-1083-experimental-215

Sign up to get free protection for your applications and to get access to all the features.
package/README.md CHANGED
@@ -70,6 +70,24 @@ executed and their results can be formatted by the compiler as well:
70
70
  const results: Array<Result> = transaction.prepareResults(rows);
71
71
  ```
72
72
 
73
+ #### Root Model
74
+
75
+ Before you can run any statements generated by the compiler that are altering the database schema, you need to create the table of the so-called "root model", which is used to store metadata for all other models.
76
+
77
+ This table is called `ronin_schema`, which mimics the default `sqlite_schema` table provided by SQLite. You can generate its respective SQL statements like so:
78
+
79
+ ```typescript
80
+ import { Transaction, ROOT_MODEL } from '@ronin/compiler';
81
+
82
+ const transaction = new Transaction([
83
+ {
84
+ create: { model: ROOT_MODEL }
85
+ }
86
+ ]);
87
+ ```
88
+
89
+ Afterward, run the statements located in `transaction.statements` to create the table for the root model. Once that is done, your database is prepared to run any statements generated by the compiler.
90
+
73
91
  #### Types
74
92
 
75
93
  In total, the following types are being exported:
@@ -103,7 +121,25 @@ new Transaction(queries, {
103
121
  // Instead of returning an array of parameters for every statement (which allows for
104
122
  // preventing SQL injections), all parameters are inlined directly into the SQL strings.
105
123
  // This option should only be used if the generated SQL will be manually verified.
106
- inlineParams: true
124
+ inlineParams: true,
125
+
126
+ // By default, in the generated SQL statements, the compiler does not alias columns if
127
+ // multiple different tables with the same column names are being joined. Only the table
128
+ // names themselves are aliased.
129
+ //
130
+ // This ensures the cleanest possible SQL statements in conjunction with the default
131
+ // behavior of SQL databases, where the result of a statement is a list (array) of
132
+ // values, which are inherently not prone to conflicts.
133
+ //
134
+ // If the driver being used instead returns an object for every row, the driver must
135
+ // 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.
137
+ //
138
+ // Drivers that return objects for rows offer this behavior as an option that is
139
+ // usually called "expand columns". If the driver being used does not offer such an
140
+ // option, you can instead activate the option in the compiler, which results in longer
141
+ // SQL statements because any duplicated column name is aliased.
142
+ expandColumns: true
107
143
  });
108
144
  ```
109
145
 
@@ -117,7 +153,7 @@ bun run dev
117
153
 
118
154
  Whenever you make a change to the source code, it will then automatically be transpiled again.
119
155
 
120
- ### Mental Model
156
+ ### Architecture
121
157
 
122
158
  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.
123
159
 
package/dist/index.d.ts CHANGED
@@ -5979,7 +5979,7 @@ interface Model<T extends Array<ModelField> = Array<ModelField>> {
5979
5979
  triggers?: Array<ModelTrigger<T>>;
5980
5980
  presets?: Array<ModelPreset>;
5981
5981
  }
5982
- type PublicModel<T extends Array<ModelField> = Array<ModelField>> = Omit<Partial<Model<T>>, 'slug' | 'identifiers' | 'system' | 'table' | 'tableAlias'> & {
5982
+ type PublicModel<T extends Array<ModelField> = Array<ModelField>> = Omit<Partial<Model<T>>, 'slug' | 'identifiers' | 'system' | 'tableAlias'> & {
5983
5983
  slug: Required<Model['slug']>;
5984
5984
  identifiers?: Partial<Model['identifiers']>;
5985
5985
  };
@@ -6005,13 +6005,22 @@ type AmountResult = {
6005
6005
  };
6006
6006
  type Result = SingleRecordResult | MultipleRecordResult | AmountResult;
6007
6007
 
6008
+ interface TransactionOptions {
6009
+ /** A list of models that already exist in the database. */
6010
+ models?: Array<PublicModel>;
6011
+ /**
6012
+ * Place statement parameters directly inside the statement strings instead of
6013
+ * separating them out into a dedicated `params` array.
6014
+ */
6015
+ inlineParams?: boolean;
6016
+ /** Alias column names that are duplicated when joining multiple tables. */
6017
+ expandColumns?: boolean;
6018
+ }
6008
6019
  declare class Transaction {
6009
6020
  statements: Array<Statement>;
6010
6021
  models: Array<Model>;
6011
6022
  private queries;
6012
- constructor(queries: Array<Query>, options?: Parameters<typeof this.compileQueries>[2] & {
6013
- models?: Array<PublicModel>;
6014
- });
6023
+ constructor(queries: Array<Query>, options?: TransactionOptions);
6015
6024
  /**
6016
6025
  * Composes SQL statements for the provided RONIN queries.
6017
6026
  *
@@ -6026,4 +6035,6 @@ declare class Transaction {
6026
6035
  prepareResults(results: Array<Array<Row>>): Array<Result>;
6027
6036
  }
6028
6037
 
6029
- export { type PublicModel as Model, type ModelField, type ModelIndex, type ModelPreset, type ModelTrigger, type Query, type Result, type Statement, Transaction };
6038
+ declare const CLEAN_ROOT_MODEL: PublicModel;
6039
+
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 };
package/dist/index.js CHANGED
@@ -65,6 +65,23 @@ var convertToCamelCase = (str) => {
65
65
  return sanitize(str).split(SPLIT_REGEX).map((part, index) => index === 0 ? part.toLowerCase() : capitalize(part)).join("");
66
66
  };
67
67
  var isObject = (value) => value != null && typeof value === "object" && Array.isArray(value) === false;
68
+ var getSymbol = (value) => {
69
+ if (!isObject(value)) return null;
70
+ const objectValue = value;
71
+ if (RONIN_MODEL_SYMBOLS.QUERY in objectValue) {
72
+ return {
73
+ type: "query",
74
+ value: objectValue[RONIN_MODEL_SYMBOLS.QUERY]
75
+ };
76
+ }
77
+ if (RONIN_MODEL_SYMBOLS.EXPRESSION in objectValue) {
78
+ return {
79
+ type: "expression",
80
+ value: objectValue[RONIN_MODEL_SYMBOLS.EXPRESSION]
81
+ };
82
+ }
83
+ return null;
84
+ };
68
85
  var findInObject = (obj, pattern, replacer) => {
69
86
  let found = false;
70
87
  for (const key in obj) {
@@ -85,14 +102,18 @@ var findInObject = (obj, pattern, replacer) => {
85
102
  var flatten = (obj, prefix = "", res = {}) => {
86
103
  for (const key in obj) {
87
104
  const path = prefix ? `${prefix}.${key}` : key;
88
- if (typeof obj[key] === "object" && obj[key] !== null) {
89
- flatten(obj[key], path, res);
105
+ const value = obj[key];
106
+ if (typeof value === "object" && value !== null && !getSymbol(value)) {
107
+ flatten(value, path, res);
90
108
  } else {
91
- res[path] = obj[key];
109
+ res[path] = value;
92
110
  }
93
111
  }
94
112
  return res;
95
113
  };
114
+ var omit = (obj, properties) => Object.fromEntries(
115
+ Object.entries(obj).filter(([key]) => !properties.includes(key))
116
+ );
96
117
  var expand = (obj) => {
97
118
  return Object.entries(obj).reduce((res, [key, val]) => {
98
119
  key.split(".").reduce((acc, part, i, arr) => {
@@ -275,23 +296,6 @@ var formatIdentifiers = ({ identifiers }, queryInstructions) => {
275
296
  [type]: newNestedInstructions
276
297
  };
277
298
  };
278
- var getSymbol = (value) => {
279
- if (!isObject(value)) return null;
280
- const objectValue = value;
281
- if (RONIN_MODEL_SYMBOLS.QUERY in objectValue) {
282
- return {
283
- type: "query",
284
- value: objectValue[RONIN_MODEL_SYMBOLS.QUERY]
285
- };
286
- }
287
- if (RONIN_MODEL_SYMBOLS.EXPRESSION in objectValue) {
288
- return {
289
- type: "expression",
290
- value: objectValue[RONIN_MODEL_SYMBOLS.EXPRESSION]
291
- };
292
- }
293
- return null;
294
- };
295
299
 
296
300
  // src/instructions/with.ts
297
301
  var getMatcher = (value, negative) => {
@@ -693,7 +697,7 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
693
697
  if (!(modelSlug && slug)) return query;
694
698
  const model = action === "create" && entity === "model" ? null : getModelBySlug(models, modelSlug);
695
699
  if (entity === "model") {
696
- let queryTypeDetails;
700
+ let queryTypeDetails = {};
697
701
  if (action === "create") {
698
702
  const newModel = jsonValue;
699
703
  const modelWithFields = addDefaultModelFields(newModel, true);
@@ -749,6 +753,8 @@ var transformMetaQuery = (models, dependencyStatements, statementParams, query)
749
753
  return handleSystemModel(models, dependencyStatements, "drop", systemModel);
750
754
  });
751
755
  }
756
+ const modelSlug2 = "to" in queryTypeDetails ? queryTypeDetails?.to?.slug : "with" in queryTypeDetails ? queryTypeDetails?.with?.slug : void 0;
757
+ if (modelSlug2 === "model") return null;
752
758
  const queryTypeAction = action === "create" ? "add" : action === "alter" ? "set" : "remove";
753
759
  return {
754
760
  [queryTypeAction]: {
@@ -1178,32 +1184,44 @@ var handleOrderedBy = (model, instruction) => {
1178
1184
  };
1179
1185
 
1180
1186
  // src/instructions/selecting.ts
1181
- var handleSelecting = (model, statementParams, instructions) => {
1187
+ var handleSelecting = (models, model, statementParams, instructions, options) => {
1182
1188
  let isJoining = false;
1183
1189
  let statement = instructions.selecting ? instructions.selecting.map((slug) => {
1184
1190
  return getFieldFromModel(model, slug, "selecting").fieldSelector;
1185
1191
  }).join(", ") : "*";
1186
1192
  if (instructions.including) {
1187
- const filteredObject = Object.entries(instructions.including).map(([key, value]) => {
1193
+ const filteredObject = Object.entries(instructions.including).flatMap(([key, value]) => {
1188
1194
  const symbol = getSymbol(value);
1189
- if (symbol) {
1190
- if (symbol.type === "query") {
1191
- isJoining = true;
1192
- return null;
1193
- }
1194
- if (symbol.type === "expression") {
1195
- value = parseFieldExpression(model, "including", symbol.value);
1196
- }
1195
+ if (symbol?.type === "query") {
1196
+ isJoining = true;
1197
+ if (!options?.expandColumns) return null;
1198
+ const { queryModel: queryModelSlug } = splitQuery(symbol.value);
1199
+ const queryModel = getModelBySlug(models, queryModelSlug);
1200
+ const tableName = `including_${key}`;
1201
+ const duplicatedFields = queryModel.fields.filter((field) => {
1202
+ if (field.type === "group") return null;
1203
+ return model.fields.some((modelField) => modelField.slug === field.slug);
1204
+ }).filter((item) => item !== null);
1205
+ return duplicatedFields.map((field) => ({
1206
+ key: `${tableName}.${field.slug}`,
1207
+ value: {
1208
+ [RONIN_MODEL_SYMBOLS.EXPRESSION]: `${RONIN_MODEL_SYMBOLS.FIELD}${field.slug}`
1209
+ }
1210
+ }));
1197
1211
  }
1198
- return [key, value];
1199
- }).filter((entry) => entry !== null);
1212
+ return { key, value };
1213
+ }).filter((entry) => entry !== null).map((entry) => [entry.key, entry.value]);
1200
1214
  const newObjectEntries = Object.entries(flatten(Object.fromEntries(filteredObject)));
1201
1215
  if (newObjectEntries.length > 0) {
1202
1216
  statement += ", ";
1203
1217
  statement += newObjectEntries.map(([key, value]) => {
1204
- if (typeof value === "string" && value.startsWith('"'))
1205
- return `(${value}) as "${key}"`;
1206
- return `${prepareStatementValue(statementParams, value)} as "${key}"`;
1218
+ const symbol = getSymbol(value);
1219
+ if (symbol?.type === "expression") {
1220
+ value = `(${parseFieldExpression(model, "including", symbol.value)})`;
1221
+ } else {
1222
+ value = prepareStatementValue(statementParams, value);
1223
+ }
1224
+ return `${value} as "${key}"`;
1207
1225
  }).join(", ");
1208
1226
  }
1209
1227
  }
@@ -1340,6 +1358,7 @@ var compileQueryInput = (defaultQuery, models, statementParams, options) => {
1340
1358
  statementParams,
1341
1359
  defaultQuery
1342
1360
  );
1361
+ if (query === null) return { dependencies: [], main: dependencyStatements[0] };
1343
1362
  const parsedQuery = splitQuery(query);
1344
1363
  const { queryType, queryModel, queryInstructions } = parsedQuery;
1345
1364
  const model = getModelBySlug(models, queryModel);
@@ -1349,10 +1368,16 @@ var compileQueryInput = (defaultQuery, models, statementParams, options) => {
1349
1368
  if (instructions && Object.hasOwn(instructions, "for")) {
1350
1369
  instructions = handleFor(model, instructions);
1351
1370
  }
1352
- const { columns, isJoining } = handleSelecting(model, statementParams, {
1353
- selecting: instructions?.selecting,
1354
- including: instructions?.including
1355
- });
1371
+ const { columns, isJoining } = handleSelecting(
1372
+ models,
1373
+ model,
1374
+ statementParams,
1375
+ {
1376
+ selecting: instructions?.selecting,
1377
+ including: instructions?.including
1378
+ },
1379
+ options
1380
+ );
1356
1381
  let statement = "";
1357
1382
  switch (queryType) {
1358
1383
  case "get":
@@ -1512,7 +1537,8 @@ var Transaction = class {
1512
1537
  const result = compileQueryInput(
1513
1538
  query,
1514
1539
  modelListWithPresets,
1515
- options?.inlineParams ? null : []
1540
+ options?.inlineParams ? null : [],
1541
+ { expandColumns: options?.expandColumns }
1516
1542
  );
1517
1543
  dependencyStatements.push(...result.dependencies);
1518
1544
  mainStatements.push(result.main);
@@ -1577,6 +1603,8 @@ var Transaction = class {
1577
1603
  });
1578
1604
  }
1579
1605
  };
1606
+ var CLEAN_ROOT_MODEL = omit(ROOT_MODEL, ["system"]);
1580
1607
  export {
1608
+ CLEAN_ROOT_MODEL as ROOT_MODEL,
1581
1609
  Transaction
1582
1610
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ronin/compiler",
3
- "version": "0.10.2",
3
+ "version": "0.10.3-leo-ron-1083-experimental-215",
4
4
  "type": "module",
5
5
  "description": "Compiles RONIN queries to SQL statements.",
6
6
  "publishConfig": {