zenstack-kit 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +313 -0
- package/dist/cli/app.d.ts +12 -0
- package/dist/cli/app.d.ts.map +1 -0
- package/dist/cli/app.js +253 -0
- package/dist/cli/commands.d.ts +70 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +308 -0
- package/dist/cli/index.d.ts +12 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +12 -0
- package/dist/cli/prompt-provider.d.ts +10 -0
- package/dist/cli/prompt-provider.d.ts.map +1 -0
- package/dist/cli/prompt-provider.js +41 -0
- package/dist/cli/prompts.d.ts +27 -0
- package/dist/cli/prompts.d.ts.map +1 -0
- package/dist/cli/prompts.js +133 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +240 -0
- package/dist/config/index.d.ts +96 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +48 -0
- package/dist/config/loader.d.ts +11 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +44 -0
- package/dist/config-loader.d.ts +6 -0
- package/dist/config-loader.d.ts.map +1 -0
- package/dist/config-loader.js +36 -0
- package/dist/config.d.ts +62 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +44 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/init-prompts.d.ts +13 -0
- package/dist/init-prompts.d.ts.map +1 -0
- package/dist/init-prompts.js +64 -0
- package/dist/introspect.d.ts +54 -0
- package/dist/introspect.d.ts.map +1 -0
- package/dist/introspect.js +75 -0
- package/dist/kysely-adapter.d.ts +49 -0
- package/dist/kysely-adapter.d.ts.map +1 -0
- package/dist/kysely-adapter.js +74 -0
- package/dist/migrate-apply.d.ts +18 -0
- package/dist/migrate-apply.d.ts.map +1 -0
- package/dist/migrate-apply.js +61 -0
- package/dist/migrate.d.ts +108 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +127 -0
- package/dist/migrations/apply.d.ts +18 -0
- package/dist/migrations/apply.d.ts.map +1 -0
- package/dist/migrations/apply.js +61 -0
- package/dist/migrations/diff.d.ts +161 -0
- package/dist/migrations/diff.d.ts.map +1 -0
- package/dist/migrations/diff.js +620 -0
- package/dist/migrations/prisma.d.ts +193 -0
- package/dist/migrations/prisma.d.ts.map +1 -0
- package/dist/migrations/prisma.js +929 -0
- package/dist/migrations.d.ts +161 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +620 -0
- package/dist/prisma-migrations.d.ts +160 -0
- package/dist/prisma-migrations.d.ts.map +1 -0
- package/dist/prisma-migrations.js +789 -0
- package/dist/prompts.d.ts +10 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +41 -0
- package/dist/pull.d.ts +23 -0
- package/dist/pull.d.ts.map +1 -0
- package/dist/pull.js +424 -0
- package/dist/schema/introspect.d.ts +54 -0
- package/dist/schema/introspect.d.ts.map +1 -0
- package/dist/schema/introspect.js +75 -0
- package/dist/schema/pull.d.ts +23 -0
- package/dist/schema/pull.d.ts.map +1 -0
- package/dist/schema/pull.js +424 -0
- package/dist/schema/snapshot.d.ts +46 -0
- package/dist/schema/snapshot.d.ts.map +1 -0
- package/dist/schema/snapshot.js +278 -0
- package/dist/schema-snapshot.d.ts +45 -0
- package/dist/schema-snapshot.d.ts.map +1 -0
- package/dist/schema-snapshot.js +265 -0
- package/dist/sql/compiler.d.ts +74 -0
- package/dist/sql/compiler.d.ts.map +1 -0
- package/dist/sql/compiler.js +270 -0
- package/dist/sql/kysely-adapter.d.ts +49 -0
- package/dist/sql/kysely-adapter.d.ts.map +1 -0
- package/dist/sql/kysely-adapter.js +74 -0
- package/dist/sql-compiler.d.ts +74 -0
- package/dist/sql-compiler.d.ts.map +1 -0
- package/dist/sql-compiler.js +243 -0
- package/package.json +81 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive prompts for the init command using ink
|
|
3
|
+
*/
|
|
4
|
+
export type InitChoice = "skip" | "reinitialize" | "baseline" | "create_initial";
|
|
5
|
+
/**
|
|
6
|
+
* Prompt user when snapshot already exists (Case A)
|
|
7
|
+
*/
|
|
8
|
+
export declare function promptSnapshotExists(): Promise<InitChoice>;
|
|
9
|
+
/**
|
|
10
|
+
* Prompt user for fresh init when no migrations exist (Case C)
|
|
11
|
+
*/
|
|
12
|
+
export declare function promptFreshInit(): Promise<InitChoice>;
|
|
13
|
+
//# sourceMappingURL=init-prompts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init-prompts.d.ts","sourceRoot":"","sources":["../src/init-prompts.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAMH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,cAAc,GAAG,UAAU,GAAG,gBAAgB,CAAC;AAsCjF;;GAEG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,UAAU,CAAC,CAyBhE;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,UAAU,CAAC,CAyB3D"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Interactive prompts for the init command using ink
|
|
4
|
+
*/
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { render, Box, Text } from "ink";
|
|
7
|
+
import SelectInput from "ink-select-input";
|
|
8
|
+
function SelectPrompt({ message, items, onSelect }) {
|
|
9
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
10
|
+
const handleSelect = (item) => {
|
|
11
|
+
onSelect(item.value);
|
|
12
|
+
};
|
|
13
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: "cyan", children: "? " }), _jsx(Text, { children: message })] }), _jsx(SelectInput, { items: items, onSelect: handleSelect, onHighlight: (item) => {
|
|
14
|
+
const idx = items.findIndex((i) => i.value === item.value);
|
|
15
|
+
if (idx !== -1)
|
|
16
|
+
setSelectedIndex(idx);
|
|
17
|
+
} }), items[selectedIndex]?.description && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: [" ", items[selectedIndex].description] }) }))] }));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Prompt user when snapshot already exists (Case A)
|
|
21
|
+
*/
|
|
22
|
+
export async function promptSnapshotExists() {
|
|
23
|
+
return new Promise((resolve) => {
|
|
24
|
+
const { unmount, waitUntilExit } = render(_jsx(SelectPrompt, { message: "Snapshot already exists. What would you like to do?", items: [
|
|
25
|
+
{
|
|
26
|
+
label: "Skip",
|
|
27
|
+
value: "skip",
|
|
28
|
+
description: "Do nothing and exit",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
label: "Reinitialize",
|
|
32
|
+
value: "reinitialize",
|
|
33
|
+
description: "Overwrite snapshot and rebuild migration log from existing migrations",
|
|
34
|
+
},
|
|
35
|
+
], onSelect: (value) => {
|
|
36
|
+
unmount();
|
|
37
|
+
resolve(value);
|
|
38
|
+
} }));
|
|
39
|
+
waitUntilExit();
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Prompt user for fresh init when no migrations exist (Case C)
|
|
44
|
+
*/
|
|
45
|
+
export async function promptFreshInit() {
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
const { unmount, waitUntilExit } = render(_jsx(SelectPrompt, { message: "No migrations found. What would you like to do?", items: [
|
|
48
|
+
{
|
|
49
|
+
label: "Baseline only",
|
|
50
|
+
value: "baseline",
|
|
51
|
+
description: "Create snapshot only - use when database already matches schema",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
label: "Create initial migration",
|
|
55
|
+
value: "create_initial",
|
|
56
|
+
description: "Create snapshot + initial migration - use when database is empty",
|
|
57
|
+
},
|
|
58
|
+
], onSelect: (value) => {
|
|
59
|
+
unmount();
|
|
60
|
+
resolve(value);
|
|
61
|
+
} }));
|
|
62
|
+
waitUntilExit();
|
|
63
|
+
});
|
|
64
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema introspection utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides functionality to introspect ZenStack schemas and databases,
|
|
5
|
+
* extracting model and field information for code generation.
|
|
6
|
+
*/
|
|
7
|
+
export interface FieldInfo {
|
|
8
|
+
/** Field name */
|
|
9
|
+
name: string;
|
|
10
|
+
/** Field type (String, Int, Boolean, etc.) */
|
|
11
|
+
type: string;
|
|
12
|
+
/** Whether the field is optional */
|
|
13
|
+
isOptional: boolean;
|
|
14
|
+
/** Whether the field is an array */
|
|
15
|
+
isArray: boolean;
|
|
16
|
+
/** Whether this is a relation field */
|
|
17
|
+
isRelation: boolean;
|
|
18
|
+
/** Whether this is the primary key */
|
|
19
|
+
isId: boolean;
|
|
20
|
+
/** Whether the field has a default value */
|
|
21
|
+
hasDefault: boolean;
|
|
22
|
+
/** Whether the field is unique */
|
|
23
|
+
isUnique: boolean;
|
|
24
|
+
/** Related model name (for relations) */
|
|
25
|
+
relationModel?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface ModelInfo {
|
|
28
|
+
/** Model name */
|
|
29
|
+
name: string;
|
|
30
|
+
/** Table name in database */
|
|
31
|
+
tableName: string;
|
|
32
|
+
/** Model fields */
|
|
33
|
+
fields: FieldInfo[];
|
|
34
|
+
}
|
|
35
|
+
export interface SchemaInfo {
|
|
36
|
+
/** All models in the schema */
|
|
37
|
+
models: ModelInfo[];
|
|
38
|
+
/** Schema version or hash */
|
|
39
|
+
version: string;
|
|
40
|
+
}
|
|
41
|
+
interface IntrospectOptions {
|
|
42
|
+
/** Path to ZenStack schema file */
|
|
43
|
+
schemaPath?: string;
|
|
44
|
+
/** Database connection URL (for database introspection) */
|
|
45
|
+
databaseUrl?: string;
|
|
46
|
+
/** Output path for generated schema */
|
|
47
|
+
outputPath?: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Introspect schema from file or database
|
|
51
|
+
*/
|
|
52
|
+
export declare function introspectSchema(options: IntrospectOptions): Promise<SchemaInfo>;
|
|
53
|
+
export {};
|
|
54
|
+
//# sourceMappingURL=introspect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"introspect.d.ts","sourceRoot":"","sources":["../src/introspect.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,MAAM,WAAW,SAAS;IACxB,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,UAAU,EAAE,OAAO,CAAC;IACpB,oCAAoC;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,uCAAuC;IACvC,UAAU,EAAE,OAAO,CAAC;IACpB,sCAAsC;IACtC,IAAI,EAAE,OAAO,CAAC;IACd,4CAA4C;IAC5C,UAAU,EAAE,OAAO,CAAC;IACpB,kCAAkC;IAClC,QAAQ,EAAE,OAAO,CAAC;IAClB,yCAAyC;IACzC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,SAAS;IACxB,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,mBAAmB;IACnB,MAAM,EAAE,SAAS,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,+BAA+B;IAC/B,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,iBAAiB;IACzB,mCAAmC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAoED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,CAUtF"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema introspection utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides functionality to introspect ZenStack schemas and databases,
|
|
5
|
+
* extracting model and field information for code generation.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from "fs/promises";
|
|
8
|
+
/**
|
|
9
|
+
* Parse a .zmodel file and extract schema information
|
|
10
|
+
* This is a simplified parser - in production, you'd use ZenStack's AST
|
|
11
|
+
*/
|
|
12
|
+
async function parseZModelFile(schemaPath) {
|
|
13
|
+
const content = await fs.readFile(schemaPath, "utf-8");
|
|
14
|
+
const models = [];
|
|
15
|
+
// Simple regex-based parser for demonstration
|
|
16
|
+
// In production, integrate with ZenStack's parser
|
|
17
|
+
const modelRegex = /model\s+(\w+)\s*\{([^}]+)\}/g;
|
|
18
|
+
const fieldRegex = /^\s*(\w+)\s+(\w+)(\[\])?\s*(\?)?\s*(.*?)$/gm;
|
|
19
|
+
let modelMatch;
|
|
20
|
+
while ((modelMatch = modelRegex.exec(content)) !== null) {
|
|
21
|
+
const modelName = modelMatch[1];
|
|
22
|
+
const modelBody = modelMatch[2];
|
|
23
|
+
const fields = [];
|
|
24
|
+
let fieldMatch;
|
|
25
|
+
const fieldPattern = /^\s*(\w+)\s+(\w+)(\[\])?\s*(\?)?(.*)$/gm;
|
|
26
|
+
while ((fieldMatch = fieldPattern.exec(modelBody)) !== null) {
|
|
27
|
+
const [, name, type, isArray, isOptional, modifiers] = fieldMatch;
|
|
28
|
+
// Skip if it looks like a directive
|
|
29
|
+
if (name.startsWith("@@") || name.startsWith("//"))
|
|
30
|
+
continue;
|
|
31
|
+
const isId = modifiers?.includes("@id") || false;
|
|
32
|
+
const hasDefault = modifiers?.includes("@default") || false;
|
|
33
|
+
const isUnique = modifiers?.includes("@unique") || isId;
|
|
34
|
+
const isRelation = modifiers?.includes("@relation") || false;
|
|
35
|
+
fields.push({
|
|
36
|
+
name,
|
|
37
|
+
type,
|
|
38
|
+
isOptional: !!isOptional,
|
|
39
|
+
isArray: !!isArray,
|
|
40
|
+
isRelation,
|
|
41
|
+
isId,
|
|
42
|
+
hasDefault,
|
|
43
|
+
isUnique,
|
|
44
|
+
relationModel: isRelation ? type : undefined,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
models.push({
|
|
48
|
+
name: modelName,
|
|
49
|
+
tableName: modelName.toLowerCase(),
|
|
50
|
+
fields,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
// Generate a simple version hash
|
|
54
|
+
const version = Buffer.from(content).toString("base64").slice(0, 8);
|
|
55
|
+
return { models, version };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Introspect a database and generate schema information
|
|
59
|
+
*/
|
|
60
|
+
async function introspectDatabase(databaseUrl) {
|
|
61
|
+
void databaseUrl;
|
|
62
|
+
throw new Error("Database introspection is not supported.");
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Introspect schema from file or database
|
|
66
|
+
*/
|
|
67
|
+
export async function introspectSchema(options) {
|
|
68
|
+
if (options.schemaPath) {
|
|
69
|
+
return parseZModelFile(options.schemaPath);
|
|
70
|
+
}
|
|
71
|
+
if (options.databaseUrl) {
|
|
72
|
+
return introspectDatabase(options.databaseUrl);
|
|
73
|
+
}
|
|
74
|
+
throw new Error("Either schemaPath or databaseUrl must be provided");
|
|
75
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kysely database adapter
|
|
3
|
+
*
|
|
4
|
+
* Provides utilities to create Kysely instances configured for use
|
|
5
|
+
* with ZenStack-generated types.
|
|
6
|
+
*/
|
|
7
|
+
import type { Kysely } from "kysely";
|
|
8
|
+
export type KyselyDialect = "sqlite" | "postgres" | "mysql";
|
|
9
|
+
export interface KyselyAdapterOptions {
|
|
10
|
+
/** Database dialect */
|
|
11
|
+
dialect: KyselyDialect;
|
|
12
|
+
/** Database connection URL */
|
|
13
|
+
connectionUrl?: string;
|
|
14
|
+
/** SQLite database path (for SQLite dialect) */
|
|
15
|
+
databasePath?: string;
|
|
16
|
+
/** Connection pool settings */
|
|
17
|
+
pool?: {
|
|
18
|
+
min?: number;
|
|
19
|
+
max?: number;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export interface KyselyAdapter<DB> {
|
|
23
|
+
/** The Kysely instance */
|
|
24
|
+
db: Kysely<DB>;
|
|
25
|
+
/** Destroy the connection pool */
|
|
26
|
+
destroy: () => Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Creates a Kysely adapter for use with ZenStack schemas
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* import { createKyselyAdapter } from "zenstack-kit";
|
|
34
|
+
* import type { Database } from "./generated/kysely-types";
|
|
35
|
+
*
|
|
36
|
+
* const { db, destroy } = await createKyselyAdapter<Database>({
|
|
37
|
+
* dialect: "postgres",
|
|
38
|
+
* connectionUrl: process.env.DATABASE_URL,
|
|
39
|
+
* });
|
|
40
|
+
*
|
|
41
|
+
* // Use db for queries
|
|
42
|
+
* const users = await db.selectFrom("user").selectAll().execute();
|
|
43
|
+
*
|
|
44
|
+
* // Clean up
|
|
45
|
+
* await destroy();
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export declare function createKyselyAdapter<DB>(options: KyselyAdapterOptions): Promise<KyselyAdapter<DB>>;
|
|
49
|
+
//# sourceMappingURL=kysely-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kysely-adapter.d.ts","sourceRoot":"","sources":["../src/kysely-adapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAW,MAAM,QAAQ,CAAC;AAE9C,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;AAE5D,MAAM,WAAW,oBAAoB;IACnC,uBAAuB;IACvB,OAAO,EAAE,aAAa,CAAC;IACvB,8BAA8B;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+BAA+B;IAC/B,IAAI,CAAC,EAAE;QACL,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED,MAAM,WAAW,aAAa,CAAC,EAAE;IAC/B,0BAA0B;IAC1B,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;IACf,kCAAkC;IAClC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,mBAAmB,CAAC,EAAE,EAC1C,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAwD5B"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kysely database adapter
|
|
3
|
+
*
|
|
4
|
+
* Provides utilities to create Kysely instances configured for use
|
|
5
|
+
* with ZenStack-generated types.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Creates a Kysely adapter for use with ZenStack schemas
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { createKyselyAdapter } from "zenstack-kit";
|
|
13
|
+
* import type { Database } from "./generated/kysely-types";
|
|
14
|
+
*
|
|
15
|
+
* const { db, destroy } = await createKyselyAdapter<Database>({
|
|
16
|
+
* dialect: "postgres",
|
|
17
|
+
* connectionUrl: process.env.DATABASE_URL,
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* // Use db for queries
|
|
21
|
+
* const users = await db.selectFrom("user").selectAll().execute();
|
|
22
|
+
*
|
|
23
|
+
* // Clean up
|
|
24
|
+
* await destroy();
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export async function createKyselyAdapter(options) {
|
|
28
|
+
// Dynamic imports based on dialect to avoid bundling unused drivers
|
|
29
|
+
let dialect;
|
|
30
|
+
switch (options.dialect) {
|
|
31
|
+
case "sqlite": {
|
|
32
|
+
const { default: Database } = await import("better-sqlite3");
|
|
33
|
+
const { SqliteDialect } = await import("kysely");
|
|
34
|
+
dialect = new SqliteDialect({
|
|
35
|
+
database: new Database(options.databasePath || ":memory:"),
|
|
36
|
+
});
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
case "postgres": {
|
|
40
|
+
// Note: User needs to install pg package
|
|
41
|
+
const { Pool } = await import("pg");
|
|
42
|
+
const { PostgresDialect } = await import("kysely");
|
|
43
|
+
dialect = new PostgresDialect({
|
|
44
|
+
pool: new Pool({
|
|
45
|
+
connectionString: options.connectionUrl,
|
|
46
|
+
min: options.pool?.min ?? 2,
|
|
47
|
+
max: options.pool?.max ?? 10,
|
|
48
|
+
}),
|
|
49
|
+
});
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
case "mysql": {
|
|
53
|
+
// Note: User needs to install mysql2 package
|
|
54
|
+
const mysql = await import("mysql2");
|
|
55
|
+
const { MysqlDialect } = await import("kysely");
|
|
56
|
+
dialect = new MysqlDialect({
|
|
57
|
+
pool: mysql.createPool({
|
|
58
|
+
uri: options.connectionUrl,
|
|
59
|
+
}),
|
|
60
|
+
});
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
default:
|
|
64
|
+
throw new Error(`Unsupported dialect: ${options.dialect}`);
|
|
65
|
+
}
|
|
66
|
+
const { Kysely } = await import("kysely");
|
|
67
|
+
const db = new Kysely({ dialect });
|
|
68
|
+
return {
|
|
69
|
+
db,
|
|
70
|
+
destroy: async () => {
|
|
71
|
+
await db.destroy();
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apply migrations using Kysely's migrator
|
|
3
|
+
*/
|
|
4
|
+
import type { KyselyDialect } from "./kysely-adapter.js";
|
|
5
|
+
export interface ApplyMigrationsOptions {
|
|
6
|
+
migrationsFolder: string;
|
|
7
|
+
dialect: KyselyDialect;
|
|
8
|
+
connectionUrl?: string;
|
|
9
|
+
databasePath?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ApplyMigrationsResult {
|
|
12
|
+
results: Array<{
|
|
13
|
+
migrationName: string;
|
|
14
|
+
status: string;
|
|
15
|
+
}>;
|
|
16
|
+
}
|
|
17
|
+
export declare function applyMigrations(options: ApplyMigrationsOptions): Promise<ApplyMigrationsResult>;
|
|
18
|
+
//# sourceMappingURL=migrate-apply.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate-apply.d.ts","sourceRoot":"","sources":["../src/migrate-apply.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGzD,MAAM,WAAW,sBAAsB;IACrC,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,aAAa,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,KAAK,CAAC;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC1D;AAED,wBAAsB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA4DrG"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apply migrations using Kysely's migrator
|
|
3
|
+
*/
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import { createKyselyAdapter } from "./kysely-adapter.js";
|
|
6
|
+
export async function applyMigrations(options) {
|
|
7
|
+
const databasePath = options.databasePath ??
|
|
8
|
+
(options.dialect === "sqlite" ? resolveSqlitePath(options.connectionUrl) : undefined);
|
|
9
|
+
const { db, destroy } = await createKyselyAdapter({
|
|
10
|
+
dialect: options.dialect,
|
|
11
|
+
connectionUrl: options.connectionUrl,
|
|
12
|
+
databasePath,
|
|
13
|
+
});
|
|
14
|
+
try {
|
|
15
|
+
const { Migrator } = await import("kysely");
|
|
16
|
+
const fs = await import("fs/promises");
|
|
17
|
+
const { default: jiti } = await import("jiti");
|
|
18
|
+
const loader = jiti(import.meta.url, { interopDefault: true });
|
|
19
|
+
const provider = {
|
|
20
|
+
async getMigrations() {
|
|
21
|
+
const entries = await fs.readdir(options.migrationsFolder);
|
|
22
|
+
const files = entries
|
|
23
|
+
.filter((file) => /\.(ts|js|mjs|cjs)$/.test(file))
|
|
24
|
+
.sort((a, b) => a.localeCompare(b));
|
|
25
|
+
const migrations = {};
|
|
26
|
+
for (const file of files) {
|
|
27
|
+
const filePath = path.join(options.migrationsFolder, file);
|
|
28
|
+
const mod = loader(filePath);
|
|
29
|
+
const migration = mod.default ?? mod;
|
|
30
|
+
if (!migration?.up || !migration?.down) {
|
|
31
|
+
throw new Error(`Migration file is missing up/down exports: ${file}`);
|
|
32
|
+
}
|
|
33
|
+
migrations[path.parse(file).name] = migration;
|
|
34
|
+
}
|
|
35
|
+
return migrations;
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
const migrator = new Migrator({ db, provider });
|
|
39
|
+
const { error, results } = await migrator.migrateToLatest();
|
|
40
|
+
if (error) {
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
results: results?.map((result) => ({
|
|
45
|
+
migrationName: result.migrationName,
|
|
46
|
+
status: result.status,
|
|
47
|
+
})) ?? [],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
await destroy();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function resolveSqlitePath(url) {
|
|
55
|
+
if (!url)
|
|
56
|
+
return undefined;
|
|
57
|
+
if (url.startsWith("file:")) {
|
|
58
|
+
return url.slice("file:".length);
|
|
59
|
+
}
|
|
60
|
+
return url;
|
|
61
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* High-level programmatic API for zenstack-kit migrations
|
|
3
|
+
*
|
|
4
|
+
* This module provides a simple interface for applying migrations from code,
|
|
5
|
+
* useful for running migrations during application startup or in CI/CD pipelines.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { migrate } from "zenstack-kit";
|
|
10
|
+
*
|
|
11
|
+
* // Apply migrations using config file
|
|
12
|
+
* await migrate();
|
|
13
|
+
*
|
|
14
|
+
* // Apply migrations with explicit options
|
|
15
|
+
* await migrate({
|
|
16
|
+
* migrationsFolder: "./prisma/migrations",
|
|
17
|
+
* dialect: "postgres",
|
|
18
|
+
* connectionUrl: process.env.DATABASE_URL,
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* // Preview migrations without applying
|
|
22
|
+
* const result = await migrate({ preview: true });
|
|
23
|
+
* console.log("Pending migrations:", result.pending);
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
import { type ApplyPrismaMigrationsResult, type PreviewPrismaMigrationsResult } from "./migrations/prisma.js";
|
|
27
|
+
import type { KyselyDialect } from "./sql/kysely-adapter.js";
|
|
28
|
+
export interface MigrateOptions {
|
|
29
|
+
/**
|
|
30
|
+
* Path to migrations folder.
|
|
31
|
+
* If not provided, will be read from config file.
|
|
32
|
+
*/
|
|
33
|
+
migrationsFolder?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Database dialect: "sqlite", "postgres", or "mysql"
|
|
36
|
+
* If not provided, will be read from config file.
|
|
37
|
+
*/
|
|
38
|
+
dialect?: KyselyDialect;
|
|
39
|
+
/**
|
|
40
|
+
* Database connection URL (for postgres/mysql).
|
|
41
|
+
* If not provided, will be read from config file.
|
|
42
|
+
*/
|
|
43
|
+
connectionUrl?: string;
|
|
44
|
+
/**
|
|
45
|
+
* SQLite database file path.
|
|
46
|
+
* If not provided, will be read from config file.
|
|
47
|
+
*/
|
|
48
|
+
databasePath?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Migrations table name.
|
|
51
|
+
* @default "_prisma_migrations"
|
|
52
|
+
*/
|
|
53
|
+
migrationsTable?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Migrations schema (PostgreSQL only).
|
|
56
|
+
* @default "public"
|
|
57
|
+
*/
|
|
58
|
+
migrationsSchema?: string;
|
|
59
|
+
/**
|
|
60
|
+
* If true, preview pending migrations without applying them.
|
|
61
|
+
* @default false
|
|
62
|
+
*/
|
|
63
|
+
preview?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Current working directory for config resolution.
|
|
66
|
+
* @default process.cwd()
|
|
67
|
+
*/
|
|
68
|
+
cwd?: string;
|
|
69
|
+
}
|
|
70
|
+
export type MigrateResult = (ApplyPrismaMigrationsResult & {
|
|
71
|
+
mode: "apply";
|
|
72
|
+
}) | (PreviewPrismaMigrationsResult & {
|
|
73
|
+
mode: "preview";
|
|
74
|
+
});
|
|
75
|
+
/**
|
|
76
|
+
* Apply or preview database migrations programmatically.
|
|
77
|
+
*
|
|
78
|
+
* This function can be used in application code to run migrations during startup,
|
|
79
|
+
* or in scripts and CI/CD pipelines.
|
|
80
|
+
*
|
|
81
|
+
* @param options - Migration options. If not provided, reads from config file.
|
|
82
|
+
* @returns Result of migration apply or preview operation.
|
|
83
|
+
* @throws Error if migrations fail or required configuration is missing.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* // Run migrations on app startup
|
|
88
|
+
* import { migrate } from "zenstack-kit";
|
|
89
|
+
*
|
|
90
|
+
* async function main() {
|
|
91
|
+
* const result = await migrate();
|
|
92
|
+
* console.log(`Applied ${result.applied.length} migrations`);
|
|
93
|
+
*
|
|
94
|
+
* // Start your app...
|
|
95
|
+
* }
|
|
96
|
+
* ```
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* // Preview in development
|
|
101
|
+
* const preview = await migrate({ preview: true });
|
|
102
|
+
* if (preview.pending.length > 0) {
|
|
103
|
+
* console.log("Pending migrations:", preview.pending.map(m => m.name));
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
export declare function migrate(options?: MigrateOptions): Promise<MigrateResult>;
|
|
108
|
+
//# sourceMappingURL=migrate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../src/migrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,EAGL,KAAK,2BAA2B,EAChC,KAAK,6BAA6B,EACnC,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAG7D,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;IAExB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,aAAa,GACrB,CAAC,2BAA2B,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,GACjD,CAAC,6BAA6B,GAAG;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,aAAa,CAAC,CAsFlF"}
|
package/dist/migrate.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* High-level programmatic API for zenstack-kit migrations
|
|
3
|
+
*
|
|
4
|
+
* This module provides a simple interface for applying migrations from code,
|
|
5
|
+
* useful for running migrations during application startup or in CI/CD pipelines.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { migrate } from "zenstack-kit";
|
|
10
|
+
*
|
|
11
|
+
* // Apply migrations using config file
|
|
12
|
+
* await migrate();
|
|
13
|
+
*
|
|
14
|
+
* // Apply migrations with explicit options
|
|
15
|
+
* await migrate({
|
|
16
|
+
* migrationsFolder: "./prisma/migrations",
|
|
17
|
+
* dialect: "postgres",
|
|
18
|
+
* connectionUrl: process.env.DATABASE_URL,
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* // Preview migrations without applying
|
|
22
|
+
* const result = await migrate({ preview: true });
|
|
23
|
+
* console.log("Pending migrations:", result.pending);
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
import { loadConfig } from "./config/loader.js";
|
|
27
|
+
import { applyPrismaMigrations, previewPrismaMigrations, } from "./migrations/prisma.js";
|
|
28
|
+
import * as path from "path";
|
|
29
|
+
/**
|
|
30
|
+
* Apply or preview database migrations programmatically.
|
|
31
|
+
*
|
|
32
|
+
* This function can be used in application code to run migrations during startup,
|
|
33
|
+
* or in scripts and CI/CD pipelines.
|
|
34
|
+
*
|
|
35
|
+
* @param options - Migration options. If not provided, reads from config file.
|
|
36
|
+
* @returns Result of migration apply or preview operation.
|
|
37
|
+
* @throws Error if migrations fail or required configuration is missing.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* // Run migrations on app startup
|
|
42
|
+
* import { migrate } from "zenstack-kit";
|
|
43
|
+
*
|
|
44
|
+
* async function main() {
|
|
45
|
+
* const result = await migrate();
|
|
46
|
+
* console.log(`Applied ${result.applied.length} migrations`);
|
|
47
|
+
*
|
|
48
|
+
* // Start your app...
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* // Preview in development
|
|
55
|
+
* const preview = await migrate({ preview: true });
|
|
56
|
+
* if (preview.pending.length > 0) {
|
|
57
|
+
* console.log("Pending migrations:", preview.pending.map(m => m.name));
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export async function migrate(options = {}) {
|
|
62
|
+
const cwd = options.cwd ?? process.cwd();
|
|
63
|
+
// Load config if options not fully provided
|
|
64
|
+
let migrationsFolder = options.migrationsFolder;
|
|
65
|
+
let dialect = options.dialect;
|
|
66
|
+
let connectionUrl = options.connectionUrl;
|
|
67
|
+
let databasePath = options.databasePath;
|
|
68
|
+
if (!migrationsFolder || !dialect) {
|
|
69
|
+
const loaded = await loadConfig(cwd);
|
|
70
|
+
if (loaded) {
|
|
71
|
+
const { config, configDir } = loaded;
|
|
72
|
+
if (!migrationsFolder) {
|
|
73
|
+
const relativeFolder = config.migrations?.migrationsFolder ?? "./prisma/migrations";
|
|
74
|
+
migrationsFolder = path.resolve(configDir, relativeFolder);
|
|
75
|
+
}
|
|
76
|
+
if (!dialect) {
|
|
77
|
+
dialect = (config.dialect ?? "sqlite");
|
|
78
|
+
}
|
|
79
|
+
if (!connectionUrl && !databasePath) {
|
|
80
|
+
const dbCredentials = config.dbCredentials;
|
|
81
|
+
if (dialect === "sqlite") {
|
|
82
|
+
databasePath = dbCredentials?.file;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
connectionUrl = dbCredentials?.url;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Validate required options
|
|
91
|
+
if (!migrationsFolder) {
|
|
92
|
+
throw new Error("migrationsFolder is required. Provide it in options or create a zenstack-kit.config.ts file.");
|
|
93
|
+
}
|
|
94
|
+
if (!dialect) {
|
|
95
|
+
throw new Error("dialect is required. Provide it in options or create a zenstack-kit.config.ts file.");
|
|
96
|
+
}
|
|
97
|
+
if (dialect !== "sqlite" && !connectionUrl) {
|
|
98
|
+
throw new Error("connectionUrl is required for postgres/mysql. Provide it in options or in your config file.");
|
|
99
|
+
}
|
|
100
|
+
const migrationsTable = options.migrationsTable ?? "_prisma_migrations";
|
|
101
|
+
const migrationsSchema = options.migrationsSchema ?? "public";
|
|
102
|
+
// Preview mode
|
|
103
|
+
if (options.preview) {
|
|
104
|
+
const result = await previewPrismaMigrations({
|
|
105
|
+
migrationsFolder,
|
|
106
|
+
dialect,
|
|
107
|
+
connectionUrl,
|
|
108
|
+
databasePath,
|
|
109
|
+
migrationsTable,
|
|
110
|
+
migrationsSchema,
|
|
111
|
+
});
|
|
112
|
+
return { ...result, mode: "preview" };
|
|
113
|
+
}
|
|
114
|
+
// Apply mode
|
|
115
|
+
const result = await applyPrismaMigrations({
|
|
116
|
+
migrationsFolder,
|
|
117
|
+
dialect,
|
|
118
|
+
connectionUrl,
|
|
119
|
+
databasePath,
|
|
120
|
+
migrationsTable,
|
|
121
|
+
migrationsSchema,
|
|
122
|
+
});
|
|
123
|
+
if (result.failed) {
|
|
124
|
+
throw new Error(`Migration failed: ${result.failed.migrationName} - ${result.failed.error}`);
|
|
125
|
+
}
|
|
126
|
+
return { ...result, mode: "apply" };
|
|
127
|
+
}
|