@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.
- package/README.md +10 -3
- package/dist/config.d.ts +9 -1
- package/dist/config.js +13 -18
- package/dist/generator/src/api.d.ts +35 -0
- package/dist/generator/src/api.js +58 -0
- package/dist/generator/src/config.d.ts +162 -0
- package/dist/generator/src/config.js +354 -0
- package/dist/generator/src/dialect.d.ts +88 -0
- package/dist/generator/src/dialect.js +318 -0
- package/dist/generator/src/dotnet-generator.d.ts +8 -0
- package/dist/generator/src/dotnet-generator.js +282 -101
- package/dist/generator/src/field-types.d.ts +96 -0
- package/dist/generator/src/field-types.js +433 -0
- package/dist/generator/src/golang-generator.d.ts +36 -0
- package/dist/generator/src/golang-generator.js +1230 -0
- package/dist/generator/src/index.js +69 -36
- package/dist/generator/src/metadata-generator.js +2 -1
- package/dist/generator/src/parser.d.ts +28 -2
- package/dist/generator/src/parser.js +92 -90
- package/dist/generator/src/push-apply.d.ts +37 -0
- package/dist/generator/src/push-apply.js +109 -0
- package/dist/generator/src/push-schema.d.ts +93 -0
- package/dist/generator/src/push-schema.js +189 -0
- package/dist/generator/src/python-generator.d.ts +19 -1
- package/dist/generator/src/python-generator.js +147 -64
- package/dist/generator/src/rust-generator.d.ts +38 -0
- package/dist/generator/src/rust-generator.js +971 -0
- package/dist/generator/src/suggest.d.ts +17 -0
- package/dist/generator/src/suggest.js +45 -0
- package/dist/generator/src/type-kinds.d.ts +27 -0
- package/dist/generator/src/type-kinds.js +72 -0
- package/dist/generator/src/types.d.ts +33 -0
- package/dist/generator/src/types.js +39 -0
- package/dist/migration-core.d.ts +16 -1
- package/dist/migration-core.js +60 -21
- package/dist/orm.d.ts +147 -0
- package/dist/orm.js +1654 -0
- package/dist/provider-support.d.ts +27 -0
- package/dist/provider-support.js +50 -0
- package/dist/sql-utils.js +8 -3
- package/generator/src/api.ts +45 -0
- package/generator/src/config.ts +496 -0
- package/generator/src/dialect.ts +415 -0
- package/generator/src/dotnet-generator.ts +277 -88
- package/generator/src/field-types.ts +473 -0
- package/generator/src/golang-generator.ts +94 -68
- package/generator/src/index.ts +71 -36
- package/generator/src/metadata-generator.ts +2 -2
- package/generator/src/parser.ts +122 -90
- package/generator/src/push-apply.ts +148 -0
- package/generator/src/push-schema.ts +250 -0
- package/generator/src/python-generator.ts +118 -62
- package/generator/src/rust-generator.ts +974 -0
- package/generator/src/suggest.ts +41 -0
- package/generator/src/type-kinds.ts +78 -0
- package/generator/src/types.ts +48 -0
- package/package.json +26 -6
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ConfigError = exports.DEFAULT_CONFIG = void 0;
|
|
7
|
+
exports.resolveOutputs = resolveOutputs;
|
|
8
|
+
exports.validateConfig = validateConfig;
|
|
9
|
+
exports.resolveConnectionString = resolveConnectionString;
|
|
10
|
+
exports.detectProvider = detectProvider;
|
|
11
|
+
exports.providerFromConfig = providerFromConfig;
|
|
12
|
+
exports.providerForProject = providerForProject;
|
|
13
|
+
exports.formatIssues = formatIssues;
|
|
14
|
+
exports.loadConfig = loadConfig;
|
|
15
|
+
/**
|
|
16
|
+
* Loading and validation for an5Orm.config.js
|
|
17
|
+
*
|
|
18
|
+
* The config used to be read as `any` and every field fell back through
|
|
19
|
+
* `config.outputs?.typescript?.outputDir || 'default'`. That made a typo
|
|
20
|
+
* invisible: `outputDirs` was not an error, it just quietly wrote to the
|
|
21
|
+
* default directory while the config file said otherwise.
|
|
22
|
+
*
|
|
23
|
+
* So the file is described once, here, and validated before anything uses it.
|
|
24
|
+
* A wrong type or an unknown key stops generation with a message naming the
|
|
25
|
+
* offending path, rather than being discovered later as output in the wrong
|
|
26
|
+
* place.
|
|
27
|
+
*/
|
|
28
|
+
const fs_1 = __importDefault(require("fs"));
|
|
29
|
+
const path_1 = __importDefault(require("path"));
|
|
30
|
+
const field_types_1 = require("./field-types");
|
|
31
|
+
const suggest_1 = require("./suggest");
|
|
32
|
+
exports.DEFAULT_CONFIG = {
|
|
33
|
+
schemaDir: 'an5Schema',
|
|
34
|
+
outputs: {
|
|
35
|
+
typescript: {
|
|
36
|
+
outputDir: 'an5Client/typescript',
|
|
37
|
+
metadataFile: 'an5Client/typescript/an5Metadata.ts',
|
|
38
|
+
},
|
|
39
|
+
python: { metadataFile: 'an5Client/python/an5_metadata.py' },
|
|
40
|
+
dotnet: { outputDir: 'an5Client/dotnet' },
|
|
41
|
+
golang: { outputDir: 'an5Client/golang' },
|
|
42
|
+
rust: { outputDir: 'an5Client/rust' },
|
|
43
|
+
},
|
|
44
|
+
pull: {
|
|
45
|
+
exclude: ['^__', '^sys\\.', '^igrations'],
|
|
46
|
+
preserveRelations: true,
|
|
47
|
+
},
|
|
48
|
+
generation: {
|
|
49
|
+
generateMetadata: true,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
function resolveOutputs(config, rootDir) {
|
|
53
|
+
const resolve = (relative) => path_1.default.resolve(rootDir, relative);
|
|
54
|
+
return {
|
|
55
|
+
schemaDir: resolve(config.schemaDir),
|
|
56
|
+
typescriptDir: resolve(config.outputs.typescript.outputDir),
|
|
57
|
+
typescriptMetadataFile: resolve(config.outputs.typescript.metadataFile),
|
|
58
|
+
pythonMetadataFile: resolve(config.outputs.python.metadataFile),
|
|
59
|
+
dotnetDir: resolve(config.outputs.dotnet.outputDir),
|
|
60
|
+
golangDir: resolve(config.outputs.golang.outputDir),
|
|
61
|
+
rustDir: resolve(config.outputs.rust.outputDir),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
class ConfigError extends Error {
|
|
65
|
+
constructor(issues) {
|
|
66
|
+
super('Invalid an5Orm.config.js');
|
|
67
|
+
this.issues = issues;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
exports.ConfigError = ConfigError;
|
|
71
|
+
function typeName(value) {
|
|
72
|
+
if (value === null)
|
|
73
|
+
return 'null';
|
|
74
|
+
if (Array.isArray(value))
|
|
75
|
+
return 'an array';
|
|
76
|
+
return typeof value;
|
|
77
|
+
}
|
|
78
|
+
function checkObject(raw, spec, at, issues) {
|
|
79
|
+
if (raw === undefined)
|
|
80
|
+
return {};
|
|
81
|
+
if (typeName(raw) !== 'object' || Array.isArray(raw)) {
|
|
82
|
+
issues.push({ path: at, message: `expected an object, received ${typeName(raw)}` });
|
|
83
|
+
return {};
|
|
84
|
+
}
|
|
85
|
+
const source = raw;
|
|
86
|
+
const accepted = {};
|
|
87
|
+
for (const key of Object.keys(source)) {
|
|
88
|
+
if (!(key in spec)) {
|
|
89
|
+
const hint = (0, suggest_1.suggest)(key, Object.keys(spec));
|
|
90
|
+
issues.push({
|
|
91
|
+
path: at ? `${at}.${key}` : key,
|
|
92
|
+
message: hint
|
|
93
|
+
? `unknown option; did you mean "${hint}"?`
|
|
94
|
+
: `unknown option; expected one of ${Object.keys(spec).map((k) => `"${k}"`).join(', ')}`,
|
|
95
|
+
});
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const expected = spec[key];
|
|
99
|
+
const value = source[key];
|
|
100
|
+
const here = at ? `${at}.${key}` : key;
|
|
101
|
+
if (expected === 'object') {
|
|
102
|
+
// Only the shape is checked here. A nested section is validated by the
|
|
103
|
+
// caller with its own key list, so this must not look at the keys — it
|
|
104
|
+
// has no spec for them and would reject all of them as unknown.
|
|
105
|
+
if (value !== undefined && (typeName(value) !== 'object' || Array.isArray(value))) {
|
|
106
|
+
issues.push({ path: here, message: `expected an object, received ${typeName(value)}` });
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
accepted[key] = value;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (value === undefined)
|
|
113
|
+
continue;
|
|
114
|
+
if (expected === 'string[]') {
|
|
115
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== 'string')) {
|
|
116
|
+
issues.push({ path: here, message: `expected an array of strings, received ${typeName(value)}` });
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
accepted[key] = value;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (typeof value !== expected) {
|
|
123
|
+
issues.push({
|
|
124
|
+
path: here,
|
|
125
|
+
message: `expected a${expected === 'string' ? ' string' : ` ${expected}`}, received ${typeName(value)}`,
|
|
126
|
+
});
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
accepted[key] = value;
|
|
130
|
+
}
|
|
131
|
+
return accepted;
|
|
132
|
+
}
|
|
133
|
+
const OUTPUT_SPEC = {
|
|
134
|
+
typescript: 'object',
|
|
135
|
+
python: 'object',
|
|
136
|
+
dotnet: 'object',
|
|
137
|
+
golang: 'object',
|
|
138
|
+
rust: 'object',
|
|
139
|
+
};
|
|
140
|
+
const OUTPUT_SECTION_SPEC = {
|
|
141
|
+
typescript: { outputDir: 'string', metadataFile: 'string' },
|
|
142
|
+
python: { metadataFile: 'string' },
|
|
143
|
+
dotnet: { outputDir: 'string' },
|
|
144
|
+
golang: { outputDir: 'string' },
|
|
145
|
+
rust: { outputDir: 'string' },
|
|
146
|
+
};
|
|
147
|
+
const TOP_LEVEL_SPEC = {
|
|
148
|
+
connectionString: 'string',
|
|
149
|
+
schemaDir: 'string',
|
|
150
|
+
outputs: 'object',
|
|
151
|
+
pull: 'object',
|
|
152
|
+
generation: 'object',
|
|
153
|
+
};
|
|
154
|
+
const PULL_SPEC = {
|
|
155
|
+
exclude: 'string[]',
|
|
156
|
+
preserveRelations: 'boolean',
|
|
157
|
+
};
|
|
158
|
+
const GENERATION_SPEC = {
|
|
159
|
+
generateMetadata: 'boolean',
|
|
160
|
+
};
|
|
161
|
+
/**
|
|
162
|
+
* Checks a loaded config object and returns it with defaults applied.
|
|
163
|
+
* Throws ConfigError listing every problem found, not just the first.
|
|
164
|
+
*/
|
|
165
|
+
function validateConfig(raw) {
|
|
166
|
+
const issues = [];
|
|
167
|
+
const top = checkObject(raw, TOP_LEVEL_SPEC, '', issues);
|
|
168
|
+
const rawOutputs = checkObject(top.outputs, OUTPUT_SPEC, 'outputs', issues);
|
|
169
|
+
// Each output section has its own allowed keys, so a typo in one is caught
|
|
170
|
+
// where it was written rather than as a missing value much later.
|
|
171
|
+
const outputs = {};
|
|
172
|
+
for (const section of Object.keys(OUTPUT_SECTION_SPEC)) {
|
|
173
|
+
outputs[section] = checkObject(rawOutputs[section], OUTPUT_SECTION_SPEC[section], `outputs.${section}`, issues);
|
|
174
|
+
}
|
|
175
|
+
const pull = checkObject(top.pull, PULL_SPEC, 'pull', issues);
|
|
176
|
+
const generation = checkObject(top.generation, GENERATION_SPEC, 'generation', issues);
|
|
177
|
+
if (issues.length > 0)
|
|
178
|
+
throw new ConfigError(issues);
|
|
179
|
+
return {
|
|
180
|
+
// Absent rather than defaulted: there is no sensible fallback connection.
|
|
181
|
+
connectionString: top.connectionString,
|
|
182
|
+
schemaDir: top.schemaDir ?? exports.DEFAULT_CONFIG.schemaDir,
|
|
183
|
+
outputs: {
|
|
184
|
+
typescript: {
|
|
185
|
+
outputDir: outputs.typescript?.outputDir ??
|
|
186
|
+
exports.DEFAULT_CONFIG.outputs.typescript.outputDir,
|
|
187
|
+
metadataFile: outputs.typescript?.metadataFile ??
|
|
188
|
+
exports.DEFAULT_CONFIG.outputs.typescript.metadataFile,
|
|
189
|
+
},
|
|
190
|
+
python: {
|
|
191
|
+
metadataFile: outputs.python?.metadataFile ??
|
|
192
|
+
exports.DEFAULT_CONFIG.outputs.python.metadataFile,
|
|
193
|
+
},
|
|
194
|
+
dotnet: {
|
|
195
|
+
outputDir: outputs.dotnet?.outputDir ??
|
|
196
|
+
exports.DEFAULT_CONFIG.outputs.dotnet.outputDir,
|
|
197
|
+
},
|
|
198
|
+
golang: {
|
|
199
|
+
outputDir: outputs.golang?.outputDir ??
|
|
200
|
+
exports.DEFAULT_CONFIG.outputs.golang.outputDir,
|
|
201
|
+
},
|
|
202
|
+
rust: {
|
|
203
|
+
outputDir: outputs.rust?.outputDir ??
|
|
204
|
+
exports.DEFAULT_CONFIG.outputs.rust.outputDir,
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
pull: {
|
|
208
|
+
exclude: pull.exclude ?? exports.DEFAULT_CONFIG.pull.exclude,
|
|
209
|
+
preserveRelations: pull.preserveRelations ?? exports.DEFAULT_CONFIG.pull.preserveRelations,
|
|
210
|
+
},
|
|
211
|
+
generation: {
|
|
212
|
+
generateMetadata: generation.generateMetadata ?? exports.DEFAULT_CONFIG.generation.generateMetadata,
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* The connection string to use, or an error explaining where to put one.
|
|
218
|
+
*
|
|
219
|
+
* DATABASE_URL wins over the config file: it is the documented override and the
|
|
220
|
+
* one CI sets. A project may therefore commit a development connection and
|
|
221
|
+
* still have a different one in a pipeline without editing the file.
|
|
222
|
+
*/
|
|
223
|
+
function resolveConnectionString(config, env = process.env, command = 'this command') {
|
|
224
|
+
const fromEnv = env.DATABASE_URL;
|
|
225
|
+
if (typeof fromEnv === 'string' && fromEnv.trim() !== '')
|
|
226
|
+
return fromEnv;
|
|
227
|
+
const fromConfig = config.connectionString;
|
|
228
|
+
if (typeof fromConfig === 'string' && fromConfig.trim() !== '')
|
|
229
|
+
return fromConfig;
|
|
230
|
+
throw new ConfigError([
|
|
231
|
+
{
|
|
232
|
+
path: 'connectionString',
|
|
233
|
+
message: `required for ${command}; not set in an5Orm.config.js and DATABASE_URL is empty. ` +
|
|
234
|
+
'Set DATABASE_URL, or add a connectionString to the config file.',
|
|
235
|
+
},
|
|
236
|
+
]);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* The provider a connection string points at.
|
|
240
|
+
*
|
|
241
|
+
* Reads the scheme exactly as `An5Adapter` does
|
|
242
|
+
* (`an5Adapters/typescript/src/an5Adapter.ts`) so the generator and the adapter
|
|
243
|
+
* always talk about the same database: the same connection string must give the
|
|
244
|
+
* same provider, otherwise validation checks the types of one database while the
|
|
245
|
+
* SQL runs on another.
|
|
246
|
+
*
|
|
247
|
+
* Falls back to SQL Server when the scheme is unknown — it is the default
|
|
248
|
+
* provider, and ADO-style strings (`Server=...;Database=...`) have no scheme to
|
|
249
|
+
* read. `sqlite://`, a bare path ending in `.sqlite`, `.sqlite3` or `.db` — the same
|
|
250
|
+
* list `An5Adapter` uses.
|
|
251
|
+
*/
|
|
252
|
+
function detectProvider(connectionString) {
|
|
253
|
+
// Lower-cased: a URI scheme is case-insensitive, so `MySQL://` is the same
|
|
254
|
+
// connection as `mysql://`. `an5Adapters/typescript/src/an5Adapter.ts` compares
|
|
255
|
+
// the same way, and the two have to agree or this picks a provider whose DDL the
|
|
256
|
+
// adapter never runs.
|
|
257
|
+
const cs = (connectionString ?? '').trim().toLowerCase();
|
|
258
|
+
if (cs === '')
|
|
259
|
+
return field_types_1.DEFAULT_PROVIDER;
|
|
260
|
+
if (cs.startsWith('googlesheets://'))
|
|
261
|
+
return 'googlesheets';
|
|
262
|
+
if (cs.startsWith('postgres://') || cs.startsWith('postgresql://'))
|
|
263
|
+
return 'postgres';
|
|
264
|
+
if (cs.startsWith('mysql://') || cs.startsWith('mariadb://'))
|
|
265
|
+
return 'mysql';
|
|
266
|
+
if (SQLITE_FILE_SUFFIXES.some((suffix) => cs.endsWith(suffix)))
|
|
267
|
+
return 'sqlite';
|
|
268
|
+
return field_types_1.DEFAULT_PROVIDER;
|
|
269
|
+
}
|
|
270
|
+
/** File extensions that mean SQLite when there is no scheme to read. */
|
|
271
|
+
const SQLITE_FILE_SUFFIXES = ['.sqlite', '.sqlite3', '.db'];
|
|
272
|
+
/**
|
|
273
|
+
* The provider for this run: `DATABASE_URL` first, then the connection string in
|
|
274
|
+
* the config file, then the default.
|
|
275
|
+
*
|
|
276
|
+
* Does not throw when there is no connection string — `generate` has to work
|
|
277
|
+
* with an empty config, and the default provider still validates the schema as
|
|
278
|
+
* before.
|
|
279
|
+
*/
|
|
280
|
+
function providerFromConfig(config, env = process.env) {
|
|
281
|
+
const fromEnv = typeof env.DATABASE_URL === 'string' && env.DATABASE_URL.trim() !== '' ? env.DATABASE_URL : undefined;
|
|
282
|
+
return detectProvider(fromEnv ?? config.connectionString);
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* The provider configured for a project directory.
|
|
286
|
+
*
|
|
287
|
+
* For the tools outside this package that read `.an5` files — the an5Agent
|
|
288
|
+
* schema tools, the VS Code extension — so they validate against the same
|
|
289
|
+
* database the generator does instead of silently assuming SQL Server.
|
|
290
|
+
*
|
|
291
|
+
* Never throws: no config, an unreadable one, or an invalid one all mean the
|
|
292
|
+
* default provider, which is what the parser does on its own anyway.
|
|
293
|
+
*/
|
|
294
|
+
function providerForProject(cwd = process.cwd(), env = process.env) {
|
|
295
|
+
try {
|
|
296
|
+
return providerFromConfig(loadConfig(cwd).config, env);
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
return field_types_1.DEFAULT_PROVIDER;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Renders issues for the terminal, one per line, paths aligned.
|
|
304
|
+
*
|
|
305
|
+
* Takes the shape rather than `ConfigIssue` so field type errors
|
|
306
|
+
* (`FieldTypeIssue`, from `./field-types`) print through the same code — both
|
|
307
|
+
* are "a path in the file and what is wrong with it".
|
|
308
|
+
*/
|
|
309
|
+
function formatIssues(issues) {
|
|
310
|
+
const width = Math.max(...issues.map((issue) => issue.path.length));
|
|
311
|
+
return issues
|
|
312
|
+
.map((issue) => ` ${issue.path.padEnd(width)} ${issue.message}`)
|
|
313
|
+
.join('\n');
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Finds, loads and validates an5Orm.config.js.
|
|
317
|
+
*
|
|
318
|
+
* Searched in order: the working directory, the same directory as `.cjs`, and
|
|
319
|
+
* the parent directory for each. When no file is found the defaults are used,
|
|
320
|
+
* so a project can generate without a config.
|
|
321
|
+
*/
|
|
322
|
+
function loadConfig(cwd = process.cwd()) {
|
|
323
|
+
// Resolved before the search because `require` treats a bare specifier as a
|
|
324
|
+
// package name: with a relative `cwd` the file is found (`existsSync` is
|
|
325
|
+
// relative to the process directory) but `require('./an5Orm.config.js')` is
|
|
326
|
+
// really `require('an5Orm.config.js')`, which throws MODULE_NOT_FOUND and
|
|
327
|
+
// loses the config without saying so.
|
|
328
|
+
const startDir = path_1.default.resolve(cwd);
|
|
329
|
+
const candidates = [
|
|
330
|
+
path_1.default.join(startDir, 'an5Orm.config.js'),
|
|
331
|
+
path_1.default.join(startDir, 'an5Orm.config.cjs'),
|
|
332
|
+
path_1.default.join(startDir, '..', 'an5Orm.config.js'),
|
|
333
|
+
path_1.default.join(startDir, '..', 'an5Orm.config.cjs'),
|
|
334
|
+
];
|
|
335
|
+
const configPath = candidates.find((candidate) => fs_1.default.existsSync(candidate)) ?? null;
|
|
336
|
+
if (configPath === null) {
|
|
337
|
+
// A schema directory one level up is the common monorepo layout; without a
|
|
338
|
+
// config the paths would otherwise point at a directory that does not exist.
|
|
339
|
+
const rootDir = fs_1.default.existsSync(path_1.default.join(startDir, '..', 'an5Schema'))
|
|
340
|
+
? path_1.default.resolve(startDir, '..')
|
|
341
|
+
: startDir;
|
|
342
|
+
return {
|
|
343
|
+
config: exports.DEFAULT_CONFIG,
|
|
344
|
+
rootDir,
|
|
345
|
+
outputs: resolveOutputs(exports.DEFAULT_CONFIG, rootDir),
|
|
346
|
+
configPath: null,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
const rootDir = path_1.default.dirname(configPath);
|
|
350
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
351
|
+
const raw = require(configPath);
|
|
352
|
+
const config = validateConfig(raw);
|
|
353
|
+
return { config, rootDir, outputs: resolveOutputs(config, rootDir), configPath };
|
|
354
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQL dialect per database provider, for the commands that write to a database.
|
|
3
|
+
*
|
|
4
|
+
* Field types are per provider (see `./field-types`), and so is the SQL that
|
|
5
|
+
* carries them. Until now `db:push` emitted SQL Server T-SQL for every connection
|
|
6
|
+
* string: `sys.tables` lookups, `[bracket]` quoting, `DEFAULT IDENTITY(1,1)`,
|
|
7
|
+
* `NEWID()`. Pointed at PostgreSQL that failed on the first catalog query with a
|
|
8
|
+
* driver error saying nothing about the real problem.
|
|
9
|
+
*
|
|
10
|
+
* Each dialect answers the same questions — how to quote, where the catalog lives,
|
|
11
|
+
* what a column definition looks like — so `db:push` reads them once instead of
|
|
12
|
+
* branching per provider. SQL Server's answers are the ones it already produced, so
|
|
13
|
+
* an existing SQL Server project sees no change apart from the two bugs called out
|
|
14
|
+
* on the builders below.
|
|
15
|
+
*/
|
|
16
|
+
import { Provider } from './field-types';
|
|
17
|
+
/**
|
|
18
|
+
* A single-quoted SQL string literal.
|
|
19
|
+
*
|
|
20
|
+
* Every provider doubles an embedded quote, so one helper covers them all. These
|
|
21
|
+
* values come from the schema files, so they are the project's own — but an
|
|
22
|
+
* apostrophe in a column name would otherwise close the literal and produce SQL
|
|
23
|
+
* that means something else entirely.
|
|
24
|
+
*/
|
|
25
|
+
export declare function sqlLiteral(value: string): string;
|
|
26
|
+
/** One column of a table, as the schema describes it — same shape `db:push` parses. */
|
|
27
|
+
export interface ColumnSpec {
|
|
28
|
+
name: string;
|
|
29
|
+
/** The type as written in the schema, already validated for the provider. */
|
|
30
|
+
sqlType: string;
|
|
31
|
+
/** `@id`. Named to match the rest of the schema model. */
|
|
32
|
+
isId: boolean;
|
|
33
|
+
isUnique: boolean;
|
|
34
|
+
isOptional: boolean;
|
|
35
|
+
/** The raw `@default(...)` expression, e.g. `now()` or `"draft"`. */
|
|
36
|
+
defaultExpr?: string | undefined;
|
|
37
|
+
}
|
|
38
|
+
export interface PushDialect {
|
|
39
|
+
readonly provider: Provider;
|
|
40
|
+
/** Whether this database can be written to with DDL at all. */
|
|
41
|
+
readonly supportsDdl: boolean;
|
|
42
|
+
/** Quotes a single identifier. */
|
|
43
|
+
quote(name: string): string;
|
|
44
|
+
/** Quotes a possibly schema-qualified name; an empty schema means no prefix. */
|
|
45
|
+
quoteTable(raw: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* Whether a compound unique can be a named table constraint.
|
|
48
|
+
*
|
|
49
|
+
* SQLite has no such thing — a UNIQUE becomes an implicit index the engine names
|
|
50
|
+
* itself — so it becomes a uniquely named index instead, which is the only way to
|
|
51
|
+
* keep `db:push` idempotent.
|
|
52
|
+
*/
|
|
53
|
+
readonly supportsNamedUniqueConstraint: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Whether `ALTER TABLE ... ADD <column>` may carry a UNIQUE.
|
|
56
|
+
*
|
|
57
|
+
* SQLite refuses outright ("Cannot add a UNIQUE column"), so there the unique is
|
|
58
|
+
* added as a separate index — otherwise a new `@unique` column could not be
|
|
59
|
+
* pushed onto an existing table at all.
|
|
60
|
+
*/
|
|
61
|
+
readonly supportsUniqueInlineOnAddColumn: boolean;
|
|
62
|
+
/** Query returning one row when the table exists. */
|
|
63
|
+
tableExists(raw: string): string;
|
|
64
|
+
/** Query returning one row when the column exists. */
|
|
65
|
+
columnExists(raw: string, column: string): string;
|
|
66
|
+
/** Query returning one row when the unique constraint exists. */
|
|
67
|
+
uniqueConstraintExists(raw: string, name: string): string;
|
|
68
|
+
/** Query returning one row when the index exists. */
|
|
69
|
+
indexExists(raw: string, name: string): string;
|
|
70
|
+
/** One column definition, as it appears inside `CREATE TABLE`. */
|
|
71
|
+
columnDefinition(column: ColumnSpec): string;
|
|
72
|
+
createTable(quotedTable: string, columnDefs: string[]): string;
|
|
73
|
+
createIndex(quotedName: string, quotedTable: string, quotedFields: string[]): string;
|
|
74
|
+
createUniqueIndex(quotedName: string, quotedTable: string, quotedFields: string[]): string;
|
|
75
|
+
/** `ALTER TABLE ... ADD <column>`, nullability included. */
|
|
76
|
+
addColumn(quotedTable: string, column: ColumnSpec): string;
|
|
77
|
+
addUniqueConstraint(quotedName: string, quotedTable: string, quotedFields: string[]): string;
|
|
78
|
+
}
|
|
79
|
+
/** The dialect that writes DDL for a provider. */
|
|
80
|
+
export declare function dialectFor(provider: Provider): PushDialect;
|
|
81
|
+
/**
|
|
82
|
+
* Renders a `@default(...)` expression as the SQL that follows the column type.
|
|
83
|
+
*
|
|
84
|
+
* Returns an empty string when the provider has no equivalent — SQLite has no
|
|
85
|
+
* default functions, so `uuid()` is left to the adapter, which fills a string
|
|
86
|
+
* primary key in before inserting.
|
|
87
|
+
*/
|
|
88
|
+
export declare function defaultClause(expression: string | undefined, provider: Provider): string;
|