@an5/orm 1.0.9 → 1.1.1

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.
Files changed (57) hide show
  1. package/README.md +10 -3
  2. package/dist/config.d.ts +9 -1
  3. package/dist/config.js +13 -18
  4. package/dist/generator/src/api.d.ts +35 -0
  5. package/dist/generator/src/api.js +58 -0
  6. package/dist/generator/src/config.d.ts +162 -0
  7. package/dist/generator/src/config.js +354 -0
  8. package/dist/generator/src/dialect.d.ts +88 -0
  9. package/dist/generator/src/dialect.js +318 -0
  10. package/dist/generator/src/dotnet-generator.d.ts +8 -0
  11. package/dist/generator/src/dotnet-generator.js +282 -101
  12. package/dist/generator/src/field-types.d.ts +96 -0
  13. package/dist/generator/src/field-types.js +433 -0
  14. package/dist/generator/src/golang-generator.d.ts +36 -0
  15. package/dist/generator/src/golang-generator.js +1230 -0
  16. package/dist/generator/src/index.js +69 -36
  17. package/dist/generator/src/metadata-generator.js +2 -1
  18. package/dist/generator/src/parser.d.ts +28 -2
  19. package/dist/generator/src/parser.js +92 -90
  20. package/dist/generator/src/push-apply.d.ts +37 -0
  21. package/dist/generator/src/push-apply.js +109 -0
  22. package/dist/generator/src/push-schema.d.ts +93 -0
  23. package/dist/generator/src/push-schema.js +189 -0
  24. package/dist/generator/src/python-generator.d.ts +19 -1
  25. package/dist/generator/src/python-generator.js +147 -64
  26. package/dist/generator/src/rust-generator.d.ts +38 -0
  27. package/dist/generator/src/rust-generator.js +971 -0
  28. package/dist/generator/src/suggest.d.ts +17 -0
  29. package/dist/generator/src/suggest.js +45 -0
  30. package/dist/generator/src/type-kinds.d.ts +27 -0
  31. package/dist/generator/src/type-kinds.js +72 -0
  32. package/dist/generator/src/types.d.ts +33 -0
  33. package/dist/generator/src/types.js +39 -0
  34. package/dist/migration-core.d.ts +16 -1
  35. package/dist/migration-core.js +60 -21
  36. package/dist/orm.d.ts +147 -0
  37. package/dist/orm.js +1654 -0
  38. package/dist/provider-support.d.ts +27 -0
  39. package/dist/provider-support.js +50 -0
  40. package/dist/sql-utils.js +8 -3
  41. package/generator/src/api.ts +45 -0
  42. package/generator/src/config.ts +496 -0
  43. package/generator/src/dialect.ts +415 -0
  44. package/generator/src/dotnet-generator.ts +277 -88
  45. package/generator/src/field-types.ts +473 -0
  46. package/generator/src/golang-generator.ts +94 -68
  47. package/generator/src/index.ts +71 -36
  48. package/generator/src/metadata-generator.ts +2 -2
  49. package/generator/src/parser.ts +122 -90
  50. package/generator/src/push-apply.ts +148 -0
  51. package/generator/src/push-schema.ts +250 -0
  52. package/generator/src/python-generator.ts +118 -62
  53. package/generator/src/rust-generator.ts +974 -0
  54. package/generator/src/suggest.ts +41 -0
  55. package/generator/src/type-kinds.ts +78 -0
  56. package/generator/src/types.ts +48 -0
  57. package/package.json +26 -6
@@ -0,0 +1,318 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sqlLiteral = sqlLiteral;
4
+ exports.dialectFor = dialectFor;
5
+ exports.defaultClause = defaultClause;
6
+ /** `schema.table` reduced to the bare object name, e.g. `dbo.widgets`. */
7
+ function objectName(raw) {
8
+ return raw.replace(/[\[\]"`]/g, '').split('.').filter(Boolean).join('.');
9
+ }
10
+ /** A possibly schema-qualified name split into parts, unquoting each. */
11
+ function splitQualified(raw) {
12
+ const parts = raw.replace(/[\[\]"`]/g, '').split('.').filter(Boolean);
13
+ if (parts.length >= 2) {
14
+ return { schema: parts[parts.length - 2], name: parts[parts.length - 1] };
15
+ }
16
+ return { schema: undefined, name: parts[0] ?? raw };
17
+ }
18
+ /**
19
+ * A single-quoted SQL string literal.
20
+ *
21
+ * Every provider doubles an embedded quote, so one helper covers them all. These
22
+ * values come from the schema files, so they are the project's own — but an
23
+ * apostrophe in a column name would otherwise close the literal and produce SQL
24
+ * that means something else entirely.
25
+ */
26
+ function sqlLiteral(value) {
27
+ return `'${value.replace(/'/g, "''")}'`;
28
+ }
29
+ /** Bracketed identifiers: SQL Server, and SQLite which accepts the same form. */
30
+ function bracketQuote(name) {
31
+ return `[${name.replace(/^\[(.*)\]$/, '$1').replace(/\]/g, ']]')}]`;
32
+ }
33
+ /** Double-quoted identifiers: the SQL standard, so PostgreSQL. */
34
+ function doubleQuote(name) {
35
+ return `"${name.replace(/^"(.*)"$/, '$1').replace(/"/g, '""')}"`;
36
+ }
37
+ /** Backtick identifiers: MySQL. */
38
+ function backtickQuote(name) {
39
+ return `\`${name.replace(/^`(.*)`$/, '$1').replace(/`/g, '``')}\``;
40
+ }
41
+ /**
42
+ * The schema an unqualified table lands in.
43
+ *
44
+ * `''` means "whatever the connection resolves", which is also what the DDL emits
45
+ * for an unqualified name — the two must agree or `db:push` cannot find the table it
46
+ * just created. PostgreSQL therefore queries `current_schema()` rather than
47
+ * hard-coding `public`: with the usual `search_path = "$user", public`, a table
48
+ * created unqualified lands in the role's own schema, and looking only in `public`
49
+ * made every push re-issue `CREATE TABLE` and fail with "relation already exists".
50
+ */
51
+ function defaultSchema(provider, schema) {
52
+ if (schema !== undefined)
53
+ return schema;
54
+ return provider === 'postgres' ? 'current_schema()' : '';
55
+ }
56
+ /**
57
+ * Where a table's existence is recorded.
58
+ *
59
+ * SQL Server needs `OBJECT_ID` because the query is by object, not by name; the
60
+ * others keep their metadata in a catalog view keyed by schema plus name.
61
+ */
62
+ function buildTableExists(provider) {
63
+ return (raw) => {
64
+ const { schema, name } = splitQualified(raw);
65
+ switch (provider) {
66
+ case 'mssql':
67
+ return `SELECT name FROM sys.tables WHERE object_id = OBJECT_ID(${sqlLiteral(objectName(raw))})`;
68
+ case 'postgres':
69
+ return `SELECT tablename FROM pg_tables WHERE schemaname = ${schema ? sqlLiteral(schema) : defaultSchema(provider, undefined)} AND tablename = ${sqlLiteral(name)}`;
70
+ case 'mysql':
71
+ // An unqualified table belongs to the database the connection selected.
72
+ return (`SELECT table_name FROM information_schema.tables WHERE table_schema = ${schema ? sqlLiteral(schema) : 'DATABASE()'} ` +
73
+ `AND table_name = ${sqlLiteral(name)}`);
74
+ case 'sqlite':
75
+ return `SELECT name FROM sqlite_master WHERE type = 'table' AND name = ${sqlLiteral(name)}`;
76
+ default:
77
+ return '';
78
+ }
79
+ };
80
+ }
81
+ function buildColumnExists(provider) {
82
+ return (raw, column) => {
83
+ const { schema, name } = splitQualified(raw);
84
+ const columnLiteral = sqlLiteral(column);
85
+ switch (provider) {
86
+ case 'mssql':
87
+ return `SELECT name FROM sys.columns WHERE object_id = OBJECT_ID(${sqlLiteral(objectName(raw))}) AND name = ${columnLiteral}`;
88
+ case 'postgres':
89
+ return (`SELECT column_name FROM information_schema.columns WHERE table_schema = ${schema ? sqlLiteral(schema) : defaultSchema(provider, undefined)} ` +
90
+ `AND table_name = ${sqlLiteral(name)} AND column_name = ${columnLiteral}`);
91
+ case 'mysql':
92
+ return (`SELECT column_name FROM information_schema.columns WHERE table_schema = ${schema ? sqlLiteral(schema) : 'DATABASE()'} ` +
93
+ `AND table_name = ${sqlLiteral(name)} AND column_name = ${columnLiteral}`);
94
+ case 'sqlite':
95
+ // A table-valued pragma, so this stays one round trip. Needs SQLite 3.16
96
+ // (2017) for `pragma_table_info` to be usable as a table function.
97
+ return `SELECT name FROM pragma_table_info(${sqlLiteral(name)}) WHERE name = ${columnLiteral}`;
98
+ default:
99
+ return '';
100
+ }
101
+ };
102
+ }
103
+ function buildIndexExists(provider) {
104
+ return (raw, name) => {
105
+ const { schema, name: table } = splitQualified(raw);
106
+ const tableLiteral = sqlLiteral(table);
107
+ const indexLiteral = sqlLiteral(name);
108
+ const schemaClause = schema ? sqlLiteral(schema) : 'DATABASE()';
109
+ switch (provider) {
110
+ case 'mssql':
111
+ return `SELECT name FROM sys.indexes WHERE object_id = OBJECT_ID(${sqlLiteral(objectName(raw))}) AND name = ${indexLiteral}`;
112
+ case 'postgres':
113
+ return (`SELECT indexname FROM pg_indexes WHERE schemaname = ${schema ? sqlLiteral(schema) : defaultSchema(provider, undefined)} ` +
114
+ `AND tablename = ${tableLiteral} AND indexname = ${indexLiteral}`);
115
+ case 'mysql':
116
+ return (`SELECT index_name FROM information_schema.statistics WHERE table_schema = ${schemaClause} ` +
117
+ `AND table_name = ${tableLiteral} AND index_name = ${indexLiteral} LIMIT 1`);
118
+ case 'sqlite':
119
+ return `SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = ${tableLiteral} AND name = ${indexLiteral}`;
120
+ default:
121
+ return '';
122
+ }
123
+ };
124
+ }
125
+ function buildUniqueConstraintExists(provider) {
126
+ return (raw, name) => {
127
+ const { schema, name: table } = splitQualified(raw);
128
+ const tableLiteral = sqlLiteral(table);
129
+ const constraintLiteral = sqlLiteral(name);
130
+ switch (provider) {
131
+ case 'mssql':
132
+ return `SELECT name FROM sys.objects WHERE type = 'UQ' AND parent_object_id = OBJECT_ID(${sqlLiteral(objectName(raw))}) AND name = ${constraintLiteral}`;
133
+ case 'postgres':
134
+ return (`SELECT con.conname FROM pg_constraint con ` +
135
+ `JOIN pg_class t ON t.oid = con.conrelid AND t.relname = ${tableLiteral} ` +
136
+ `JOIN pg_namespace n ON n.oid = t.relnamespace ` +
137
+ `WHERE con.contype = 'u' AND con.conname = ${constraintLiteral} ` +
138
+ `AND n.nspname = ${schema ? sqlLiteral(schema) : defaultSchema(provider, undefined)}`);
139
+ case 'mysql':
140
+ return (`SELECT constraint_name FROM information_schema.table_constraints ` +
141
+ `WHERE table_schema = ${schema ? sqlLiteral(schema) : 'DATABASE()'} AND table_name = ${tableLiteral} ` +
142
+ `AND constraint_name = ${constraintLiteral} AND constraint_type = 'UNIQUE'`);
143
+ case 'sqlite':
144
+ // No named constraints to look for; the unique index carries the name, so
145
+ // the index catalog answers for it.
146
+ return buildIndexExists(provider)(raw, name);
147
+ default:
148
+ return '';
149
+ }
150
+ };
151
+ }
152
+ /**
153
+ * The keyword that makes a column auto-incrementing, or an empty string when the
154
+ * expression did not ask for one.
155
+ *
156
+ * SQLite is checked rather than passed through: it accepts AUTOINCREMENT only on
157
+ * an `INTEGER PRIMARY KEY`, so a schema asking for `@default(autoincrement())` on
158
+ * anything else has to hear about it here rather than from the engine.
159
+ */
160
+ function identityKeyword(column, provider) {
161
+ if (column.defaultExpr?.trim() !== 'autoincrement()')
162
+ return '';
163
+ switch (provider) {
164
+ case 'mssql':
165
+ return 'IDENTITY(1,1)';
166
+ case 'postgres':
167
+ return 'GENERATED BY DEFAULT AS IDENTITY';
168
+ case 'mysql':
169
+ return 'AUTO_INCREMENT';
170
+ case 'sqlite': {
171
+ // The whole declared type, not the base name: `INTEGER(8)` is not an
172
+ // INTEGER PRIMARY KEY as far as SQLite is concerned, and letting it through
173
+ // would replace this message with the engine's.
174
+ const declared = column.sqlType.trim().toUpperCase();
175
+ if (!column.isId) {
176
+ throw new Error(`SQLite: @default(autoincrement()) on "${column.name}" needs @id — ` +
177
+ 'SQLite only auto-increments an INTEGER PRIMARY KEY.');
178
+ }
179
+ if (declared !== 'INTEGER') {
180
+ throw new Error(`SQLite: @default(autoincrement()) on "${column.name}" needs the type INTEGER, ` +
181
+ `not ${column.sqlType} — SQLite only auto-increments an INTEGER PRIMARY KEY.`);
182
+ }
183
+ return 'AUTOINCREMENT';
184
+ }
185
+ default:
186
+ return '';
187
+ }
188
+ }
189
+ function createDialect(provider, quote) {
190
+ const supportsUniqueInlineOnAddColumn = provider !== 'sqlite';
191
+ const quoteTable = (raw) => {
192
+ const { schema, name } = splitQualified(raw);
193
+ return schema ? `${quote(schema)}.${quote(name)}` : quote(name);
194
+ };
195
+ const list = (fields) => fields.join(', ');
196
+ // Named rather than a method so `addColumn` keeps working if the dialect is
197
+ // destructured by a caller.
198
+ const columnDefinition = (column) => {
199
+ const identity = identityKeyword(column, provider);
200
+ // Where the identity keyword goes is not a detail: SQL Server takes it as part
201
+ // of the column type (`INT IDENTITY(1,1)`), while SQLite only accepts it after
202
+ // `PRIMARY KEY` and only spells the documented form `INTEGER PRIMARY KEY
203
+ // AUTOINCREMENT`.
204
+ const type = identity && provider !== 'sqlite'
205
+ ? `${quote(column.name)} ${column.sqlType} ${identity}`
206
+ : `${quote(column.name)} ${column.sqlType}`;
207
+ const nullable = column.isOptional ? '' : ' NOT NULL';
208
+ const unique = column.isUnique && !column.isId ? ' UNIQUE' : '';
209
+ const defaultSql = defaultClause(column.defaultExpr, provider);
210
+ const defaultPart = defaultSql ? ` ${defaultSql}` : '';
211
+ if (column.isId) {
212
+ const key = `${type} PRIMARY KEY`;
213
+ return `${key}${identity && provider === 'sqlite' ? ` ${identity}` : ''}${defaultPart}`;
214
+ }
215
+ return `${type}${nullable}${defaultPart}${unique}`;
216
+ };
217
+ return {
218
+ provider,
219
+ supportsDdl: true,
220
+ quote,
221
+ quoteTable,
222
+ supportsNamedUniqueConstraint: provider !== 'sqlite',
223
+ supportsUniqueInlineOnAddColumn,
224
+ tableExists: buildTableExists(provider),
225
+ columnExists: buildColumnExists(provider),
226
+ uniqueConstraintExists: buildUniqueConstraintExists(provider),
227
+ indexExists: buildIndexExists(provider),
228
+ columnDefinition,
229
+ createTable: (table, columnDefs) => `CREATE TABLE ${table} (\n ${columnDefs.join(',\n ')}\n)`,
230
+ createIndex: (name, table, fields) => `CREATE INDEX ${name} ON ${table} (${list(fields)})`,
231
+ createUniqueIndex: (name, table, fields) => `CREATE UNIQUE INDEX ${name} ON ${table} (${list(fields)})`,
232
+ addColumn: (table, column) => {
233
+ // SQLite refuses "Cannot add a UNIQUE column", so the unique is left out here
234
+ // and added as a separate index by the caller — silently dropping it instead
235
+ // would push a column that is not unique after all.
236
+ const withoutUnique = supportsUniqueInlineOnAddColumn
237
+ ? column
238
+ : { ...column, isUnique: false };
239
+ return `ALTER TABLE ${table} ADD ${columnDefinition(withoutUnique)}`;
240
+ },
241
+ addUniqueConstraint: (name, table, fields) => `ALTER TABLE ${table} ADD CONSTRAINT ${name} UNIQUE (${list(fields)})`,
242
+ };
243
+ }
244
+ /**
245
+ * Google Sheets has no tables and therefore no DDL: a sheet is a range of cells the
246
+ * adapter reads and writes. Every builder is inert and `supportsDdl` is false, so
247
+ * the caller explains that instead of sending SQL to a database that cannot run it.
248
+ */
249
+ const SHEETS_DIALECT = {
250
+ provider: 'googlesheets',
251
+ supportsDdl: false,
252
+ quote: (name) => name,
253
+ quoteTable: (raw) => objectName(raw),
254
+ supportsNamedUniqueConstraint: false,
255
+ supportsUniqueInlineOnAddColumn: false,
256
+ tableExists: () => '',
257
+ columnExists: () => '',
258
+ uniqueConstraintExists: () => '',
259
+ indexExists: () => '',
260
+ columnDefinition: () => '',
261
+ createTable: () => '',
262
+ createIndex: () => '',
263
+ createUniqueIndex: () => '',
264
+ addColumn: () => '',
265
+ addUniqueConstraint: () => '',
266
+ };
267
+ /** The dialect that writes DDL for a provider. */
268
+ function dialectFor(provider) {
269
+ switch (provider) {
270
+ case 'postgres':
271
+ return createDialect(provider, doubleQuote);
272
+ case 'mysql':
273
+ return createDialect(provider, backtickQuote);
274
+ case 'sqlite':
275
+ return createDialect(provider, bracketQuote);
276
+ case 'googlesheets':
277
+ return SHEETS_DIALECT;
278
+ case 'mssql':
279
+ default:
280
+ return createDialect(provider, bracketQuote);
281
+ }
282
+ }
283
+ /**
284
+ * Renders a `@default(...)` expression as the SQL that follows the column type.
285
+ *
286
+ * Returns an empty string when the provider has no equivalent — SQLite has no
287
+ * default functions, so `uuid()` is left to the adapter, which fills a string
288
+ * primary key in before inserting.
289
+ */
290
+ function defaultClause(expression, provider) {
291
+ const value = (expression ?? '').trim();
292
+ switch (value) {
293
+ case '':
294
+ case 'autoincrement()':
295
+ // Not a default value: it is the identity clause on the column type.
296
+ return '';
297
+ case 'uuid()':
298
+ case 'cuid()':
299
+ if (provider === 'mssql')
300
+ return 'DEFAULT NEWID()';
301
+ if (provider === 'postgres')
302
+ return 'DEFAULT gen_random_uuid()';
303
+ if (provider === 'mysql')
304
+ return 'DEFAULT (UUID())';
305
+ return '';
306
+ case 'now()':
307
+ return 'DEFAULT CURRENT_TIMESTAMP';
308
+ case 'true':
309
+ // PostgreSQL has a real boolean, so use it rather than 1/0.
310
+ return provider === 'postgres' ? 'DEFAULT TRUE' : 'DEFAULT 1';
311
+ case 'false':
312
+ return provider === 'postgres' ? 'DEFAULT FALSE' : 'DEFAULT 0';
313
+ default:
314
+ if (/^".*"$/.test(value))
315
+ return `DEFAULT '${value.slice(1, -1).replace(/'/g, "''")}'`;
316
+ return `DEFAULT ${value}`;
317
+ }
318
+ }
@@ -1,11 +1,19 @@
1
1
  import { Model } from './types';
2
2
  export declare class DotnetGenerator {
3
3
  private outputDir;
4
+ /** The database being generated for; decides types like `TIMESTAMP`. */
5
+ private provider;
4
6
  constructor(outputDir: string);
5
7
  generate(models: Model[]): void;
6
8
  private getCsFilterType;
7
9
  private generateOrmTypes;
8
10
  private generateConfigClass;
11
+ /**
12
+ * The C# type for a field.
13
+ *
14
+ * Driven by the declared type, so a `DECIMAL` column is a `decimal` and a `FLOAT`
15
+ * one a `double`: both arrived as `number` and took the `int` branch.
16
+ */
9
17
  private mapType;
10
18
  private generateEntityClass;
11
19
  private generateDbContext;