@aurios/mizzling 1.1.1 → 1.1.3

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.
@@ -1,6 +1,11 @@
1
1
  import { type MizzleConfig } from "../config";
2
2
  import { discoverSchema } from "../discovery";
3
- import { loadSnapshot, saveSnapshot, generateSnapshot, getNextMigrationVersion } from "@aurios/mizzle/snapshot";
3
+ import {
4
+ loadSnapshot,
5
+ saveSnapshot,
6
+ generateSnapshot,
7
+ getNextMigrationVersion,
8
+ } from "@aurios/mizzle/snapshot";
4
9
  import { compareSchema, type SchemaChange } from "@aurios/mizzle/diff";
5
10
  import { join } from "path";
6
11
  import { writeFile, mkdir } from "fs/promises";
@@ -8,102 +13,102 @@ import { existsSync } from "fs";
8
13
  import { text, isCancel, cancel, intro, outro } from "@clack/prompts";
9
14
 
10
15
  interface GenerateOptions {
11
- config: MizzleConfig;
12
- name?: string;
13
- discoverSchema?: typeof discoverSchema;
16
+ config: MizzleConfig;
17
+ name?: string;
18
+ discoverSchema?: typeof discoverSchema;
14
19
  }
15
20
 
16
21
  export async function generateCommand(options: GenerateOptions) {
17
- intro("Mizzle Generate");
18
- const { config } = options;
19
- const discover = options.discoverSchema || discoverSchema;
20
-
21
- try {
22
- // 1. Discover Schema
23
- const schema = await discover(config); // Returns { tables, entities }
24
-
25
- // 2. Load Snapshot
26
- const migrationsDir = config.out;
27
- const existingSnapshot = await loadSnapshot(migrationsDir) || { version: "0", tables: {} };
28
-
29
- // 3. Compare
30
- const currentSnapshot = generateSnapshot(schema);
31
- const changes = compareSchema(schema, existingSnapshot);
32
-
33
- if (changes.length === 0) {
34
- outro("No changes detected.");
35
- return;
36
- }
37
-
38
- console.log(`Detected ${changes.length} changes.`);
39
-
40
- // 4. Generate Migration Script
41
- const version = await getNextMigrationVersion(migrationsDir);
42
-
43
- let name = options.name;
44
- if (!name) {
45
- name = await text({
46
- message: "Enter migration name",
47
- placeholder: "init",
48
- initialValue: "migration",
49
- validate(value) {
50
- if (value.length === 0) return "Name is required";
51
- }
52
- }) as string;
53
- }
54
-
55
- if (isCancel(name)) {
56
- cancel("Operation cancelled.");
57
- return;
58
- }
59
-
60
- const filename = `${version}_${name}.ts`;
61
- if (!existsSync(migrationsDir)) {
62
- await mkdir(migrationsDir, { recursive: true });
63
- }
64
- const filePath = join(migrationsDir, filename);
65
-
66
- const scriptContent = generateMigrationScript(changes);
67
- await writeFile(filePath, scriptContent);
68
- console.log(`Created migration: ${filename}`);
69
-
70
- // 5. Save new Snapshot
71
- await saveSnapshot(migrationsDir, currentSnapshot);
72
- outro("Updated snapshot.json");
73
- } catch (error) {
74
- console.error("Error generating migration:", error);
75
- process.exit(1);
22
+ intro("Mizzle Generate");
23
+ const { config } = options;
24
+ const discover = options.discoverSchema || discoverSchema;
25
+
26
+ try {
27
+ // 1. Discover Schema
28
+ const schema = await discover(config); // Returns { tables, entities }
29
+
30
+ // 2. Load Snapshot
31
+ const migrationsDir = config.out;
32
+ const existingSnapshot = (await loadSnapshot(migrationsDir)) || { version: "0", tables: {} };
33
+
34
+ // 3. Compare
35
+ const currentSnapshot = generateSnapshot(schema);
36
+ const changes = compareSchema(schema, existingSnapshot);
37
+
38
+ if (changes.length === 0) {
39
+ outro("No changes detected.");
40
+ return;
41
+ }
42
+
43
+ console.log(`Detected ${changes.length} changes.`);
44
+
45
+ // 4. Generate Migration Script
46
+ const version = await getNextMigrationVersion(migrationsDir);
47
+
48
+ let name = options.name;
49
+ if (!name) {
50
+ name = (await text({
51
+ message: "Enter migration name",
52
+ placeholder: "init",
53
+ initialValue: "migration",
54
+ validate(value) {
55
+ if (value.length === 0) return "Name is required";
56
+ },
57
+ })) as string;
76
58
  }
59
+
60
+ if (isCancel(name)) {
61
+ cancel("Operation cancelled.");
62
+ return;
63
+ }
64
+
65
+ const filename = `${version}_${name}.ts`;
66
+ if (!existsSync(migrationsDir)) {
67
+ await mkdir(migrationsDir, { recursive: true });
68
+ }
69
+ const filePath = join(migrationsDir, filename);
70
+
71
+ const scriptContent = generateMigrationScript(changes);
72
+ await writeFile(filePath, scriptContent);
73
+ console.log(`Created migration: ${filename}`);
74
+
75
+ // 5. Save new Snapshot
76
+ await saveSnapshot(migrationsDir, currentSnapshot);
77
+ outro("Updated snapshot.json");
78
+ } catch (error) {
79
+ console.error("Error generating migration:", error);
80
+ process.exit(1);
81
+ }
77
82
  }
78
83
 
79
84
  function generateMigrationScript(changes: SchemaChange[]): string {
80
- const upSteps: string[] = [];
81
- const downSteps: string[] = [];
85
+ const upSteps: string[] = [];
86
+ const downSteps: string[] = [];
82
87
 
83
- for (const change of changes) {
84
- if (change.type === "create") {
85
- upSteps.push(`// Create Table: ${change.table.TableName}`);
86
- upSteps.push(`await db.createTable("${change.table.TableName}", ${JSON.stringify(change.table, null, 2)});
88
+ for (const change of changes) {
89
+ if (change.type === "create") {
90
+ upSteps.push(`// Create Table: ${change.table.TableName}`);
91
+ upSteps.push(`await db.createTable("${change.table.TableName}", ${JSON.stringify(change.table, null, 2)});
87
92
  `);
88
-
89
- downSteps.unshift(`// Drop Table: ${change.table.TableName}`);
90
- downSteps.unshift(`await db.deleteTable("${change.table.TableName}");
93
+
94
+ downSteps.unshift(`// Drop Table: ${change.table.TableName}`);
95
+ downSteps.unshift(`await db.deleteTable("${change.table.TableName}");
91
96
  `);
92
- } else if (change.type === "delete") {
93
- upSteps.push(`// Drop Table: ${change.tableName}`);
94
- upSteps.push(`await db.deleteTable("${change.tableName}");
97
+ } else if (change.type === "delete") {
98
+ upSteps.push(`// Drop Table: ${change.tableName}`);
99
+ upSteps.push(`await db.deleteTable("${change.tableName}");
95
100
  `);
96
-
97
- downSteps.unshift(`// Create Table: ${change.tableName}`);
98
- downSteps.unshift(`// TODO: Restore table definition for rollback
101
+
102
+ downSteps.unshift(`// Create Table: ${change.tableName}`);
103
+ downSteps.unshift(`// TODO: Restore table definition for rollback
99
104
  `);
100
- } else if (change.type === "update") {
101
- upSteps.push(`// Update Table: ${change.tableName}`);
102
- downSteps.unshift(`// Revert Update Table: ${change.tableName}`);
103
- }
105
+ } else if (change.type === "update") {
106
+ upSteps.push(`// Update Table: ${change.tableName}`);
107
+ downSteps.unshift(`// Revert Update Table: ${change.tableName}`);
104
108
  }
109
+ }
105
110
 
106
- return `import { Mizzle } from "@aurios/mizzle";
111
+ return `import { Mizzle } from "@aurios/mizzle";
107
112
 
108
113
  export async function up(db: Mizzle) {
109
114
  ${upSteps.join(" ")}
@@ -67,7 +67,7 @@ export default defineConfig({
67
67
  schema: "${schema}",
68
68
  out: "${out}",
69
69
  region: "${region}",
70
- ${endpoint ? `endpoint: "${endpoint}",` : "// endpoint: \"http://localhost:8000\","}
70
+ ${endpoint ? `endpoint: "${endpoint}",` : '// endpoint: "http://localhost:8000",'}
71
71
  });
72
72
  `;
73
73
 
@@ -4,45 +4,47 @@ import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
4
4
  import { intro, outro, spinner } from "@clack/prompts";
5
5
 
6
6
  interface ListOptions {
7
- config: MizzleConfig;
8
- client?: DynamoDBClient;
7
+ config: MizzleConfig;
8
+ client?: DynamoDBClient;
9
9
  }
10
10
 
11
11
  export async function listCommand(options: ListOptions) {
12
- intro("Mizzle List Tables");
13
-
14
- const client = options.client || getClient(options.config);
15
- const s = spinner();
16
- s.start("Fetching remote tables...");
17
-
18
- try {
19
- const snapshot = await getRemoteSnapshot(client);
20
- s.stop("Fetched remote tables.");
21
-
22
- const tables = Object.values(snapshot.tables);
23
-
24
- if (tables.length === 0) {
25
- console.log("No tables found in the remote environment.");
26
- outro("Done");
27
- return;
28
- }
29
-
30
- console.log(`Found ${tables.length} tables:`);
31
- for (const table of tables) {
32
- console.log(`- ${table.TableName}`);
33
- const pk = table.KeySchema.find(k => k.KeyType === "HASH")?.AttributeName;
34
- const sk = table.KeySchema.find(k => k.KeyType === "RANGE")?.AttributeName;
35
- console.log(` PK: ${pk}, SK: ${sk || "(none)"}`);
36
-
37
- if (table.GlobalSecondaryIndexes && table.GlobalSecondaryIndexes.length > 0) {
38
- console.log(` GSIs: ${table.GlobalSecondaryIndexes.map(g => g.IndexName).join(", ")}`);
39
- }
40
- }
41
-
42
- outro("Done");
43
- } catch (error) {
44
- s.stop("Failed to fetch tables.");
45
- console.error("Error listing tables:", error);
46
- process.exit(1);
12
+ intro("Mizzle List Tables");
13
+
14
+ const client = options.client || getClient(options.config);
15
+ const s = spinner();
16
+ s.start("Fetching remote tables...");
17
+
18
+ try {
19
+ const snapshot = await getRemoteSnapshot(client);
20
+ s.stop("Fetched remote tables.");
21
+
22
+ const tables = Object.values(snapshot.tables);
23
+
24
+ if (tables.length === 0) {
25
+ console.log("No tables found in the remote environment.");
26
+ outro("Done");
27
+ return;
47
28
  }
29
+
30
+ console.log(`Found ${tables.length} tables:`);
31
+ for (const table of tables) {
32
+ console.log(`- ${table.TableName}`);
33
+ const pk = table.KeySchema.find((k: any) => k.KeyType === "HASH")?.AttributeName;
34
+ const sk = table.KeySchema.find((k: any) => k.KeyType === "RANGE")?.AttributeName;
35
+ console.log(` PK: ${pk}, SK: ${sk || "(none)"}`);
36
+
37
+ if (table.GlobalSecondaryIndexes && table.GlobalSecondaryIndexes.length > 0) {
38
+ console.log(
39
+ ` GSIs: ${table.GlobalSecondaryIndexes.map((g: any) => g.IndexName).join(", ")}`,
40
+ );
41
+ }
42
+ }
43
+
44
+ outro("Done");
45
+ } catch (error) {
46
+ s.stop("Failed to fetch tables.");
47
+ console.error("Error listing tables:", error);
48
+ process.exit(1);
49
+ }
48
50
  }
@@ -6,70 +6,70 @@ import { DynamoDBClient, CreateTableCommand } from "@aws-sdk/client-dynamodb";
6
6
  import { confirm, isCancel, cancel, intro, outro, spinner } from "@clack/prompts";
7
7
 
8
8
  interface PushOptions {
9
- config: MizzleConfig;
10
- force?: boolean;
11
- discoverSchema?: typeof discoverSchema;
12
- client?: DynamoDBClient;
9
+ config: MizzleConfig;
10
+ force?: boolean;
11
+ discoverSchema?: typeof discoverSchema;
12
+ client?: DynamoDBClient;
13
13
  }
14
14
 
15
15
  export async function pushCommand(options: PushOptions) {
16
- intro("Mizzle Push");
17
- const { config, force } = options;
18
- const discover = options.discoverSchema || discoverSchema;
19
-
20
- const client = options.client || getClient(config);
16
+ intro("Mizzle Push");
17
+ const { config, force } = options;
18
+ const discover = options.discoverSchema || discoverSchema;
21
19
 
22
- try {
23
- const schema = await discover(config);
24
- const remoteSnapshot = await getRemoteSnapshot(client);
20
+ const client = options.client || getClient(config);
25
21
 
26
- const changes = compareSchema(schema, remoteSnapshot);
22
+ try {
23
+ const schema = await discover(config);
24
+ const remoteSnapshot = await getRemoteSnapshot(client);
27
25
 
28
- if (changes.length === 0) {
29
- outro("Remote is up to date.");
30
- return;
31
- }
26
+ const changes = compareSchema(schema, remoteSnapshot);
32
27
 
33
- console.log(`Pushing ${changes.length} changes to remote...`);
28
+ if (changes.length === 0) {
29
+ outro("Remote is up to date.");
30
+ return;
31
+ }
32
+
33
+ console.log(`Pushing ${changes.length} changes to remote...`);
34
34
 
35
- let shouldContinue = force;
36
- if (!shouldContinue) {
37
- shouldContinue = await confirm({
38
- message: "Do you want to apply these changes?"
39
- }) as boolean;
40
- }
35
+ let shouldContinue = force;
36
+ if (!shouldContinue) {
37
+ shouldContinue = (await confirm({
38
+ message: "Do you want to apply these changes?",
39
+ })) as boolean;
40
+ }
41
41
 
42
- if (isCancel(shouldContinue) || !shouldContinue) {
43
- cancel("Operation cancelled.");
44
- return;
45
- }
42
+ if (isCancel(shouldContinue) || !shouldContinue) {
43
+ cancel("Operation cancelled.");
44
+ return;
45
+ }
46
46
 
47
- const s = spinner();
48
- s.start("Pushing changes...");
47
+ const s = spinner();
48
+ s.start("Pushing changes...");
49
49
 
50
- for (const change of changes) {
51
- if (change.type === "create") {
52
- s.message(`Creating table: ${change.table.TableName}`);
53
- await client.send(
54
- new CreateTableCommand({
55
- TableName: change.table.TableName,
56
- AttributeDefinitions: change.table.AttributeDefinitions,
57
- KeySchema: change.table.KeySchema,
58
- GlobalSecondaryIndexes: change.table.GlobalSecondaryIndexes,
59
- LocalSecondaryIndexes: change.table.LocalSecondaryIndexes,
60
- BillingMode: "PAY_PER_REQUEST",
61
- }),
62
- );
63
- } else if (change.type === "delete") {
64
- console.log(`Untracked table found: ${change.tableName} (Skipping deletion)`);
65
- } else if (change.type === "update") {
66
- s.message(`Updating table: ${change.tableName} (Not fully implemented)`);
67
- }
68
- }
69
- s.stop("Push complete.");
70
- outro("Done");
71
- } catch (error) {
72
- console.error("Error pushing changes:", error);
73
- process.exit(1);
50
+ for (const change of changes) {
51
+ if (change.type === "create") {
52
+ s.message(`Creating table: ${change.table.TableName}`);
53
+ await client.send(
54
+ new CreateTableCommand({
55
+ TableName: change.table.TableName,
56
+ AttributeDefinitions: change.table.AttributeDefinitions,
57
+ KeySchema: change.table.KeySchema,
58
+ GlobalSecondaryIndexes: change.table.GlobalSecondaryIndexes,
59
+ LocalSecondaryIndexes: change.table.LocalSecondaryIndexes,
60
+ BillingMode: "PAY_PER_REQUEST",
61
+ }),
62
+ );
63
+ } else if (change.type === "delete") {
64
+ console.log(`Untracked table found: ${change.tableName} (Skipping deletion)`);
65
+ } else if (change.type === "update") {
66
+ s.message(`Updating table: ${change.tableName} (Not fully implemented)`);
67
+ }
74
68
  }
69
+ s.stop("Push complete.");
70
+ outro("Done");
71
+ } catch (error) {
72
+ console.error("Error pushing changes:", error);
73
+ process.exit(1);
74
+ }
75
75
  }
package/src/config.ts CHANGED
@@ -66,20 +66,20 @@ export interface MizzleConfig {
66
66
 
67
67
  /**
68
68
  * Helper function to define the Mizzle CLI configuration with type safety and autocompletion.
69
- *
69
+ *
70
70
  * Typically used in a `mizzle.config.ts` file at the root of your project.
71
- *
71
+ *
72
72
  * @example
73
73
  * ```ts
74
74
  * import { defineConfig } from "@aurios/mizzling";
75
- *
75
+ *
76
76
  * export default defineConfig({
77
77
  * schema: "./src/schema.ts",
78
78
  * out: "./mizzle",
79
79
  * region: "us-east-1",
80
80
  * });
81
81
  * ```
82
- *
82
+ *
83
83
  * @param config The Mizzle configuration object.
84
84
  * @returns The same configuration object, validated by TypeScript.
85
85
  */
@@ -89,20 +89,20 @@ export function defineConfig(config: MizzleConfig): MizzleConfig {
89
89
 
90
90
  /**
91
91
  * Creates a configured DynamoDBClient instance based on the provided configuration.
92
- *
92
+ *
93
93
  * It prioritizes credentials in the following order:
94
94
  * 1. Explicitly provided `credentials` object.
95
95
  * 2. Explicitly provided AWS `profile`.
96
96
  * 3. Default "local" credentials if the endpoint is localhost/127.0.0.1.
97
97
  * 4. Default AWS SDK credential provider chain (environment variables, IAM roles, etc.).
98
- *
98
+ *
99
99
  * @param config The Mizzle configuration.
100
100
  * @returns A configured DynamoDBClient instance.
101
101
  */
102
102
  export function getClient(config: MizzleConfig): DynamoDBClient {
103
103
  const agentOptions = {
104
- keepAlive: true,
105
- maxSockets: Infinity,
104
+ keepAlive: true,
105
+ maxSockets: Infinity,
106
106
  };
107
107
 
108
108
  const clientConfig: DynamoDBClientConfig = {
@@ -110,8 +110,8 @@ export function getClient(config: MizzleConfig): DynamoDBClient {
110
110
  endpoint: config.endpoint,
111
111
  maxAttempts: config.maxAttempts,
112
112
  requestHandler: new NodeHttpHandler({
113
- httpAgent: new http.Agent(agentOptions),
114
- httpsAgent: new https.Agent(agentOptions),
113
+ httpAgent: new http.Agent(agentOptions),
114
+ httpsAgent: new https.Agent(agentOptions),
115
115
  }),
116
116
  };
117
117
 
@@ -134,10 +134,10 @@ export function getClient(config: MizzleConfig): DynamoDBClient {
134
134
 
135
135
  /**
136
136
  * Loads the Mizzle configuration from a file (defaulting to mizzle.config.ts).
137
- *
137
+ *
138
138
  * Environment variables (MIZZLE_REGION, MIZZLE_ENDPOINT, MIZZLE_SCHEMA, MIZZLE_OUT)
139
139
  * will override values provided in the configuration file.
140
- *
140
+ *
141
141
  * @param configName The name of the config file to load.
142
142
  * @returns A promise that resolves to the loaded and overridden configuration.
143
143
  * @throws Error if the configuration file is missing or invalid.
@@ -155,7 +155,7 @@ export async function loadConfig(configName = "mizzle.config.ts"): Promise<Mizzl
155
155
  const config = imported.default || imported;
156
156
 
157
157
  if (!config || typeof config !== "object") {
158
- throw new Error("Invalid config: default export must be an object");
158
+ throw new Error("Invalid config: default export must be an object");
159
159
  }
160
160
 
161
161
  if (!config.schema) {
@@ -178,7 +178,7 @@ export async function loadConfig(configName = "mizzle.config.ts"): Promise<Mizzl
178
178
  return finalConfig;
179
179
  } catch (error) {
180
180
  if (error instanceof Error && error.message.startsWith("Invalid config")) {
181
- throw error;
181
+ throw error;
182
182
  }
183
183
  const message = error instanceof Error ? error.message : String(error);
184
184
  throw new Error(`Failed to load config: ${message}`);
package/src/discovery.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  import { type MizzleConfig } from "./config";
2
2
  import { PhysicalTable, Entity } from "@aurios/mizzle/table";
3
- import { TABLE_SYMBOLS, ENTITY_SYMBOLS } from "@mizzle/shared";
3
+ import { TABLE_SYMBOLS, ENTITY_SYMBOLS } from "@repo/shared";
4
4
  import fg from "fast-glob";
5
5
  import { stat } from "fs/promises";
6
6
  import { resolve } from "path";
7
7
 
8
- export async function discoverSchema(config: MizzleConfig): Promise<{ tables: PhysicalTable[], entities: Entity[] }> {
8
+ export async function discoverSchema(
9
+ config: MizzleConfig,
10
+ ): Promise<{ tables: PhysicalTable[]; entities: Entity[] }> {
9
11
  const schemaPatterns = Array.isArray(config.schema) ? config.schema : [config.schema];
10
12
  const tables: PhysicalTable[] = [];
11
13
  const entities: Entity[] = [];
@@ -15,47 +17,53 @@ export async function discoverSchema(config: MizzleConfig): Promise<{ tables: Ph
15
17
  const absolutePath = resolve(process.cwd(), file);
16
18
  if (scannedFiles.has(absolutePath)) return;
17
19
  scannedFiles.add(absolutePath);
18
-
20
+
19
21
  try {
20
- const imported = await import(absolutePath);
21
- for (const key in imported) {
22
- const exportVal = imported[key];
23
- if (!exportVal || typeof exportVal !== "object") continue;
24
-
25
- if (exportVal instanceof PhysicalTable || exportVal[TABLE_SYMBOLS.TABLE_NAME] !== undefined) {
26
- tables.push(exportVal as PhysicalTable);
27
- } else if (exportVal instanceof Entity || exportVal[ENTITY_SYMBOLS.ENTITY_NAME] !== undefined) {
28
- entities.push(exportVal as Entity);
29
- }
22
+ const imported = await import(absolutePath);
23
+ for (const key in imported) {
24
+ const exportVal = imported[key];
25
+ if (!exportVal || typeof exportVal !== "object") continue;
26
+
27
+ if (
28
+ exportVal instanceof PhysicalTable ||
29
+ exportVal[TABLE_SYMBOLS.TABLE_NAME] !== undefined
30
+ ) {
31
+ tables.push(exportVal as PhysicalTable);
32
+ } else if (
33
+ exportVal instanceof Entity ||
34
+ exportVal[ENTITY_SYMBOLS.ENTITY_NAME] !== undefined
35
+ ) {
36
+ entities.push(exportVal as Entity);
30
37
  }
38
+ }
31
39
  } catch (e) {
32
- console.warn(`Failed to import schema file: ${absolutePath}`, e);
40
+ console.warn(`Failed to import schema file: ${absolutePath}`, e);
33
41
  }
34
42
  };
35
43
 
36
44
  for (const pattern of schemaPatterns) {
37
45
  let searchPattern = pattern;
38
46
  let isDirectFile = false;
39
-
47
+
40
48
  try {
41
- const stats = await stat(pattern);
42
- if (stats.isDirectory()) {
43
- searchPattern = `${pattern}/**/*.{ts,js,tsx,jsx}`;
44
- } else if (stats.isFile()) {
45
- isDirectFile = true;
46
- }
49
+ const stats = await stat(pattern);
50
+ if (stats.isDirectory()) {
51
+ searchPattern = `${pattern}/**/*.{ts,js,tsx,jsx}`;
52
+ } else if (stats.isFile()) {
53
+ isDirectFile = true;
54
+ }
47
55
  } catch {
48
- // Not a file/dir, assume it's a glob pattern
56
+ // Not a file/dir, assume it's a glob pattern
49
57
  }
50
58
 
51
59
  if (isDirectFile) {
52
- await processFile(pattern);
53
- continue;
60
+ await processFile(pattern);
61
+ continue;
54
62
  }
55
63
 
56
64
  const files = await fg(searchPattern, { absolute: true });
57
65
  for (const file of files) {
58
- await processFile(file);
66
+ await processFile(file);
59
67
  }
60
68
  }
61
69