@zenstackhq/cli 3.5.6 → 3.6.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/dist/index.js DELETED
@@ -1,4224 +0,0 @@
1
- var __defProp = Object.defineProperty;
2
- var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
- }) : x)(function(x) {
6
- if (typeof require !== "undefined") return require.apply(this, arguments);
7
- throw Error('Dynamic require of "' + x + '" is not supported');
8
- });
9
- var __export = (target, all) => {
10
- for (var name in all)
11
- __defProp(target, name, { get: all[name], enumerable: true });
12
- };
13
-
14
- // src/index.ts
15
- import { ZModelLanguageMetaData as ZModelLanguageMetaData2 } from "@zenstackhq/language";
16
- import colors12 from "colors";
17
- import { Command, CommanderError, Option } from "commander";
18
- import "dotenv/config";
19
-
20
- // src/actions/check.ts
21
- import { isPlugin } from "@zenstackhq/language/ast";
22
- import colors2 from "colors";
23
- import path2 from "path";
24
-
25
- // src/actions/action-utils.ts
26
- import { invariant } from "@zenstackhq/common-helpers";
27
- import { loadDocument } from "@zenstackhq/language";
28
- import { isDataSource } from "@zenstackhq/language/ast";
29
- import { PrismaSchemaGenerator } from "@zenstackhq/sdk";
30
- import colors from "colors";
31
- import { createJiti } from "jiti";
32
- import fs from "fs";
33
- import { createRequire } from "module";
34
- import path from "path";
35
- import { pathToFileURL } from "url";
36
- import terminalLink from "terminal-link";
37
- import { z } from "zod";
38
-
39
- // src/cli-error.ts
40
- var CliError = class extends Error {
41
- static {
42
- __name(this, "CliError");
43
- }
44
- };
45
-
46
- // src/actions/action-utils.ts
47
- function getSchemaFile(file) {
48
- if (file) {
49
- if (!fs.existsSync(file)) {
50
- throw new CliError(`Schema file not found: ${file}`);
51
- }
52
- return file;
53
- }
54
- const pkgJsonConfig = getPkgJsonConfig(process.cwd());
55
- if (pkgJsonConfig.schema) {
56
- if (!fs.existsSync(pkgJsonConfig.schema)) {
57
- throw new CliError(`Schema file not found: ${pkgJsonConfig.schema}`);
58
- }
59
- if (fs.statSync(pkgJsonConfig.schema).isDirectory()) {
60
- const schemaPath = path.join(pkgJsonConfig.schema, "schema.zmodel");
61
- if (!fs.existsSync(schemaPath)) {
62
- throw new CliError(`Schema file not found: ${schemaPath}`);
63
- }
64
- return schemaPath;
65
- } else {
66
- return pkgJsonConfig.schema;
67
- }
68
- }
69
- if (fs.existsSync("./schema.zmodel")) {
70
- return "./schema.zmodel";
71
- } else if (fs.existsSync("./zenstack/schema.zmodel")) {
72
- return "./zenstack/schema.zmodel";
73
- } else {
74
- throw new CliError('Schema file not found in default locations ("./schema.zmodel" or "./zenstack/schema.zmodel").');
75
- }
76
- }
77
- __name(getSchemaFile, "getSchemaFile");
78
- async function loadSchemaDocument(schemaFile, opts = {}) {
79
- const returnServices = opts.returnServices ?? false;
80
- const mergeImports = opts.mergeImports ?? true;
81
- const loadResult = await loadDocument(schemaFile, [], mergeImports);
82
- if (!loadResult.success) {
83
- loadResult.errors.forEach((err) => {
84
- console.error(colors.red(err));
85
- });
86
- throw new CliError("Schema contains errors. See above for details.");
87
- }
88
- loadResult.warnings.forEach((warn) => {
89
- console.warn(colors.yellow(warn));
90
- });
91
- if (returnServices) return {
92
- model: loadResult.model,
93
- services: loadResult.services
94
- };
95
- return loadResult.model;
96
- }
97
- __name(loadSchemaDocument, "loadSchemaDocument");
98
- function handleSubProcessError(err) {
99
- if (err instanceof Error && "status" in err && typeof err.status === "number") {
100
- process.exit(err.status);
101
- } else {
102
- process.exit(1);
103
- }
104
- }
105
- __name(handleSubProcessError, "handleSubProcessError");
106
- async function generateTempPrismaSchema(zmodelPath, folder) {
107
- const model = await loadSchemaDocument(zmodelPath);
108
- if (!model.declarations.some(isDataSource)) {
109
- throw new CliError("Schema must define a datasource");
110
- }
111
- const prismaSchema = await new PrismaSchemaGenerator(model).generate();
112
- if (!folder) {
113
- folder = path.dirname(zmodelPath);
114
- }
115
- const prismaSchemaFile = path.resolve(folder, "~schema.prisma");
116
- fs.writeFileSync(prismaSchemaFile, prismaSchema);
117
- return prismaSchemaFile;
118
- }
119
- __name(generateTempPrismaSchema, "generateTempPrismaSchema");
120
- function getPkgJsonConfig(startPath) {
121
- const result = {
122
- schema: void 0,
123
- output: void 0,
124
- seed: void 0
125
- };
126
- const pkgJsonFile = findUp([
127
- "package.json"
128
- ], startPath, false);
129
- if (!pkgJsonFile) {
130
- return result;
131
- }
132
- let pkgJson = void 0;
133
- try {
134
- pkgJson = JSON.parse(fs.readFileSync(pkgJsonFile, "utf8"));
135
- } catch {
136
- return result;
137
- }
138
- if (pkgJson.zenstack && typeof pkgJson.zenstack === "object") {
139
- result.schema = pkgJson.zenstack.schema && typeof pkgJson.zenstack.schema === "string" ? path.resolve(path.dirname(pkgJsonFile), pkgJson.zenstack.schema) : void 0;
140
- result.output = pkgJson.zenstack.output && typeof pkgJson.zenstack.output === "string" ? path.resolve(path.dirname(pkgJsonFile), pkgJson.zenstack.output) : void 0;
141
- result.seed = typeof pkgJson.zenstack.seed === "string" && pkgJson.zenstack.seed ? pkgJson.zenstack.seed : void 0;
142
- }
143
- return result;
144
- }
145
- __name(getPkgJsonConfig, "getPkgJsonConfig");
146
- function findUp(names, cwd = process.cwd(), multiple = false, result = []) {
147
- if (!names.some((name) => !!name)) {
148
- return void 0;
149
- }
150
- const target = names.find((name) => fs.existsSync(path.join(cwd, name)));
151
- if (multiple === false && target) {
152
- return path.resolve(cwd, target);
153
- }
154
- if (target) {
155
- result.push(path.resolve(cwd, target));
156
- }
157
- const up = path.resolve(cwd, "..");
158
- if (up === cwd) {
159
- return multiple && result.length > 0 ? result : void 0;
160
- }
161
- return findUp(names, up, multiple, result);
162
- }
163
- __name(findUp, "findUp");
164
- async function requireDataSourceUrl(schemaFile) {
165
- const zmodel = await loadSchemaDocument(schemaFile);
166
- const dataSource = zmodel.declarations.find(isDataSource);
167
- if (!dataSource?.fields.some((f) => f.name === "url")) {
168
- throw new CliError(`The schema's "datasource" must have a "url" field to use this command.`);
169
- }
170
- }
171
- __name(requireDataSourceUrl, "requireDataSourceUrl");
172
- function getOutputPath(options, schemaFile) {
173
- if (options.output) {
174
- return options.output;
175
- }
176
- const pkgJsonConfig = getPkgJsonConfig(process.cwd());
177
- if (pkgJsonConfig.output) {
178
- return pkgJsonConfig.output;
179
- } else {
180
- return path.dirname(schemaFile);
181
- }
182
- }
183
- __name(getOutputPath, "getOutputPath");
184
- async function getZenStackPackages(searchPath) {
185
- const pkgJsonFile = findUp([
186
- "package.json"
187
- ], searchPath, false);
188
- if (!pkgJsonFile) {
189
- return [];
190
- }
191
- let pkgJson;
192
- try {
193
- pkgJson = JSON.parse(fs.readFileSync(pkgJsonFile, "utf8"));
194
- } catch {
195
- return [];
196
- }
197
- const packages = Array.from(new Set([
198
- ...Object.keys(pkgJson.dependencies ?? {}),
199
- ...Object.keys(pkgJson.devDependencies ?? {})
200
- ].filter((p) => p.startsWith("@zenstackhq/")))).sort();
201
- const require2 = createRequire(pkgJsonFile);
202
- const result = packages.map((pkg) => {
203
- try {
204
- const depPkgJson = require2(`${pkg}/package.json`);
205
- if (depPkgJson.private) {
206
- return void 0;
207
- }
208
- return {
209
- pkg,
210
- version: depPkgJson.version
211
- };
212
- } catch {
213
- return {
214
- pkg,
215
- version: void 0
216
- };
217
- }
218
- });
219
- return result.filter((p) => !!p);
220
- }
221
- __name(getZenStackPackages, "getZenStackPackages");
222
- function getPluginProvider(plugin3) {
223
- const providerField = plugin3.fields.find((f) => f.name === "provider");
224
- invariant(providerField, `Plugin ${plugin3.name} does not have a provider field`);
225
- const provider = providerField.value.value;
226
- return provider;
227
- }
228
- __name(getPluginProvider, "getPluginProvider");
229
- async function loadPluginModule(provider, basePath) {
230
- if (provider.toLowerCase().endsWith(".zmodel")) {
231
- return void 0;
232
- }
233
- let moduleSpec = provider;
234
- if (moduleSpec.startsWith(".")) {
235
- moduleSpec = path.resolve(basePath, moduleSpec);
236
- }
237
- const importAsEsm = /* @__PURE__ */ __name(async (spec) => {
238
- try {
239
- const result = (await import(spec)).default;
240
- return typeof result?.generate === "function" ? result : void 0;
241
- } catch (err) {
242
- throw new CliError(`Failed to load plugin module from ${spec}: ${err.message}`);
243
- }
244
- }, "importAsEsm");
245
- const jiti = createJiti(pathToFileURL(basePath).toString());
246
- const importAsTs = /* @__PURE__ */ __name(async (spec) => {
247
- try {
248
- const result = await jiti.import(spec, {
249
- default: true
250
- });
251
- return typeof result?.generate === "function" ? result : void 0;
252
- } catch (err) {
253
- throw new CliError(`Failed to load plugin module from ${spec}: ${err.message}`);
254
- }
255
- }, "importAsTs");
256
- const esmSuffixes = [
257
- ".js",
258
- ".mjs"
259
- ];
260
- const tsSuffixes = [
261
- ".ts",
262
- ".mts"
263
- ];
264
- if (fs.existsSync(moduleSpec) && fs.statSync(moduleSpec).isFile()) {
265
- if (esmSuffixes.some((suffix) => moduleSpec.endsWith(suffix))) {
266
- return await importAsEsm(pathToFileURL(moduleSpec).toString());
267
- }
268
- if (tsSuffixes.some((suffix) => moduleSpec.endsWith(suffix))) {
269
- return await importAsTs(moduleSpec);
270
- }
271
- }
272
- for (const suffix of esmSuffixes) {
273
- const indexPath = path.join(moduleSpec, `index${suffix}`);
274
- if (fs.existsSync(indexPath)) {
275
- return await importAsEsm(pathToFileURL(indexPath).toString());
276
- }
277
- }
278
- for (const suffix of tsSuffixes) {
279
- const indexPath = path.join(moduleSpec, `index${suffix}`);
280
- if (fs.existsSync(indexPath)) {
281
- return await importAsTs(indexPath);
282
- }
283
- }
284
- try {
285
- const result = await jiti.import(moduleSpec, {
286
- default: true
287
- });
288
- return typeof result.generate === "function" ? result : void 0;
289
- } catch {
290
- }
291
- try {
292
- const mod = await import(moduleSpec);
293
- return typeof mod.default?.generate === "function" ? mod.default : void 0;
294
- } catch (err) {
295
- const errorCode = err?.code;
296
- if (errorCode === "ERR_MODULE_NOT_FOUND" || errorCode === "MODULE_NOT_FOUND") {
297
- throw new CliError(`Cannot find plugin module "${provider}". Please make sure the package exists.`);
298
- }
299
- throw new CliError(`Failed to load plugin module "${provider}": ${err.message}`);
300
- }
301
- }
302
- __name(loadPluginModule, "loadPluginModule");
303
- var FETCH_CLI_MAX_TIME = 1e3;
304
- var CLI_CONFIG_ENDPOINT = "https://zenstack.dev/config/cli-v3.json";
305
- var usageTipsSchema = z.object({
306
- notifications: z.array(z.object({
307
- title: z.string(),
308
- url: z.url().optional(),
309
- active: z.boolean()
310
- }))
311
- });
312
- function startUsageTipsFetch() {
313
- let fetchedData = void 0;
314
- let fetchComplete = false;
315
- const start = Date.now();
316
- const controller = new AbortController();
317
- fetch(CLI_CONFIG_ENDPOINT, {
318
- headers: {
319
- accept: "application/json"
320
- },
321
- signal: controller.signal
322
- }).then(async (res) => {
323
- if (!res.ok) return;
324
- const data = await res.json();
325
- const parseResult = usageTipsSchema.safeParse(data);
326
- if (parseResult.success) {
327
- fetchedData = parseResult.data;
328
- }
329
- }).catch(() => {
330
- }).finally(() => {
331
- fetchComplete = true;
332
- });
333
- return async () => {
334
- const elapsed = Date.now() - start;
335
- if (!fetchComplete && elapsed < FETCH_CLI_MAX_TIME) {
336
- await new Promise((resolve) => setTimeout(resolve, FETCH_CLI_MAX_TIME - elapsed));
337
- }
338
- if (!fetchComplete) {
339
- controller.abort();
340
- return;
341
- }
342
- if (!fetchedData) return;
343
- const activeItems = fetchedData.notifications.filter((item) => item.active);
344
- if (activeItems.length > 0) {
345
- const item = activeItems[Math.floor(Math.random() * activeItems.length)];
346
- if (item.url) {
347
- console.log(terminalLink(item.title, item.url));
348
- } else {
349
- console.log(item.title);
350
- }
351
- }
352
- };
353
- }
354
- __name(startUsageTipsFetch, "startUsageTipsFetch");
355
-
356
- // src/actions/check.ts
357
- async function run(options) {
358
- const schemaFile = getSchemaFile(options.schema);
359
- try {
360
- const model = await loadSchemaDocument(schemaFile);
361
- await checkPluginResolution(schemaFile, model);
362
- console.log(colors2.green("\u2713 Schema validation completed successfully."));
363
- } catch (error) {
364
- console.error(colors2.red("\u2717 Schema validation failed."));
365
- throw error;
366
- }
367
- }
368
- __name(run, "run");
369
- async function checkPluginResolution(schemaFile, model) {
370
- const plugins = model.declarations.filter(isPlugin);
371
- for (const plugin3 of plugins) {
372
- const provider = getPluginProvider(plugin3);
373
- if (!provider.startsWith("@core/")) {
374
- const pluginSourcePath = plugin3.$cstNode?.parent?.element.$document?.uri?.fsPath ?? schemaFile;
375
- await loadPluginModule(provider, path2.dirname(pluginSourcePath));
376
- }
377
- }
378
- }
379
- __name(checkPluginResolution, "checkPluginResolution");
380
-
381
- // src/actions/db.ts
382
- import { formatDocument, ZModelCodeGenerator } from "@zenstackhq/language";
383
- import { DataModel, Enum } from "@zenstackhq/language/ast";
384
- import colors4 from "colors";
385
- import fs2 from "fs";
386
- import path3 from "path";
387
- import ora from "ora";
388
-
389
- // src/utils/exec-utils.ts
390
- import { execSync as _exec } from "child_process";
391
- import { fileURLToPath } from "url";
392
- function execSync(cmd, options) {
393
- const { env: env2, ...restOptions } = options ?? {};
394
- const mergedEnv = env2 ? {
395
- ...process.env,
396
- ...env2
397
- } : void 0;
398
- _exec(cmd, {
399
- encoding: "utf-8",
400
- stdio: options?.stdio ?? "inherit",
401
- env: mergedEnv,
402
- ...restOptions
403
- });
404
- }
405
- __name(execSync, "execSync");
406
- function execPackage(cmd, options) {
407
- const packageManager = process?.versions?.["bun"] ? "bunx" : "npx";
408
- execSync(`${packageManager} ${cmd}`, options);
409
- }
410
- __name(execPackage, "execPackage");
411
- function execPrisma(args, options) {
412
- let prismaPath;
413
- try {
414
- if (typeof import.meta.resolve === "function") {
415
- prismaPath = fileURLToPath(import.meta.resolve("prisma/build/index.js"));
416
- } else {
417
- prismaPath = __require.resolve("prisma/build/index.js");
418
- }
419
- } catch {
420
- }
421
- const _options = {
422
- ...options,
423
- env: {
424
- ...options?.env,
425
- PRISMA_HIDE_UPDATE_MESSAGE: "1"
426
- }
427
- };
428
- if (!prismaPath) {
429
- execPackage(`prisma ${args}`, _options);
430
- return;
431
- }
432
- execSync(`node "${prismaPath}" ${args}`, _options);
433
- }
434
- __name(execPrisma, "execPrisma");
435
-
436
- // src/actions/pull/index.ts
437
- import colors3 from "colors";
438
- import { isEnum } from "@zenstackhq/language/ast";
439
- import { DataFieldAttributeFactory, DataFieldFactory, DataModelFactory, EnumFactory } from "@zenstackhq/language/factory";
440
- import { AstUtils } from "langium";
441
- import { lowerCaseFirst } from "@zenstackhq/common-helpers";
442
-
443
- // src/actions/pull/utils.ts
444
- import { isInvocationExpr } from "@zenstackhq/language/ast";
445
- import { getLiteralArray, getStringLiteral } from "@zenstackhq/language/utils";
446
- function isDatabaseManagedAttribute(name) {
447
- return [
448
- "@relation",
449
- "@id",
450
- "@unique"
451
- ].includes(name) || name.startsWith("@db.");
452
- }
453
- __name(isDatabaseManagedAttribute, "isDatabaseManagedAttribute");
454
- function getDatasource(model) {
455
- const datasource = model.declarations.find((d) => d.$type === "DataSource");
456
- if (!datasource) {
457
- throw new CliError("No datasource declaration found in the schema.");
458
- }
459
- const urlField = datasource.fields.find((f) => f.name === "url");
460
- if (!urlField) throw new CliError(`No url field found in the datasource declaration.`);
461
- let url = getStringLiteral(urlField.value);
462
- if (!url && isInvocationExpr(urlField.value)) {
463
- const envName = getStringLiteral(urlField.value.args[0]?.value);
464
- if (!envName) {
465
- throw new CliError("The url field must be a string literal or an env().");
466
- }
467
- if (!process.env[envName]) {
468
- throw new CliError(`Environment variable ${envName} is not set, please set it to the database connection string.`);
469
- }
470
- url = process.env[envName];
471
- }
472
- if (!url) {
473
- throw new CliError("The url field must be a string literal or an env().");
474
- }
475
- if (url.startsWith("file:")) {
476
- url = new URL(url, `file:${model.$document.uri.path}`).pathname;
477
- if (process.platform === "win32" && url[0] === "/") url = url.slice(1);
478
- }
479
- const defaultSchemaField = datasource.fields.find((f) => f.name === "defaultSchema");
480
- const defaultSchema = defaultSchemaField && getStringLiteral(defaultSchemaField.value) || "public";
481
- const schemasField = datasource.fields.find((f) => f.name === "schemas");
482
- const schemas = schemasField && getLiteralArray(schemasField.value)?.filter((s) => s !== void 0) || [];
483
- const provider = getStringLiteral(datasource.fields.find((f) => f.name === "provider")?.value);
484
- if (!provider) {
485
- throw new CliError(`Datasource "${datasource.name}" is missing a "provider" field.`);
486
- }
487
- return {
488
- name: datasource.name,
489
- provider,
490
- url,
491
- defaultSchema,
492
- schemas,
493
- allSchemas: [
494
- defaultSchema,
495
- ...schemas
496
- ]
497
- };
498
- }
499
- __name(getDatasource, "getDatasource");
500
- function getDbName(decl, includeSchema = false) {
501
- if (!("attributes" in decl)) return decl.name;
502
- const schemaAttr = decl.attributes.find((a) => a.decl.ref?.name === "@@schema");
503
- let schema = "public";
504
- if (schemaAttr) {
505
- const schemaAttrValue = schemaAttr.args[0]?.value;
506
- if (schemaAttrValue?.$type === "StringLiteral") {
507
- schema = schemaAttrValue.value;
508
- }
509
- }
510
- const formatName = /* @__PURE__ */ __name((name) => `${schema && includeSchema ? `${schema}.` : ""}${name}`, "formatName");
511
- const nameAttr = decl.attributes.find((a) => a.decl.ref?.name === "@@map" || a.decl.ref?.name === "@map");
512
- if (!nameAttr) return formatName(decl.name);
513
- const attrValue = nameAttr.args[0]?.value;
514
- if (attrValue?.$type !== "StringLiteral") return formatName(decl.name);
515
- return formatName(attrValue.value);
516
- }
517
- __name(getDbName, "getDbName");
518
- function getRelationFkName(decl) {
519
- const relationAttr = decl?.attributes.find((a) => a.decl.ref?.name === "@relation");
520
- const schemaAttrValue = relationAttr?.args.find((a) => a.name === "map")?.value;
521
- return schemaAttrValue?.value;
522
- }
523
- __name(getRelationFkName, "getRelationFkName");
524
- function getRelationFieldsKey(decl) {
525
- const relationAttr = decl?.attributes.find((a) => a.decl.ref?.name === "@relation");
526
- if (!relationAttr) return void 0;
527
- const fieldsArg = relationAttr.args.find((a) => a.name === "fields")?.value;
528
- if (!fieldsArg || fieldsArg.$type !== "ArrayExpr") return void 0;
529
- const fieldNames = fieldsArg.items.filter((item) => item.$type === "ReferenceExpr").map((item) => item.target?.$refText || item.target?.ref?.name).filter((name) => !!name).sort();
530
- return fieldNames.length > 0 ? fieldNames.join(",") : void 0;
531
- }
532
- __name(getRelationFieldsKey, "getRelationFieldsKey");
533
- function getDeclarationRef(type2, name, services) {
534
- const node = services.shared.workspace.IndexManager.allElements(type2).find((m) => m.node && getDbName(m.node) === name)?.node;
535
- if (!node) throw new CliError(`Declaration not found: ${name}`);
536
- return node;
537
- }
538
- __name(getDeclarationRef, "getDeclarationRef");
539
- function getEnumRef(name, services) {
540
- return getDeclarationRef("Enum", name, services);
541
- }
542
- __name(getEnumRef, "getEnumRef");
543
- function getAttributeRef(name, services) {
544
- return getDeclarationRef("Attribute", name, services);
545
- }
546
- __name(getAttributeRef, "getAttributeRef");
547
- function getFunctionRef(name, services) {
548
- return getDeclarationRef("FunctionDecl", name, services);
549
- }
550
- __name(getFunctionRef, "getFunctionRef");
551
- function normalizeFloatDefault(val) {
552
- if (/^-?\d+$/.test(val)) {
553
- return (ab) => ab.NumberLiteral.setValue(val + ".0");
554
- }
555
- if (/^-?\d+\.\d+$/.test(val)) {
556
- return (ab) => ab.NumberLiteral.setValue(val);
557
- }
558
- return (ab) => ab.NumberLiteral.setValue(val);
559
- }
560
- __name(normalizeFloatDefault, "normalizeFloatDefault");
561
- function normalizeDecimalDefault(val) {
562
- if (/^-?\d+$/.test(val)) {
563
- return (ab) => ab.NumberLiteral.setValue(val + ".00");
564
- }
565
- if (/^-?\d+\.\d+$/.test(val)) {
566
- const [integerPart, fractionalPart] = val.split(".");
567
- let normalized = fractionalPart.replace(/0+$/, "");
568
- if (normalized.length < 2) {
569
- normalized = normalized.padEnd(2, "0");
570
- }
571
- return (ab) => ab.NumberLiteral.setValue(`${integerPart}.${normalized}`);
572
- }
573
- return (ab) => ab.NumberLiteral.setValue(val);
574
- }
575
- __name(normalizeDecimalDefault, "normalizeDecimalDefault");
576
-
577
- // src/actions/pull/casing.ts
578
- function resolveNameCasing(casing, originalName) {
579
- let name = originalName;
580
- const fieldPrefix = /[0-9]/g.test(name.charAt(0)) ? "_" : "";
581
- switch (casing) {
582
- case "pascal":
583
- name = toPascalCase(originalName);
584
- break;
585
- case "camel":
586
- name = toCamelCase(originalName);
587
- break;
588
- case "snake":
589
- name = toSnakeCase(originalName);
590
- break;
591
- }
592
- return {
593
- modified: name !== originalName || fieldPrefix !== "",
594
- name: `${fieldPrefix}${name}`
595
- };
596
- }
597
- __name(resolveNameCasing, "resolveNameCasing");
598
- function isAllUpperCase(str) {
599
- return str === str.toUpperCase();
600
- }
601
- __name(isAllUpperCase, "isAllUpperCase");
602
- function toPascalCase(str) {
603
- if (isAllUpperCase(str)) return str;
604
- return str.replace(/[_\- ]+(\w)/g, (_, c) => c.toUpperCase()).replace(/^\w/, (c) => c.toUpperCase());
605
- }
606
- __name(toPascalCase, "toPascalCase");
607
- function toCamelCase(str) {
608
- if (isAllUpperCase(str)) return str;
609
- return str.replace(/[_\- ]+(\w)/g, (_, c) => c.toUpperCase()).replace(/^\w/, (c) => c.toLowerCase());
610
- }
611
- __name(toCamelCase, "toCamelCase");
612
- function toSnakeCase(str) {
613
- if (isAllUpperCase(str)) return str;
614
- return str.replace(/[- ]+/g, "_").replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
615
- }
616
- __name(toSnakeCase, "toSnakeCase");
617
-
618
- // src/actions/pull/index.ts
619
- function syncEnums({ dbEnums, model, oldModel, provider, options, services, defaultSchema }) {
620
- if (provider.isSupportedFeature("NativeEnum")) {
621
- for (const dbEnum of dbEnums) {
622
- const { modified, name } = resolveNameCasing(options.modelCasing, dbEnum.enum_type);
623
- if (modified) console.log(colors3.gray(`Mapping enum ${dbEnum.enum_type} to ${name}`));
624
- const factory = new EnumFactory().setName(name);
625
- if (modified || options.alwaysMap) factory.addAttribute((builder) => builder.setDecl(getAttributeRef("@@map", services)).addArg((argBuilder) => argBuilder.StringLiteral.setValue(dbEnum.enum_type)));
626
- dbEnum.values.forEach((v) => {
627
- const { name: name2, modified: modified2 } = resolveNameCasing(options.fieldCasing, v);
628
- factory.addField((builder) => {
629
- builder.setName(name2);
630
- if (modified2 || options.alwaysMap) builder.addAttribute((builder2) => builder2.setDecl(getAttributeRef("@map", services)).addArg((argBuilder) => argBuilder.StringLiteral.setValue(v)));
631
- return builder;
632
- });
633
- });
634
- if (dbEnum.schema_name && dbEnum.schema_name !== "" && dbEnum.schema_name !== defaultSchema) {
635
- factory.addAttribute((b) => b.setDecl(getAttributeRef("@@schema", services)).addArg((a) => a.StringLiteral.setValue(dbEnum.schema_name)));
636
- }
637
- model.declarations.push(factory.get({
638
- $container: model
639
- }));
640
- }
641
- } else {
642
- const dummyBuildReference = /* @__PURE__ */ __name((_node, _property, _refNode, refText) => ({
643
- $refText: refText
644
- }), "dummyBuildReference");
645
- oldModel.declarations.filter((d) => isEnum(d)).forEach((d) => {
646
- const copy = AstUtils.copyAstNode(d, dummyBuildReference);
647
- copy.$container = model;
648
- model.declarations.push(copy);
649
- });
650
- }
651
- }
652
- __name(syncEnums, "syncEnums");
653
- function syncTable({ model, provider, table, services, options, defaultSchema }) {
654
- const idAttribute = getAttributeRef("@id", services);
655
- const modelIdAttribute = getAttributeRef("@@id", services);
656
- const uniqueAttribute = getAttributeRef("@unique", services);
657
- const modelUniqueAttribute = getAttributeRef("@@unique", services);
658
- const fieldMapAttribute = getAttributeRef("@map", services);
659
- const tableMapAttribute = getAttributeRef("@@map", services);
660
- const modelindexAttribute = getAttributeRef("@@index", services);
661
- const relations = [];
662
- const { name, modified } = resolveNameCasing(options.modelCasing, table.name);
663
- const multiPk = table.columns.filter((c) => c.pk).length > 1;
664
- const modelFactory = new DataModelFactory().setName(name).setIsView(table.type === "view");
665
- modelFactory.setContainer(model);
666
- if (modified || options.alwaysMap) {
667
- modelFactory.addAttribute((builder) => builder.setDecl(tableMapAttribute).addArg((argBuilder) => argBuilder.StringLiteral.setValue(table.name)));
668
- }
669
- const fkGroups = /* @__PURE__ */ new Map();
670
- table.columns.forEach((column) => {
671
- if (column.foreign_key_table && column.foreign_key_name) {
672
- const group = fkGroups.get(column.foreign_key_name) ?? [];
673
- group.push(column);
674
- fkGroups.set(column.foreign_key_name, group);
675
- }
676
- });
677
- for (const [fkName, fkColumns] of fkGroups) {
678
- const firstCol = fkColumns[0];
679
- const isSingleColumnPk = fkColumns.length === 1 && !multiPk && firstCol.pk;
680
- const isUniqueRelation = fkColumns.length === 1 && firstCol.unique || isSingleColumnPk;
681
- relations.push({
682
- schema: table.schema,
683
- table: table.name,
684
- columns: fkColumns.map((c) => c.name),
685
- type: "one",
686
- fk_name: fkName,
687
- foreign_key_on_delete: firstCol.foreign_key_on_delete,
688
- foreign_key_on_update: firstCol.foreign_key_on_update,
689
- nullable: firstCol.nullable,
690
- references: {
691
- schema: firstCol.foreign_key_schema,
692
- table: firstCol.foreign_key_table,
693
- columns: fkColumns.map((c) => c.foreign_key_column),
694
- type: isUniqueRelation ? "one" : "many"
695
- }
696
- });
697
- }
698
- table.columns.forEach((column) => {
699
- const { name: name2, modified: modified2 } = resolveNameCasing(options.fieldCasing, column.name);
700
- const builtinType = provider.getBuiltinType(column.datatype);
701
- modelFactory.addField((builder) => {
702
- builder.setName(name2);
703
- builder.setType((typeBuilder) => {
704
- typeBuilder.setArray(builtinType.isArray);
705
- typeBuilder.setOptional(builtinType.isArray ? false : column.nullable);
706
- if (column.computed) {
707
- typeBuilder.setUnsupported((unsupportedBuilder) => unsupportedBuilder.setValue((lt) => lt.StringLiteral.setValue(column.datatype)));
708
- } else if (column.datatype === "enum") {
709
- const ref = model.declarations.find((d) => isEnum(d) && getDbName(d) === column.datatype_name);
710
- if (!ref) {
711
- throw new CliError(`Enum ${column.datatype_name} not found`);
712
- }
713
- typeBuilder.setReference(ref);
714
- } else {
715
- if (builtinType.type !== "Unsupported") {
716
- typeBuilder.setType(builtinType.type);
717
- } else {
718
- typeBuilder.setUnsupported((unsupportedBuilder) => unsupportedBuilder.setValue((lt) => lt.StringLiteral.setValue(column.datatype)));
719
- }
720
- }
721
- return typeBuilder;
722
- });
723
- if (column.pk && !multiPk) {
724
- builder.addAttribute((b) => b.setDecl(idAttribute));
725
- }
726
- const fieldAttrs = provider.getFieldAttributes({
727
- fieldName: column.name,
728
- fieldType: builtinType.type,
729
- datatype: column.datatype,
730
- length: column.length,
731
- precision: column.precision,
732
- services
733
- });
734
- fieldAttrs.forEach(builder.addAttribute.bind(builder));
735
- if (column.default && !column.computed) {
736
- const defaultExprBuilder = provider.getDefaultValue({
737
- fieldType: builtinType.type,
738
- datatype: column.datatype,
739
- datatype_name: column.datatype_name,
740
- defaultValue: column.default,
741
- services,
742
- enums: model.declarations.filter((d) => d.$type === "Enum")
743
- });
744
- if (defaultExprBuilder) {
745
- const defaultAttr = new DataFieldAttributeFactory().setDecl(getAttributeRef("@default", services)).addArg(defaultExprBuilder);
746
- builder.addAttribute(defaultAttr);
747
- }
748
- }
749
- if (column.unique && !column.pk) {
750
- builder.addAttribute((b) => {
751
- b.setDecl(uniqueAttribute);
752
- const isDefaultName = !column.unique_name || column.unique_name === `${table.name}_${column.name}_key` || column.unique_name === column.name;
753
- if (!isDefaultName) {
754
- b.addArg((ab) => ab.StringLiteral.setValue(column.unique_name), "map");
755
- }
756
- return b;
757
- });
758
- }
759
- if (modified2 || options.alwaysMap) {
760
- builder.addAttribute((ab) => ab.setDecl(fieldMapAttribute).addArg((ab2) => ab2.StringLiteral.setValue(column.name)));
761
- }
762
- return builder;
763
- });
764
- });
765
- const pkColumns = table.columns.filter((c) => c.pk).map((c) => c.name);
766
- if (multiPk) {
767
- modelFactory.addAttribute((builder) => builder.setDecl(modelIdAttribute).addArg((argBuilder) => {
768
- const arrayExpr = argBuilder.ArrayExpr;
769
- pkColumns.forEach((c) => {
770
- const ref = modelFactory.node.fields.find((f) => getDbName(f) === c);
771
- if (!ref) {
772
- throw new CliError(`Field ${c} not found`);
773
- }
774
- arrayExpr.addItem((itemBuilder) => itemBuilder.ReferenceExpr.setTarget(ref));
775
- });
776
- return arrayExpr;
777
- }));
778
- }
779
- const hasUniqueConstraint = table.columns.some((c) => c.unique || c.pk) || table.indexes.some((i) => i.unique);
780
- if (!hasUniqueConstraint) {
781
- modelFactory.addAttribute((a) => a.setDecl(getAttributeRef("@@ignore", services)));
782
- modelFactory.addComment("/// The underlying table does not contain a valid unique identifier and can therefore currently not be handled by Zenstack Client.");
783
- }
784
- const sortedIndexes = table.indexes.reverse().sort((a, b) => {
785
- if (a.unique && !b.unique) return -1;
786
- if (!a.unique && b.unique) return 1;
787
- return 0;
788
- });
789
- sortedIndexes.forEach((index) => {
790
- if (index.predicate) {
791
- console.warn(colors3.yellow(`These constraints are not supported by Zenstack. Read more: https://pris.ly/d/check-constraints
792
- - Model: "${table.name}", constraint: "${index.name}"`));
793
- return;
794
- }
795
- if (index.columns.find((c) => c.expression)) {
796
- console.warn(colors3.yellow(`These constraints are not supported by Zenstack. Read more: https://pris.ly/d/check-constraints
797
- - Model: "${table.name}", constraint: "${index.name}"`));
798
- return;
799
- }
800
- if (index.primary) {
801
- return;
802
- }
803
- if (index.columns.length === 1 && (index.columns.find((c) => pkColumns.includes(c.name)) || index.unique)) {
804
- return;
805
- }
806
- modelFactory.addAttribute((builder) => {
807
- const attr = builder.setDecl(index.unique ? modelUniqueAttribute : modelindexAttribute).addArg((argBuilder) => {
808
- const arrayExpr = argBuilder.ArrayExpr;
809
- index.columns.forEach((c) => {
810
- const ref = modelFactory.node.fields.find((f) => getDbName(f) === c.name);
811
- if (!ref) {
812
- throw new CliError(`Column ${c.name} not found in model ${table.name}`);
813
- }
814
- arrayExpr.addItem((itemBuilder) => {
815
- const refExpr = itemBuilder.ReferenceExpr.setTarget(ref);
816
- if (c.order && c.order !== "ASC") refExpr.addArg((ab) => ab.StringLiteral.setValue("DESC"), "sort");
817
- return refExpr;
818
- });
819
- });
820
- return arrayExpr;
821
- });
822
- const suffix = index.unique ? "_key" : "_idx";
823
- if (index.name !== `${table.name}_${index.columns.map((c) => c.name).join("_")}${suffix}`) {
824
- attr.addArg((argBuilder) => argBuilder.StringLiteral.setValue(index.name), "map");
825
- }
826
- return attr;
827
- });
828
- });
829
- if (table.schema && table.schema !== "" && table.schema !== defaultSchema) {
830
- modelFactory.addAttribute((b) => b.setDecl(getAttributeRef("@@schema", services)).addArg((a) => a.StringLiteral.setValue(table.schema)));
831
- }
832
- model.declarations.push(modelFactory.node);
833
- return relations;
834
- }
835
- __name(syncTable, "syncTable");
836
- function syncRelation({ model, relation, services, options, selfRelation, similarRelations }) {
837
- const idAttribute = getAttributeRef("@id", services);
838
- const uniqueAttribute = getAttributeRef("@unique", services);
839
- const relationAttribute = getAttributeRef("@relation", services);
840
- const fieldMapAttribute = getAttributeRef("@map", services);
841
- const tableMapAttribute = getAttributeRef("@@map", services);
842
- const includeRelationName = selfRelation || similarRelations > 0;
843
- if (!idAttribute || !uniqueAttribute || !relationAttribute || !fieldMapAttribute || !tableMapAttribute) {
844
- throw new CliError("Cannot find required attributes in the model.");
845
- }
846
- const sourceModel = model.declarations.find((d) => d.$type === "DataModel" && getDbName(d) === relation.table);
847
- if (!sourceModel) return;
848
- const sourceFields = [];
849
- for (const colName of relation.columns) {
850
- const idx = sourceModel.fields.findIndex((f) => getDbName(f) === colName);
851
- const field = sourceModel.fields[idx];
852
- if (!field) return;
853
- sourceFields.push({
854
- field,
855
- index: idx
856
- });
857
- }
858
- const targetModel = model.declarations.find((d) => d.$type === "DataModel" && getDbName(d) === relation.references.table);
859
- if (!targetModel) return;
860
- const targetFields = [];
861
- for (const colName of relation.references.columns) {
862
- const field = targetModel.fields.find((f) => getDbName(f) === colName);
863
- if (!field) return;
864
- targetFields.push(field);
865
- }
866
- const firstSourceField = sourceFields[0].field;
867
- const firstSourceFieldId = sourceFields[0].index;
868
- const firstColumn = relation.columns[0];
869
- const fieldPrefix = /[0-9]/g.test(sourceModel.name.charAt(0)) ? "_" : "";
870
- const relationName = `${relation.table}${similarRelations > 0 ? `_${firstColumn}` : ""}To${relation.references.table}`;
871
- const sourceNameFromReference = firstSourceField.name.toLowerCase().endsWith("id") ? `${resolveNameCasing(options.fieldCasing, firstSourceField.name.slice(0, -2)).name}${relation.type === "many" ? "s" : ""}` : void 0;
872
- const sourceFieldFromReference = sourceModel.fields.find((f) => f.name === sourceNameFromReference);
873
- let { name: sourceFieldName } = resolveNameCasing(options.fieldCasing, similarRelations > 0 ? `${fieldPrefix}${lowerCaseFirst(sourceModel.name)}_${firstColumn}` : `${(!sourceFieldFromReference ? sourceNameFromReference : void 0) || lowerCaseFirst(resolveNameCasing(options.fieldCasing, targetModel.name).name)}${relation.type === "many" ? "s" : ""}`);
874
- if (sourceModel.fields.find((f) => f.name === sourceFieldName)) {
875
- sourceFieldName = `${sourceFieldName}To${lowerCaseFirst(targetModel.name)}_${relation.references.columns[0]}`;
876
- }
877
- const sourceFieldFactory = new DataFieldFactory().setContainer(sourceModel).setName(sourceFieldName).setType((tb) => tb.setOptional(relation.nullable).setArray(relation.type === "many").setReference(targetModel));
878
- sourceFieldFactory.addAttribute((ab) => {
879
- ab.setDecl(relationAttribute);
880
- if (includeRelationName) ab.addArg((ab2) => ab2.StringLiteral.setValue(relationName));
881
- ab.addArg((ab2) => {
882
- const arrayExpr = ab2.ArrayExpr;
883
- for (const { field } of sourceFields) {
884
- arrayExpr.addItem((aeb) => aeb.ReferenceExpr.setTarget(field));
885
- }
886
- return arrayExpr;
887
- }, "fields");
888
- ab.addArg((ab2) => {
889
- const arrayExpr = ab2.ArrayExpr;
890
- for (const field of targetFields) {
891
- arrayExpr.addItem((aeb) => aeb.ReferenceExpr.setTarget(field));
892
- }
893
- return arrayExpr;
894
- }, "references");
895
- const onDeleteDefault = relation.nullable ? "SET NULL" : "RESTRICT";
896
- if (relation.foreign_key_on_delete && relation.foreign_key_on_delete !== onDeleteDefault) {
897
- const enumRef = getEnumRef("ReferentialAction", services);
898
- if (!enumRef) throw new CliError("ReferentialAction enum not found");
899
- const enumFieldRef = enumRef.fields.find((f) => f.name.toLowerCase() === relation.foreign_key_on_delete.replace(/ /g, "").toLowerCase());
900
- if (!enumFieldRef) throw new CliError(`ReferentialAction ${relation.foreign_key_on_delete} not found`);
901
- ab.addArg((a) => a.ReferenceExpr.setTarget(enumFieldRef), "onDelete");
902
- }
903
- if (relation.foreign_key_on_update && relation.foreign_key_on_update !== "CASCADE") {
904
- const enumRef = getEnumRef("ReferentialAction", services);
905
- if (!enumRef) throw new CliError("ReferentialAction enum not found");
906
- const enumFieldRef = enumRef.fields.find((f) => f.name.toLowerCase() === relation.foreign_key_on_update.replace(/ /g, "").toLowerCase());
907
- if (!enumFieldRef) throw new CliError(`ReferentialAction ${relation.foreign_key_on_update} not found`);
908
- ab.addArg((a) => a.ReferenceExpr.setTarget(enumFieldRef), "onUpdate");
909
- }
910
- const defaultFkName = `${relation.table}_${relation.columns.join("_")}_fkey`;
911
- if (relation.fk_name && relation.fk_name !== defaultFkName) ab.addArg((ab2) => ab2.StringLiteral.setValue(relation.fk_name), "map");
912
- return ab;
913
- });
914
- sourceModel.fields.splice(firstSourceFieldId, 0, sourceFieldFactory.node);
915
- const oppositeFieldPrefix = /[0-9]/g.test(targetModel.name.charAt(0)) ? "_" : "";
916
- let { name: oppositeFieldName } = resolveNameCasing(options.fieldCasing, similarRelations > 0 ? `${oppositeFieldPrefix}${lowerCaseFirst(sourceModel.name)}_${firstColumn}` : `${lowerCaseFirst(resolveNameCasing(options.fieldCasing, sourceModel.name).name)}${relation.references.type === "many" ? "s" : ""}`);
917
- if (targetModel.fields.find((f) => f.name === oppositeFieldName)) {
918
- ({ name: oppositeFieldName } = resolveNameCasing(options.fieldCasing, `${lowerCaseFirst(sourceModel.name)}_${firstColumn}To${relation.references.table}_${relation.references.columns[0]}`));
919
- }
920
- const targetFieldFactory = new DataFieldFactory().setContainer(targetModel).setName(oppositeFieldName).setType((tb) => tb.setOptional(relation.references.type === "one").setArray(relation.references.type === "many").setReference(sourceModel));
921
- if (includeRelationName) targetFieldFactory.addAttribute((ab) => ab.setDecl(relationAttribute).addArg((ab2) => ab2.StringLiteral.setValue(relationName)));
922
- targetModel.fields.push(targetFieldFactory.node);
923
- }
924
- __name(syncRelation, "syncRelation");
925
- function consolidateEnums({ newModel, oldModel }) {
926
- const newEnums = newModel.declarations.filter((d) => isEnum(d));
927
- const newDataModels = newModel.declarations.filter((d) => d.$type === "DataModel");
928
- const oldDataModels = oldModel.declarations.filter((d) => d.$type === "DataModel");
929
- const enumMapping = /* @__PURE__ */ new Map();
930
- for (const newEnum of newEnums) {
931
- for (const newDM of newDataModels) {
932
- for (const field of newDM.fields) {
933
- if (field.$type !== "DataField" || field.type.reference?.ref !== newEnum) continue;
934
- const oldDM = oldDataModels.find((d) => getDbName(d) === getDbName(newDM));
935
- if (!oldDM) continue;
936
- const oldField = oldDM.fields.find((f) => getDbName(f) === getDbName(field));
937
- if (!oldField || oldField.$type !== "DataField" || !oldField.type.reference?.ref) continue;
938
- const oldEnum = oldField.type.reference.ref;
939
- if (!isEnum(oldEnum)) continue;
940
- enumMapping.set(newEnum, oldEnum);
941
- break;
942
- }
943
- if (enumMapping.has(newEnum)) break;
944
- }
945
- }
946
- const reverseMapping = /* @__PURE__ */ new Map();
947
- for (const [newEnum, oldEnum] of enumMapping) {
948
- if (!reverseMapping.has(oldEnum)) {
949
- reverseMapping.set(oldEnum, []);
950
- }
951
- reverseMapping.get(oldEnum).push(newEnum);
952
- }
953
- for (const [oldEnum, newEnumsGroup] of reverseMapping) {
954
- const keepEnum = newEnumsGroup[0];
955
- if (newEnumsGroup.length === 1 && keepEnum.name === oldEnum.name) continue;
956
- const oldValues = new Set(oldEnum.fields.map((f) => getDbName(f)));
957
- const allMatch = newEnumsGroup.every((ne) => {
958
- const newValues = new Set(ne.fields.map((f) => getDbName(f)));
959
- return oldValues.size === newValues.size && [
960
- ...oldValues
961
- ].every((v) => newValues.has(v));
962
- });
963
- if (!allMatch) continue;
964
- keepEnum.name = oldEnum.name;
965
- keepEnum.attributes = oldEnum.attributes.map((attr) => {
966
- const copy = {
967
- ...attr,
968
- $container: keepEnum
969
- };
970
- return copy;
971
- });
972
- for (let i = 1; i < newEnumsGroup.length; i++) {
973
- const idx = newModel.declarations.indexOf(newEnumsGroup[i]);
974
- if (idx >= 0) {
975
- newModel.declarations.splice(idx, 1);
976
- }
977
- }
978
- for (const newDM of newDataModels) {
979
- for (const field of newDM.fields) {
980
- if (field.$type !== "DataField") continue;
981
- const ref = field.type.reference?.ref;
982
- if (ref && newEnumsGroup.includes(ref)) {
983
- field.type.reference = {
984
- ref: keepEnum,
985
- $refText: keepEnum.name
986
- };
987
- }
988
- }
989
- }
990
- console.log(colors3.gray(`Consolidated enum${newEnumsGroup.length > 1 ? "s" : ""} ${newEnumsGroup.map((e) => e.name).join(", ")} \u2192 ${oldEnum.name}`));
991
- }
992
- }
993
- __name(consolidateEnums, "consolidateEnums");
994
-
995
- // src/actions/pull/provider/mysql.ts
996
- import { DataFieldAttributeFactory as DataFieldAttributeFactory2 } from "@zenstackhq/language/factory";
997
- function normalizeGenerationExpression(typeDef) {
998
- return typeDef.replace(/_([0-9A-Za-z_]+)\\?'/g, "'").replace(/\\'/g, "'");
999
- }
1000
- __name(normalizeGenerationExpression, "normalizeGenerationExpression");
1001
- var mysql = {
1002
- isSupportedFeature(feature) {
1003
- switch (feature) {
1004
- case "NativeEnum":
1005
- return true;
1006
- case "Schema":
1007
- default:
1008
- return false;
1009
- }
1010
- },
1011
- getBuiltinType(type2) {
1012
- const t = (type2 || "").toLowerCase().trim();
1013
- const isArray = false;
1014
- switch (t) {
1015
- // integers
1016
- case "tinyint":
1017
- case "smallint":
1018
- case "mediumint":
1019
- case "int":
1020
- case "integer":
1021
- return {
1022
- type: "Int",
1023
- isArray
1024
- };
1025
- case "bigint":
1026
- return {
1027
- type: "BigInt",
1028
- isArray
1029
- };
1030
- // decimals and floats
1031
- case "decimal":
1032
- case "numeric":
1033
- return {
1034
- type: "Decimal",
1035
- isArray
1036
- };
1037
- case "float":
1038
- case "double":
1039
- case "real":
1040
- return {
1041
- type: "Float",
1042
- isArray
1043
- };
1044
- // boolean (MySQL uses TINYINT(1) for boolean)
1045
- case "boolean":
1046
- case "bool":
1047
- return {
1048
- type: "Boolean",
1049
- isArray
1050
- };
1051
- // strings
1052
- case "char":
1053
- case "varchar":
1054
- case "tinytext":
1055
- case "text":
1056
- case "mediumtext":
1057
- case "longtext":
1058
- return {
1059
- type: "String",
1060
- isArray
1061
- };
1062
- // dates/times
1063
- case "date":
1064
- case "time":
1065
- case "datetime":
1066
- case "timestamp":
1067
- case "year":
1068
- return {
1069
- type: "DateTime",
1070
- isArray
1071
- };
1072
- // binary
1073
- case "binary":
1074
- case "varbinary":
1075
- case "tinyblob":
1076
- case "blob":
1077
- case "mediumblob":
1078
- case "longblob":
1079
- return {
1080
- type: "Bytes",
1081
- isArray
1082
- };
1083
- // json
1084
- case "json":
1085
- return {
1086
- type: "Json",
1087
- isArray
1088
- };
1089
- default:
1090
- if (t.startsWith("enum(")) {
1091
- return {
1092
- type: "String",
1093
- isArray
1094
- };
1095
- }
1096
- if (t.startsWith("set(")) {
1097
- return {
1098
- type: "String",
1099
- isArray
1100
- };
1101
- }
1102
- return {
1103
- type: "Unsupported",
1104
- isArray
1105
- };
1106
- }
1107
- },
1108
- getDefaultDatabaseType(type2) {
1109
- switch (type2) {
1110
- case "String":
1111
- return {
1112
- type: "varchar",
1113
- precision: 191
1114
- };
1115
- case "Boolean":
1116
- return {
1117
- type: "boolean"
1118
- };
1119
- case "Int":
1120
- return {
1121
- type: "int"
1122
- };
1123
- case "BigInt":
1124
- return {
1125
- type: "bigint"
1126
- };
1127
- case "Float":
1128
- return {
1129
- type: "double"
1130
- };
1131
- case "Decimal":
1132
- return {
1133
- type: "decimal",
1134
- precision: 65
1135
- };
1136
- case "DateTime":
1137
- return {
1138
- type: "datetime",
1139
- precision: 3
1140
- };
1141
- case "Json":
1142
- return {
1143
- type: "json"
1144
- };
1145
- case "Bytes":
1146
- return {
1147
- type: "longblob"
1148
- };
1149
- }
1150
- },
1151
- async introspect(connectionString, options) {
1152
- const mysql2 = await import("mysql2/promise");
1153
- const connection = await mysql2.createConnection(connectionString);
1154
- try {
1155
- const url = new URL(connectionString);
1156
- const databaseName = url.pathname.replace("/", "");
1157
- if (!databaseName) {
1158
- throw new CliError("Database name not found in connection string");
1159
- }
1160
- const [tableRows] = await connection.execute(getTableIntrospectionQuery(), [
1161
- databaseName
1162
- ]);
1163
- const tables = [];
1164
- for (const row of tableRows) {
1165
- const columns = typeof row.columns === "string" ? JSON.parse(row.columns) : row.columns;
1166
- const indexes = typeof row.indexes === "string" ? JSON.parse(row.indexes) : row.indexes;
1167
- const sortedColumns = (columns || []).sort((a, b) => (a.ordinal_position ?? 0) - (b.ordinal_position ?? 0)).map((col) => {
1168
- if (col.datatype === "enum" && col.datatype_name) {
1169
- return {
1170
- ...col,
1171
- datatype_name: resolveNameCasing(options.modelCasing, col.datatype_name).name
1172
- };
1173
- }
1174
- if (col.computed && typeof col.datatype === "string") {
1175
- return {
1176
- ...col,
1177
- datatype: normalizeGenerationExpression(col.datatype)
1178
- };
1179
- }
1180
- return col;
1181
- });
1182
- const filteredIndexes = (indexes || []).filter((idx) => !(idx.columns.length === 1 && idx.name === `${row.name}_${idx.columns[0]?.name}_fkey`));
1183
- tables.push({
1184
- schema: "",
1185
- name: row.name,
1186
- type: row.type,
1187
- definition: row.definition,
1188
- columns: sortedColumns,
1189
- indexes: filteredIndexes
1190
- });
1191
- }
1192
- const [enumRows] = await connection.execute(getEnumIntrospectionQuery(), [
1193
- databaseName
1194
- ]);
1195
- const enums = enumRows.map((row) => {
1196
- const values = parseEnumValues(row.column_type);
1197
- const syntheticName = `${row.table_name}_${row.column_name}`;
1198
- const { name } = resolveNameCasing(options.modelCasing, syntheticName);
1199
- return {
1200
- schema_name: "",
1201
- enum_type: name,
1202
- values
1203
- };
1204
- });
1205
- return {
1206
- tables,
1207
- enums
1208
- };
1209
- } finally {
1210
- await connection.end();
1211
- }
1212
- },
1213
- getDefaultValue({ defaultValue, fieldType, datatype, datatype_name, services, enums }) {
1214
- const val = defaultValue.trim();
1215
- if (val.toUpperCase() === "NULL") {
1216
- return null;
1217
- }
1218
- if (datatype === "enum" && datatype_name) {
1219
- const enumDef = enums.find((e) => getDbName(e) === datatype_name);
1220
- if (enumDef) {
1221
- const enumValue = val.startsWith("'") && val.endsWith("'") ? val.slice(1, -1) : val;
1222
- const enumField = enumDef.fields.find((f) => getDbName(f) === enumValue);
1223
- if (enumField) {
1224
- return (ab) => ab.ReferenceExpr.setTarget(enumField);
1225
- }
1226
- }
1227
- }
1228
- switch (fieldType) {
1229
- case "DateTime":
1230
- if (/^CURRENT_TIMESTAMP(\(\d*\))?$/i.test(val) || val.toLowerCase() === "current_timestamp()" || val.toLowerCase() === "now()") {
1231
- return (ab) => ab.InvocationExpr.setFunction(getFunctionRef("now", services));
1232
- }
1233
- return (ab) => ab.StringLiteral.setValue(val);
1234
- case "Int":
1235
- case "BigInt":
1236
- if (val.toLowerCase() === "auto_increment") {
1237
- return (ab) => ab.InvocationExpr.setFunction(getFunctionRef("autoincrement", services));
1238
- }
1239
- return (ab) => ab.NumberLiteral.setValue(val);
1240
- case "Float":
1241
- return normalizeFloatDefault(val);
1242
- case "Decimal":
1243
- return normalizeDecimalDefault(val);
1244
- case "Boolean":
1245
- return (ab) => ab.BooleanLiteral.setValue(val.toLowerCase() === "true" || val === "1" || val === "b'1'");
1246
- case "String":
1247
- if (val.toLowerCase() === "uuid()") {
1248
- return (ab) => ab.InvocationExpr.setFunction(getFunctionRef("uuid", services));
1249
- }
1250
- return (ab) => ab.StringLiteral.setValue(val);
1251
- case "Json":
1252
- return (ab) => ab.StringLiteral.setValue(val);
1253
- case "Bytes":
1254
- return (ab) => ab.StringLiteral.setValue(val);
1255
- }
1256
- if (val.includes("(") && val.includes(")")) {
1257
- return (ab) => ab.InvocationExpr.setFunction(getFunctionRef("dbgenerated", services)).addArg((a) => a.setValue((v) => v.StringLiteral.setValue(val)));
1258
- }
1259
- console.warn(`Unsupported default value type: "${defaultValue}" for field type "${fieldType}". Skipping default value.`);
1260
- return null;
1261
- },
1262
- getFieldAttributes({ fieldName, fieldType, datatype, length, precision, services }) {
1263
- const factories = [];
1264
- if (fieldType === "DateTime" && (fieldName.toLowerCase() === "updatedat" || fieldName.toLowerCase() === "updated_at")) {
1265
- factories.push(new DataFieldAttributeFactory2().setDecl(getAttributeRef("@updatedAt", services)));
1266
- }
1267
- const dbAttr = services.shared.workspace.IndexManager.allElements("Attribute").find((d) => d.name.toLowerCase() === `@db.${datatype.toLowerCase()}`)?.node;
1268
- const defaultDatabaseType = this.getDefaultDatabaseType(fieldType);
1269
- if (dbAttr && defaultDatabaseType && (defaultDatabaseType.type !== datatype || defaultDatabaseType.precision && defaultDatabaseType.precision !== (length ?? precision))) {
1270
- const dbAttrFactory = new DataFieldAttributeFactory2().setDecl(dbAttr);
1271
- const sizeValue = length ?? precision;
1272
- if (sizeValue !== void 0 && sizeValue !== null) {
1273
- dbAttrFactory.addArg((a) => a.NumberLiteral.setValue(sizeValue));
1274
- }
1275
- factories.push(dbAttrFactory);
1276
- }
1277
- return factories;
1278
- }
1279
- };
1280
- function getTableIntrospectionQuery() {
1281
- return `
1282
- -- Main query: one row per table/view with columns and indexes as nested JSON arrays.
1283
- -- Uses INFORMATION_SCHEMA which is MySQL's standard metadata catalog.
1284
- SELECT
1285
- t.TABLE_NAME AS \`name\`, -- table or view name
1286
- CASE t.TABLE_TYPE -- map MySQL table type strings to our internal types
1287
- WHEN 'BASE TABLE' THEN 'table'
1288
- WHEN 'VIEW' THEN 'view'
1289
- ELSE NULL
1290
- END AS \`type\`,
1291
- CASE -- for views, retrieve the SQL definition
1292
- WHEN t.TABLE_TYPE = 'VIEW' THEN v.VIEW_DEFINITION
1293
- ELSE NULL
1294
- END AS \`definition\`,
1295
-
1296
- -- ===== COLUMNS subquery =====
1297
- -- Wraps an ordered subquery in JSON_ARRAYAGG to produce a JSON array of column objects.
1298
- (
1299
- SELECT JSON_ARRAYAGG(col_json)
1300
- FROM (
1301
- SELECT JSON_OBJECT(
1302
- 'ordinal_position', c.ORDINAL_POSITION, -- column position (used for sorting)
1303
- 'name', c.COLUMN_NAME, -- column name
1304
-
1305
- -- datatype: for generated/computed columns, construct the full DDL-like type definition
1306
- -- (e.g., "int GENERATED ALWAYS AS (col1 + col2) STORED") so it can be rendered as
1307
- -- Unsupported("..."); special-case tinyint(1) as 'boolean' (MySQL's boolean convention);
1308
- -- otherwise use the DATA_TYPE (e.g., 'int', 'varchar', 'datetime').
1309
- 'datatype', CASE
1310
- WHEN c.GENERATION_EXPRESSION IS NOT NULL AND c.GENERATION_EXPRESSION != '' THEN
1311
- CONCAT(
1312
- c.COLUMN_TYPE,
1313
- ' GENERATED ALWAYS AS (',
1314
- c.GENERATION_EXPRESSION,
1315
- ') ',
1316
- CASE
1317
- WHEN c.EXTRA LIKE '%STORED GENERATED%' THEN 'STORED'
1318
- ELSE 'VIRTUAL'
1319
- END
1320
- )
1321
- WHEN c.DATA_TYPE = 'tinyint' AND c.COLUMN_TYPE = 'tinyint(1)' THEN 'boolean'
1322
- ELSE c.DATA_TYPE
1323
- END,
1324
-
1325
- -- datatype_name: for enum columns, generate a synthetic name "TableName_ColumnName"
1326
- -- (MySQL doesn't have named enum types like PostgreSQL)
1327
- 'datatype_name', CASE
1328
- WHEN c.DATA_TYPE = 'enum' THEN CONCAT(t.TABLE_NAME, '_', c.COLUMN_NAME)
1329
- ELSE NULL
1330
- END,
1331
-
1332
- 'datatype_schema', '', -- MySQL doesn't support multi-schema
1333
- 'length', c.CHARACTER_MAXIMUM_LENGTH, -- max length for string types (e.g., VARCHAR(255) -> 255)
1334
- 'precision', COALESCE(c.NUMERIC_PRECISION, c.DATETIME_PRECISION), -- numeric or datetime precision
1335
-
1336
- 'nullable', c.IS_NULLABLE = 'YES', -- true if column allows NULL
1337
-
1338
- -- default: for auto_increment columns, report 'auto_increment' instead of NULL;
1339
- -- otherwise use the COLUMN_DEFAULT value
1340
- 'default', CASE
1341
- WHEN c.EXTRA LIKE '%auto_increment%' THEN 'auto_increment'
1342
- ELSE c.COLUMN_DEFAULT
1343
- END,
1344
-
1345
- 'pk', c.COLUMN_KEY = 'PRI', -- true if column is part of the primary key
1346
-
1347
- -- unique: true if the column has a single-column unique index.
1348
- -- COLUMN_KEY = 'UNI' covers most cases, but may not be set when the column
1349
- -- also participates in other indexes (showing 'MUL' instead on some MySQL versions).
1350
- -- Also check INFORMATION_SCHEMA.STATISTICS for single-column unique indexes
1351
- -- (NON_UNIQUE = 0) to match the PostgreSQL introspection behavior.
1352
- 'unique', (
1353
- c.COLUMN_KEY = 'UNI'
1354
- OR EXISTS (
1355
- SELECT 1
1356
- FROM INFORMATION_SCHEMA.STATISTICS s_uni
1357
- WHERE s_uni.TABLE_SCHEMA = c.TABLE_SCHEMA
1358
- AND s_uni.TABLE_NAME = c.TABLE_NAME
1359
- AND s_uni.COLUMN_NAME = c.COLUMN_NAME
1360
- AND s_uni.NON_UNIQUE = 0
1361
- AND s_uni.INDEX_NAME != 'PRIMARY'
1362
- AND (
1363
- SELECT COUNT(*)
1364
- FROM INFORMATION_SCHEMA.STATISTICS s_cnt
1365
- WHERE s_cnt.TABLE_SCHEMA = s_uni.TABLE_SCHEMA
1366
- AND s_cnt.TABLE_NAME = s_uni.TABLE_NAME
1367
- AND s_cnt.INDEX_NAME = s_uni.INDEX_NAME
1368
- ) = 1
1369
- )
1370
- ),
1371
- 'unique_name', (
1372
- SELECT COALESCE(
1373
- CASE WHEN c.COLUMN_KEY = 'UNI' THEN c.COLUMN_NAME ELSE NULL END,
1374
- (
1375
- SELECT s_uni.INDEX_NAME
1376
- FROM INFORMATION_SCHEMA.STATISTICS s_uni
1377
- WHERE s_uni.TABLE_SCHEMA = c.TABLE_SCHEMA
1378
- AND s_uni.TABLE_NAME = c.TABLE_NAME
1379
- AND s_uni.COLUMN_NAME = c.COLUMN_NAME
1380
- AND s_uni.NON_UNIQUE = 0
1381
- AND s_uni.INDEX_NAME != 'PRIMARY'
1382
- AND (
1383
- SELECT COUNT(*)
1384
- FROM INFORMATION_SCHEMA.STATISTICS s_cnt
1385
- WHERE s_cnt.TABLE_SCHEMA = s_uni.TABLE_SCHEMA
1386
- AND s_cnt.TABLE_NAME = s_uni.TABLE_NAME
1387
- AND s_cnt.INDEX_NAME = s_uni.INDEX_NAME
1388
- ) = 1
1389
- LIMIT 1
1390
- )
1391
- )
1392
- ),
1393
-
1394
- -- computed: true if column has a generation expression (virtual or stored)
1395
- 'computed', c.GENERATION_EXPRESSION IS NOT NULL AND c.GENERATION_EXPRESSION != '',
1396
-
1397
- -- options: for enum columns, the full COLUMN_TYPE string (e.g., "enum('a','b','c')")
1398
- -- which gets parsed into individual values later
1399
- 'options', CASE
1400
- WHEN c.DATA_TYPE = 'enum' THEN c.COLUMN_TYPE
1401
- ELSE NULL
1402
- END,
1403
-
1404
- -- Foreign key info (NULL if column is not part of a FK)
1405
- 'foreign_key_schema', NULL, -- MySQL doesn't support cross-schema FKs here
1406
- 'foreign_key_table', kcu_fk.REFERENCED_TABLE_NAME, -- referenced table
1407
- 'foreign_key_column', kcu_fk.REFERENCED_COLUMN_NAME, -- referenced column
1408
- 'foreign_key_name', kcu_fk.CONSTRAINT_NAME, -- FK constraint name
1409
- 'foreign_key_on_update', rc.UPDATE_RULE, -- referential action on update (CASCADE, SET NULL, etc.)
1410
- 'foreign_key_on_delete', rc.DELETE_RULE -- referential action on delete
1411
- ) AS col_json
1412
-
1413
- FROM INFORMATION_SCHEMA.COLUMNS c -- one row per column in the database
1414
-
1415
- -- Join KEY_COLUMN_USAGE to find foreign key references for this column.
1416
- -- Filter to only FK entries (REFERENCED_TABLE_NAME IS NOT NULL).
1417
- LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu_fk
1418
- ON c.TABLE_SCHEMA = kcu_fk.TABLE_SCHEMA
1419
- AND c.TABLE_NAME = kcu_fk.TABLE_NAME
1420
- AND c.COLUMN_NAME = kcu_fk.COLUMN_NAME
1421
- AND kcu_fk.REFERENCED_TABLE_NAME IS NOT NULL
1422
-
1423
- -- Join REFERENTIAL_CONSTRAINTS to get ON UPDATE / ON DELETE rules for the FK.
1424
- LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc
1425
- ON kcu_fk.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
1426
- AND kcu_fk.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
1427
-
1428
- WHERE c.TABLE_SCHEMA = t.TABLE_SCHEMA
1429
- AND c.TABLE_NAME = t.TABLE_NAME
1430
- ORDER BY c.ORDINAL_POSITION -- preserve original column order
1431
- ) AS cols_ordered
1432
- ) AS \`columns\`,
1433
-
1434
- -- ===== INDEXES subquery =====
1435
- -- Aggregates all indexes for this table into a JSON array.
1436
- (
1437
- SELECT JSON_ARRAYAGG(idx_json)
1438
- FROM (
1439
- SELECT JSON_OBJECT(
1440
- 'name', s.INDEX_NAME, -- index name (e.g., 'PRIMARY', 'idx_email')
1441
- 'method', s.INDEX_TYPE, -- index type (e.g., 'BTREE', 'HASH', 'FULLTEXT')
1442
- 'unique', s.NON_UNIQUE = 0, -- NON_UNIQUE=0 means it IS unique
1443
- 'primary', s.INDEX_NAME = 'PRIMARY', -- MySQL names the PK index 'PRIMARY'
1444
- 'valid', TRUE, -- MySQL doesn't expose index validity status
1445
- 'ready', TRUE, -- MySQL doesn't expose index readiness status
1446
- 'partial', FALSE, -- MySQL doesn't support partial indexes
1447
- 'predicate', NULL, -- no WHERE clause on indexes in MySQL
1448
-
1449
- -- Index columns: nested subquery for columns in this index
1450
- 'columns', (
1451
- SELECT JSON_ARRAYAGG(idx_col_json)
1452
- FROM (
1453
- SELECT JSON_OBJECT(
1454
- 'name', s2.COLUMN_NAME, -- column name in the index
1455
- 'expression', NULL, -- MySQL doesn't expose expression indexes via STATISTICS
1456
- -- COLLATION: 'A' = ascending, 'D' = descending, NULL = not sorted
1457
- 'order', CASE s2.COLLATION WHEN 'A' THEN 'ASC' WHEN 'D' THEN 'DESC' ELSE NULL END,
1458
- 'nulls', NULL -- MySQL doesn't expose NULLS FIRST/LAST
1459
- ) AS idx_col_json
1460
- FROM INFORMATION_SCHEMA.STATISTICS s2 -- one row per column per index
1461
- WHERE s2.TABLE_SCHEMA = s.TABLE_SCHEMA
1462
- AND s2.TABLE_NAME = s.TABLE_NAME
1463
- AND s2.INDEX_NAME = s.INDEX_NAME
1464
- ORDER BY s2.SEQ_IN_INDEX -- preserve column order within the index
1465
- ) AS idx_cols_ordered
1466
- )
1467
- ) AS idx_json
1468
- FROM (
1469
- -- Deduplicate: STATISTICS has one row per (index, column), but we need one row per index.
1470
- -- DISTINCT on INDEX_NAME gives us one entry per index with its metadata.
1471
- SELECT DISTINCT INDEX_NAME, INDEX_TYPE, NON_UNIQUE, TABLE_SCHEMA, TABLE_NAME
1472
- FROM INFORMATION_SCHEMA.STATISTICS
1473
- WHERE TABLE_SCHEMA = t.TABLE_SCHEMA AND TABLE_NAME = t.TABLE_NAME
1474
- ) s
1475
- ) AS idxs_ordered
1476
- ) AS \`indexes\`
1477
-
1478
- -- === Main FROM: INFORMATION_SCHEMA.TABLES lists all tables and views ===
1479
- FROM INFORMATION_SCHEMA.TABLES t
1480
- -- Join VIEWS to get VIEW_DEFINITION for view tables
1481
- LEFT JOIN INFORMATION_SCHEMA.VIEWS v
1482
- ON t.TABLE_SCHEMA = v.TABLE_SCHEMA AND t.TABLE_NAME = v.TABLE_NAME
1483
- WHERE t.TABLE_SCHEMA = ? -- only the target database
1484
- AND t.TABLE_TYPE IN ('BASE TABLE', 'VIEW') -- exclude system tables like SYSTEM VIEW
1485
- AND t.TABLE_NAME <> '_prisma_migrations' -- exclude Prisma migration tracking table
1486
- ORDER BY t.TABLE_NAME;
1487
- `;
1488
- }
1489
- __name(getTableIntrospectionQuery, "getTableIntrospectionQuery");
1490
- function getEnumIntrospectionQuery() {
1491
- return `
1492
- SELECT
1493
- c.TABLE_NAME AS table_name, -- table containing the enum column
1494
- c.COLUMN_NAME AS column_name, -- column name
1495
- c.COLUMN_TYPE AS column_type -- full type string including values (e.g., "enum('val1','val2')")
1496
- FROM INFORMATION_SCHEMA.COLUMNS c
1497
- WHERE c.TABLE_SCHEMA = ? -- only the target database
1498
- AND c.DATA_TYPE = 'enum' -- only enum columns
1499
- ORDER BY c.TABLE_NAME, c.COLUMN_NAME;
1500
- `;
1501
- }
1502
- __name(getEnumIntrospectionQuery, "getEnumIntrospectionQuery");
1503
- function parseEnumValues(columnType) {
1504
- const match = columnType.match(/^enum\((.+)\)$/i);
1505
- if (!match || !match[1]) return [];
1506
- const valuesString = match[1];
1507
- const values = [];
1508
- let current = "";
1509
- let inQuote = false;
1510
- let i = 0;
1511
- while (i < valuesString.length) {
1512
- const char = valuesString[i];
1513
- if (char === "'" && !inQuote) {
1514
- inQuote = true;
1515
- i++;
1516
- continue;
1517
- }
1518
- if (char === "'" && inQuote) {
1519
- if (valuesString[i + 1] === "'") {
1520
- current += "'";
1521
- i += 2;
1522
- continue;
1523
- }
1524
- values.push(current);
1525
- current = "";
1526
- inQuote = false;
1527
- i++;
1528
- while (i < valuesString.length && (valuesString[i] === "," || valuesString[i] === " ")) {
1529
- i++;
1530
- }
1531
- continue;
1532
- }
1533
- if (inQuote) {
1534
- current += char;
1535
- }
1536
- i++;
1537
- }
1538
- return values;
1539
- }
1540
- __name(parseEnumValues, "parseEnumValues");
1541
-
1542
- // src/actions/pull/provider/postgresql.ts
1543
- import { DataFieldAttributeFactory as DataFieldAttributeFactory3 } from "@zenstackhq/language/factory";
1544
- var pgTypnameToStandard = {
1545
- int2: "smallint",
1546
- int4: "integer",
1547
- int8: "bigint",
1548
- float4: "real",
1549
- float8: "double precision",
1550
- bool: "boolean",
1551
- bpchar: "character",
1552
- numeric: "decimal"
1553
- };
1554
- var standardTypePrecisions = {
1555
- int2: 16,
1556
- smallint: 16,
1557
- int4: 32,
1558
- integer: 32,
1559
- int8: 64,
1560
- bigint: 64,
1561
- float4: 24,
1562
- real: 24,
1563
- float8: 53,
1564
- "double precision": 53
1565
- };
1566
- var pgTypnameToZenStackNativeType = {
1567
- // integers
1568
- int2: "SmallInt",
1569
- smallint: "SmallInt",
1570
- int4: "Integer",
1571
- integer: "Integer",
1572
- int8: "BigInt",
1573
- bigint: "BigInt",
1574
- // decimals and floats
1575
- numeric: "Decimal",
1576
- decimal: "Decimal",
1577
- float4: "Real",
1578
- real: "Real",
1579
- float8: "DoublePrecision",
1580
- "double precision": "DoublePrecision",
1581
- // boolean
1582
- bool: "Boolean",
1583
- boolean: "Boolean",
1584
- // strings
1585
- text: "Text",
1586
- varchar: "VarChar",
1587
- "character varying": "VarChar",
1588
- bpchar: "Char",
1589
- character: "Char",
1590
- // uuid
1591
- uuid: "Uuid",
1592
- // dates/times
1593
- date: "Date",
1594
- time: "Time",
1595
- timetz: "Timetz",
1596
- timestamp: "Timestamp",
1597
- timestamptz: "Timestamptz",
1598
- // binary
1599
- bytea: "ByteA",
1600
- // json
1601
- json: "Json",
1602
- jsonb: "JsonB",
1603
- // xml
1604
- xml: "Xml",
1605
- // network types
1606
- inet: "Inet",
1607
- // bit strings
1608
- bit: "Bit",
1609
- varbit: "VarBit",
1610
- // oid
1611
- oid: "Oid",
1612
- // money
1613
- money: "Money",
1614
- // citext extension
1615
- citext: "Citext"
1616
- };
1617
- var postgresql = {
1618
- isSupportedFeature(feature) {
1619
- const supportedFeatures = [
1620
- "Schema",
1621
- "NativeEnum"
1622
- ];
1623
- return supportedFeatures.includes(feature);
1624
- },
1625
- getBuiltinType(type2) {
1626
- const t = (type2 || "").toLowerCase();
1627
- const isArray = t.startsWith("_");
1628
- switch (t.replace(/^_/, "")) {
1629
- // integers
1630
- case "int2":
1631
- case "smallint":
1632
- case "int4":
1633
- case "integer":
1634
- return {
1635
- type: "Int",
1636
- isArray
1637
- };
1638
- case "int8":
1639
- case "bigint":
1640
- return {
1641
- type: "BigInt",
1642
- isArray
1643
- };
1644
- // decimals and floats
1645
- case "numeric":
1646
- case "decimal":
1647
- return {
1648
- type: "Decimal",
1649
- isArray
1650
- };
1651
- case "float4":
1652
- case "real":
1653
- case "float8":
1654
- case "double precision":
1655
- return {
1656
- type: "Float",
1657
- isArray
1658
- };
1659
- // boolean
1660
- case "bool":
1661
- case "boolean":
1662
- return {
1663
- type: "Boolean",
1664
- isArray
1665
- };
1666
- // strings
1667
- case "text":
1668
- case "varchar":
1669
- case "bpchar":
1670
- case "character varying":
1671
- case "character":
1672
- return {
1673
- type: "String",
1674
- isArray
1675
- };
1676
- // uuid
1677
- case "uuid":
1678
- return {
1679
- type: "String",
1680
- isArray
1681
- };
1682
- // dates/times
1683
- case "date":
1684
- case "time":
1685
- case "timetz":
1686
- case "timestamp":
1687
- case "timestamptz":
1688
- return {
1689
- type: "DateTime",
1690
- isArray
1691
- };
1692
- // binary
1693
- case "bytea":
1694
- return {
1695
- type: "Bytes",
1696
- isArray
1697
- };
1698
- // json
1699
- case "json":
1700
- case "jsonb":
1701
- return {
1702
- type: "Json",
1703
- isArray
1704
- };
1705
- default:
1706
- return {
1707
- type: "Unsupported",
1708
- isArray
1709
- };
1710
- }
1711
- },
1712
- async introspect(connectionString, options) {
1713
- const { Client } = await import("pg");
1714
- const client = new Client({
1715
- connectionString
1716
- });
1717
- await client.connect();
1718
- try {
1719
- const { rows: tables } = await client.query(tableIntrospectionQuery);
1720
- const { rows: enums } = await client.query(enumIntrospectionQuery);
1721
- const filteredTables = tables.filter((t) => options.schemas.includes(t.schema));
1722
- const filteredEnums = enums.filter((e) => options.schemas.includes(e.schema_name));
1723
- return {
1724
- enums: filteredEnums,
1725
- tables: filteredTables
1726
- };
1727
- } finally {
1728
- await client.end();
1729
- }
1730
- },
1731
- getDefaultDatabaseType(type2) {
1732
- switch (type2) {
1733
- case "String":
1734
- return {
1735
- type: "text"
1736
- };
1737
- case "Boolean":
1738
- return {
1739
- type: "boolean"
1740
- };
1741
- case "Int":
1742
- return {
1743
- type: "integer"
1744
- };
1745
- case "BigInt":
1746
- return {
1747
- type: "bigint"
1748
- };
1749
- case "Float":
1750
- return {
1751
- type: "double precision"
1752
- };
1753
- case "Decimal":
1754
- return {
1755
- type: "decimal"
1756
- };
1757
- case "DateTime":
1758
- return {
1759
- type: "timestamp",
1760
- precision: 3
1761
- };
1762
- case "Json":
1763
- return {
1764
- type: "jsonb"
1765
- };
1766
- case "Bytes":
1767
- return {
1768
- type: "bytea"
1769
- };
1770
- }
1771
- },
1772
- getDefaultValue({ defaultValue, fieldType, datatype, datatype_name, services, enums }) {
1773
- const val = defaultValue.trim();
1774
- if (datatype === "enum" && datatype_name) {
1775
- const enumDef = enums.find((e) => getDbName(e) === datatype_name);
1776
- if (enumDef) {
1777
- const enumValue = val.replace(/'/g, "").split("::")[0]?.trim();
1778
- const enumField = enumDef.fields.find((f) => getDbName(f) === enumValue);
1779
- if (enumField) {
1780
- return (ab) => ab.ReferenceExpr.setTarget(enumField);
1781
- }
1782
- }
1783
- return typeCastingConvert({
1784
- defaultValue,
1785
- enums,
1786
- val,
1787
- services
1788
- });
1789
- }
1790
- switch (fieldType) {
1791
- case "DateTime":
1792
- if (val === "CURRENT_TIMESTAMP" || val === "now()") {
1793
- return (ab) => ab.InvocationExpr.setFunction(getFunctionRef("now", services));
1794
- }
1795
- if (val.includes("::")) {
1796
- return typeCastingConvert({
1797
- defaultValue,
1798
- enums,
1799
- val,
1800
- services
1801
- });
1802
- }
1803
- return (ab) => ab.StringLiteral.setValue(val);
1804
- case "Int":
1805
- case "BigInt":
1806
- if (val.startsWith("nextval(")) {
1807
- return (ab) => ab.InvocationExpr.setFunction(getFunctionRef("autoincrement", services));
1808
- }
1809
- if (val.includes("::")) {
1810
- return typeCastingConvert({
1811
- defaultValue,
1812
- enums,
1813
- val,
1814
- services
1815
- });
1816
- }
1817
- return (ab) => ab.NumberLiteral.setValue(val);
1818
- case "Float":
1819
- if (val.includes("::")) {
1820
- return typeCastingConvert({
1821
- defaultValue,
1822
- enums,
1823
- val,
1824
- services
1825
- });
1826
- }
1827
- return normalizeFloatDefault(val);
1828
- case "Decimal":
1829
- if (val.includes("::")) {
1830
- return typeCastingConvert({
1831
- defaultValue,
1832
- enums,
1833
- val,
1834
- services
1835
- });
1836
- }
1837
- return normalizeDecimalDefault(val);
1838
- case "Boolean":
1839
- return (ab) => ab.BooleanLiteral.setValue(val === "true");
1840
- case "String":
1841
- if (val.includes("::")) {
1842
- return typeCastingConvert({
1843
- defaultValue,
1844
- enums,
1845
- val,
1846
- services
1847
- });
1848
- }
1849
- if (val.startsWith("'") && val.endsWith("'")) {
1850
- return (ab) => ab.StringLiteral.setValue(val.slice(1, -1).replace(/''/g, "'"));
1851
- }
1852
- return (ab) => ab.StringLiteral.setValue(val);
1853
- case "Json":
1854
- if (val.includes("::")) {
1855
- return typeCastingConvert({
1856
- defaultValue,
1857
- enums,
1858
- val,
1859
- services
1860
- });
1861
- }
1862
- return (ab) => ab.StringLiteral.setValue(val);
1863
- case "Bytes":
1864
- if (val.includes("::")) {
1865
- return typeCastingConvert({
1866
- defaultValue,
1867
- enums,
1868
- val,
1869
- services
1870
- });
1871
- }
1872
- return (ab) => ab.StringLiteral.setValue(val);
1873
- }
1874
- if (val.includes("(") && val.includes(")")) {
1875
- return (ab) => ab.InvocationExpr.setFunction(getFunctionRef("dbgenerated", services)).addArg((a) => a.setValue((v) => v.StringLiteral.setValue(val)));
1876
- }
1877
- console.warn(`Unsupported default value type: "${defaultValue}" for field type "${fieldType}". Skipping default value.`);
1878
- return null;
1879
- },
1880
- getFieldAttributes({ fieldName, fieldType, datatype, length, precision, services }) {
1881
- const factories = [];
1882
- if (fieldType === "DateTime" && (fieldName.toLowerCase() === "updatedat" || fieldName.toLowerCase() === "updated_at")) {
1883
- factories.push(new DataFieldAttributeFactory3().setDecl(getAttributeRef("@updatedAt", services)));
1884
- }
1885
- const nativeTypeName = pgTypnameToZenStackNativeType[datatype.toLowerCase()] ?? datatype;
1886
- const dbAttr = services.shared.workspace.IndexManager.allElements("Attribute").find((d) => d.name.toLowerCase() === `@db.${nativeTypeName.toLowerCase()}`)?.node;
1887
- const defaultDatabaseType = this.getDefaultDatabaseType(fieldType);
1888
- const normalizedDatatype = pgTypnameToStandard[datatype.toLowerCase()] ?? datatype.toLowerCase();
1889
- const standardPrecision = standardTypePrecisions[datatype.toLowerCase()];
1890
- const isStandardPrecision = standardPrecision !== void 0 && precision === standardPrecision;
1891
- if (dbAttr && defaultDatabaseType && (defaultDatabaseType.type !== normalizedDatatype || defaultDatabaseType.precision && defaultDatabaseType.precision !== (length ?? precision))) {
1892
- const dbAttrFactory = new DataFieldAttributeFactory3().setDecl(dbAttr);
1893
- if ((length || precision) && !isStandardPrecision) {
1894
- dbAttrFactory.addArg((a) => a.NumberLiteral.setValue(length || precision));
1895
- }
1896
- factories.push(dbAttrFactory);
1897
- }
1898
- return factories;
1899
- }
1900
- };
1901
- var enumIntrospectionQuery = `
1902
- SELECT
1903
- n.nspname AS schema_name, -- schema the enum belongs to (e.g., 'public')
1904
- t.typname AS enum_type, -- enum type name as defined in CREATE TYPE
1905
- coalesce(json_agg(e.enumlabel ORDER BY e.enumsortorder), '[]') AS values -- ordered list of enum labels as JSON array
1906
- FROM pg_type t -- pg_type: catalog of all data types
1907
- JOIN pg_enum e ON t.oid = e.enumtypid -- pg_enum: one row per enum label; join to get labels for this enum type
1908
- JOIN pg_namespace n ON n.oid = t.typnamespace -- pg_namespace: schema info; join to get the schema name
1909
- GROUP BY schema_name, enum_type -- one row per enum type, with all labels aggregated
1910
- ORDER BY schema_name, enum_type;`;
1911
- var tableIntrospectionQuery = `
1912
- -- Main query: one row per table/view with columns and indexes as nested JSON arrays.
1913
- -- Joins pg_class (tables/views) with pg_namespace (schemas).
1914
- SELECT
1915
- "ns"."nspname" AS "schema", -- schema name (e.g., 'public')
1916
- "cls"."relname" AS "name", -- table or view name
1917
- CASE "cls"."relkind" -- relkind: 'r' = ordinary table, 'v' = view
1918
- WHEN 'r' THEN 'table'
1919
- WHEN 'v' THEN 'view'
1920
- ELSE NULL
1921
- END AS "type",
1922
- CASE -- for views, retrieve the SQL definition
1923
- WHEN "cls"."relkind" = 'v' THEN pg_get_viewdef("cls"."oid", true)
1924
- ELSE NULL
1925
- END AS "definition",
1926
-
1927
- -- ===== COLUMNS subquery =====
1928
- -- Aggregates all columns for this table into a JSON array.
1929
- (
1930
- SELECT coalesce(json_agg(agg), '[]')
1931
- FROM (
1932
- SELECT
1933
- "att"."attname" AS "name", -- column name
1934
-
1935
- -- datatype: if the type is an enum, report 'enum';
1936
- -- if the column is generated/computed, construct the full DDL-like type definition
1937
- -- (e.g., "text GENERATED ALWAYS AS (expr) STORED") so it can be rendered as Unsupported("...");
1938
- -- otherwise use the pg_type name.
1939
- CASE
1940
- WHEN EXISTS (
1941
- SELECT 1 FROM "pg_catalog"."pg_enum" AS "e"
1942
- WHERE "e"."enumtypid" = "typ"."oid"
1943
- ) THEN 'enum'
1944
- WHEN "att"."attgenerated" != '' THEN
1945
- format_type("att"."atttypid", "att"."atttypmod")
1946
- || ' GENERATED ALWAYS AS ('
1947
- || pg_get_expr("def"."adbin", "def"."adrelid")
1948
- || ') '
1949
- || CASE "att"."attgenerated"
1950
- WHEN 's' THEN 'STORED'
1951
- WHEN 'v' THEN 'VIRTUAL'
1952
- ELSE 'STORED'
1953
- END
1954
- ELSE "typ"."typname"::text -- internal type name (e.g., 'int4', 'varchar', 'text'); cast to text to prevent CASE from coercing result to name type (max 63 chars)
1955
- END AS "datatype",
1956
-
1957
- -- datatype_name: for enums only, the actual enum type name (used to look up the enum definition)
1958
- CASE
1959
- WHEN EXISTS (
1960
- SELECT 1 FROM "pg_catalog"."pg_enum" AS "e"
1961
- WHERE "e"."enumtypid" = "typ"."oid"
1962
- ) THEN "typ"."typname"
1963
- ELSE NULL
1964
- END AS "datatype_name",
1965
-
1966
- "tns"."nspname" AS "datatype_schema", -- schema where the data type is defined
1967
- "c"."character_maximum_length" AS "length", -- max length for char/varchar types (from information_schema)
1968
- COALESCE("c"."numeric_precision", "c"."datetime_precision") AS "precision", -- numeric or datetime precision
1969
-
1970
- -- Foreign key info (NULL if column is not part of a FK constraint)
1971
- "fk_ns"."nspname" AS "foreign_key_schema", -- schema of the referenced table
1972
- "fk_cls"."relname" AS "foreign_key_table", -- referenced table name
1973
- "fk_att"."attname" AS "foreign_key_column", -- referenced column name
1974
- "fk_con"."conname" AS "foreign_key_name", -- FK constraint name
1975
-
1976
- -- FK referential actions: decode single-char codes to human-readable strings
1977
- CASE "fk_con"."confupdtype"
1978
- WHEN 'a' THEN 'NO ACTION'
1979
- WHEN 'r' THEN 'RESTRICT'
1980
- WHEN 'c' THEN 'CASCADE'
1981
- WHEN 'n' THEN 'SET NULL'
1982
- WHEN 'd' THEN 'SET DEFAULT'
1983
- ELSE NULL
1984
- END AS "foreign_key_on_update",
1985
- CASE "fk_con"."confdeltype"
1986
- WHEN 'a' THEN 'NO ACTION'
1987
- WHEN 'r' THEN 'RESTRICT'
1988
- WHEN 'c' THEN 'CASCADE'
1989
- WHEN 'n' THEN 'SET NULL'
1990
- WHEN 'd' THEN 'SET DEFAULT'
1991
- ELSE NULL
1992
- END AS "foreign_key_on_delete",
1993
-
1994
- -- pk: true if this column is part of the table's primary key constraint
1995
- "pk_con"."conkey" IS NOT NULL AS "pk",
1996
-
1997
- -- unique: true if the column has a single-column UNIQUE constraint OR a single-column unique index
1998
- (
1999
- -- Check for a single-column UNIQUE constraint (contype = 'u')
2000
- EXISTS (
2001
- SELECT 1
2002
- FROM "pg_catalog"."pg_constraint" AS "u_con"
2003
- WHERE "u_con"."contype" = 'u' -- 'u' = unique constraint
2004
- AND "u_con"."conrelid" = "cls"."oid" -- on this table
2005
- AND array_length("u_con"."conkey", 1) = 1 -- single-column only
2006
- AND "att"."attnum" = ANY ("u_con"."conkey") -- this column is in the constraint
2007
- )
2008
- OR
2009
- -- Check for a single-column unique index (may exist without an explicit constraint)
2010
- EXISTS (
2011
- SELECT 1
2012
- FROM "pg_catalog"."pg_index" AS "u_idx"
2013
- WHERE "u_idx"."indrelid" = "cls"."oid" -- on this table
2014
- AND "u_idx"."indisunique" = TRUE -- it's a unique index
2015
- AND "u_idx"."indnkeyatts" = 1 -- single key column
2016
- AND "att"."attnum" = ANY ("u_idx"."indkey"::int2[]) -- this column is the key
2017
- )
2018
- ) AS "unique",
2019
-
2020
- -- unique_name: the name of the unique constraint or index (whichever exists first)
2021
- (
2022
- SELECT COALESCE(
2023
- -- Try constraint name first
2024
- (
2025
- SELECT "u_con"."conname"
2026
- FROM "pg_catalog"."pg_constraint" AS "u_con"
2027
- WHERE "u_con"."contype" = 'u'
2028
- AND "u_con"."conrelid" = "cls"."oid"
2029
- AND array_length("u_con"."conkey", 1) = 1
2030
- AND "att"."attnum" = ANY ("u_con"."conkey")
2031
- LIMIT 1
2032
- ),
2033
- -- Fall back to unique index name
2034
- (
2035
- SELECT "u_idx_cls"."relname"
2036
- FROM "pg_catalog"."pg_index" AS "u_idx"
2037
- JOIN "pg_catalog"."pg_class" AS "u_idx_cls" ON "u_idx"."indexrelid" = "u_idx_cls"."oid"
2038
- WHERE "u_idx"."indrelid" = "cls"."oid"
2039
- AND "u_idx"."indisunique" = TRUE
2040
- AND "u_idx"."indnkeyatts" = 1
2041
- AND "att"."attnum" = ANY ("u_idx"."indkey"::int2[])
2042
- LIMIT 1
2043
- )
2044
- )
2045
- ) AS "unique_name",
2046
-
2047
- "att"."attgenerated" != '' AS "computed", -- true if column is a generated/computed column
2048
- -- For generated columns, pg_attrdef stores the generation expression (not a default),
2049
- -- so we must null it out to avoid emitting a spurious @default(dbgenerated(...)) attribute.
2050
- CASE
2051
- WHEN "att"."attgenerated" != '' THEN NULL
2052
- ELSE pg_get_expr("def"."adbin", "def"."adrelid")
2053
- END AS "default", -- column default expression as text (e.g., 'nextval(...)', '0', 'now()')
2054
- "att"."attnotnull" != TRUE AS "nullable", -- true if column allows NULL values
2055
-
2056
- -- options: for enum columns, aggregates all allowed enum labels into a JSON array
2057
- coalesce(
2058
- (
2059
- SELECT json_agg("enm"."enumlabel") AS "o"
2060
- FROM "pg_catalog"."pg_enum" AS "enm"
2061
- WHERE "enm"."enumtypid" = "typ"."oid"
2062
- ),
2063
- '[]'
2064
- ) AS "options"
2065
-
2066
- -- === FROM / JOINs for the columns subquery ===
2067
-
2068
- -- pg_attribute: one row per table column (attnum >= 0 excludes system columns)
2069
- FROM "pg_catalog"."pg_attribute" AS "att"
2070
-
2071
- -- pg_type: data type of the column (e.g., int4, text, custom_enum)
2072
- INNER JOIN "pg_catalog"."pg_type" AS "typ" ON "typ"."oid" = "att"."atttypid"
2073
-
2074
- -- pg_namespace for the type: needed to determine which schema the type lives in
2075
- INNER JOIN "pg_catalog"."pg_namespace" AS "tns" ON "tns"."oid" = "typ"."typnamespace"
2076
-
2077
- -- information_schema.columns: provides length/precision info not easily available from pg_catalog
2078
- LEFT JOIN "information_schema"."columns" AS "c" ON "c"."table_schema" = "ns"."nspname"
2079
- AND "c"."table_name" = "cls"."relname"
2080
- AND "c"."column_name" = "att"."attname"
2081
-
2082
- -- pg_constraint (primary key): join on contype='p' to detect if column is part of PK
2083
- LEFT JOIN "pg_catalog"."pg_constraint" AS "pk_con" ON "pk_con"."contype" = 'p'
2084
- AND "pk_con"."conrelid" = "cls"."oid"
2085
- AND "att"."attnum" = ANY ("pk_con"."conkey")
2086
-
2087
- -- pg_constraint (foreign key): join on contype='f' to get FK details for this column
2088
- LEFT JOIN "pg_catalog"."pg_constraint" AS "fk_con" ON "fk_con"."contype" = 'f'
2089
- AND "fk_con"."conrelid" = "cls"."oid"
2090
- AND "att"."attnum" = ANY ("fk_con"."conkey")
2091
-
2092
- -- pg_class for FK target table: resolve the referenced table's OID to its name
2093
- LEFT JOIN "pg_catalog"."pg_class" AS "fk_cls" ON "fk_cls"."oid" = "fk_con"."confrelid"
2094
-
2095
- -- pg_namespace for FK target: get the schema of the referenced table
2096
- LEFT JOIN "pg_catalog"."pg_namespace" AS "fk_ns" ON "fk_ns"."oid" = "fk_cls"."relnamespace"
2097
-
2098
- -- pg_attribute for FK target column: resolve the referenced column number to its name.
2099
- -- Use array_position to correlate by position: find this source column's index in conkey,
2100
- -- then pick the referenced attnum at that same index from confkey.
2101
- -- This ensures composite FKs correctly map each source column to its corresponding target column.
2102
- LEFT JOIN "pg_catalog"."pg_attribute" AS "fk_att" ON "fk_att"."attrelid" = "fk_cls"."oid"
2103
- AND "fk_att"."attnum" = "fk_con"."confkey"[array_position("fk_con"."conkey", "att"."attnum")]
2104
-
2105
- -- pg_attrdef: column defaults; adbin contains the internal expression, decoded via pg_get_expr()
2106
- LEFT JOIN "pg_catalog"."pg_attrdef" AS "def" ON "def"."adrelid" = "cls"."oid" AND "def"."adnum" = "att"."attnum"
2107
-
2108
- WHERE
2109
- "att"."attrelid" = "cls"."oid" -- only columns belonging to this table
2110
- AND "att"."attnum" >= 0 -- exclude system columns (ctid, xmin, etc. have attnum < 0)
2111
- AND "att"."attisdropped" != TRUE -- exclude dropped (deleted) columns
2112
- ORDER BY "att"."attnum" -- preserve original column order
2113
- ) AS agg
2114
- ) AS "columns",
2115
-
2116
- -- ===== INDEXES subquery =====
2117
- -- Aggregates all indexes for this table into a JSON array.
2118
- (
2119
- SELECT coalesce(json_agg(agg), '[]')
2120
- FROM (
2121
- SELECT
2122
- "idx_cls"."relname" AS "name", -- index name
2123
- "am"."amname" AS "method", -- access method (e.g., 'btree', 'hash', 'gin', 'gist')
2124
- "idx"."indisunique" AS "unique", -- true if unique index
2125
- "idx"."indisprimary" AS "primary", -- true if this is the PK index
2126
- "idx"."indisvalid" AS "valid", -- false during concurrent index builds
2127
- "idx"."indisready" AS "ready", -- true when index is ready for inserts
2128
- ("idx"."indpred" IS NOT NULL) AS "partial", -- true if index has a WHERE clause (partial index)
2129
- pg_get_expr("idx"."indpred", "idx"."indrelid") AS "predicate", -- the WHERE clause expression for partial indexes
2130
-
2131
- -- Index columns: iterate over each position in the index key array
2132
- (
2133
- SELECT json_agg(
2134
- json_build_object(
2135
- -- 'name': column name, or for expression indexes the expression text
2136
- 'name', COALESCE("att"."attname", pg_get_indexdef("idx"."indexrelid", "s"."i", true)),
2137
- -- 'expression': non-null only for expression-based index columns (e.g., lower(name))
2138
- 'expression', CASE WHEN "att"."attname" IS NULL THEN pg_get_indexdef("idx"."indexrelid", "s"."i", true) ELSE NULL END,
2139
- -- 'order': sort direction; bit 0 of indoption = 1 means DESC
2140
- 'order', CASE ((( "idx"."indoption"::int2[] )["s"."i"] & 1)) WHEN 1 THEN 'DESC' ELSE 'ASC' END,
2141
- -- 'nulls': null ordering; bit 1 of indoption = 1 means NULLS FIRST
2142
- 'nulls', CASE (((( "idx"."indoption"::int2[] )["s"."i"] >> 1) & 1)) WHEN 1 THEN 'NULLS FIRST' ELSE 'NULLS LAST' END
2143
- )
2144
- ORDER BY "s"."i" -- preserve column order within the index
2145
- )
2146
- -- generate_subscripts creates one row per index key position (1-based)
2147
- FROM generate_subscripts("idx"."indkey"::int2[], 1) AS "s"("i")
2148
- -- Join to pg_attribute to resolve column numbers to names
2149
- -- NULL attname means it's an expression index column
2150
- LEFT JOIN "pg_catalog"."pg_attribute" AS "att"
2151
- ON "att"."attrelid" = "cls"."oid"
2152
- AND "att"."attnum" = ("idx"."indkey"::int2[])["s"."i"]
2153
- ) AS "columns"
2154
-
2155
- FROM "pg_catalog"."pg_index" AS "idx" -- pg_index: one row per index
2156
- JOIN "pg_catalog"."pg_class" AS "idx_cls" ON "idx"."indexrelid" = "idx_cls"."oid" -- index's own pg_class entry (for the name)
2157
- JOIN "pg_catalog"."pg_am" AS "am" ON "idx_cls"."relam" = "am"."oid" -- access method catalog
2158
- WHERE "idx"."indrelid" = "cls"."oid" -- only indexes on this table
2159
- ORDER BY "idx_cls"."relname"
2160
- ) AS agg
2161
- ) AS "indexes"
2162
-
2163
- -- === Main FROM: pg_class (tables and views) joined with pg_namespace (schemas) ===
2164
- FROM "pg_catalog"."pg_class" AS "cls"
2165
- INNER JOIN "pg_catalog"."pg_namespace" AS "ns" ON "cls"."relnamespace" = "ns"."oid"
2166
- WHERE
2167
- "ns"."nspname" !~ '^pg_' -- exclude PostgreSQL internal schemas (pg_catalog, pg_toast, etc.)
2168
- AND "ns"."nspname" != 'information_schema' -- exclude the information_schema
2169
- AND "cls"."relkind" IN ('r', 'v') -- only tables ('r') and views ('v')
2170
- AND "cls"."relname" !~ '^pg_' -- exclude system tables starting with pg_
2171
- AND "cls"."relname" !~ '_prisma_migrations' -- exclude Prisma migration tracking table
2172
- ORDER BY "ns"."nspname", "cls"."relname" ASC;
2173
- `;
2174
- function typeCastingConvert({ defaultValue, enums, val, services }) {
2175
- const [value, type2] = val.replace(/'/g, "").split("::").map((s) => s.trim());
2176
- switch (type2) {
2177
- case "character varying":
2178
- case "uuid":
2179
- case "json":
2180
- case "jsonb":
2181
- case "text":
2182
- if (value === "NULL") return null;
2183
- return (ab) => ab.StringLiteral.setValue(value);
2184
- case "real":
2185
- return (ab) => ab.NumberLiteral.setValue(value);
2186
- default: {
2187
- const enumDef = enums.find((e) => getDbName(e, true) === type2);
2188
- if (!enumDef) {
2189
- return (ab) => ab.InvocationExpr.setFunction(getFunctionRef("dbgenerated", services)).addArg((a) => a.setValue((v) => v.StringLiteral.setValue(val)));
2190
- }
2191
- const enumField = enumDef.fields.find((v) => getDbName(v) === value);
2192
- if (!enumField) {
2193
- throw new CliError(`Enum value ${value} not found in enum ${type2} for default value ${defaultValue}`);
2194
- }
2195
- return (ab) => ab.ReferenceExpr.setTarget(enumField);
2196
- }
2197
- }
2198
- }
2199
- __name(typeCastingConvert, "typeCastingConvert");
2200
-
2201
- // src/actions/pull/provider/sqlite.ts
2202
- import { DataFieldAttributeFactory as DataFieldAttributeFactory4 } from "@zenstackhq/language/factory";
2203
- var sqlite = {
2204
- isSupportedFeature(feature) {
2205
- switch (feature) {
2206
- case "Schema":
2207
- return false;
2208
- case "NativeEnum":
2209
- return false;
2210
- default:
2211
- return false;
2212
- }
2213
- },
2214
- getBuiltinType(type2) {
2215
- const t = (type2 || "").toLowerCase().trim().replace(/\(.*\)$/, "").trim();
2216
- const isArray = false;
2217
- switch (t) {
2218
- // INTEGER types (SQLite: INT, INTEGER, TINYINT, SMALLINT, MEDIUMINT, INT2, INT8)
2219
- case "integer":
2220
- case "int":
2221
- case "tinyint":
2222
- case "smallint":
2223
- case "mediumint":
2224
- case "int2":
2225
- case "int8":
2226
- return {
2227
- type: "Int",
2228
- isArray
2229
- };
2230
- // BIGINT - map to BigInt for large integers
2231
- case "bigint":
2232
- case "unsigned big int":
2233
- return {
2234
- type: "BigInt",
2235
- isArray
2236
- };
2237
- // TEXT types (SQLite: CHARACTER, VARCHAR, VARYING CHARACTER, NCHAR, NATIVE CHARACTER, NVARCHAR, TEXT, CLOB)
2238
- case "text":
2239
- case "varchar":
2240
- case "char":
2241
- case "character":
2242
- case "varying character":
2243
- case "nchar":
2244
- case "native character":
2245
- case "nvarchar":
2246
- case "clob":
2247
- return {
2248
- type: "String",
2249
- isArray
2250
- };
2251
- // BLOB type
2252
- case "blob":
2253
- return {
2254
- type: "Bytes",
2255
- isArray
2256
- };
2257
- // REAL types (SQLite: REAL, DOUBLE, DOUBLE PRECISION, FLOAT)
2258
- case "real":
2259
- case "float":
2260
- case "double":
2261
- case "double precision":
2262
- return {
2263
- type: "Float",
2264
- isArray
2265
- };
2266
- // NUMERIC types (SQLite: NUMERIC, DECIMAL)
2267
- case "numeric":
2268
- case "decimal":
2269
- return {
2270
- type: "Decimal",
2271
- isArray
2272
- };
2273
- // DateTime types
2274
- case "datetime":
2275
- case "date":
2276
- case "time":
2277
- case "timestamp":
2278
- return {
2279
- type: "DateTime",
2280
- isArray
2281
- };
2282
- // JSON types
2283
- case "json":
2284
- case "jsonb":
2285
- return {
2286
- type: "Json",
2287
- isArray
2288
- };
2289
- // Boolean types
2290
- case "boolean":
2291
- case "bool":
2292
- return {
2293
- type: "Boolean",
2294
- isArray
2295
- };
2296
- default: {
2297
- if (!t) {
2298
- return {
2299
- type: "Bytes",
2300
- isArray
2301
- };
2302
- }
2303
- if (t.includes("int")) {
2304
- return {
2305
- type: "Int",
2306
- isArray
2307
- };
2308
- }
2309
- if (t.includes("char") || t.includes("clob") || t.includes("text")) {
2310
- return {
2311
- type: "String",
2312
- isArray
2313
- };
2314
- }
2315
- if (t.includes("blob")) {
2316
- return {
2317
- type: "Bytes",
2318
- isArray
2319
- };
2320
- }
2321
- if (t.includes("real") || t.includes("floa") || t.includes("doub")) {
2322
- return {
2323
- type: "Float",
2324
- isArray
2325
- };
2326
- }
2327
- return {
2328
- type: "Unsupported",
2329
- isArray
2330
- };
2331
- }
2332
- }
2333
- },
2334
- getDefaultDatabaseType() {
2335
- return void 0;
2336
- },
2337
- async introspect(connectionString, _options) {
2338
- const SQLite = (await import("better-sqlite3")).default;
2339
- const db = new SQLite(connectionString, {
2340
- readonly: true
2341
- });
2342
- try {
2343
- const all = /* @__PURE__ */ __name((sql) => {
2344
- const stmt = db.prepare(sql);
2345
- return stmt.all();
2346
- }, "all");
2347
- const tablesRaw = all("SELECT name, type, sql AS definition FROM sqlite_schema WHERE type IN ('table','view') AND name NOT LIKE 'sqlite_%' AND name <> '_prisma_migrations' ORDER BY name");
2348
- const autoIncrementTables = /* @__PURE__ */ new Set();
2349
- for (const t of tablesRaw) {
2350
- if (t.type === "table" && t.definition) {
2351
- if (/\bAUTOINCREMENT\b/i.test(t.definition)) {
2352
- autoIncrementTables.add(t.name);
2353
- }
2354
- }
2355
- }
2356
- const tables = [];
2357
- for (const t of tablesRaw) {
2358
- const tableName = t.name;
2359
- const schema = "";
2360
- const hasAutoIncrement = autoIncrementTables.has(tableName);
2361
- const columnsInfo = all(`PRAGMA table_xinfo('${tableName.replace(/'/g, "''")}')`);
2362
- const tableNameEsc = tableName.replace(/'/g, "''");
2363
- const idxList = all(`PRAGMA index_list('${tableNameEsc}')`).filter((r) => !r.name.startsWith("sqlite_autoindex_"));
2364
- const uniqueSingleColumn = /* @__PURE__ */ new Set();
2365
- const uniqueIndexRows = idxList.filter((r) => r.unique === 1 && r.partial !== 1);
2366
- for (const idx of uniqueIndexRows) {
2367
- const idxCols = all(`PRAGMA index_info('${idx.name.replace(/'/g, "''")}')`);
2368
- if (idxCols.length === 1 && idxCols[0]?.name) {
2369
- uniqueSingleColumn.add(idxCols[0].name);
2370
- }
2371
- }
2372
- const indexes = idxList.map((idx) => {
2373
- const idxCols = all(`PRAGMA index_info('${idx.name.replace(/'/g, "''")}')`);
2374
- return {
2375
- name: idx.name,
2376
- method: null,
2377
- unique: idx.unique === 1,
2378
- primary: false,
2379
- valid: true,
2380
- ready: true,
2381
- partial: idx.partial === 1,
2382
- predicate: idx.partial === 1 ? "[partial]" : null,
2383
- columns: idxCols.map((col) => ({
2384
- name: col.name,
2385
- expression: null,
2386
- order: null,
2387
- nulls: null
2388
- }))
2389
- };
2390
- });
2391
- const fkRows = all(`PRAGMA foreign_key_list('${tableName.replace(/'/g, "''")}')`);
2392
- const fkConstraintNames = /* @__PURE__ */ new Map();
2393
- if (t.definition) {
2394
- const fkRegex = /CONSTRAINT\s+(?:["'`]([^"'`]+)["'`]|(\w+))\s+FOREIGN\s+KEY\s*\(([^)]+)\)/gi;
2395
- let match;
2396
- while ((match = fkRegex.exec(t.definition)) !== null) {
2397
- const constraintName = match[1] || match[2];
2398
- const columnList = match[3];
2399
- if (constraintName && columnList) {
2400
- const columns2 = columnList.split(",").map((col) => col.trim().replace(/^["'`]|["'`]$/g, ""));
2401
- for (const col of columns2) {
2402
- if (col) {
2403
- fkConstraintNames.set(col, constraintName);
2404
- }
2405
- }
2406
- }
2407
- }
2408
- }
2409
- const fkByColumn = /* @__PURE__ */ new Map();
2410
- for (const fk of fkRows) {
2411
- fkByColumn.set(fk.from, {
2412
- foreign_key_schema: "",
2413
- foreign_key_table: fk.table || null,
2414
- foreign_key_column: fk.to || null,
2415
- foreign_key_name: fkConstraintNames.get(fk.from) ?? null,
2416
- foreign_key_on_update: fk.on_update ?? null,
2417
- foreign_key_on_delete: fk.on_delete ?? null
2418
- });
2419
- }
2420
- const generatedColDefs = t.definition ? extractColumnTypeDefs(t.definition) : /* @__PURE__ */ new Map();
2421
- const columns = [];
2422
- for (const c of columnsInfo) {
2423
- const hidden = c.hidden ?? 0;
2424
- if (hidden === 1) continue;
2425
- const isGenerated = hidden === 2 || hidden === 3;
2426
- const fk = fkByColumn.get(c.name);
2427
- let defaultValue = c.dflt_value;
2428
- if (hasAutoIncrement && c.pk) {
2429
- defaultValue = "autoincrement";
2430
- }
2431
- let datatype = c.type || "";
2432
- if (isGenerated) {
2433
- const fullDef = generatedColDefs.get(c.name);
2434
- if (fullDef) {
2435
- datatype = fullDef;
2436
- }
2437
- }
2438
- columns.push({
2439
- name: c.name,
2440
- datatype,
2441
- datatype_name: null,
2442
- length: null,
2443
- precision: null,
2444
- datatype_schema: schema,
2445
- foreign_key_schema: fk?.foreign_key_schema ?? null,
2446
- foreign_key_table: fk?.foreign_key_table ?? null,
2447
- foreign_key_column: fk?.foreign_key_column ?? null,
2448
- foreign_key_name: fk?.foreign_key_name ?? null,
2449
- foreign_key_on_update: fk?.foreign_key_on_update ?? null,
2450
- foreign_key_on_delete: fk?.foreign_key_on_delete ?? null,
2451
- pk: !!c.pk,
2452
- computed: isGenerated,
2453
- nullable: c.notnull !== 1,
2454
- default: defaultValue,
2455
- unique: uniqueSingleColumn.has(c.name),
2456
- unique_name: null
2457
- });
2458
- }
2459
- tables.push({
2460
- schema,
2461
- name: tableName,
2462
- columns,
2463
- type: t.type,
2464
- definition: t.definition,
2465
- indexes
2466
- });
2467
- }
2468
- const enums = [];
2469
- return {
2470
- tables,
2471
- enums
2472
- };
2473
- } finally {
2474
- db.close();
2475
- }
2476
- },
2477
- getDefaultValue({ defaultValue, fieldType, services, enums }) {
2478
- const val = defaultValue.trim();
2479
- switch (fieldType) {
2480
- case "DateTime":
2481
- if (val === "CURRENT_TIMESTAMP" || val === "now()") {
2482
- return (ab) => ab.InvocationExpr.setFunction(getFunctionRef("now", services));
2483
- }
2484
- return (ab) => ab.StringLiteral.setValue(val);
2485
- case "Int":
2486
- case "BigInt":
2487
- if (val === "autoincrement") {
2488
- return (ab) => ab.InvocationExpr.setFunction(getFunctionRef("autoincrement", services));
2489
- }
2490
- return (ab) => ab.NumberLiteral.setValue(val);
2491
- case "Float":
2492
- return normalizeFloatDefault(val);
2493
- case "Decimal":
2494
- return normalizeDecimalDefault(val);
2495
- case "Boolean":
2496
- return (ab) => ab.BooleanLiteral.setValue(val === "true" || val === "1");
2497
- case "String":
2498
- if (val.startsWith("'") && val.endsWith("'")) {
2499
- const strippedName = val.slice(1, -1);
2500
- const enumDef = enums.find((e) => e.fields.find((v) => getDbName(v) === strippedName));
2501
- if (enumDef) {
2502
- const enumField = enumDef.fields.find((v) => getDbName(v) === strippedName);
2503
- if (enumField) return (ab) => ab.ReferenceExpr.setTarget(enumField);
2504
- }
2505
- return (ab) => ab.StringLiteral.setValue(strippedName);
2506
- }
2507
- return (ab) => ab.StringLiteral.setValue(val);
2508
- case "Json":
2509
- return (ab) => ab.StringLiteral.setValue(val);
2510
- case "Bytes":
2511
- return (ab) => ab.StringLiteral.setValue(val);
2512
- }
2513
- console.warn(`Unsupported default value type: "${defaultValue}" for field type "${fieldType}". Skipping default value.`);
2514
- return null;
2515
- },
2516
- getFieldAttributes({ fieldName, fieldType, services }) {
2517
- const factories = [];
2518
- if (fieldType === "DateTime" && (fieldName.toLowerCase() === "updatedat" || fieldName.toLowerCase() === "updated_at")) {
2519
- factories.push(new DataFieldAttributeFactory4().setDecl(getAttributeRef("@updatedAt", services)));
2520
- }
2521
- return factories;
2522
- }
2523
- };
2524
- function extractColumnTypeDefs(ddl) {
2525
- const openIdx = ddl.indexOf("(");
2526
- if (openIdx === -1) return /* @__PURE__ */ new Map();
2527
- let depth = 1;
2528
- let closeIdx = -1;
2529
- for (let i = openIdx + 1; i < ddl.length; i++) {
2530
- if (ddl[i] === "(") depth++;
2531
- else if (ddl[i] === ")") {
2532
- depth--;
2533
- if (depth === 0) {
2534
- closeIdx = i;
2535
- break;
2536
- }
2537
- }
2538
- }
2539
- if (closeIdx === -1) return /* @__PURE__ */ new Map();
2540
- const content = ddl.substring(openIdx + 1, closeIdx);
2541
- const defs = [];
2542
- let current = "";
2543
- depth = 0;
2544
- for (const char of content) {
2545
- if (char === "(") depth++;
2546
- else if (char === ")") depth--;
2547
- else if (char === "," && depth === 0) {
2548
- defs.push(current.trim());
2549
- current = "";
2550
- continue;
2551
- }
2552
- current += char;
2553
- }
2554
- if (current.trim()) defs.push(current.trim());
2555
- const result = /* @__PURE__ */ new Map();
2556
- for (const def of defs) {
2557
- const nameMatch = def.match(/^(?:["'`]([^"'`]+)["'`]|(\w+))\s+(.+)/s);
2558
- if (nameMatch) {
2559
- const name = nameMatch[1] || nameMatch[2];
2560
- const typeDef = nameMatch[3];
2561
- if (name && typeDef) {
2562
- result.set(name, typeDef.trim());
2563
- }
2564
- }
2565
- }
2566
- return result;
2567
- }
2568
- __name(extractColumnTypeDefs, "extractColumnTypeDefs");
2569
-
2570
- // src/actions/pull/provider/index.ts
2571
- var providers = {
2572
- mysql,
2573
- postgresql,
2574
- sqlite
2575
- };
2576
-
2577
- // src/actions/db.ts
2578
- async function run2(command, options) {
2579
- switch (command) {
2580
- case "push":
2581
- await runPush(options);
2582
- break;
2583
- case "pull":
2584
- await runPull(options);
2585
- break;
2586
- }
2587
- }
2588
- __name(run2, "run");
2589
- async function runPush(options) {
2590
- const schemaFile = getSchemaFile(options.schema);
2591
- await requireDataSourceUrl(schemaFile);
2592
- const prismaSchemaFile = await generateTempPrismaSchema(schemaFile);
2593
- try {
2594
- const cmd = [
2595
- "db push",
2596
- ` --schema "${prismaSchemaFile}"`,
2597
- options.acceptDataLoss ? " --accept-data-loss" : "",
2598
- options.forceReset ? " --force-reset" : "",
2599
- " --skip-generate"
2600
- ].join("");
2601
- try {
2602
- execPrisma(cmd);
2603
- } catch (err) {
2604
- handleSubProcessError(err);
2605
- }
2606
- } finally {
2607
- if (fs2.existsSync(prismaSchemaFile)) {
2608
- fs2.unlinkSync(prismaSchemaFile);
2609
- }
2610
- }
2611
- }
2612
- __name(runPush, "runPush");
2613
- async function runPull(options) {
2614
- const spinner = ora();
2615
- try {
2616
- const schemaFile = getSchemaFile(options.schema);
2617
- const outPath = options.output ? path3.resolve(options.output) : void 0;
2618
- const treatAsFile = !!outPath && (fs2.existsSync(outPath) && fs2.lstatSync(outPath).isFile() || path3.extname(outPath) !== "");
2619
- const { model, services } = await loadSchemaDocument(schemaFile, {
2620
- returnServices: true,
2621
- mergeImports: treatAsFile
2622
- });
2623
- const SUPPORTED_PROVIDERS = Object.keys(providers);
2624
- const datasource = getDatasource(model);
2625
- if (!SUPPORTED_PROVIDERS.includes(datasource.provider)) {
2626
- throw new CliError(`Unsupported datasource provider: ${datasource.provider}`);
2627
- }
2628
- const provider = providers[datasource.provider];
2629
- if (!provider) {
2630
- throw new CliError(`No introspection provider found for: ${datasource.provider}`);
2631
- }
2632
- spinner.start("Introspecting database...");
2633
- const { enums, tables } = await provider.introspect(datasource.url, {
2634
- schemas: datasource.allSchemas,
2635
- modelCasing: options.modelCasing
2636
- });
2637
- spinner.succeed("Database introspected");
2638
- console.log(colors4.blue("Syncing schema..."));
2639
- const newModel = {
2640
- $type: "Model",
2641
- $container: void 0,
2642
- $containerProperty: void 0,
2643
- $containerIndex: void 0,
2644
- declarations: [
2645
- ...model.declarations.filter((d) => [
2646
- "DataSource"
2647
- ].includes(d.$type))
2648
- ],
2649
- imports: model.imports
2650
- };
2651
- syncEnums({
2652
- dbEnums: enums,
2653
- model: newModel,
2654
- services,
2655
- options,
2656
- defaultSchema: datasource.defaultSchema,
2657
- oldModel: model,
2658
- provider
2659
- });
2660
- const resolvedRelations = [];
2661
- for (const table of tables) {
2662
- const relations = syncTable({
2663
- table,
2664
- model: newModel,
2665
- provider,
2666
- services,
2667
- options,
2668
- defaultSchema: datasource.defaultSchema,
2669
- oldModel: model
2670
- });
2671
- resolvedRelations.push(...relations);
2672
- }
2673
- for (const relation of resolvedRelations) {
2674
- const similarRelations = resolvedRelations.filter((rr) => {
2675
- return rr !== relation && (rr.schema === relation.schema && rr.table === relation.table && rr.references.schema === relation.references.schema && rr.references.table === relation.references.table || rr.schema === relation.references.schema && rr.columns[0] === relation.references.columns[0] && rr.references.schema === relation.schema && rr.references.table === relation.table);
2676
- }).length;
2677
- const selfRelation = relation.references.schema === relation.schema && relation.references.table === relation.table;
2678
- syncRelation({
2679
- model: newModel,
2680
- relation,
2681
- services,
2682
- options,
2683
- selfRelation,
2684
- similarRelations
2685
- });
2686
- }
2687
- consolidateEnums({
2688
- newModel,
2689
- oldModel: model
2690
- });
2691
- console.log(colors4.blue("Schema synced"));
2692
- const baseDir = path3.dirname(path3.resolve(schemaFile));
2693
- const baseDirUrlPath = new URL(`file://${baseDir}`).pathname;
2694
- const docs = services.shared.workspace.LangiumDocuments.all.filter(({ uri }) => uri.path.toLowerCase().startsWith(baseDirUrlPath.toLowerCase())).toArray();
2695
- const docsSet = new Set(docs.map((d) => d.uri.toString()));
2696
- console.log(colors4.bold("\nApplying changes to ZModel..."));
2697
- const deletedModels = [];
2698
- const deletedEnums = [];
2699
- const addedModels = [];
2700
- const addedEnums = [];
2701
- const modelChanges = /* @__PURE__ */ new Map();
2702
- const getModelChanges = /* @__PURE__ */ __name((modelName) => {
2703
- if (!modelChanges.has(modelName)) {
2704
- modelChanges.set(modelName, {
2705
- addedFields: [],
2706
- deletedFields: [],
2707
- updatedFields: [],
2708
- addedAttributes: [],
2709
- deletedAttributes: [],
2710
- updatedAttributes: []
2711
- });
2712
- }
2713
- return modelChanges.get(modelName);
2714
- }, "getModelChanges");
2715
- services.shared.workspace.IndexManager.allElements("DataModel", docsSet).filter((declaration) => !newModel.declarations.find((d) => getDbName(d) === getDbName(declaration.node))).forEach((decl) => {
2716
- const model2 = decl.node.$container;
2717
- const index = model2.declarations.findIndex((d) => d === decl.node);
2718
- model2.declarations.splice(index, 1);
2719
- deletedModels.push(colors4.red(`- Model ${decl.name} deleted`));
2720
- });
2721
- if (provider.isSupportedFeature("NativeEnum")) services.shared.workspace.IndexManager.allElements("Enum", docsSet).filter((declaration) => !newModel.declarations.find((d) => getDbName(d) === getDbName(declaration.node))).forEach((decl) => {
2722
- const model2 = decl.node.$container;
2723
- const index = model2.declarations.findIndex((d) => d === decl.node);
2724
- model2.declarations.splice(index, 1);
2725
- deletedEnums.push(colors4.red(`- Enum ${decl.name} deleted`));
2726
- });
2727
- newModel.declarations.filter((d) => [
2728
- DataModel,
2729
- Enum
2730
- ].includes(d.$type)).forEach((_declaration) => {
2731
- const newDataModel = _declaration;
2732
- const declarations = services.shared.workspace.IndexManager.allElements(newDataModel.$type, docsSet).toArray();
2733
- const originalDataModel = declarations.find((d) => getDbName(d.node) === getDbName(newDataModel))?.node;
2734
- if (!originalDataModel) {
2735
- if (newDataModel.$type === "DataModel") {
2736
- addedModels.push(colors4.green(`+ Model ${newDataModel.name} added`));
2737
- } else if (newDataModel.$type === "Enum") {
2738
- addedEnums.push(colors4.green(`+ Enum ${newDataModel.name} added`));
2739
- }
2740
- model.declarations.push(newDataModel);
2741
- newDataModel.$container = model;
2742
- newDataModel.fields.forEach((f) => {
2743
- if (f.$type === "DataField" && f.type.reference?.ref) {
2744
- const ref = declarations.find((d) => getDbName(d.node) === getDbName(f.type.reference.ref))?.node;
2745
- if (ref && f.type.reference) {
2746
- f.type.reference = {
2747
- ref,
2748
- $refText: ref.name ?? f.type.reference.$refText
2749
- };
2750
- }
2751
- }
2752
- });
2753
- return;
2754
- }
2755
- newDataModel.fields.forEach((f) => {
2756
- let originalFields = originalDataModel.fields.filter((d) => getDbName(d) === getDbName(f));
2757
- const isRelationField = f.$type === "DataField" && !!f.attributes?.some((a) => a?.decl?.ref?.name === "@relation");
2758
- if (originalFields.length === 0 && isRelationField && !getRelationFieldsKey(f)) {
2759
- return;
2760
- }
2761
- if (originalFields.length === 0) {
2762
- const newFieldsKey = getRelationFieldsKey(f);
2763
- if (newFieldsKey) {
2764
- originalFields = originalDataModel.fields.filter((d) => getRelationFieldsKey(d) === newFieldsKey);
2765
- }
2766
- }
2767
- if (originalFields.length === 0) {
2768
- originalFields = originalDataModel.fields.filter((d) => getRelationFkName(d) === getRelationFkName(f) && !!getRelationFkName(d) && !!getRelationFkName(f));
2769
- }
2770
- if (originalFields.length === 0) {
2771
- originalFields = originalDataModel.fields.filter((d) => f.$type === "DataField" && d.$type === "DataField" && f.type.reference?.ref && d.type.reference?.ref && getDbName(f.type.reference.ref) === getDbName(d.type.reference.ref));
2772
- }
2773
- if (originalFields.length > 1) {
2774
- const isBackReferenceField = !getRelationFieldsKey(f);
2775
- if (!isBackReferenceField) {
2776
- console.warn(colors4.yellow(`Found more original fields, need to tweak the search algorithm. ${originalDataModel.name}->[${originalFields.map((of) => of.name).join(", ")}](${f.name})`));
2777
- }
2778
- return;
2779
- }
2780
- const originalField = originalFields.at(0);
2781
- if (originalField && f.$type === "DataField" && originalField.$type === "DataField") {
2782
- const newType = f.type;
2783
- const oldType = originalField.type;
2784
- const fieldUpdates = [];
2785
- const isOldTypeEnumWithoutNativeSupport = oldType.reference?.ref?.$type === "Enum" && !provider.isSupportedFeature("NativeEnum");
2786
- if (newType.type && oldType.type !== newType.type && !isOldTypeEnumWithoutNativeSupport) {
2787
- fieldUpdates.push(`type: ${oldType.type} -> ${newType.type}`);
2788
- oldType.type = newType.type;
2789
- }
2790
- if (newType.reference?.ref && oldType.reference?.ref) {
2791
- const newRefName = getDbName(newType.reference.ref);
2792
- const oldRefName = getDbName(oldType.reference.ref);
2793
- if (newRefName !== oldRefName) {
2794
- fieldUpdates.push(`reference: ${oldType.reference.$refText} -> ${newType.reference.$refText}`);
2795
- oldType.reference = {
2796
- ref: newType.reference.ref,
2797
- $refText: newType.reference.$refText
2798
- };
2799
- }
2800
- } else if (newType.reference?.ref && !oldType.reference) {
2801
- fieldUpdates.push(`type: ${oldType.type} -> ${newType.reference.$refText}`);
2802
- oldType.reference = newType.reference;
2803
- oldType.type = void 0;
2804
- } else if (!newType.reference && oldType.reference?.ref && newType.type) {
2805
- const isEnumWithoutNativeSupport = oldType.reference.ref.$type === "Enum" && !provider.isSupportedFeature("NativeEnum");
2806
- if (!isEnumWithoutNativeSupport) {
2807
- fieldUpdates.push(`type: ${oldType.reference.$refText} -> ${newType.type}`);
2808
- oldType.type = newType.type;
2809
- oldType.reference = void 0;
2810
- }
2811
- }
2812
- if (!!newType.optional !== !!oldType.optional) {
2813
- fieldUpdates.push(`optional: ${!!oldType.optional} -> ${!!newType.optional}`);
2814
- oldType.optional = newType.optional;
2815
- }
2816
- if (!!newType.array !== !!oldType.array) {
2817
- fieldUpdates.push(`array: ${!!oldType.array} -> ${!!newType.array}`);
2818
- oldType.array = newType.array;
2819
- }
2820
- if (fieldUpdates.length > 0) {
2821
- getModelChanges(originalDataModel.name).updatedFields.push(colors4.yellow(`~ ${originalField.name} (${fieldUpdates.join(", ")})`));
2822
- }
2823
- const newDefaultAttr = f.attributes.find((a) => a.decl.$refText === "@default");
2824
- const oldDefaultAttr = originalField.attributes.find((a) => a.decl.$refText === "@default");
2825
- if (newDefaultAttr && oldDefaultAttr) {
2826
- const serializeArgs = /* @__PURE__ */ __name((args) => args.map((arg) => {
2827
- if (arg.value?.$type === "StringLiteral") return `"${arg.value.value}"`;
2828
- if (arg.value?.$type === "NumberLiteral") return String(arg.value.value);
2829
- if (arg.value?.$type === "BooleanLiteral") return String(arg.value.value);
2830
- if (arg.value?.$type === "InvocationExpr") return arg.value.function?.$refText ?? "";
2831
- if (arg.value?.$type === "ReferenceExpr") return arg.value.target?.$refText ?? "";
2832
- if (arg.value?.$type === "ArrayExpr") {
2833
- return `[${(arg.value.items ?? []).map((item) => {
2834
- if (item.$type === "ReferenceExpr") return item.target?.$refText ?? "";
2835
- return item.$type ?? "unknown";
2836
- }).join(",")}]`;
2837
- }
2838
- return arg.value?.$type ?? "unknown";
2839
- }).join(","), "serializeArgs");
2840
- const newArgsStr = serializeArgs(newDefaultAttr.args ?? []);
2841
- const oldArgsStr = serializeArgs(oldDefaultAttr.args ?? []);
2842
- if (newArgsStr !== oldArgsStr) {
2843
- oldDefaultAttr.args = newDefaultAttr.args.map((arg) => ({
2844
- ...arg,
2845
- $container: oldDefaultAttr
2846
- }));
2847
- getModelChanges(originalDataModel.name).updatedAttributes.push(colors4.yellow(`~ @default on ${originalDataModel.name}.${originalField.name}`));
2848
- }
2849
- }
2850
- }
2851
- if (!originalField) {
2852
- getModelChanges(originalDataModel.name).addedFields.push(colors4.green(`+ ${f.name}`));
2853
- f.$container = originalDataModel;
2854
- originalDataModel.fields.push(f);
2855
- if (f.$type === "DataField" && f.type.reference?.ref) {
2856
- const ref = declarations.find((d) => getDbName(d.node) === getDbName(f.type.reference.ref))?.node;
2857
- if (ref) {
2858
- f.type.reference = {
2859
- ref,
2860
- $refText: ref.name ?? f.type.reference.$refText
2861
- };
2862
- }
2863
- }
2864
- return;
2865
- }
2866
- originalField.attributes.filter((attr) => !f.attributes.find((d) => d.decl.$refText === attr.decl.$refText) && isDatabaseManagedAttribute(attr.decl.$refText)).forEach((attr) => {
2867
- const field = attr.$container;
2868
- const index = field.attributes.findIndex((d) => d === attr);
2869
- field.attributes.splice(index, 1);
2870
- getModelChanges(originalDataModel.name).deletedAttributes.push(colors4.yellow(`- ${attr.decl.$refText} from field: ${originalDataModel.name}.${field.name}`));
2871
- });
2872
- f.attributes.filter((attr) => !originalField.attributes.find((d) => d.decl.$refText === attr.decl.$refText) && isDatabaseManagedAttribute(attr.decl.$refText)).forEach((attr) => {
2873
- const cloned = {
2874
- ...attr,
2875
- $container: originalField
2876
- };
2877
- originalField.attributes.push(cloned);
2878
- getModelChanges(originalDataModel.name).addedAttributes.push(colors4.green(`+ ${attr.decl.$refText} to field: ${originalDataModel.name}.${f.name}`));
2879
- });
2880
- });
2881
- originalDataModel.fields.filter((f) => {
2882
- const matchByDbName = newDataModel.fields.find((d) => getDbName(d) === getDbName(f));
2883
- if (matchByDbName) return false;
2884
- const originalFieldsKey = getRelationFieldsKey(f);
2885
- if (originalFieldsKey) {
2886
- const matchByFieldsKey = newDataModel.fields.find((d) => getRelationFieldsKey(d) === originalFieldsKey);
2887
- if (matchByFieldsKey) return false;
2888
- }
2889
- const matchByFkName = newDataModel.fields.find((d) => getRelationFkName(d) === getRelationFkName(f) && !!getRelationFkName(d) && !!getRelationFkName(f));
2890
- if (matchByFkName) return false;
2891
- const matchByTypeRef = newDataModel.fields.find((d) => f.$type === "DataField" && d.$type === "DataField" && f.type.reference?.ref && d.type.reference?.ref && getDbName(f.type.reference.ref) === getDbName(d.type.reference.ref));
2892
- return !matchByTypeRef;
2893
- }).forEach((f) => {
2894
- const _model = f.$container;
2895
- const index = _model.fields.findIndex((d) => d === f);
2896
- _model.fields.splice(index, 1);
2897
- getModelChanges(_model.name).deletedFields.push(colors4.red(`- ${f.name}`));
2898
- });
2899
- });
2900
- if (deletedModels.length > 0) {
2901
- console.log(colors4.bold("\nDeleted Models:"));
2902
- deletedModels.forEach((msg) => {
2903
- console.log(msg);
2904
- });
2905
- }
2906
- if (deletedEnums.length > 0) {
2907
- console.log(colors4.bold("\nDeleted Enums:"));
2908
- deletedEnums.forEach((msg) => {
2909
- console.log(msg);
2910
- });
2911
- }
2912
- if (addedModels.length > 0) {
2913
- console.log(colors4.bold("\nAdded Models:"));
2914
- addedModels.forEach((msg) => {
2915
- console.log(msg);
2916
- });
2917
- }
2918
- if (addedEnums.length > 0) {
2919
- console.log(colors4.bold("\nAdded Enums:"));
2920
- addedEnums.forEach((msg) => {
2921
- console.log(msg);
2922
- });
2923
- }
2924
- if (modelChanges.size > 0) {
2925
- console.log(colors4.bold("\nModel Changes:"));
2926
- modelChanges.forEach((changes, modelName) => {
2927
- const hasChanges = changes.addedFields.length > 0 || changes.deletedFields.length > 0 || changes.updatedFields.length > 0 || changes.addedAttributes.length > 0 || changes.deletedAttributes.length > 0 || changes.updatedAttributes.length > 0;
2928
- if (hasChanges) {
2929
- console.log(colors4.cyan(` ${modelName}:`));
2930
- if (changes.addedFields.length > 0) {
2931
- console.log(colors4.gray(" Added Fields:"));
2932
- changes.addedFields.forEach((msg) => {
2933
- console.log(` ${msg}`);
2934
- });
2935
- }
2936
- if (changes.deletedFields.length > 0) {
2937
- console.log(colors4.gray(" Deleted Fields:"));
2938
- changes.deletedFields.forEach((msg) => {
2939
- console.log(` ${msg}`);
2940
- });
2941
- }
2942
- if (changes.updatedFields.length > 0) {
2943
- console.log(colors4.gray(" Updated Fields:"));
2944
- changes.updatedFields.forEach((msg) => {
2945
- console.log(` ${msg}`);
2946
- });
2947
- }
2948
- if (changes.addedAttributes.length > 0) {
2949
- console.log(colors4.gray(" Added Attributes:"));
2950
- changes.addedAttributes.forEach((msg) => {
2951
- console.log(` ${msg}`);
2952
- });
2953
- }
2954
- if (changes.deletedAttributes.length > 0) {
2955
- console.log(colors4.gray(" Deleted Attributes:"));
2956
- changes.deletedAttributes.forEach((msg) => {
2957
- console.log(` ${msg}`);
2958
- });
2959
- }
2960
- if (changes.updatedAttributes.length > 0) {
2961
- console.log(colors4.gray(" Updated Attributes:"));
2962
- changes.updatedAttributes.forEach((msg) => {
2963
- console.log(` ${msg}`);
2964
- });
2965
- }
2966
- }
2967
- });
2968
- }
2969
- const generator = new ZModelCodeGenerator({
2970
- quote: options.quote ?? "single",
2971
- indent: options.indent ?? 4
2972
- });
2973
- if (options.output) {
2974
- if (treatAsFile) {
2975
- const zmodelSchema = await formatDocument(generator.generate(newModel));
2976
- console.log(colors4.blue(`Writing to ${outPath}`));
2977
- fs2.mkdirSync(path3.dirname(outPath), {
2978
- recursive: true
2979
- });
2980
- fs2.writeFileSync(outPath, zmodelSchema);
2981
- } else {
2982
- fs2.mkdirSync(outPath, {
2983
- recursive: true
2984
- });
2985
- const baseDir2 = path3.dirname(path3.resolve(schemaFile));
2986
- for (const { uri, parseResult: { value: documentModel } } of docs) {
2987
- const zmodelSchema = await formatDocument(generator.generate(documentModel));
2988
- const relPath = path3.relative(baseDir2, uri.fsPath);
2989
- const targetFile = path3.join(outPath, relPath);
2990
- fs2.mkdirSync(path3.dirname(targetFile), {
2991
- recursive: true
2992
- });
2993
- console.log(colors4.blue(`Writing to ${targetFile}`));
2994
- fs2.writeFileSync(targetFile, zmodelSchema);
2995
- }
2996
- }
2997
- } else {
2998
- for (const { uri, parseResult: { value: documentModel } } of docs) {
2999
- const zmodelSchema = await formatDocument(generator.generate(documentModel));
3000
- console.log(colors4.blue(`Writing to ${path3.relative(process.cwd(), uri.fsPath).replace(/\\/g, "/")}`));
3001
- fs2.writeFileSync(uri.fsPath, zmodelSchema);
3002
- }
3003
- }
3004
- console.log(colors4.green.bold("\nPull completed successfully!"));
3005
- } catch (error) {
3006
- spinner.fail("Pull failed");
3007
- console.error(error);
3008
- throw error;
3009
- }
3010
- }
3011
- __name(runPull, "runPull");
3012
-
3013
- // src/actions/format.ts
3014
- import { formatDocument as formatDocument2 } from "@zenstackhq/language";
3015
- import colors5 from "colors";
3016
- import fs3 from "fs";
3017
- async function run3(options) {
3018
- const schemaFile = getSchemaFile(options.schema);
3019
- let formattedContent;
3020
- try {
3021
- formattedContent = await formatDocument2(fs3.readFileSync(schemaFile, "utf-8"));
3022
- } catch (error) {
3023
- console.error(colors5.red("\u2717 Schema formatting failed."));
3024
- throw error;
3025
- }
3026
- fs3.writeFileSync(schemaFile, formattedContent, "utf-8");
3027
- console.log(colors5.green("\u2713 Schema formatting completed successfully."));
3028
- }
3029
- __name(run3, "run");
3030
-
3031
- // src/actions/generate.ts
3032
- import { invariant as invariant2, singleDebounce } from "@zenstackhq/common-helpers";
3033
- import { ZModelLanguageMetaData } from "@zenstackhq/language";
3034
- import { isPlugin as isPlugin2 } from "@zenstackhq/language/ast";
3035
- import { getLiteral, getLiteralArray as getLiteralArray2 } from "@zenstackhq/language/utils";
3036
- import { watch } from "chokidar";
3037
- import colors6 from "colors";
3038
- import path6 from "path";
3039
- import ora2 from "ora";
3040
- import semver from "semver";
3041
-
3042
- // src/plugins/index.ts
3043
- var plugins_exports = {};
3044
- __export(plugins_exports, {
3045
- prisma: () => prisma_default,
3046
- typescript: () => typescript_default
3047
- });
3048
-
3049
- // src/plugins/prisma.ts
3050
- import { PrismaSchemaGenerator as PrismaSchemaGenerator2 } from "@zenstackhq/sdk";
3051
- import fs4 from "fs";
3052
- import path4 from "path";
3053
- var plugin = {
3054
- name: "Prisma Schema Generator",
3055
- statusText: "Generating Prisma schema",
3056
- async generate({ model, defaultOutputPath, pluginOptions }) {
3057
- let outFile = path4.join(defaultOutputPath, "schema.prisma");
3058
- if (typeof pluginOptions["output"] === "string") {
3059
- outFile = path4.resolve(defaultOutputPath, pluginOptions["output"]);
3060
- if (!fs4.existsSync(path4.dirname(outFile))) {
3061
- fs4.mkdirSync(path4.dirname(outFile), {
3062
- recursive: true
3063
- });
3064
- }
3065
- }
3066
- const prismaSchema = await new PrismaSchemaGenerator2(model).generate();
3067
- fs4.writeFileSync(outFile, prismaSchema);
3068
- }
3069
- };
3070
- var prisma_default = plugin;
3071
-
3072
- // src/plugins/typescript.ts
3073
- import { TsSchemaGenerator } from "@zenstackhq/sdk";
3074
- import fs5 from "fs";
3075
- import path5 from "path";
3076
- var plugin2 = {
3077
- name: "TypeScript Schema Generator",
3078
- statusText: "Generating TypeScript schema",
3079
- async generate({ model, defaultOutputPath, pluginOptions }) {
3080
- let outDir = defaultOutputPath;
3081
- if (typeof pluginOptions["output"] === "string") {
3082
- outDir = path5.resolve(defaultOutputPath, pluginOptions["output"]);
3083
- if (!fs5.existsSync(outDir)) {
3084
- fs5.mkdirSync(outDir, {
3085
- recursive: true
3086
- });
3087
- }
3088
- }
3089
- const lite = pluginOptions["lite"] === true;
3090
- const liteOnly = pluginOptions["liteOnly"] === true;
3091
- const importWithFileExtension = pluginOptions["importWithFileExtension"];
3092
- if (importWithFileExtension && typeof importWithFileExtension !== "string") {
3093
- throw new Error('The "importWithFileExtension" option must be a string if specified.');
3094
- }
3095
- const generateModelTypes = pluginOptions["generateModels"] !== false;
3096
- const generateInputTypes = pluginOptions["generateInput"] !== false;
3097
- await new TsSchemaGenerator().generate(model, {
3098
- outDir,
3099
- lite,
3100
- liteOnly,
3101
- importWithFileExtension,
3102
- generateModelTypes,
3103
- generateInputTypes
3104
- });
3105
- }
3106
- };
3107
- var typescript_default = plugin2;
3108
-
3109
- // src/actions/generate.ts
3110
- async function run4(options) {
3111
- try {
3112
- await checkForMismatchedPackages(process.cwd());
3113
- } catch (err) {
3114
- console.warn(colors6.yellow(`Failed to check for mismatched ZenStack packages: ${err}`));
3115
- }
3116
- const maybeShowUsageTips = options.tips && !options.silent && !options.watch ? startUsageTipsFetch() : void 0;
3117
- const model = await pureGenerate(options, false);
3118
- await maybeShowUsageTips?.();
3119
- if (options.watch) {
3120
- const logsEnabled = !options.silent;
3121
- if (logsEnabled) {
3122
- console.log(colors6.green(`
3123
- Enabled watch mode!`));
3124
- }
3125
- const schemaExtensions = ZModelLanguageMetaData.fileExtensions;
3126
- const getRootModelWatchPaths = /* @__PURE__ */ __name((model2) => new Set(model2.declarations.filter((v) => v.$cstNode?.parent?.element.$type === "Model" && !!v.$cstNode.parent.element.$document?.uri?.fsPath).map((v) => v.$cstNode.parent.element.$document.uri.fsPath)), "getRootModelWatchPaths");
3127
- const watchedPaths = getRootModelWatchPaths(model);
3128
- if (logsEnabled) {
3129
- const logPaths = [
3130
- ...watchedPaths
3131
- ].map((at) => `- ${at}`).join("\n");
3132
- console.log(`Watched file paths:
3133
- ${logPaths}`);
3134
- }
3135
- const watcher = watch([
3136
- ...watchedPaths
3137
- ], {
3138
- alwaysStat: false,
3139
- ignoreInitial: true,
3140
- ignorePermissionErrors: true,
3141
- ignored: /* @__PURE__ */ __name((at) => !schemaExtensions.some((ext) => at.endsWith(ext)), "ignored")
3142
- });
3143
- const reGenerateSchema = singleDebounce(async () => {
3144
- if (logsEnabled) {
3145
- console.log("Got changes, run generation!");
3146
- }
3147
- try {
3148
- const newModel = await pureGenerate(options, true);
3149
- const allModelsPaths = getRootModelWatchPaths(newModel);
3150
- const newModelPaths = [
3151
- ...allModelsPaths
3152
- ].filter((at) => !watchedPaths.has(at));
3153
- const removeModelPaths = [
3154
- ...watchedPaths
3155
- ].filter((at) => !allModelsPaths.has(at));
3156
- if (newModelPaths.length) {
3157
- if (logsEnabled) {
3158
- const logPaths = newModelPaths.map((at) => `- ${at}`).join("\n");
3159
- console.log(`Added file(s) to watch:
3160
- ${logPaths}`);
3161
- }
3162
- newModelPaths.forEach((at) => watchedPaths.add(at));
3163
- watcher.add(newModelPaths);
3164
- }
3165
- if (removeModelPaths.length) {
3166
- if (logsEnabled) {
3167
- const logPaths = removeModelPaths.map((at) => `- ${at}`).join("\n");
3168
- console.log(`Removed file(s) from watch:
3169
- ${logPaths}`);
3170
- }
3171
- removeModelPaths.forEach((at) => watchedPaths.delete(at));
3172
- watcher.unwatch(removeModelPaths);
3173
- }
3174
- } catch (e) {
3175
- console.error(e);
3176
- }
3177
- }, 500, true);
3178
- watcher.on("unlink", (pathAt) => {
3179
- if (logsEnabled) {
3180
- console.log(`Removed file from watch: ${pathAt}`);
3181
- }
3182
- watchedPaths.delete(pathAt);
3183
- watcher.unwatch(pathAt);
3184
- reGenerateSchema();
3185
- });
3186
- watcher.on("change", () => {
3187
- reGenerateSchema();
3188
- });
3189
- }
3190
- }
3191
- __name(run4, "run");
3192
- async function pureGenerate(options, fromWatch) {
3193
- const start = Date.now();
3194
- const schemaFile = getSchemaFile(options.schema);
3195
- const model = await loadSchemaDocument(schemaFile);
3196
- const outputPath = getOutputPath(options, schemaFile);
3197
- await runPlugins(schemaFile, model, outputPath, options);
3198
- if (!options.silent) {
3199
- console.log(colors6.green(`Generation completed successfully in ${Date.now() - start}ms.
3200
- `));
3201
- if (!fromWatch) {
3202
- console.log(`You can now create a ZenStack client with it.
3203
-
3204
- \`\`\`ts
3205
- import { ZenStackClient } from '@zenstackhq/orm';
3206
- import { schema } from '${path6.relative(".", outputPath)}/schema';
3207
-
3208
- const client = new ZenStackClient(schema, {
3209
- dialect: { ... }
3210
- });
3211
- \`\`\`
3212
-
3213
- Check documentation: https://zenstack.dev/docs/`);
3214
- }
3215
- }
3216
- return model;
3217
- }
3218
- __name(pureGenerate, "pureGenerate");
3219
- async function runPlugins(schemaFile, model, outputPath, options) {
3220
- const plugins = model.declarations.filter(isPlugin2);
3221
- const processedPlugins = [];
3222
- for (const plugin3 of plugins) {
3223
- const provider = getPluginProvider(plugin3);
3224
- let cliPlugin;
3225
- if (provider.startsWith("@core/")) {
3226
- cliPlugin = plugins_exports[provider.slice("@core/".length)];
3227
- if (!cliPlugin) {
3228
- throw new CliError(`Unknown core plugin: ${provider}`);
3229
- }
3230
- } else {
3231
- const pluginSourcePath = plugin3.$cstNode?.parent?.element.$document?.uri?.fsPath ?? schemaFile;
3232
- cliPlugin = await loadPluginModule(provider, path6.dirname(pluginSourcePath));
3233
- }
3234
- if (cliPlugin) {
3235
- const pluginOptions = getPluginOptions(plugin3);
3236
- if (provider === "@core/typescript") {
3237
- if (options.lite !== void 0) {
3238
- pluginOptions["lite"] = options.lite;
3239
- }
3240
- if (options.liteOnly !== void 0) {
3241
- pluginOptions["liteOnly"] = options.liteOnly;
3242
- }
3243
- if (options.generateModels !== void 0) {
3244
- pluginOptions["generateModels"] = options.generateModels;
3245
- }
3246
- if (options.generateInput !== void 0) {
3247
- pluginOptions["generateInput"] = options.generateInput;
3248
- }
3249
- }
3250
- processedPlugins.push({
3251
- cliPlugin,
3252
- pluginOptions
3253
- });
3254
- }
3255
- }
3256
- const defaultPlugins = [
3257
- {
3258
- plugin: typescript_default,
3259
- options: {
3260
- lite: options.lite,
3261
- liteOnly: options.liteOnly,
3262
- generateModels: options.generateModels,
3263
- generateInput: options.generateInput
3264
- }
3265
- }
3266
- ];
3267
- defaultPlugins.forEach(({ plugin: plugin3, options: options2 }) => {
3268
- if (!processedPlugins.some((p) => p.cliPlugin === plugin3)) {
3269
- processedPlugins.unshift({
3270
- cliPlugin: plugin3,
3271
- pluginOptions: options2
3272
- });
3273
- }
3274
- });
3275
- for (const { cliPlugin, pluginOptions } of processedPlugins) {
3276
- invariant2(typeof cliPlugin.generate === "function", `Plugin ${cliPlugin.name} does not have a generate function`);
3277
- let spinner;
3278
- if (!options.silent) {
3279
- spinner = ora2(cliPlugin.statusText ?? `Running plugin ${cliPlugin.name}`).start();
3280
- }
3281
- try {
3282
- await cliPlugin.generate({
3283
- schemaFile,
3284
- model,
3285
- defaultOutputPath: outputPath,
3286
- pluginOptions
3287
- });
3288
- spinner?.succeed();
3289
- } catch (err) {
3290
- spinner?.fail();
3291
- throw err;
3292
- }
3293
- }
3294
- }
3295
- __name(runPlugins, "runPlugins");
3296
- function getPluginOptions(plugin3) {
3297
- const result = {};
3298
- for (const field of plugin3.fields) {
3299
- if (field.name === "provider") {
3300
- continue;
3301
- }
3302
- const value = getLiteral(field.value) ?? getLiteralArray2(field.value);
3303
- if (value === void 0) {
3304
- console.warn(`Plugin "${plugin3.name}" option "${field.name}" has unsupported value, skipping`);
3305
- continue;
3306
- }
3307
- result[field.name] = value;
3308
- }
3309
- return result;
3310
- }
3311
- __name(getPluginOptions, "getPluginOptions");
3312
- async function checkForMismatchedPackages(projectPath) {
3313
- const packages = await getZenStackPackages(projectPath);
3314
- if (!packages.length) {
3315
- return false;
3316
- }
3317
- const versions = /* @__PURE__ */ new Set();
3318
- for (const { version: version2 } of packages) {
3319
- if (version2) {
3320
- versions.add(version2);
3321
- }
3322
- }
3323
- if (versions.size > 1) {
3324
- const message = "WARNING: Multiple versions of ZenStack packages detected.\n This will probably cause issues and break your types.";
3325
- const slashes = "/".repeat(73);
3326
- const latestVersion = semver.sort(Array.from(versions)).reverse()[0];
3327
- console.warn(colors6.yellow(`${slashes}
3328
-
3329
- ${message}
3330
- `));
3331
- for (const { pkg, version: version2 } of packages) {
3332
- if (!version2) continue;
3333
- if (version2 === latestVersion) {
3334
- console.log(` ${pkg.padEnd(32)} ${colors6.green(version2)}`);
3335
- } else {
3336
- console.log(` ${pkg.padEnd(32)} ${colors6.yellow(version2)}`);
3337
- }
3338
- }
3339
- console.warn(`
3340
- ${colors6.yellow(slashes)}`);
3341
- return true;
3342
- }
3343
- return false;
3344
- }
3345
- __name(checkForMismatchedPackages, "checkForMismatchedPackages");
3346
-
3347
- // src/actions/info.ts
3348
- import colors7 from "colors";
3349
- async function run5(projectPath) {
3350
- const packages = await getZenStackPackages(projectPath);
3351
- if (!packages.length) {
3352
- console.error("Unable to locate package.json. Are you in a valid project directory?");
3353
- return;
3354
- }
3355
- console.log("Installed ZenStack Packages:");
3356
- const versions = /* @__PURE__ */ new Set();
3357
- for (const { pkg, version: version2 } of packages) {
3358
- if (version2) {
3359
- versions.add(version2);
3360
- }
3361
- console.log(` ${colors7.green(pkg.padEnd(20))} ${version2}`);
3362
- }
3363
- if (versions.size > 1) {
3364
- console.warn(colors7.yellow("WARNING: Multiple versions of Zenstack packages detected. This may cause issues."));
3365
- }
3366
- }
3367
- __name(run5, "run");
3368
-
3369
- // src/actions/init.ts
3370
- import colors8 from "colors";
3371
- import fs6 from "fs";
3372
- import path7 from "path";
3373
- import ora3 from "ora";
3374
- import { detect, resolveCommand } from "package-manager-detector";
3375
-
3376
- // src/actions/templates.ts
3377
- var STARTER_ZMODEL = `// This is a sample model to get you started.
3378
-
3379
- /// A sample data source using local sqlite db.
3380
- datasource db {
3381
- provider = 'sqlite'
3382
- url = 'file:./dev.db'
3383
- }
3384
-
3385
- /// User model
3386
- model User {
3387
- id String @id @default(cuid())
3388
- email String @unique @email @length(6, 32)
3389
- posts Post[]
3390
- }
3391
-
3392
- /// Post model
3393
- model Post {
3394
- id String @id @default(cuid())
3395
- createdAt DateTime @default(now())
3396
- updatedAt DateTime @updatedAt
3397
- title String @length(1, 256)
3398
- content String
3399
- published Boolean @default(false)
3400
- author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
3401
- authorId String
3402
- }
3403
- `;
3404
-
3405
- // src/actions/init.ts
3406
- async function run6(projectPath) {
3407
- const packages = [
3408
- {
3409
- name: "@zenstackhq/cli@latest",
3410
- dev: true
3411
- },
3412
- {
3413
- name: "@zenstackhq/schema@latest",
3414
- dev: false
3415
- },
3416
- {
3417
- name: "@zenstackhq/orm@latest",
3418
- dev: false
3419
- }
3420
- ];
3421
- let pm = await detect();
3422
- if (!pm) {
3423
- pm = {
3424
- agent: "npm",
3425
- name: "npm"
3426
- };
3427
- }
3428
- console.log(colors8.gray(`Using package manager: ${pm.agent}`));
3429
- for (const pkg of packages) {
3430
- const resolved = resolveCommand(pm.agent, "add", [
3431
- pkg.name,
3432
- ...pkg.dev ? [
3433
- pm.agent.startsWith("yarn") || pm.agent === "bun" ? "--dev" : "--save-dev"
3434
- ] : []
3435
- ]);
3436
- if (!resolved) {
3437
- throw new CliError(`Unable to determine how to install package "${pkg.name}". Please install it manually.`);
3438
- }
3439
- const spinner = ora3(`Installing "${pkg.name}"`).start();
3440
- try {
3441
- execSync(`${resolved.command} ${resolved.args.join(" ")}`, {
3442
- cwd: projectPath
3443
- });
3444
- spinner.succeed();
3445
- } catch (e) {
3446
- spinner.fail();
3447
- throw e;
3448
- }
3449
- }
3450
- const generationFolder = "zenstack";
3451
- if (!fs6.existsSync(path7.join(projectPath, generationFolder))) {
3452
- fs6.mkdirSync(path7.join(projectPath, generationFolder));
3453
- }
3454
- if (!fs6.existsSync(path7.join(projectPath, generationFolder, "schema.zmodel"))) {
3455
- fs6.writeFileSync(path7.join(projectPath, generationFolder, "schema.zmodel"), STARTER_ZMODEL);
3456
- } else {
3457
- console.log(colors8.yellow("Schema file already exists. Skipping generation of sample."));
3458
- }
3459
- console.log(colors8.green("ZenStack project initialized successfully!"));
3460
- console.log(colors8.gray(`See "${generationFolder}/schema.zmodel" for your database schema.`));
3461
- console.log(colors8.gray("Run `zenstack generate` to compile the the schema into a TypeScript file."));
3462
- }
3463
- __name(run6, "run");
3464
-
3465
- // src/actions/migrate.ts
3466
- import fs7 from "fs";
3467
- import path8 from "path";
3468
-
3469
- // src/actions/seed.ts
3470
- import colors9 from "colors";
3471
- import { execaCommand } from "execa";
3472
- async function run7(options, args) {
3473
- const pkgJsonConfig = getPkgJsonConfig(process.cwd());
3474
- if (!pkgJsonConfig.seed) {
3475
- if (!options.noWarnings) {
3476
- console.warn(colors9.yellow("No seed script defined in package.json. Skipping seeding."));
3477
- }
3478
- return;
3479
- }
3480
- const command = `${pkgJsonConfig.seed}${args.length > 0 ? " " + args.join(" ") : ""}`;
3481
- if (options.printStatus) {
3482
- console.log(colors9.gray(`Running seed script "${command}"...`));
3483
- }
3484
- try {
3485
- await execaCommand(command, {
3486
- stdout: "inherit",
3487
- stderr: "inherit"
3488
- });
3489
- } catch (err) {
3490
- console.error(colors9.red(err instanceof Error ? err.message : String(err)));
3491
- throw new CliError("Failed to seed the database. Please check the error message above for details.");
3492
- }
3493
- }
3494
- __name(run7, "run");
3495
-
3496
- // src/actions/migrate.ts
3497
- async function run8(command, options) {
3498
- const schemaFile = getSchemaFile(options.schema);
3499
- await requireDataSourceUrl(schemaFile);
3500
- const prismaSchemaDir = options.migrations ? path8.dirname(options.migrations) : void 0;
3501
- const prismaSchemaFile = await generateTempPrismaSchema(schemaFile, prismaSchemaDir);
3502
- try {
3503
- switch (command) {
3504
- case "dev":
3505
- await runDev(prismaSchemaFile, options);
3506
- break;
3507
- case "reset":
3508
- await runReset(prismaSchemaFile, options);
3509
- break;
3510
- case "deploy":
3511
- await runDeploy(prismaSchemaFile, options);
3512
- break;
3513
- case "status":
3514
- await runStatus(prismaSchemaFile, options);
3515
- break;
3516
- case "resolve":
3517
- await runResolve(prismaSchemaFile, options);
3518
- break;
3519
- }
3520
- } finally {
3521
- if (fs7.existsSync(prismaSchemaFile)) {
3522
- fs7.unlinkSync(prismaSchemaFile);
3523
- }
3524
- }
3525
- }
3526
- __name(run8, "run");
3527
- function runDev(prismaSchemaFile, options) {
3528
- try {
3529
- const cmd = [
3530
- "migrate dev",
3531
- ` --schema "${prismaSchemaFile}"`,
3532
- " --skip-generate",
3533
- " --skip-seed",
3534
- options.name ? ` --name "${options.name}"` : "",
3535
- options.createOnly ? " --create-only" : ""
3536
- ].join("");
3537
- execPrisma(cmd);
3538
- } catch (err) {
3539
- handleSubProcessError2(err);
3540
- }
3541
- }
3542
- __name(runDev, "runDev");
3543
- async function runReset(prismaSchemaFile, options) {
3544
- try {
3545
- const cmd = [
3546
- "migrate reset",
3547
- ` --schema "${prismaSchemaFile}"`,
3548
- " --skip-generate",
3549
- " --skip-seed",
3550
- options.force ? " --force" : ""
3551
- ].join("");
3552
- execPrisma(cmd);
3553
- } catch (err) {
3554
- handleSubProcessError2(err);
3555
- }
3556
- if (!options.skipSeed) {
3557
- await run7({
3558
- noWarnings: true,
3559
- printStatus: true
3560
- }, []);
3561
- }
3562
- }
3563
- __name(runReset, "runReset");
3564
- function runDeploy(prismaSchemaFile, _options) {
3565
- try {
3566
- const cmd = [
3567
- "migrate deploy",
3568
- ` --schema "${prismaSchemaFile}"`
3569
- ].join("");
3570
- execPrisma(cmd);
3571
- } catch (err) {
3572
- handleSubProcessError2(err);
3573
- }
3574
- }
3575
- __name(runDeploy, "runDeploy");
3576
- function runStatus(prismaSchemaFile, _options) {
3577
- try {
3578
- execPrisma(`migrate status --schema "${prismaSchemaFile}"`);
3579
- } catch (err) {
3580
- handleSubProcessError2(err);
3581
- }
3582
- }
3583
- __name(runStatus, "runStatus");
3584
- function runResolve(prismaSchemaFile, options) {
3585
- if (!options.applied && !options.rolledBack) {
3586
- throw new CliError("Either --applied or --rolled-back option must be provided");
3587
- }
3588
- try {
3589
- const cmd = [
3590
- "migrate resolve",
3591
- ` --schema "${prismaSchemaFile}"`,
3592
- options.applied ? ` --applied "${options.applied}"` : "",
3593
- options.rolledBack ? ` --rolled-back "${options.rolledBack}"` : ""
3594
- ].join("");
3595
- execPrisma(cmd);
3596
- } catch (err) {
3597
- handleSubProcessError2(err);
3598
- }
3599
- }
3600
- __name(runResolve, "runResolve");
3601
- function handleSubProcessError2(err) {
3602
- if (err instanceof Error && "status" in err && typeof err.status === "number") {
3603
- process.exit(err.status);
3604
- } else {
3605
- process.exit(1);
3606
- }
3607
- }
3608
- __name(handleSubProcessError2, "handleSubProcessError");
3609
-
3610
- // src/actions/proxy.ts
3611
- import { isDataSource as isDataSource2, isInvocationExpr as isInvocationExpr2, isLiteralExpr } from "@zenstackhq/language/ast";
3612
- import { getStringLiteral as getStringLiteral2 } from "@zenstackhq/language/utils";
3613
- import { ZenStackClient } from "@zenstackhq/orm";
3614
- import { MysqlDialect } from "@zenstackhq/orm/dialects/mysql";
3615
- import { PostgresDialect } from "@zenstackhq/orm/dialects/postgres";
3616
- import { SqliteDialect } from "@zenstackhq/orm/dialects/sqlite";
3617
- import { RPCApiHandler } from "@zenstackhq/server/api";
3618
- import { ZenStackMiddleware } from "@zenstackhq/server/express";
3619
- import colors11 from "colors";
3620
- import cors from "cors";
3621
- import express from "express";
3622
- import { createJiti as createJiti2 } from "jiti";
3623
- import path10 from "path";
3624
-
3625
- // src/utils/version-utils.ts
3626
- import colors10 from "colors";
3627
- import fs8 from "fs";
3628
- import path9 from "path";
3629
- import { fileURLToPath as fileURLToPath2 } from "url";
3630
- import semver2 from "semver";
3631
- var CHECK_VERSION_TIMEOUT = 2e3;
3632
- var VERSION_CHECK_TAG = "latest";
3633
- function getVersion() {
3634
- try {
3635
- const _dirname = typeof __dirname !== "undefined" ? __dirname : path9.dirname(fileURLToPath2(import.meta.url));
3636
- return JSON.parse(fs8.readFileSync(path9.join(_dirname, "../package.json"), "utf8")).version;
3637
- } catch {
3638
- return void 0;
3639
- }
3640
- }
3641
- __name(getVersion, "getVersion");
3642
- async function checkNewVersion() {
3643
- const currVersion = getVersion();
3644
- let latestVersion;
3645
- try {
3646
- latestVersion = await getLatestVersion();
3647
- } catch {
3648
- return;
3649
- }
3650
- if (latestVersion && currVersion && semver2.gt(latestVersion, currVersion)) {
3651
- console.log(`A newer version ${colors10.cyan(latestVersion)} is available.`);
3652
- }
3653
- }
3654
- __name(checkNewVersion, "checkNewVersion");
3655
- async function getLatestVersion() {
3656
- const fetchResult = await fetch(`https://registry.npmjs.org/@zenstackhq/cli/${VERSION_CHECK_TAG}`, {
3657
- headers: {
3658
- accept: "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*"
3659
- },
3660
- signal: AbortSignal.timeout(CHECK_VERSION_TIMEOUT)
3661
- });
3662
- if (fetchResult.ok) {
3663
- const data = await fetchResult.json();
3664
- const latestVersion = data?.version;
3665
- if (typeof latestVersion === "string" && semver2.valid(latestVersion)) {
3666
- return latestVersion;
3667
- }
3668
- }
3669
- throw new Error("invalid npm registry response");
3670
- }
3671
- __name(getLatestVersion, "getLatestVersion");
3672
-
3673
- // src/actions/proxy.ts
3674
- async function run9(options) {
3675
- const allowedLogLevels = [
3676
- "error",
3677
- "query"
3678
- ];
3679
- const log = options.logLevel?.filter((level) => allowedLogLevels.includes(level));
3680
- const schemaFile = getSchemaFile(options.schema);
3681
- console.log(colors11.gray(`Loading ZModel schema from: ${schemaFile}`));
3682
- let outputPath = getOutputPath(options, schemaFile);
3683
- if (!path10.isAbsolute(outputPath)) {
3684
- outputPath = path10.resolve(process.cwd(), outputPath);
3685
- }
3686
- const model = await loadSchemaDocument(schemaFile);
3687
- const dataSource = model.declarations.find(isDataSource2);
3688
- let databaseUrl = options.databaseUrl;
3689
- if (!databaseUrl) {
3690
- const schemaUrl = dataSource?.fields.find((f) => f.name === "url")?.value;
3691
- if (!schemaUrl) {
3692
- throw new CliError(`The schema's "datasource" does not have a "url" field, please provide it with -d option.`);
3693
- }
3694
- databaseUrl = evaluateUrl(schemaUrl);
3695
- }
3696
- const provider = getStringLiteral2(dataSource?.fields.find((f) => f.name === "provider")?.value);
3697
- const dialect = await createDialect(provider, databaseUrl, outputPath);
3698
- const fileUrl = typeof __filename !== "undefined" ? __filename : import.meta.url;
3699
- const jiti = createJiti2(fileUrl);
3700
- const schemaModule = await jiti.import(path10.join(outputPath, "schema"));
3701
- const schema = schemaModule.schema;
3702
- const omit = {};
3703
- for (const [modelName, modelDef] of Object.entries(schema.models)) {
3704
- const omitFields = {};
3705
- for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) {
3706
- if (fieldDef.computed === true || fieldDef.type === "Unsupported") {
3707
- omitFields[fieldName] = true;
3708
- }
3709
- }
3710
- if (Object.keys(omitFields).length > 0) {
3711
- omit[modelName] = omitFields;
3712
- }
3713
- }
3714
- const db = new ZenStackClient(schema, {
3715
- dialect,
3716
- log: log && log.length > 0 ? log : void 0,
3717
- omit: Object.keys(omit).length > 0 ? omit : void 0,
3718
- skipValidationForComputedFields: true
3719
- });
3720
- try {
3721
- await db.$connect();
3722
- } catch (err) {
3723
- throw new CliError(`Failed to connect to the database: ${err instanceof Error ? err.message : String(err)}`);
3724
- }
3725
- startServer(db, schemaModule.schema, options);
3726
- }
3727
- __name(run9, "run");
3728
- function evaluateUrl(schemaUrl) {
3729
- if (isLiteralExpr(schemaUrl)) {
3730
- return getStringLiteral2(schemaUrl);
3731
- } else if (isInvocationExpr2(schemaUrl)) {
3732
- const envFunction = schemaUrl;
3733
- const envName = getStringLiteral2(envFunction.args[0]?.value);
3734
- const envValue = process.env[envName];
3735
- if (!envValue) {
3736
- throw new CliError(`Environment variable ${envName} is not set`);
3737
- }
3738
- return envValue;
3739
- } else {
3740
- throw new CliError(`Unable to resolve the "url" field value.`);
3741
- }
3742
- }
3743
- __name(evaluateUrl, "evaluateUrl");
3744
- function redactDatabaseUrl(url) {
3745
- try {
3746
- const parsedUrl = new URL(url);
3747
- if (parsedUrl.password) {
3748
- parsedUrl.password = "***";
3749
- }
3750
- if (parsedUrl.username) {
3751
- parsedUrl.username = "***";
3752
- }
3753
- return parsedUrl.toString();
3754
- } catch {
3755
- return url;
3756
- }
3757
- }
3758
- __name(redactDatabaseUrl, "redactDatabaseUrl");
3759
- async function createDialect(provider, databaseUrl, outputPath) {
3760
- switch (provider) {
3761
- case "sqlite": {
3762
- let SQLite;
3763
- try {
3764
- SQLite = (await import("better-sqlite3")).default;
3765
- } catch {
3766
- throw new CliError(`Package "better-sqlite3" is required for SQLite support. Please install it with: npm install better-sqlite3`);
3767
- }
3768
- let resolvedUrl = databaseUrl.trim();
3769
- if (resolvedUrl.startsWith("file:")) {
3770
- const filePath = resolvedUrl.substring("file:".length);
3771
- if (!path10.isAbsolute(filePath)) {
3772
- resolvedUrl = path10.join(outputPath, filePath);
3773
- }
3774
- }
3775
- console.log(colors11.gray(`Connecting to SQLite database at: ${resolvedUrl}`));
3776
- return new SqliteDialect({
3777
- database: new SQLite(resolvedUrl)
3778
- });
3779
- }
3780
- case "postgresql": {
3781
- let PgPool;
3782
- try {
3783
- PgPool = (await import("pg")).Pool;
3784
- } catch {
3785
- throw new CliError(`Package "pg" is required for PostgreSQL support. Please install it with: npm install pg`);
3786
- }
3787
- console.log(colors11.gray(`Connecting to PostgreSQL database at: ${redactDatabaseUrl(databaseUrl)}`));
3788
- return new PostgresDialect({
3789
- pool: new PgPool({
3790
- connectionString: databaseUrl
3791
- })
3792
- });
3793
- }
3794
- case "mysql": {
3795
- let createMysqlPool;
3796
- try {
3797
- createMysqlPool = (await import("mysql2")).createPool;
3798
- } catch {
3799
- throw new CliError(`Package "mysql2" is required for MySQL support. Please install it with: npm install mysql2`);
3800
- }
3801
- console.log(colors11.gray(`Connecting to MySQL database at: ${redactDatabaseUrl(databaseUrl)}`));
3802
- return new MysqlDialect({
3803
- pool: createMysqlPool(databaseUrl)
3804
- });
3805
- }
3806
- default:
3807
- throw new CliError(`Unsupported database provider: ${provider}`);
3808
- }
3809
- }
3810
- __name(createDialect, "createDialect");
3811
- function createProxyApp(client, schema) {
3812
- const app = express();
3813
- app.use(cors());
3814
- app.use(express.json({
3815
- limit: "5mb"
3816
- }));
3817
- app.use(express.urlencoded({
3818
- extended: true,
3819
- limit: "5mb"
3820
- }));
3821
- app.use("/api/model", ZenStackMiddleware({
3822
- apiHandler: new RPCApiHandler({
3823
- schema
3824
- }),
3825
- getClient: /* @__PURE__ */ __name(() => client, "getClient")
3826
- }));
3827
- app.get("/api/schema", (_req, res) => {
3828
- res.json({
3829
- ...schema,
3830
- zenstackVersion: getVersion()
3831
- });
3832
- });
3833
- return app;
3834
- }
3835
- __name(createProxyApp, "createProxyApp");
3836
- function startServer(client, schema, options) {
3837
- const app = createProxyApp(client, schema);
3838
- const server = app.listen(options.port, () => {
3839
- console.log(`ZenStack proxy server is running on port: ${options.port}`);
3840
- console.log(`You can visit ZenStack Studio at: ${colors11.blue("https://studio.zenstack.dev")}`);
3841
- });
3842
- server.on("error", (err) => {
3843
- if (err.code === "EADDRINUSE") {
3844
- console.error(colors11.red(`Port ${options.port} is already in use. Please choose a different port using -p option.`));
3845
- } else {
3846
- throw new CliError(`Failed to start the server: ${err.message}`);
3847
- }
3848
- process.exit(1);
3849
- });
3850
- process.on("SIGTERM", async () => {
3851
- server.close(() => {
3852
- console.log("\nZenStack proxy server closed");
3853
- });
3854
- await client.$disconnect();
3855
- process.exit(0);
3856
- });
3857
- process.on("SIGINT", async () => {
3858
- server.close(() => {
3859
- console.log("\nZenStack proxy server closed");
3860
- });
3861
- await client.$disconnect();
3862
- process.exit(0);
3863
- });
3864
- }
3865
- __name(startServer, "startServer");
3866
-
3867
- // src/telemetry.ts
3868
- import { init } from "mixpanel";
3869
- import { randomUUID as randomUUID2 } from "crypto";
3870
- import fs12 from "fs";
3871
- import * as os2 from "os";
3872
-
3873
- // src/constants.ts
3874
- var TELEMETRY_TRACKING_TOKEN = "74944eb779d7d3b4ce185be843fde9fc";
3875
-
3876
- // src/utils/is-ci.ts
3877
- import { env } from "process";
3878
- var isInCi = env["CI"] !== "0" && env["CI"] !== "false" && ("CI" in env || "CONTINUOUS_INTEGRATION" in env || Object.keys(env).some((key) => key.startsWith("CI_")));
3879
-
3880
- // src/utils/is-container.ts
3881
- import fs10 from "fs";
3882
-
3883
- // src/utils/is-docker.ts
3884
- import fs9 from "fs";
3885
- var isDockerCached;
3886
- function hasDockerEnv() {
3887
- try {
3888
- fs9.statSync("/.dockerenv");
3889
- return true;
3890
- } catch {
3891
- return false;
3892
- }
3893
- }
3894
- __name(hasDockerEnv, "hasDockerEnv");
3895
- function hasDockerCGroup() {
3896
- try {
3897
- return fs9.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
3898
- } catch {
3899
- return false;
3900
- }
3901
- }
3902
- __name(hasDockerCGroup, "hasDockerCGroup");
3903
- function isDocker() {
3904
- if (isDockerCached === void 0) {
3905
- isDockerCached = hasDockerEnv() || hasDockerCGroup();
3906
- }
3907
- return isDockerCached;
3908
- }
3909
- __name(isDocker, "isDocker");
3910
-
3911
- // src/utils/is-container.ts
3912
- var cachedResult;
3913
- var hasContainerEnv = /* @__PURE__ */ __name(() => {
3914
- try {
3915
- fs10.statSync("/run/.containerenv");
3916
- return true;
3917
- } catch {
3918
- return false;
3919
- }
3920
- }, "hasContainerEnv");
3921
- function isInContainer() {
3922
- if (cachedResult === void 0) {
3923
- cachedResult = hasContainerEnv() || isDocker();
3924
- }
3925
- return cachedResult;
3926
- }
3927
- __name(isInContainer, "isInContainer");
3928
-
3929
- // src/utils/is-wsl.ts
3930
- import process2 from "process";
3931
- import os from "os";
3932
- import fs11 from "fs";
3933
- var isWsl = /* @__PURE__ */ __name(() => {
3934
- if (process2.platform !== "linux") {
3935
- return false;
3936
- }
3937
- if (os.release().toLowerCase().includes("microsoft")) {
3938
- return true;
3939
- }
3940
- try {
3941
- return fs11.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft");
3942
- } catch {
3943
- return false;
3944
- }
3945
- }, "isWsl");
3946
-
3947
- // src/utils/machine-id-utils.ts
3948
- import { execSync as execSync2 } from "child_process";
3949
- import { createHash, randomUUID } from "crypto";
3950
- var { platform } = process;
3951
- var win32RegBinPath = {
3952
- native: "%windir%\\System32",
3953
- mixed: "%windir%\\sysnative\\cmd.exe /c %windir%\\System32"
3954
- };
3955
- var guid = {
3956
- darwin: "ioreg -rd1 -c IOPlatformExpertDevice",
3957
- win32: `${win32RegBinPath[isWindowsProcessMixedOrNativeArchitecture()]}\\REG.exe QUERY HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography /v MachineGuid`,
3958
- linux: "( cat /var/lib/dbus/machine-id /etc/machine-id 2> /dev/null || hostname 2> /dev/null) | head -n 1 || :",
3959
- freebsd: "kenv -q smbios.system.uuid || sysctl -n kern.hostuuid"
3960
- };
3961
- function isWindowsProcessMixedOrNativeArchitecture() {
3962
- if (process.arch === "ia32" && process.env.hasOwnProperty("PROCESSOR_ARCHITEW6432")) {
3963
- return "mixed";
3964
- }
3965
- return "native";
3966
- }
3967
- __name(isWindowsProcessMixedOrNativeArchitecture, "isWindowsProcessMixedOrNativeArchitecture");
3968
- function hash(guid2) {
3969
- return createHash("sha256").update(guid2).digest("hex");
3970
- }
3971
- __name(hash, "hash");
3972
- function expose(result) {
3973
- switch (platform) {
3974
- case "darwin":
3975
- return result.split("IOPlatformUUID")[1]?.split("\n")[0]?.replace(/=|\s+|"/gi, "").toLowerCase();
3976
- case "win32":
3977
- return result.toString().split("REG_SZ")[1]?.replace(/\r+|\n+|\s+/gi, "").toLowerCase();
3978
- case "linux":
3979
- return result.toString().replace(/\r+|\n+|\s+/gi, "").toLowerCase();
3980
- case "freebsd":
3981
- return result.toString().replace(/\r+|\n+|\s+/gi, "").toLowerCase();
3982
- default:
3983
- throw new Error(`Unsupported platform: ${process.platform}`);
3984
- }
3985
- }
3986
- __name(expose, "expose");
3987
- function getMachineId() {
3988
- if (!(platform in guid)) {
3989
- return randomUUID();
3990
- }
3991
- try {
3992
- const value = execSync2(guid[platform]);
3993
- const id = expose(value.toString());
3994
- if (!id) {
3995
- return randomUUID();
3996
- }
3997
- return hash(id);
3998
- } catch {
3999
- return randomUUID();
4000
- }
4001
- }
4002
- __name(getMachineId, "getMachineId");
4003
-
4004
- // src/telemetry.ts
4005
- var Telemetry = class {
4006
- static {
4007
- __name(this, "Telemetry");
4008
- }
4009
- mixpanel;
4010
- hostId = getMachineId();
4011
- sessionid = randomUUID2();
4012
- _os_type = os2.type();
4013
- _os_release = os2.release();
4014
- _os_arch = os2.arch();
4015
- _os_version = os2.version();
4016
- _os_platform = os2.platform();
4017
- version = getVersion();
4018
- prismaVersion = this.getPrismaVersion();
4019
- isDocker = isDocker();
4020
- isWsl = isWsl();
4021
- isContainer = isInContainer();
4022
- isCi = isInCi;
4023
- constructor() {
4024
- if (process.env["DO_NOT_TRACK"] !== "1" && TELEMETRY_TRACKING_TOKEN) {
4025
- this.mixpanel = init(TELEMETRY_TRACKING_TOKEN, {
4026
- geolocate: true
4027
- });
4028
- }
4029
- }
4030
- get isTracking() {
4031
- return !!this.mixpanel;
4032
- }
4033
- track(event, properties = {}) {
4034
- if (this.mixpanel) {
4035
- const payload = {
4036
- distinct_id: this.hostId,
4037
- session: this.sessionid,
4038
- time: /* @__PURE__ */ new Date(),
4039
- $os: this._os_type,
4040
- osType: this._os_type,
4041
- osRelease: this._os_release,
4042
- osPlatform: this._os_platform,
4043
- osArch: this._os_arch,
4044
- osVersion: this._os_version,
4045
- nodeVersion: process.version,
4046
- version: this.version,
4047
- prismaVersion: this.prismaVersion,
4048
- isDocker: this.isDocker,
4049
- isWsl: this.isWsl,
4050
- isContainer: this.isContainer,
4051
- isCi: this.isCi,
4052
- ...properties
4053
- };
4054
- this.mixpanel.track(event, payload);
4055
- }
4056
- }
4057
- trackError(err) {
4058
- this.track("cli:error", {
4059
- message: err.message,
4060
- stack: err.stack
4061
- });
4062
- }
4063
- async trackSpan(startEvent, completeEvent, errorEvent, properties, action) {
4064
- this.track(startEvent, properties);
4065
- const start = Date.now();
4066
- let success = true;
4067
- try {
4068
- return await action();
4069
- } catch (err) {
4070
- this.track(errorEvent, {
4071
- message: err.message,
4072
- stack: err.stack,
4073
- ...properties
4074
- });
4075
- success = false;
4076
- throw err;
4077
- } finally {
4078
- this.track(completeEvent, {
4079
- duration: Date.now() - start,
4080
- success,
4081
- ...properties
4082
- });
4083
- }
4084
- }
4085
- async trackCommand(command, action) {
4086
- await this.trackSpan("cli:command:start", "cli:command:complete", "cli:command:error", {
4087
- command
4088
- }, action);
4089
- }
4090
- async trackCli(action) {
4091
- await this.trackSpan("cli:start", "cli:complete", "cli:error", {}, action);
4092
- }
4093
- getPrismaVersion() {
4094
- try {
4095
- const packageJsonPath = import.meta.resolve("prisma/package.json");
4096
- const packageJsonUrl = new URL(packageJsonPath);
4097
- const packageJson = JSON.parse(fs12.readFileSync(packageJsonUrl, "utf8"));
4098
- return packageJson.version;
4099
- } catch {
4100
- return void 0;
4101
- }
4102
- }
4103
- };
4104
- var telemetry = new Telemetry();
4105
-
4106
- // src/index.ts
4107
- var generateAction = /* @__PURE__ */ __name(async (options) => {
4108
- await telemetry.trackCommand("generate", () => run4(options));
4109
- }, "generateAction");
4110
- var migrateAction = /* @__PURE__ */ __name(async (subCommand, options) => {
4111
- await telemetry.trackCommand(`migrate ${subCommand}`, () => run8(subCommand, options));
4112
- }, "migrateAction");
4113
- var dbAction = /* @__PURE__ */ __name(async (subCommand, options) => {
4114
- await telemetry.trackCommand(`db ${subCommand}`, () => run2(subCommand, options));
4115
- }, "dbAction");
4116
- var infoAction = /* @__PURE__ */ __name(async (projectPath) => {
4117
- await telemetry.trackCommand("info", () => run5(projectPath));
4118
- }, "infoAction");
4119
- var initAction = /* @__PURE__ */ __name(async (projectPath) => {
4120
- await telemetry.trackCommand("init", () => run6(projectPath));
4121
- }, "initAction");
4122
- var checkAction = /* @__PURE__ */ __name(async (options) => {
4123
- await telemetry.trackCommand("check", () => run(options));
4124
- }, "checkAction");
4125
- var formatAction = /* @__PURE__ */ __name(async (options) => {
4126
- await telemetry.trackCommand("format", () => run3(options));
4127
- }, "formatAction");
4128
- var seedAction = /* @__PURE__ */ __name(async (options, args) => {
4129
- await telemetry.trackCommand("db seed", () => run7(options, args));
4130
- }, "seedAction");
4131
- var proxyAction = /* @__PURE__ */ __name(async (options) => {
4132
- await telemetry.trackCommand("proxy", () => run9(options));
4133
- }, "proxyAction");
4134
- function triStateBooleanOption(flag, description) {
4135
- return new Option(flag, description).choices([
4136
- "true",
4137
- "false"
4138
- ]).argParser((value) => {
4139
- if (value === void 0 || value === "true") return true;
4140
- if (value === "false") return false;
4141
- throw new CliError(`Invalid value for ${flag}: ${value}`);
4142
- });
4143
- }
4144
- __name(triStateBooleanOption, "triStateBooleanOption");
4145
- function createProgram() {
4146
- const program = new Command("zen").alias("zenstack").helpOption("-h, --help", "Show this help message").version(getVersion(), "-v --version", "Show CLI version");
4147
- const schemaExtensions = ZModelLanguageMetaData2.fileExtensions.join(", ");
4148
- program.description(`${colors12.bold.blue("\u03B6")} ZenStack is the modern data layer for TypeScript apps.
4149
-
4150
- Documentation: https://zenstack.dev/docs`).showHelpAfterError().showSuggestionAfterError();
4151
- const schemaOption = new Option("--schema <file>", `schema file (with extension ${schemaExtensions}). Defaults to "zenstack/schema.zmodel" unless specified in package.json.`);
4152
- const noVersionCheckOption = new Option("--no-version-check", "do not check for new version");
4153
- const noTipsOption = new Option("--no-tips", "do not show usage tips");
4154
- program.command("generate").description("Run code generation plugins").addOption(schemaOption).addOption(noVersionCheckOption).addOption(noTipsOption).addOption(new Option("-o, --output <path>", "default output directory for code generation")).addOption(new Option("-w, --watch", "enable watch mode").default(false)).addOption(triStateBooleanOption("--lite [boolean]", "also generate a lite version of schema without attributes, defaults to false")).addOption(triStateBooleanOption("--lite-only [boolean]", "only generate lite version of schema without attributes, defaults to false")).addOption(triStateBooleanOption("--generate-models [boolean]", "generate models.ts file, defaults to true")).addOption(triStateBooleanOption("--generate-input [boolean]", "generate input.ts file, defaults to true")).addOption(new Option("--silent", "suppress all output except errors").default(false)).action(generateAction);
4155
- const migrateCommand = program.command("migrate").description("Run database schema migration related tasks.");
4156
- const migrationsOption = new Option("--migrations <path>", 'path that contains the "migrations" directory');
4157
- migrateCommand.command("dev").addOption(schemaOption).addOption(noVersionCheckOption).addOption(new Option("-n, --name <name>", "migration name")).addOption(new Option("--create-only", "only create migration, do not apply")).addOption(migrationsOption).description("Create a migration from changes in schema and apply it to the database").action((options) => migrateAction("dev", options));
4158
- migrateCommand.command("reset").addOption(schemaOption).addOption(new Option("--force", "skip the confirmation prompt")).addOption(migrationsOption).addOption(new Option("--skip-seed", "skip seeding the database after reset")).addOption(noVersionCheckOption).description("Reset your database and apply all migrations, all data will be lost").addHelpText("after", "\nIf there is a seed script defined in package.json, it will be run after the reset. Use --skip-seed to skip it.").action((options) => migrateAction("reset", options));
4159
- migrateCommand.command("deploy").addOption(schemaOption).addOption(noVersionCheckOption).addOption(migrationsOption).description("Deploy your pending migrations to your production/staging database").action((options) => migrateAction("deploy", options));
4160
- migrateCommand.command("status").addOption(schemaOption).addOption(noVersionCheckOption).addOption(migrationsOption).description("Check the status of your database migrations").action((options) => migrateAction("status", options));
4161
- migrateCommand.command("resolve").addOption(schemaOption).addOption(noVersionCheckOption).addOption(migrationsOption).addOption(new Option("--applied <migration>", "record a specific migration as applied")).addOption(new Option("--rolled-back <migration>", "record a specific migration as rolled back")).description("Resolve issues with database migrations in deployment databases").action((options) => migrateAction("resolve", options));
4162
- const dbCommand = program.command("db").description("Manage your database schema during development");
4163
- dbCommand.command("push").description("Push the state from your schema to your database").addOption(schemaOption).addOption(noVersionCheckOption).addOption(new Option("--accept-data-loss", "ignore data loss warnings")).addOption(new Option("--force-reset", "force a reset of the database before push")).action((options) => dbAction("push", options));
4164
- dbCommand.command("pull").description("Introspect your database.").addOption(schemaOption).addOption(noVersionCheckOption).addOption(new Option("-o, --output <path>", "set custom output path for the introspected schema. If a file path is provided, all schemas are merged into that single file. If a directory path is provided, files are written to the directory and imports are kept.")).addOption(new Option("--model-casing <pascal|camel|snake|none>", "set the casing of generated models").default("pascal")).addOption(new Option("--field-casing <pascal|camel|snake|none>", "set the casing of generated fields").default("camel")).addOption(new Option("--always-map", "always add @map and @@map attributes to models and fields").default(false)).addOption(new Option("--quote <double|single>", "set the quote style of generated schema files").default("single")).addOption(new Option("--indent <number>", "set the indentation of the generated schema files").default(4)).action((options) => dbAction("pull", options));
4165
- dbCommand.command("seed").description("Seed the database").allowExcessArguments(true).addHelpText("after", `
4166
- Seed script is configured under the "zenstack.seed" field in package.json.
4167
- E.g.:
4168
- {
4169
- "zenstack": {
4170
- "seed": "ts-node ./zenstack/seed.ts"
4171
- }
4172
- }
4173
-
4174
- Arguments following -- are passed to the seed script. E.g.: "zen db seed -- --users 10"`).addOption(noVersionCheckOption).action((options, command) => seedAction(options, command.args));
4175
- program.command("info").description("Get information of installed ZenStack packages").argument("[path]", "project path", ".").addOption(noVersionCheckOption).action(infoAction);
4176
- program.command("init").description("Initialize an existing project for ZenStack").argument("[path]", "project path", ".").addOption(noVersionCheckOption).action(initAction);
4177
- program.command("check").description("Check a ZModel schema for syntax or semantic errors").addOption(schemaOption).addOption(noVersionCheckOption).action(checkAction);
4178
- program.command("format").description("Format a ZModel schema file").addOption(schemaOption).addOption(noVersionCheckOption).action(formatAction);
4179
- program.command("proxy").alias("studio").description("Start the ZenStack proxy server").addOption(schemaOption).addOption(new Option("-p, --port <port>", "port to run the proxy server on").default(2311)).addOption(new Option("-o, --output <path>", "output directory for `zen generate` command")).addOption(new Option("-d, --databaseUrl <url>", "database connection URL")).addOption(new Option("-l, --logLevel <level...>", "Query log levels (e.g., query, error)")).addOption(noVersionCheckOption).action(proxyAction);
4180
- program.addHelpCommand("help [command]", "Display help for a command");
4181
- program.hook("preAction", async (_thisCommand, actionCommand) => {
4182
- if (actionCommand.getOptionValue("versionCheck") !== false) {
4183
- await checkNewVersion();
4184
- }
4185
- });
4186
- return program;
4187
- }
4188
- __name(createProgram, "createProgram");
4189
- async function main() {
4190
- let exitCode = 0;
4191
- const program = createProgram();
4192
- program.exitOverride();
4193
- try {
4194
- await telemetry.trackCli(async () => {
4195
- await program.parseAsync();
4196
- });
4197
- } catch (e) {
4198
- if (e instanceof CommanderError) {
4199
- exitCode = e.exitCode;
4200
- } else if (e instanceof CliError) {
4201
- console.error(colors12.red(e.message));
4202
- exitCode = 1;
4203
- } else {
4204
- console.error(colors12.red(`Unhandled error: ${e}`));
4205
- exitCode = 1;
4206
- }
4207
- }
4208
- if (program.args.includes("generate") && (program.args.includes("-w") || program.args.includes("--watch")) || [
4209
- "proxy",
4210
- "studio"
4211
- ].some((cmd) => program.args.includes(cmd))) {
4212
- return;
4213
- }
4214
- if (telemetry.isTracking) {
4215
- setTimeout(() => {
4216
- process.exit(exitCode);
4217
- }, 200);
4218
- } else {
4219
- process.exit(exitCode);
4220
- }
4221
- }
4222
- __name(main, "main");
4223
- main();
4224
- //# sourceMappingURL=index.js.map