@dbsp/cli 1.0.0
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/LICENSE +21 -0
- package/README.md +64 -0
- package/dist/chunk-AQC34IO5.js +107 -0
- package/dist/chunk-AQC34IO5.js.map +1 -0
- package/dist/chunk-U5DSGBS2.js +123 -0
- package/dist/chunk-U5DSGBS2.js.map +1 -0
- package/dist/chunk-UZEMCTNH.js +243 -0
- package/dist/chunk-UZEMCTNH.js.map +1 -0
- package/dist/chunk-ZSGVJFWG.js +2304 -0
- package/dist/chunk-ZSGVJFWG.js.map +1 -0
- package/dist/generators/schema-codegen.d.ts +39 -0
- package/dist/generators/schema-codegen.js +7 -0
- package/dist/generators/schema-codegen.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1138 -0
- package/dist/index.js.map +1 -0
- package/dist/repl/batch.d.ts +343 -0
- package/dist/repl/batch.js +407 -0
- package/dist/repl/batch.js.map +1 -0
- package/dist/repl-4OFERLKZ.js +1454 -0
- package/dist/repl-4OFERLKZ.js.map +1 -0
- package/dist/utils/schema-loader.d.ts +47 -0
- package/dist/utils/schema-loader.js +15 -0
- package/dist/utils/schema-loader.js.map +1 -0
- package/package.json +94 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Olivier Orabona
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# @dbsp/cli
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@dbsp/cli)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
CLI tools for `@dbsp` — interactive REPL, schema verification, DDL provisioning, and batch execution.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# As a dev dependency (recommended)
|
|
12
|
+
pnpm add -D @dbsp/cli
|
|
13
|
+
|
|
14
|
+
# Or globally
|
|
15
|
+
npm install -g @dbsp/cli
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Interactive REPL with NQL tab-completion
|
|
22
|
+
npx dbsp repl --schema ./dbsp.schema.ts --db postgres://user:pass@localhost/mydb
|
|
23
|
+
|
|
24
|
+
# Verify schema against a live database (drift detection)
|
|
25
|
+
npx dbsp verify --schema ./dbsp.schema.ts --db postgres://user:pass@localhost/mydb
|
|
26
|
+
|
|
27
|
+
# Push schema changes to the database
|
|
28
|
+
npx dbsp push --schema ./dbsp.schema.ts --db postgres://user:pass@localhost/mydb
|
|
29
|
+
|
|
30
|
+
# Generate DDL SQL for provisioning
|
|
31
|
+
npx dbsp generate ddl --schema ./dbsp.schema.ts -o ./generated
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Commands
|
|
35
|
+
|
|
36
|
+
| Command | Description |
|
|
37
|
+
|---------|-------------|
|
|
38
|
+
| `dbsp repl` | Interactive REPL with NQL syntax, tab-completion, and query history |
|
|
39
|
+
| `dbsp verify` | Compare schema against live database; exit code 1 on drift |
|
|
40
|
+
| `dbsp push` | Apply schema changes (DDL provisioning) with advisory lock |
|
|
41
|
+
| `dbsp migrate` | Generate and apply UP/DOWN migration files |
|
|
42
|
+
| `dbsp generate ddl` | Generate SQL CREATE TABLE statements for provisioning |
|
|
43
|
+
| `dbsp introspect` | Generate schema.ts from database introspection |
|
|
44
|
+
|
|
45
|
+
## Key features
|
|
46
|
+
|
|
47
|
+
- **REPL with completion** — Tab-complete table names, columns, NQL keywords, and relation paths
|
|
48
|
+
- **Query history** — Persistent history across sessions
|
|
49
|
+
- **Batch mode** — Use `repl --eval` for single queries or `repl --input` for batch files
|
|
50
|
+
- **DDL provisioning** — `push` computes schema diff and applies the minimum required DDL
|
|
51
|
+
- **Destructive-change safety** — Warns before dropping columns or tables; `--force` required
|
|
52
|
+
- **Drift detection** — `verify` compares live introspection against declared schema
|
|
53
|
+
- **Migration tracking** — `migrate` generates UP/DOWN migration files with advisory locks
|
|
54
|
+
- **JSON output** — `--json` flag on most commands for CI pipeline integration
|
|
55
|
+
|
|
56
|
+
## Documentation
|
|
57
|
+
|
|
58
|
+
- [Guides](https://oorabona.github.io/db-semantic-planner/guide/)
|
|
59
|
+
- [Schema versioning guide](https://oorabona.github.io/db-semantic-planner/guide/schema-versioning)
|
|
60
|
+
- [Batch values guide](https://oorabona.github.io/db-semantic-planner/guide/batch-values)
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
MIT
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// src/utils/schema-loader.ts
|
|
2
|
+
import { existsSync, realpathSync } from "fs";
|
|
3
|
+
import { resolve, sep } from "path";
|
|
4
|
+
import { pathToFileURL } from "url";
|
|
5
|
+
import { isValidSchema } from "@dbsp/types";
|
|
6
|
+
var DEFAULT_SCHEMA_FILES = [
|
|
7
|
+
"dbsp.schema.ts",
|
|
8
|
+
"dbsp.schema.js",
|
|
9
|
+
"schema.ts",
|
|
10
|
+
"schema.js"
|
|
11
|
+
];
|
|
12
|
+
var SchemaLoadError = class extends Error {
|
|
13
|
+
constructor(message) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = "SchemaLoadError";
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
function findSchemaFile(cwd) {
|
|
19
|
+
for (const file of DEFAULT_SCHEMA_FILES) {
|
|
20
|
+
const fullPath = resolve(cwd, file);
|
|
21
|
+
if (existsSync(fullPath)) {
|
|
22
|
+
return fullPath;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
async function loadSchema(schemaPath) {
|
|
28
|
+
const resolvedPath = resolve(schemaPath);
|
|
29
|
+
const cwd = resolve(process.cwd());
|
|
30
|
+
if (!resolvedPath.startsWith(cwd + sep)) {
|
|
31
|
+
throw new SchemaLoadError(
|
|
32
|
+
`Schema file must be inside the current working directory: ${resolvedPath}`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const realPath = realpathSync(resolvedPath);
|
|
37
|
+
const realCwd = realpathSync(cwd);
|
|
38
|
+
if (!realPath.startsWith(realCwd + sep)) {
|
|
39
|
+
throw new SchemaLoadError(
|
|
40
|
+
`Schema file must be inside the current working directory: ${resolvedPath}`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
} catch (e) {
|
|
44
|
+
if (e instanceof SchemaLoadError) {
|
|
45
|
+
throw e;
|
|
46
|
+
}
|
|
47
|
+
if (e.code !== "ENOENT") {
|
|
48
|
+
throw e;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!existsSync(resolvedPath)) {
|
|
52
|
+
throw new SchemaLoadError(`Schema file not found: ${resolvedPath}`);
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const fileUrl = pathToFileURL(resolvedPath).href;
|
|
56
|
+
const module = await import(fileUrl);
|
|
57
|
+
const schema = module.schema ?? module.default;
|
|
58
|
+
if (!schema) {
|
|
59
|
+
throw new SchemaLoadError(
|
|
60
|
+
`Schema file must export 'schema' or default export: ${resolvedPath}`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
if (!isValidSchema(schema)) {
|
|
64
|
+
throw new SchemaLoadError(
|
|
65
|
+
`Invalid schema format in ${resolvedPath}. Use schema() from @dbsp/core to create schemas.`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return schema;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (error instanceof SchemaLoadError) {
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
74
|
+
if (resolvedPath.endsWith(".ts") && message.includes("Cannot find module")) {
|
|
75
|
+
throw new SchemaLoadError(
|
|
76
|
+
`Failed to load TypeScript schema. Make sure 'tsx' is installed:
|
|
77
|
+
pnpm add -D tsx
|
|
78
|
+
|
|
79
|
+
Then run dbsp via tsx:
|
|
80
|
+
pnpm tsx node_modules/.bin/dbsp generate manifest
|
|
81
|
+
|
|
82
|
+
Original error: ${message}`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
throw new SchemaLoadError(`Failed to load schema: ${message}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async function loadSchemaFromCwd(cwd = process.cwd()) {
|
|
89
|
+
const schemaPath = findSchemaFile(cwd);
|
|
90
|
+
if (!schemaPath) {
|
|
91
|
+
throw new SchemaLoadError(
|
|
92
|
+
`No schema file found in ${cwd}.
|
|
93
|
+
Expected one of: ${DEFAULT_SCHEMA_FILES.join(", ")}`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
const schema = await loadSchema(schemaPath);
|
|
97
|
+
return { schema, path: schemaPath };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export {
|
|
101
|
+
DEFAULT_SCHEMA_FILES,
|
|
102
|
+
SchemaLoadError,
|
|
103
|
+
findSchemaFile,
|
|
104
|
+
loadSchema,
|
|
105
|
+
loadSchemaFromCwd
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=chunk-AQC34IO5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/schema-loader.ts"],"sourcesContent":["/**\n * Schema Loader Utility\n *\n * Loads dbsp.schema.ts files using tsx for TypeScript support.\n * Falls back to native import for .js files.\n *\n * NOTE: Similar code exists in packages/mcp-server/src/schema-loader.ts\n * The MCP version has additional security features (path traversal protection,\n * allowedRoots, error codes) that are required for network-exposed services.\n * This CLI version is simpler as it runs locally with user trust.\n * See ARCH-004 for analysis of this intentional duplication.\n */\n\nimport { existsSync, realpathSync } from 'node:fs';\nimport { resolve, sep } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { LoadedSchema } from '@dbsp/types';\nimport { isValidSchema } from '@dbsp/types';\n\nexport type { LoadedSchema } from '@dbsp/types';\n\n/**\n * Default schema file names to search for.\n */\nexport const DEFAULT_SCHEMA_FILES = [\n\t'dbsp.schema.ts',\n\t'dbsp.schema.js',\n\t'schema.ts',\n\t'schema.js',\n];\n\n/**\n * Error thrown when schema loading fails.\n */\nexport class SchemaLoadError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'SchemaLoadError';\n\t}\n}\n\n/**\n * Find the schema file in the given directory.\n */\nexport function findSchemaFile(cwd: string): string | null {\n\tfor (const file of DEFAULT_SCHEMA_FILES) {\n\t\tconst fullPath = resolve(cwd, file);\n\t\tif (existsSync(fullPath)) {\n\t\t\treturn fullPath;\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * Load a schema from a TypeScript or JavaScript file.\n *\n * ARCH-005: Only accepts new schema() format, not legacy defineSchema().\n * For TypeScript files, this uses tsx loader via dynamic import.\n */\nexport async function loadSchema(schemaPath: string): Promise<LoadedSchema> {\n\tconst resolvedPath = resolve(schemaPath);\n\n\t// SEC-8: Prevent path traversal — schema must be under cwd.\n\t// This check runs before existsSync so traversal attempts are caught\n\t// regardless of whether the file exists.\n\tconst cwd = resolve(process.cwd());\n\tif (!resolvedPath.startsWith(cwd + sep)) {\n\t\tthrow new SchemaLoadError(\n\t\t\t`Schema file must be inside the current working directory: ${resolvedPath}`,\n\t\t);\n\t}\n\n\t// SEC-8b: Symlink bypass — a symlink inside cwd can point outside cwd.\n\t// Resolve symlinks and re-check containment. Use try/catch because\n\t// realpathSync throws ENOENT for non-existent paths; fall through to the\n\t// existsSync check below in that case (non-existent file still blocked).\n\ttry {\n\t\tconst realPath = realpathSync(resolvedPath);\n\t\tconst realCwd = realpathSync(cwd);\n\t\tif (!realPath.startsWith(realCwd + sep)) {\n\t\t\tthrow new SchemaLoadError(\n\t\t\t\t`Schema file must be inside the current working directory: ${resolvedPath}`,\n\t\t\t);\n\t\t}\n\t} catch (e) {\n\t\tif (e instanceof SchemaLoadError) {\n\t\t\tthrow e;\n\t\t}\n\t\t// Re-throw non-ENOENT errors (EACCES, ELOOP, EPERM, EIO, etc.) so real\n\t\t// filesystem problems are not silently swallowed. Only ENOENT is expected\n\t\t// here — the file simply doesn't exist yet; existsSync below will handle it.\n\t\tif ((e as NodeJS.ErrnoException).code !== 'ENOENT') {\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\tif (!existsSync(resolvedPath)) {\n\t\tthrow new SchemaLoadError(`Schema file not found: ${resolvedPath}`);\n\t}\n\n\ttry {\n\t\t// For TypeScript files, we need tsx to be in the loader chain\n\t\t// This works when running via `tsx` or when tsx is registered\n\t\tconst fileUrl = pathToFileURL(resolvedPath).href;\n\t\tconst module = await import(fileUrl);\n\n\t\t// Look for schema export (named 'schema' or default)\n\t\tconst schema = module.schema ?? module.default;\n\n\t\tif (!schema) {\n\t\t\tthrow new SchemaLoadError(\n\t\t\t\t`Schema file must export 'schema' or default export: ${resolvedPath}`,\n\t\t\t);\n\t\t}\n\n\t\t// ARCH-005: Validate schema() format\n\t\tif (!isValidSchema(schema)) {\n\t\t\tthrow new SchemaLoadError(\n\t\t\t\t`Invalid schema format in ${resolvedPath}. ` +\n\t\t\t\t\t`Use schema() from @dbsp/core to create schemas.`,\n\t\t\t);\n\t\t}\n\n\t\treturn schema;\n\t} catch (error) {\n\t\tif (error instanceof SchemaLoadError) {\n\t\t\tthrow error;\n\t\t}\n\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\n\t\t// Provide helpful error for TypeScript files\n\t\tif (\n\t\t\tresolvedPath.endsWith('.ts') &&\n\t\t\tmessage.includes('Cannot find module')\n\t\t) {\n\t\t\tthrow new SchemaLoadError(\n\t\t\t\t`Failed to load TypeScript schema. Make sure 'tsx' is installed:\\n` +\n\t\t\t\t\t` pnpm add -D tsx\\n\\n` +\n\t\t\t\t\t`Then run dbsp via tsx:\\n` +\n\t\t\t\t\t` pnpm tsx node_modules/.bin/dbsp generate manifest\\n\\n` +\n\t\t\t\t\t`Original error: ${message}`,\n\t\t\t);\n\t\t}\n\n\t\tthrow new SchemaLoadError(`Failed to load schema: ${message}`);\n\t}\n}\n\n/**\n * Load schema from the current working directory.\n * Searches for default schema file names.\n */\nexport async function loadSchemaFromCwd(cwd: string = process.cwd()): Promise<{\n\tschema: LoadedSchema;\n\tpath: string;\n}> {\n\tconst schemaPath = findSchemaFile(cwd);\n\n\tif (!schemaPath) {\n\t\tthrow new SchemaLoadError(\n\t\t\t`No schema file found in ${cwd}.\\n` +\n\t\t\t\t`Expected one of: ${DEFAULT_SCHEMA_FILES.join(', ')}`,\n\t\t);\n\t}\n\n\tconst schema = await loadSchema(schemaPath);\n\treturn { schema, path: schemaPath };\n}\n"],"mappings":";AAaA,SAAS,YAAY,oBAAoB;AACzC,SAAS,SAAS,WAAW;AAC7B,SAAS,qBAAqB;AAE9B,SAAS,qBAAqB;AAOvB,IAAM,uBAAuB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD;AAKO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EAC1C,YAAY,SAAiB;AAC5B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACb;AACD;AAKO,SAAS,eAAe,KAA4B;AAC1D,aAAW,QAAQ,sBAAsB;AACxC,UAAM,WAAW,QAAQ,KAAK,IAAI;AAClC,QAAI,WAAW,QAAQ,GAAG;AACzB,aAAO;AAAA,IACR;AAAA,EACD;AACA,SAAO;AACR;AAQA,eAAsB,WAAW,YAA2C;AAC3E,QAAM,eAAe,QAAQ,UAAU;AAKvC,QAAM,MAAM,QAAQ,QAAQ,IAAI,CAAC;AACjC,MAAI,CAAC,aAAa,WAAW,MAAM,GAAG,GAAG;AACxC,UAAM,IAAI;AAAA,MACT,6DAA6D,YAAY;AAAA,IAC1E;AAAA,EACD;AAMA,MAAI;AACH,UAAM,WAAW,aAAa,YAAY;AAC1C,UAAM,UAAU,aAAa,GAAG;AAChC,QAAI,CAAC,SAAS,WAAW,UAAU,GAAG,GAAG;AACxC,YAAM,IAAI;AAAA,QACT,6DAA6D,YAAY;AAAA,MAC1E;AAAA,IACD;AAAA,EACD,SAAS,GAAG;AACX,QAAI,aAAa,iBAAiB;AACjC,YAAM;AAAA,IACP;AAIA,QAAK,EAA4B,SAAS,UAAU;AACnD,YAAM;AAAA,IACP;AAAA,EACD;AAEA,MAAI,CAAC,WAAW,YAAY,GAAG;AAC9B,UAAM,IAAI,gBAAgB,0BAA0B,YAAY,EAAE;AAAA,EACnE;AAEA,MAAI;AAGH,UAAM,UAAU,cAAc,YAAY,EAAE;AAC5C,UAAM,SAAS,MAAM,OAAO;AAG5B,UAAM,SAAS,OAAO,UAAU,OAAO;AAEvC,QAAI,CAAC,QAAQ;AACZ,YAAM,IAAI;AAAA,QACT,uDAAuD,YAAY;AAAA,MACpE;AAAA,IACD;AAGA,QAAI,CAAC,cAAc,MAAM,GAAG;AAC3B,YAAM,IAAI;AAAA,QACT,4BAA4B,YAAY;AAAA,MAEzC;AAAA,IACD;AAEA,WAAO;AAAA,EACR,SAAS,OAAO;AACf,QAAI,iBAAiB,iBAAiB;AACrC,YAAM;AAAA,IACP;AAEA,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAGrE,QACC,aAAa,SAAS,KAAK,KAC3B,QAAQ,SAAS,oBAAoB,GACpC;AACD,YAAM,IAAI;AAAA,QACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAIoB,OAAO;AAAA,MAC5B;AAAA,IACD;AAEA,UAAM,IAAI,gBAAgB,0BAA0B,OAAO,EAAE;AAAA,EAC9D;AACD;AAMA,eAAsB,kBAAkB,MAAc,QAAQ,IAAI,GAG/D;AACF,QAAM,aAAa,eAAe,GAAG;AAErC,MAAI,CAAC,YAAY;AAChB,UAAM,IAAI;AAAA,MACT,2BAA2B,GAAG;AAAA,mBACT,qBAAqB,KAAK,IAAI,CAAC;AAAA,IACrD;AAAA,EACD;AAEA,QAAM,SAAS,MAAM,WAAW,UAAU;AAC1C,SAAO,EAAE,QAAQ,MAAM,WAAW;AACnC;","names":[]}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
var DEFAULT_TABLE_CONFIG = {
|
|
6
|
+
borderStyle: "all",
|
|
7
|
+
overflow: "wrap",
|
|
8
|
+
headerFormatter: "capitalCase",
|
|
9
|
+
padding: 1
|
|
10
|
+
};
|
|
11
|
+
var DEFAULT_CONFIG = {
|
|
12
|
+
table: DEFAULT_TABLE_CONFIG
|
|
13
|
+
};
|
|
14
|
+
function getDefaultConfigDir() {
|
|
15
|
+
return path.join(os.homedir(), ".dbsp");
|
|
16
|
+
}
|
|
17
|
+
function getDefaultConfigPath() {
|
|
18
|
+
return path.join(getDefaultConfigDir(), "config.json");
|
|
19
|
+
}
|
|
20
|
+
var ConfigManager = class {
|
|
21
|
+
config = structuredClone(DEFAULT_CONFIG);
|
|
22
|
+
configPath = getDefaultConfigPath();
|
|
23
|
+
loaded = false;
|
|
24
|
+
/** Set custom config path */
|
|
25
|
+
setConfigPath(configPath) {
|
|
26
|
+
this.configPath = configPath;
|
|
27
|
+
this.loaded = false;
|
|
28
|
+
}
|
|
29
|
+
/** Get current config path */
|
|
30
|
+
getConfigPath() {
|
|
31
|
+
return this.configPath;
|
|
32
|
+
}
|
|
33
|
+
/** Load configuration from file */
|
|
34
|
+
load() {
|
|
35
|
+
if (this.loaded) return this.config;
|
|
36
|
+
try {
|
|
37
|
+
if (fs.existsSync(this.configPath)) {
|
|
38
|
+
const content = fs.readFileSync(this.configPath, "utf-8");
|
|
39
|
+
const parsed = JSON.parse(content);
|
|
40
|
+
this.config = {
|
|
41
|
+
table: {
|
|
42
|
+
...DEFAULT_TABLE_CONFIG,
|
|
43
|
+
...parsed.table ?? {}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
this.config = structuredClone(DEFAULT_CONFIG);
|
|
49
|
+
}
|
|
50
|
+
this.loaded = true;
|
|
51
|
+
return this.config;
|
|
52
|
+
}
|
|
53
|
+
/** Save configuration to file */
|
|
54
|
+
save() {
|
|
55
|
+
try {
|
|
56
|
+
const dir = path.dirname(this.configPath);
|
|
57
|
+
if (!fs.existsSync(dir)) {
|
|
58
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
59
|
+
}
|
|
60
|
+
fs.writeFileSync(
|
|
61
|
+
this.configPath,
|
|
62
|
+
JSON.stringify(this.config, null, 2),
|
|
63
|
+
"utf-8"
|
|
64
|
+
);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error(`Failed to save config: ${error}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** Get current configuration */
|
|
70
|
+
get() {
|
|
71
|
+
if (!this.loaded) this.load();
|
|
72
|
+
return this.config;
|
|
73
|
+
}
|
|
74
|
+
/** Get table configuration */
|
|
75
|
+
getTable() {
|
|
76
|
+
return this.get().table;
|
|
77
|
+
}
|
|
78
|
+
/** Update table configuration */
|
|
79
|
+
updateTable(updates) {
|
|
80
|
+
this.config.table = { ...this.config.table, ...updates };
|
|
81
|
+
this.save();
|
|
82
|
+
}
|
|
83
|
+
/** Reset table configuration to defaults */
|
|
84
|
+
resetTable() {
|
|
85
|
+
this.config.table = structuredClone(DEFAULT_TABLE_CONFIG);
|
|
86
|
+
this.save();
|
|
87
|
+
}
|
|
88
|
+
/** Reset all configuration to defaults */
|
|
89
|
+
reset() {
|
|
90
|
+
this.config = structuredClone(DEFAULT_CONFIG);
|
|
91
|
+
this.save();
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
var config = new ConfigManager();
|
|
95
|
+
var TABLE_OPTIONS = {
|
|
96
|
+
borderStyle: [
|
|
97
|
+
"all",
|
|
98
|
+
"outline",
|
|
99
|
+
"headers-only",
|
|
100
|
+
"vertical",
|
|
101
|
+
"horizontal",
|
|
102
|
+
"none"
|
|
103
|
+
],
|
|
104
|
+
overflow: [
|
|
105
|
+
"wrap",
|
|
106
|
+
"truncate",
|
|
107
|
+
"truncate-middle",
|
|
108
|
+
"truncate-start",
|
|
109
|
+
"truncate-end"
|
|
110
|
+
],
|
|
111
|
+
headerFormatter: ["capitalCase", "none", "snakeCase", "camelCase"],
|
|
112
|
+
padding: [0, 1, 2, 3, 4]
|
|
113
|
+
};
|
|
114
|
+
function isValidTableOption(option, value) {
|
|
115
|
+
return TABLE_OPTIONS[option].includes(value);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export {
|
|
119
|
+
config,
|
|
120
|
+
TABLE_OPTIONS,
|
|
121
|
+
isValidTableOption
|
|
122
|
+
};
|
|
123
|
+
//# sourceMappingURL=chunk-U5DSGBS2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts"],"sourcesContent":["/**\n * CLI Configuration Module\n *\n * Handles persistent configuration for the REPL, including table display settings.\n * Default config path: ~/.dbsp/config.json\n * Override with -c flag.\n */\n\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\n\n/** Table border style options */\nexport type BorderStyle =\n\t| 'all'\n\t| 'outline'\n\t| 'headers-only'\n\t| 'vertical'\n\t| 'horizontal'\n\t| 'none';\n\n/** Table overflow handling options */\nexport type OverflowStyle =\n\t| 'wrap'\n\t| 'truncate'\n\t| 'truncate-middle'\n\t| 'truncate-start'\n\t| 'truncate-end';\n\n/** Table header formatting options */\nexport type HeaderFormatter =\n\t| 'capitalCase'\n\t| 'none'\n\t| 'snakeCase'\n\t| 'camelCase';\n\n/** Table display configuration */\nexport interface TableConfig {\n\tborderStyle: BorderStyle;\n\toverflow: OverflowStyle;\n\theaderFormatter: HeaderFormatter;\n\tpadding: number;\n}\n\n/** Full CLI configuration */\nexport interface CliConfig {\n\ttable: TableConfig;\n}\n\n/** Default table configuration */\nconst DEFAULT_TABLE_CONFIG: TableConfig = {\n\tborderStyle: 'all',\n\toverflow: 'wrap',\n\theaderFormatter: 'capitalCase',\n\tpadding: 1,\n};\n\n/** Default CLI configuration */\nconst DEFAULT_CONFIG: CliConfig = {\n\ttable: DEFAULT_TABLE_CONFIG,\n};\n\n/** Get default config directory path */\nfunction getDefaultConfigDir(): string {\n\treturn path.join(os.homedir(), '.dbsp');\n}\n\n/** Get default config file path */\nfunction getDefaultConfigPath(): string {\n\treturn path.join(getDefaultConfigDir(), 'config.json');\n}\n\n/** Configuration manager singleton */\nclass ConfigManager {\n\tprivate config: CliConfig = structuredClone(DEFAULT_CONFIG);\n\tprivate configPath: string = getDefaultConfigPath();\n\tprivate loaded = false;\n\n\t/** Set custom config path */\n\tsetConfigPath(configPath: string): void {\n\t\tthis.configPath = configPath;\n\t\tthis.loaded = false;\n\t}\n\n\t/** Get current config path */\n\tgetConfigPath(): string {\n\t\treturn this.configPath;\n\t}\n\n\t/** Load configuration from file */\n\tload(): CliConfig {\n\t\tif (this.loaded) return this.config;\n\n\t\ttry {\n\t\t\tif (fs.existsSync(this.configPath)) {\n\t\t\t\tconst content = fs.readFileSync(this.configPath, 'utf-8');\n\t\t\t\tconst parsed = JSON.parse(content) as Partial<CliConfig>;\n\t\t\t\tthis.config = {\n\t\t\t\t\ttable: {\n\t\t\t\t\t\t...DEFAULT_TABLE_CONFIG,\n\t\t\t\t\t\t...(parsed.table ?? {}),\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t} catch {\n\t\t\t// If config file is invalid, use defaults\n\t\t\tthis.config = structuredClone(DEFAULT_CONFIG);\n\t\t}\n\n\t\tthis.loaded = true;\n\t\treturn this.config;\n\t}\n\n\t/** Save configuration to file */\n\tsave(): void {\n\t\ttry {\n\t\t\tconst dir = path.dirname(this.configPath);\n\t\t\tif (!fs.existsSync(dir)) {\n\t\t\t\tfs.mkdirSync(dir, { recursive: true });\n\t\t\t}\n\t\t\tfs.writeFileSync(\n\t\t\t\tthis.configPath,\n\t\t\t\tJSON.stringify(this.config, null, 2),\n\t\t\t\t'utf-8',\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Failed to save config: ${error}`);\n\t\t}\n\t}\n\n\t/** Get current configuration */\n\tget(): CliConfig {\n\t\tif (!this.loaded) this.load();\n\t\treturn this.config;\n\t}\n\n\t/** Get table configuration */\n\tgetTable(): TableConfig {\n\t\treturn this.get().table;\n\t}\n\n\t/** Update table configuration */\n\tupdateTable(updates: Partial<TableConfig>): void {\n\t\tthis.config.table = { ...this.config.table, ...updates };\n\t\tthis.save();\n\t}\n\n\t/** Reset table configuration to defaults */\n\tresetTable(): void {\n\t\tthis.config.table = structuredClone(DEFAULT_TABLE_CONFIG);\n\t\tthis.save();\n\t}\n\n\t/** Reset all configuration to defaults */\n\treset(): void {\n\t\tthis.config = structuredClone(DEFAULT_CONFIG);\n\t\tthis.save();\n\t}\n}\n\n/** Global config manager instance */\nexport const config = new ConfigManager();\n\n/** Valid values for each table option (for validation) */\nexport const TABLE_OPTIONS = {\n\tborderStyle: [\n\t\t'all',\n\t\t'outline',\n\t\t'headers-only',\n\t\t'vertical',\n\t\t'horizontal',\n\t\t'none',\n\t] as const,\n\toverflow: [\n\t\t'wrap',\n\t\t'truncate',\n\t\t'truncate-middle',\n\t\t'truncate-start',\n\t\t'truncate-end',\n\t] as const,\n\theaderFormatter: ['capitalCase', 'none', 'snakeCase', 'camelCase'] as const,\n\tpadding: [0, 1, 2, 3, 4] as const,\n};\n\n/** Check if a value is valid for a given option */\nexport function isValidTableOption<K extends keyof typeof TABLE_OPTIONS>(\n\toption: K,\n\tvalue: unknown,\n): value is (typeof TABLE_OPTIONS)[K][number] {\n\treturn TABLE_OPTIONS[option].includes(value as never);\n}\n"],"mappings":";AAQA,YAAY,QAAQ;AACpB,YAAY,QAAQ;AACpB,YAAY,UAAU;AAwCtB,IAAM,uBAAoC;AAAA,EACzC,aAAa;AAAA,EACb,UAAU;AAAA,EACV,iBAAiB;AAAA,EACjB,SAAS;AACV;AAGA,IAAM,iBAA4B;AAAA,EACjC,OAAO;AACR;AAGA,SAAS,sBAA8B;AACtC,SAAY,UAAQ,WAAQ,GAAG,OAAO;AACvC;AAGA,SAAS,uBAA+B;AACvC,SAAY,UAAK,oBAAoB,GAAG,aAAa;AACtD;AAGA,IAAM,gBAAN,MAAoB;AAAA,EACX,SAAoB,gBAAgB,cAAc;AAAA,EAClD,aAAqB,qBAAqB;AAAA,EAC1C,SAAS;AAAA;AAAA,EAGjB,cAAc,YAA0B;AACvC,SAAK,aAAa;AAClB,SAAK,SAAS;AAAA,EACf;AAAA;AAAA,EAGA,gBAAwB;AACvB,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,OAAkB;AACjB,QAAI,KAAK,OAAQ,QAAO,KAAK;AAE7B,QAAI;AACH,UAAO,cAAW,KAAK,UAAU,GAAG;AACnC,cAAM,UAAa,gBAAa,KAAK,YAAY,OAAO;AACxD,cAAM,SAAS,KAAK,MAAM,OAAO;AACjC,aAAK,SAAS;AAAA,UACb,OAAO;AAAA,YACN,GAAG;AAAA,YACH,GAAI,OAAO,SAAS,CAAC;AAAA,UACtB;AAAA,QACD;AAAA,MACD;AAAA,IACD,QAAQ;AAEP,WAAK,SAAS,gBAAgB,cAAc;AAAA,IAC7C;AAEA,SAAK,SAAS;AACd,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,OAAa;AACZ,QAAI;AACH,YAAM,MAAW,aAAQ,KAAK,UAAU;AACxC,UAAI,CAAI,cAAW,GAAG,GAAG;AACxB,QAAG,aAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,MACtC;AACA,MAAG;AAAA,QACF,KAAK;AAAA,QACL,KAAK,UAAU,KAAK,QAAQ,MAAM,CAAC;AAAA,QACnC;AAAA,MACD;AAAA,IACD,SAAS,OAAO;AACf,cAAQ,MAAM,0BAA0B,KAAK,EAAE;AAAA,IAChD;AAAA,EACD;AAAA;AAAA,EAGA,MAAiB;AAChB,QAAI,CAAC,KAAK,OAAQ,MAAK,KAAK;AAC5B,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,WAAwB;AACvB,WAAO,KAAK,IAAI,EAAE;AAAA,EACnB;AAAA;AAAA,EAGA,YAAY,SAAqC;AAChD,SAAK,OAAO,QAAQ,EAAE,GAAG,KAAK,OAAO,OAAO,GAAG,QAAQ;AACvD,SAAK,KAAK;AAAA,EACX;AAAA;AAAA,EAGA,aAAmB;AAClB,SAAK,OAAO,QAAQ,gBAAgB,oBAAoB;AACxD,SAAK,KAAK;AAAA,EACX;AAAA;AAAA,EAGA,QAAc;AACb,SAAK,SAAS,gBAAgB,cAAc;AAC5C,SAAK,KAAK;AAAA,EACX;AACD;AAGO,IAAM,SAAS,IAAI,cAAc;AAGjC,IAAM,gBAAgB;AAAA,EAC5B,aAAa;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AAAA,EACA,UAAU;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AAAA,EACA,iBAAiB,CAAC,eAAe,QAAQ,aAAa,WAAW;AAAA,EACjE,SAAS,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC;AACxB;AAGO,SAAS,mBACf,QACA,OAC6C;AAC7C,SAAO,cAAc,MAAM,EAAE,SAAS,KAAc;AACrD;","names":[]}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
// src/utils/db-utils.ts
|
|
2
|
+
async function createDbConnection(connectionUrl) {
|
|
3
|
+
let pg;
|
|
4
|
+
try {
|
|
5
|
+
const mod = await import("pg");
|
|
6
|
+
pg = mod.default;
|
|
7
|
+
} catch {
|
|
8
|
+
throw new Error(
|
|
9
|
+
"pg is required for this command. Install it with: pnpm add pg"
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
const pool = new pg.Pool({
|
|
13
|
+
connectionString: connectionUrl
|
|
14
|
+
});
|
|
15
|
+
return { pool };
|
|
16
|
+
}
|
|
17
|
+
function redactDbUrl(url) {
|
|
18
|
+
try {
|
|
19
|
+
const u = new URL(url);
|
|
20
|
+
if (u.password) {
|
|
21
|
+
u.password = "***";
|
|
22
|
+
}
|
|
23
|
+
return u.toString();
|
|
24
|
+
} catch {
|
|
25
|
+
return url.replace(/:[^:@]+@/, ":***@");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/generators/schema-codegen.ts
|
|
30
|
+
function snakeToCamelCase(name) {
|
|
31
|
+
return name.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
32
|
+
}
|
|
33
|
+
function singleQuoteEscape(s) {
|
|
34
|
+
return `'${s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t")}'`;
|
|
35
|
+
}
|
|
36
|
+
function quoteKey(name) {
|
|
37
|
+
if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name)) {
|
|
38
|
+
return name;
|
|
39
|
+
}
|
|
40
|
+
return singleQuoteEscape(name);
|
|
41
|
+
}
|
|
42
|
+
function emitDefault(d) {
|
|
43
|
+
if (d === null || d === void 0) return "null";
|
|
44
|
+
if (typeof d === "number" || typeof d === "boolean") return String(d);
|
|
45
|
+
if (typeof d === "string") return singleQuoteEscape(d);
|
|
46
|
+
if (typeof d === "object" && "sql" in d && typeof d.sql === "string") {
|
|
47
|
+
return `{ sql: ${singleQuoteEscape(d.sql)} }`;
|
|
48
|
+
}
|
|
49
|
+
throw new Error(
|
|
50
|
+
`[schema-codegen] Unrecognized default shape: ${JSON.stringify(d)}`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
function generateColumnCode(column, isPrimaryKey, fkInfo, options) {
|
|
54
|
+
if (fkInfo) {
|
|
55
|
+
return generateRefCode(column, fkInfo, isPrimaryKey, options);
|
|
56
|
+
}
|
|
57
|
+
const canUseShortForm = !isPrimaryKey && !column.nullable && column.default === void 0 && !column.unique;
|
|
58
|
+
if (canUseShortForm) {
|
|
59
|
+
let code2 = `'${column.type}'`;
|
|
60
|
+
if (options.includeDbTypeComments && column.originalDbType) {
|
|
61
|
+
code2 += ` /* from: ${column.originalDbType} */`;
|
|
62
|
+
}
|
|
63
|
+
return code2;
|
|
64
|
+
}
|
|
65
|
+
const props = [];
|
|
66
|
+
props.push(`type: '${column.type}'`);
|
|
67
|
+
if (isPrimaryKey) {
|
|
68
|
+
props.push("primaryKey: true");
|
|
69
|
+
}
|
|
70
|
+
if (column.nullable) {
|
|
71
|
+
props.push("nullable: true");
|
|
72
|
+
}
|
|
73
|
+
if (column.default !== void 0) {
|
|
74
|
+
props.push(`default: ${emitDefault(column.default)}`);
|
|
75
|
+
}
|
|
76
|
+
if (column.unique) {
|
|
77
|
+
props.push("unique: true");
|
|
78
|
+
}
|
|
79
|
+
let code = `{ ${props.join(", ")} }`;
|
|
80
|
+
if (options.includeDbTypeComments && column.originalDbType) {
|
|
81
|
+
code += ` /* from: ${column.originalDbType} */`;
|
|
82
|
+
}
|
|
83
|
+
return code;
|
|
84
|
+
}
|
|
85
|
+
function generateRefCode(column, fkInfo, isPrimaryKey, options) {
|
|
86
|
+
const refOptions = [];
|
|
87
|
+
if (fkInfo.column) {
|
|
88
|
+
const refColName = options.dbCasing === "snake_case" ? snakeToCamelCase(fkInfo.column) : fkInfo.column;
|
|
89
|
+
refOptions.push(`references: [${singleQuoteEscape(refColName)}]`);
|
|
90
|
+
}
|
|
91
|
+
if (isPrimaryKey) {
|
|
92
|
+
refOptions.push("isPrimaryKey: true");
|
|
93
|
+
}
|
|
94
|
+
if (fkInfo.nullable || column.nullable) {
|
|
95
|
+
refOptions.push("nullable: true");
|
|
96
|
+
}
|
|
97
|
+
if (fkInfo.unique || column.unique) {
|
|
98
|
+
refOptions.push("unique: true");
|
|
99
|
+
}
|
|
100
|
+
if (fkInfo.onDelete && fkInfo.onDelete !== "NO ACTION") {
|
|
101
|
+
refOptions.push(`onDelete: ${singleQuoteEscape(fkInfo.onDelete)}`);
|
|
102
|
+
}
|
|
103
|
+
if (fkInfo.onUpdate && fkInfo.onUpdate !== "NO ACTION") {
|
|
104
|
+
refOptions.push(`onUpdate: ${singleQuoteEscape(fkInfo.onUpdate)}`);
|
|
105
|
+
}
|
|
106
|
+
if (fkInfo.isSelfRef) {
|
|
107
|
+
const baseName = column.name.replace(/_?[iI]d$/, "");
|
|
108
|
+
refOptions.push(
|
|
109
|
+
`roles: { parent: '${baseName}', children: '${baseName === "parent" ? "children" : `${baseName}s`}' }`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
const refTable = options.dbCasing === "snake_case" ? snakeToCamelCase(fkInfo.table) : fkInfo.table;
|
|
113
|
+
let code;
|
|
114
|
+
if (refOptions.length === 0) {
|
|
115
|
+
code = `ref('${refTable}')`;
|
|
116
|
+
} else {
|
|
117
|
+
code = `ref('${refTable}', { ${refOptions.join(", ")} })`;
|
|
118
|
+
}
|
|
119
|
+
if (options.includeDbTypeComments && column.originalDbType) {
|
|
120
|
+
code += ` /* from: ${column.originalDbType} */`;
|
|
121
|
+
}
|
|
122
|
+
return code;
|
|
123
|
+
}
|
|
124
|
+
function generateTableCode(table, options) {
|
|
125
|
+
const fkMap = /* @__PURE__ */ new Map();
|
|
126
|
+
for (const fk of table.foreignKeys) {
|
|
127
|
+
const localCol = fk.columns[0];
|
|
128
|
+
const refCol = fk.references.columns[0];
|
|
129
|
+
if (fk.columns.length === 1 && fk.references.columns.length === 1 && localCol && refCol) {
|
|
130
|
+
const colDef = table.columns.find(
|
|
131
|
+
(c) => c.name === localCol || c.name === snakeToCamelCase(localCol)
|
|
132
|
+
);
|
|
133
|
+
const entry = {
|
|
134
|
+
table: fk.references.table,
|
|
135
|
+
isSelfRef: fk.references.table === table.name
|
|
136
|
+
};
|
|
137
|
+
if (refCol !== "id") {
|
|
138
|
+
entry.column = refCol;
|
|
139
|
+
}
|
|
140
|
+
if (colDef?.nullable) {
|
|
141
|
+
entry.nullable = true;
|
|
142
|
+
}
|
|
143
|
+
if (colDef?.unique) {
|
|
144
|
+
entry.unique = true;
|
|
145
|
+
}
|
|
146
|
+
if (fk.onDelete && fk.onDelete !== "NO ACTION") {
|
|
147
|
+
entry.onDelete = fk.onDelete;
|
|
148
|
+
}
|
|
149
|
+
if (fk.onUpdate && fk.onUpdate !== "NO ACTION") {
|
|
150
|
+
entry.onUpdate = fk.onUpdate;
|
|
151
|
+
}
|
|
152
|
+
fkMap.set(localCol, entry);
|
|
153
|
+
const camelCol = snakeToCamelCase(localCol);
|
|
154
|
+
if (camelCol !== localCol) {
|
|
155
|
+
fkMap.set(camelCol, entry);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const shouldCamelCase = options.dbCasing === "snake_case";
|
|
160
|
+
const columnLines = table.columns.map((col) => {
|
|
161
|
+
const isPrimaryKey = table.primaryKey ? typeof table.primaryKey === "string" ? col.name === table.primaryKey : table.primaryKey.includes(col.name) : false;
|
|
162
|
+
const fkInfo = fkMap.get(col.name);
|
|
163
|
+
const code = generateColumnCode(col, isPrimaryKey, fkInfo, options);
|
|
164
|
+
const rawColName = shouldCamelCase ? snakeToCamelCase(col.name) : col.name;
|
|
165
|
+
const colKey = quoteKey(rawColName);
|
|
166
|
+
return ` ${colKey}: ${code}`;
|
|
167
|
+
});
|
|
168
|
+
const rawTableName = shouldCamelCase ? snakeToCamelCase(table.name) : table.name;
|
|
169
|
+
const tableKey = quoteKey(rawTableName);
|
|
170
|
+
return ` ${tableKey}: {
|
|
171
|
+
${columnLines.join(",\n")},
|
|
172
|
+
}`;
|
|
173
|
+
}
|
|
174
|
+
function generateSchemaFile(model, options = {}) {
|
|
175
|
+
const lines = [];
|
|
176
|
+
lines.push("/**");
|
|
177
|
+
lines.push(" * Auto-generated by: dbsp introspect");
|
|
178
|
+
if (options.sourceUrl) {
|
|
179
|
+
const redactedUrl = redactDbUrl(options.sourceUrl);
|
|
180
|
+
lines.push(` * Source: ${redactedUrl}`);
|
|
181
|
+
}
|
|
182
|
+
if (options.introspectedAt) {
|
|
183
|
+
lines.push(` * Generated: ${options.introspectedAt.toISOString()}`);
|
|
184
|
+
}
|
|
185
|
+
lines.push(" *");
|
|
186
|
+
if (options.warnings && options.warnings.length > 0) {
|
|
187
|
+
lines.push(" * \u26A0\uFE0F Warnings:");
|
|
188
|
+
for (const warning of options.warnings) {
|
|
189
|
+
lines.push(` * - ${warning}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
lines.push(" * Review before using in production.");
|
|
193
|
+
lines.push(" */");
|
|
194
|
+
lines.push("");
|
|
195
|
+
const hasForeignKeys = Array.from(model.tables.values()).some(
|
|
196
|
+
(table) => table.foreignKeys.length > 0
|
|
197
|
+
);
|
|
198
|
+
const coreImports = ["schema"];
|
|
199
|
+
if (hasForeignKeys) {
|
|
200
|
+
coreImports.push("ref");
|
|
201
|
+
}
|
|
202
|
+
lines.push(`import { ${coreImports.join(", ")} } from '@dbsp/core';`);
|
|
203
|
+
if (options.dbCasing && options.dbCasing !== "preserve") {
|
|
204
|
+
lines.push("import { createPgsqlAdapter } from '@dbsp/adapter-pgsql';");
|
|
205
|
+
}
|
|
206
|
+
lines.push("");
|
|
207
|
+
lines.push("export const dbSchema = schema({");
|
|
208
|
+
const tables = Array.from(model.tables.values());
|
|
209
|
+
const tableLines = tables.map((table) => generateTableCode(table, options));
|
|
210
|
+
lines.push(tableLines.join(",\n\n"));
|
|
211
|
+
lines.push("});");
|
|
212
|
+
lines.push("");
|
|
213
|
+
if (options.dbCasing && options.dbCasing !== "preserve") {
|
|
214
|
+
lines.push("/**");
|
|
215
|
+
lines.push(
|
|
216
|
+
` * Usage: columns above are camelCase; the database uses ${options.dbCasing}.`
|
|
217
|
+
);
|
|
218
|
+
lines.push(
|
|
219
|
+
" * Pass dbCasing to the adapter so it maps camelCase \u2194 snake_case automatically."
|
|
220
|
+
);
|
|
221
|
+
lines.push(" *");
|
|
222
|
+
lines.push(" * @example");
|
|
223
|
+
lines.push(" * ```typescript");
|
|
224
|
+
lines.push(" * const orm = createOrm({");
|
|
225
|
+
lines.push(" * model: dbSchema.model,");
|
|
226
|
+
lines.push(
|
|
227
|
+
` * adapter: createPgsqlAdapter(pool, { dbCasing: '${options.dbCasing}' }),`
|
|
228
|
+
);
|
|
229
|
+
lines.push(" * });");
|
|
230
|
+
lines.push(" * ```");
|
|
231
|
+
lines.push(" */");
|
|
232
|
+
lines.push(`export const dbCasing = '${options.dbCasing}' as const;`);
|
|
233
|
+
lines.push("");
|
|
234
|
+
}
|
|
235
|
+
return lines.join("\n");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export {
|
|
239
|
+
createDbConnection,
|
|
240
|
+
redactDbUrl,
|
|
241
|
+
generateSchemaFile
|
|
242
|
+
};
|
|
243
|
+
//# sourceMappingURL=chunk-UZEMCTNH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/db-utils.ts","../src/generators/schema-codegen.ts"],"sourcesContent":["/**\n * Shared database utilities for CLI commands.\n *\n * E09: Extracted from verify.ts and introspect.ts to DRY up code.\n * E09b: Shared URL redaction for secure logging.\n */\n\n/**\n * Create a database connection pool.\n * pg is an optional peer dependency.\n *\n * @param connectionUrl - PostgreSQL connection URL\n * @returns Pool instance\n * @throws Error if pg is not installed\n */\nexport async function createDbConnection(connectionUrl: string) {\n\tlet pg: typeof import('pg').default;\n\ttry {\n\t\tconst mod = await import('pg');\n\t\tpg = mod.default;\n\t} catch {\n\t\tthrow new Error(\n\t\t\t'pg is required for this command. Install it with: pnpm add pg',\n\t\t);\n\t}\n\n\tconst pool = new pg.Pool({\n\t\tconnectionString: connectionUrl,\n\t});\n\treturn { pool };\n}\n\n/**\n * Redact password from a database connection URL for safe logging.\n *\n * @param url - Database connection URL (e.g., postgres://user:pass@host/db)\n * @returns URL with password replaced by ***\n *\n * @example\n * redactDbUrl('postgres://user:secret@localhost/mydb')\n * // => 'postgres://user:***@localhost/mydb'\n */\nexport function redactDbUrl(url: string): string {\n\ttry {\n\t\tconst u = new URL(url);\n\t\tif (u.password) {\n\t\t\tu.password = '***';\n\t\t}\n\t\treturn u.toString();\n\t} catch {\n\t\t// Fall back to regex for non-standard or malformed URLs\n\t\treturn url.replace(/:[^:@]+@/, ':***@');\n\t}\n}\n","/**\n * CLI-DDL: Schema Codegen Module\n *\n * Generates TypeScript schema files from IntrospectedModelIR.\n * Used by `dbsp introspect` to create dbsp.schema.ts from a database.\n */\n\nimport type { ModelIR, TableIR } from '@dbsp/core';\nimport { redactDbUrl } from '../utils/db-utils.js';\n\n/**\n * Convert a snake_case string to camelCase.\n * @example 'author_id' → 'authorId'\n */\nfunction snakeToCamelCase(name: string): string {\n\treturn name.replace(/_([a-z])/g, (_, letter: string) => letter.toUpperCase());\n}\n\n/**\n * Options for schema code generation.\n */\nexport interface SchemaCodegenOptions {\n\t/** Source database URL (for comment header) */\n\treadonly sourceUrl?: string;\n\t/** Include original DB types as comments */\n\treadonly includeDbTypeComments?: boolean;\n\t/** Warnings from introspection to include as comments */\n\treadonly warnings?: readonly string[];\n\t/** Timestamp of introspection */\n\treadonly introspectedAt?: Date;\n\t/**\n\t * Database column casing convention.\n\t * When set to 'snake_case', column names are converted to camelCase\n\t * and a `dbCasing: 'snake_case'` comment is added to the generated code.\n\t * @default 'preserve'\n\t */\n\treadonly dbCasing?: 'snake_case' | 'camelCase' | 'preserve';\n}\n\n/**\n * Serialize a column default value to valid TypeScript source code.\n *\n * CODEX-11/CODEX-14: String(value) produced \"[object Object]\" for SQL-expression\n * defaults like { sql: 'now()' }, and single-quote wrapping without escape broke\n * strings containing quotes, backslashes, or newlines.\n *\n * Rules:\n * - null/undefined → 'null'\n * - number | boolean → unquoted literal (String(value))\n * - string → singleQuoteEscape (escapes \\, ', \\n, \\r, \\t — produces single-quoted TS literal)\n * - { sql: '...' } shape → `{ sql: ${JSON.stringify(sqlExpr)} }`\n * - anything else → throws (unrecognized shape; prevents silent [object Object])\n */\n\n/**\n * Wrap a string value in single quotes for TypeScript source output,\n * escaping any single quotes and backslashes contained in the value.\n *\n * Examples:\n * 'hello' → \"'hello'\"\n * \"O'Brien\" → \"'O\\\\'Brien'\"\n * 'C:\\\\Users' → \"'C:\\\\\\\\Users'\"\n */\nfunction singleQuoteEscape(s: string): string {\n\treturn `'${s\n\t\t.replace(/\\\\/g, '\\\\\\\\')\n\t\t.replace(/'/g, \"\\\\'\")\n\t\t.replace(/\\n/g, '\\\\n')\n\t\t.replace(/\\r/g, '\\\\r')\n\t\t.replace(/\\t/g, '\\\\t')}'`;\n}\n\n/**\n * Return the key suitable for use as a TypeScript object key.\n * Valid JS identifiers can be used bare; anything else is single-quote-wrapped.\n * A valid JS identifier starts with a letter, underscore, or $, followed by\n * letters, digits, underscores, or $.\n */\nfunction quoteKey(name: string): string {\n\tif (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name)) {\n\t\treturn name;\n\t}\n\t// F3: Delegate to singleQuoteEscape() which also handles \\n, \\r, \\t.\n\t// The previous inline chain only escaped \\\\ and ' — identifiers containing\n\t// newlines, CR, or tabs would have produced invalid TypeScript output.\n\treturn singleQuoteEscape(name);\n}\n\nfunction emitDefault(d: unknown): string {\n\tif (d === null || d === undefined) return 'null';\n\tif (typeof d === 'number' || typeof d === 'boolean') return String(d);\n\tif (typeof d === 'string') return singleQuoteEscape(d);\n\tif (\n\t\ttypeof d === 'object' &&\n\t\t'sql' in d &&\n\t\ttypeof (d as { sql: unknown }).sql === 'string'\n\t) {\n\t\treturn `{ sql: ${singleQuoteEscape((d as { sql: string }).sql)} }`;\n\t}\n\tthrow new Error(\n\t\t`[schema-codegen] Unrecognized default shape: ${JSON.stringify(d)}`,\n\t);\n}\n\n/**\n * Generate column definition object code.\n */\nfunction generateColumnCode(\n\tcolumn: TableIR['columns'][number],\n\tisPrimaryKey: boolean,\n\tfkInfo:\n\t\t| {\n\t\t\t\ttable: string;\n\t\t\t\tcolumn?: string;\n\t\t\t\tnullable?: boolean;\n\t\t\t\tunique?: boolean;\n\t\t\t\tonDelete?: string;\n\t\t\t\tonUpdate?: string;\n\t\t\t\tisSelfRef?: boolean;\n\t\t }\n\t\t| undefined,\n\toptions: SchemaCodegenOptions,\n): string {\n\t// CODEX-13: FK + PK overlap — a column can be both FK and PK (e.g. shared-PK 1:1).\n\t// We must preserve isPrimaryKey even when fkInfo is present.\n\tif (fkInfo) {\n\t\treturn generateRefCode(column, fkInfo, isPrimaryKey, options);\n\t}\n\n\t// Check if we can use short form (just 'type' string)\n\tconst canUseShortForm =\n\t\t!isPrimaryKey &&\n\t\t!column.nullable &&\n\t\tcolumn.default === undefined &&\n\t\t!column.unique;\n\n\tif (canUseShortForm) {\n\t\t// Short form: 'type'\n\t\tlet code = `'${column.type}'`;\n\t\tif (options.includeDbTypeComments && column.originalDbType) {\n\t\t\tcode += ` /* from: ${column.originalDbType} */`;\n\t\t}\n\t\treturn code;\n\t}\n\n\t// Long form: { type, primaryKey?, nullable?, default?, unique? }\n\tconst props: string[] = [];\n\tprops.push(`type: '${column.type}'`);\n\n\tif (isPrimaryKey) {\n\t\tprops.push('primaryKey: true');\n\t}\n\n\tif (column.nullable) {\n\t\tprops.push('nullable: true');\n\t}\n\n\tif (column.default !== undefined) {\n\t\tprops.push(`default: ${emitDefault(column.default)}`);\n\t}\n\n\tif (column.unique) {\n\t\tprops.push('unique: true');\n\t}\n\n\tlet code = `{ ${props.join(', ')} }`;\n\n\tif (options.includeDbTypeComments && column.originalDbType) {\n\t\tcode += ` /* from: ${column.originalDbType} */`;\n\t}\n\n\treturn code;\n}\n\n/**\n * Generate ref() call for a foreign key column.\n * ARCH-005: Use ref() instead of references: { table, column }\n */\nfunction generateRefCode(\n\tcolumn: TableIR['columns'][number],\n\tfkInfo: {\n\t\ttable: string;\n\t\tcolumn?: string;\n\t\tnullable?: boolean;\n\t\tunique?: boolean;\n\t\tonDelete?: string;\n\t\tonUpdate?: string;\n\t\tisSelfRef?: boolean;\n\t},\n\tisPrimaryKey: boolean,\n\toptions: SchemaCodegenOptions,\n): string {\n\tconst refOptions: string[] = [];\n\n\t// C2: emit references option for non-PK FK target columns.\n\t// Now that buildRefColumn plumbs options.references into ModelIR,\n\t// emitting ref('table', { references: ['col'] }) round-trips correctly.\n\t// L1-followup-4: apply snake→camel transform to the target column name when\n\t// dbCasing='snake_case' so the generated references array round-trips correctly.\n\tif (fkInfo.column) {\n\t\tconst refColName =\n\t\t\toptions.dbCasing === 'snake_case'\n\t\t\t\t? snakeToCamelCase(fkInfo.column)\n\t\t\t\t: fkInfo.column;\n\t\trefOptions.push(`references: [${singleQuoteEscape(refColName)}]`);\n\t}\n\n\t// CODEX-13: FK column that is also PK — emit isPrimaryKey inside ref() options\n\tif (isPrimaryKey) {\n\t\trefOptions.push('isPrimaryKey: true');\n\t}\n\n\t// Nullable FK\n\tif (fkInfo.nullable || column.nullable) {\n\t\trefOptions.push('nullable: true');\n\t}\n\n\t// Unique FK → hasOne (1:1 relation)\n\tif (fkInfo.unique || column.unique) {\n\t\trefOptions.push('unique: true');\n\t}\n\n\t// onDelete action\n\tif (fkInfo.onDelete && fkInfo.onDelete !== 'NO ACTION') {\n\t\trefOptions.push(`onDelete: ${singleQuoteEscape(fkInfo.onDelete)}`);\n\t}\n\n\t// CODEX-12: onUpdate action\n\tif (fkInfo.onUpdate && fkInfo.onUpdate !== 'NO ACTION') {\n\t\trefOptions.push(`onUpdate: ${singleQuoteEscape(fkInfo.onUpdate)}`);\n\t}\n\n\t// Self-referential FK needs roles\n\tif (fkInfo.isSelfRef) {\n\t\t// Infer role names from column name\n\t\t// e.g., 'parentId' → parent: 'parent', children: 'children'\n\t\tconst baseName = column.name.replace(/_?[iI]d$/, '');\n\t\trefOptions.push(\n\t\t\t`roles: { parent: '${baseName}', children: '${baseName === 'parent' ? 'children' : `${baseName}s`}' }`,\n\t\t);\n\t}\n\n\t// Build the ref() call — table name converted if dbCasing applies\n\tconst refTable =\n\t\toptions.dbCasing === 'snake_case'\n\t\t\t? snakeToCamelCase(fkInfo.table)\n\t\t\t: fkInfo.table;\n\tlet code: string;\n\tif (refOptions.length === 0) {\n\t\tcode = `ref('${refTable}')`;\n\t} else {\n\t\tcode = `ref('${refTable}', { ${refOptions.join(', ')} })`;\n\t}\n\n\t// Add comment for original DB type if requested\n\tif (options.includeDbTypeComments && column.originalDbType) {\n\t\tcode += ` /* from: ${column.originalDbType} */`;\n\t}\n\n\treturn code;\n}\n\n/**\n * Generate table definition code.\n */\nfunction generateTableCode(\n\ttable: TableIR,\n\toptions: SchemaCodegenOptions,\n): string {\n\t// ARCH-005: Build FK info map with extended properties\n\tconst fkMap = new Map<\n\t\tstring,\n\t\t{\n\t\t\ttable: string;\n\t\t\tcolumn?: string;\n\t\t\tnullable?: boolean;\n\t\t\tunique?: boolean;\n\t\t\tonDelete?: string;\n\t\t\tonUpdate?: string;\n\t\t\tisSelfRef?: boolean;\n\t\t}\n\t>();\n\n\tfor (const fk of table.foreignKeys) {\n\t\tconst localCol = fk.columns[0];\n\t\tconst refCol = fk.references.columns[0];\n\t\tif (\n\t\t\tfk.columns.length === 1 &&\n\t\t\tfk.references.columns.length === 1 &&\n\t\t\tlocalCol &&\n\t\t\trefCol\n\t\t) {\n\t\t\t// Find the column to check nullable/unique\n\t\t\t// FK column names may be snake_case (from raw SQL) while column.name\n\t\t\t// is camelCase (when CamelCasePlugin is active)\n\t\t\tconst colDef = table.columns.find(\n\t\t\t\t(c) => c.name === localCol || c.name === snakeToCamelCase(localCol),\n\t\t\t);\n\n\t\t\tconst entry: {\n\t\t\t\ttable: string;\n\t\t\t\tcolumn?: string;\n\t\t\t\tnullable?: boolean;\n\t\t\t\tunique?: boolean;\n\t\t\t\tonDelete?: string;\n\t\t\t\tonUpdate?: string;\n\t\t\t\tisSelfRef?: boolean;\n\t\t\t} = {\n\t\t\t\ttable: fk.references.table,\n\t\t\t\tisSelfRef: fk.references.table === table.name,\n\t\t\t};\n\n\t\t\t// Only include column if not 'id' (the default)\n\t\t\tif (refCol !== 'id') {\n\t\t\t\tentry.column = refCol;\n\t\t\t}\n\n\t\t\t// Include nullable if true\n\t\t\tif (colDef?.nullable) {\n\t\t\t\tentry.nullable = true;\n\t\t\t}\n\n\t\t\t// Include unique if true\n\t\t\tif (colDef?.unique) {\n\t\t\t\tentry.unique = true;\n\t\t\t}\n\n\t\t\t// Include onDelete if not the default\n\t\t\tif (fk.onDelete && fk.onDelete !== 'NO ACTION') {\n\t\t\t\tentry.onDelete = fk.onDelete;\n\t\t\t}\n\n\t\t\t// CODEX-12: Include onUpdate if not the default\n\t\t\tif (fk.onUpdate && fk.onUpdate !== 'NO ACTION') {\n\t\t\t\tentry.onUpdate = fk.onUpdate;\n\t\t\t}\n\n\t\t\t// Store under both snake_case and camelCase keys so fkMap.get(col.name) works\n\t\t\t// regardless of naming convention\n\t\t\tfkMap.set(localCol, entry);\n\t\t\tconst camelCol = snakeToCamelCase(localCol);\n\t\t\tif (camelCol !== localCol) {\n\t\t\t\tfkMap.set(camelCol, entry);\n\t\t\t}\n\t\t}\n\t}\n\n\tconst shouldCamelCase = options.dbCasing === 'snake_case';\n\n\tconst columnLines = table.columns.map((col) => {\n\t\tconst isPrimaryKey = table.primaryKey\n\t\t\t? typeof table.primaryKey === 'string'\n\t\t\t\t? col.name === table.primaryKey\n\t\t\t\t: table.primaryKey.includes(col.name)\n\t\t\t: false;\n\t\tconst fkInfo = fkMap.get(col.name);\n\t\tconst code = generateColumnCode(col, isPrimaryKey, fkInfo, options);\n\t\tconst rawColName = shouldCamelCase ? snakeToCamelCase(col.name) : col.name;\n\t\t// C7: quote the key if it's not a valid JS identifier (e.g. contains hyphens)\n\t\tconst colKey = quoteKey(rawColName);\n\t\treturn `\\t\\t${colKey}: ${code}`;\n\t});\n\n\tconst rawTableName = shouldCamelCase\n\t\t? snakeToCamelCase(table.name)\n\t\t: table.name;\n\t// C7: quote the table key if it's not a valid JS identifier\n\tconst tableKey = quoteKey(rawTableName);\n\treturn `\\t${tableKey}: {\\n${columnLines.join(',\\n')},\\n\\t}`;\n}\n\n/**\n * Generate a TypeScript schema file from ModelIR.\n *\n * @param model - The ModelIR (or IntrospectedModelIR) to generate from\n * @param options - Code generation options\n * @returns TypeScript source code for a schema file\n */\nexport function generateSchemaFile(\n\tmodel: ModelIR,\n\toptions: SchemaCodegenOptions = {},\n): string {\n\tconst lines: string[] = [];\n\n\t// Header comment\n\tlines.push('/**');\n\tlines.push(' * Auto-generated by: dbsp introspect');\n\tif (options.sourceUrl) {\n\t\tconst redactedUrl = redactDbUrl(options.sourceUrl);\n\t\tlines.push(` * Source: ${redactedUrl}`);\n\t}\n\tif (options.introspectedAt) {\n\t\tlines.push(` * Generated: ${options.introspectedAt.toISOString()}`);\n\t}\n\tlines.push(' *');\n\tif (options.warnings && options.warnings.length > 0) {\n\t\tlines.push(' * ⚠️ Warnings:');\n\t\tfor (const warning of options.warnings) {\n\t\t\tlines.push(` * - ${warning}`);\n\t\t}\n\t}\n\tlines.push(' * Review before using in production.');\n\tlines.push(' */');\n\tlines.push('');\n\n\t// ARCH-005: Check if any table has FKs to determine imports\n\tconst hasForeignKeys = Array.from(model.tables.values()).some(\n\t\t(table) => table.foreignKeys.length > 0,\n\t);\n\n\t// Imports - ARCH-005: Use schema() + ref() instead of defineSchema()\n\tconst coreImports = ['schema'];\n\tif (hasForeignKeys) {\n\t\tcoreImports.push('ref');\n\t}\n\tlines.push(`import { ${coreImports.join(', ')} } from '@dbsp/core';`);\n\tif (options.dbCasing && options.dbCasing !== 'preserve') {\n\t\tlines.push(\"import { createPgsqlAdapter } from '@dbsp/adapter-pgsql';\");\n\t}\n\tlines.push('');\n\n\t// Schema definition - ARCH-005: Use schema() instead of defineSchema()\n\tlines.push('export const dbSchema = schema({');\n\n\t// Generate each table\n\tconst tables = Array.from(model.tables.values());\n\tconst tableLines = tables.map((table) => generateTableCode(table, options));\n\tlines.push(tableLines.join(',\\n\\n'));\n\n\tlines.push('});');\n\tlines.push('');\n\n\t// Usage hint with dbCasing\n\tif (options.dbCasing && options.dbCasing !== 'preserve') {\n\t\tlines.push('/**');\n\t\tlines.push(\n\t\t\t` * Usage: columns above are camelCase; the database uses ${options.dbCasing}.`,\n\t\t);\n\t\tlines.push(\n\t\t\t' * Pass dbCasing to the adapter so it maps camelCase ↔ snake_case automatically.',\n\t\t);\n\t\tlines.push(' *');\n\t\tlines.push(' * @example');\n\t\tlines.push(' * ```typescript');\n\t\tlines.push(' * const orm = createOrm({');\n\t\tlines.push(' * model: dbSchema.model,');\n\t\tlines.push(\n\t\t\t` * adapter: createPgsqlAdapter(pool, { dbCasing: '${options.dbCasing}' }),`,\n\t\t);\n\t\tlines.push(' * });');\n\t\tlines.push(' * ```');\n\t\tlines.push(' */');\n\t\tlines.push(`export const dbCasing = '${options.dbCasing}' as const;`);\n\t\tlines.push('');\n\t}\n\n\treturn lines.join('\\n');\n}\n"],"mappings":"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","names":["code"]}
|