@aurios/mizzling 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/CHANGELOG.md ADDED
@@ -0,0 +1,27 @@
1
+ # mizzling
2
+
3
+ ## 1.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+ - @mizzle/shared@0.0.1
9
+
10
+ ## 1.0.1
11
+
12
+ ### Patch Changes
13
+
14
+ - fix: resolve schema discovery issues in monorepos and stabilize test suite.
15
+
16
+ ## 1.0.0
17
+
18
+ ### Major Changes
19
+
20
+ - Initial production release of Mizzle ORM and Mizzling CLI.
21
+ Includes:
22
+ - Fluent Query Builder (Select, Insert, Update, Delete)
23
+ - Native Date Support with ISO 8601 storage
24
+ - Relational Queries with Single-Table optimization
25
+ - Atomic Transactions support
26
+ - Migration CLI with schema discovery and push/pull
27
+ - High-performance tsup-based bundling
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ import{Command as He}from"commander";import*as b from"@clack/prompts";import{join as X}from"path";import{existsSync as Q}from"fs";import{DynamoDBClient as ee}from"@aws-sdk/client-dynamodb";import{fromIni as te}from"@aws-sdk/credential-provider-ini";import{NodeHttpHandler as ne}from"@smithy/node-http-handler";import oe from"http";import ae from"https";function h(e){let n={keepAlive:!0,maxSockets:1/0},t={region:e.region||"us-east-1",endpoint:e.endpoint,maxAttempts:e.maxAttempts,requestHandler:new ne({httpAgent:new oe.Agent(n),httpsAgent:new ae.Agent(n)})};return e.credentials?t.credentials=e.credentials:e.profile?t.credentials=te({profile:e.profile}):e.endpoint&&(e.endpoint.includes("localhost")||e.endpoint.includes("127.0.0.1"))&&(t.credentials={accessKeyId:"local",secretAccessKey:"local"}),new ee(t)}async function N(e="mizzle.config.ts"){let t=process.env.MIZZLE_CONFIG||X(process.cwd(),e);if(!Q(t))throw new Error(`Could not find ${e} in current directory.`);try{let o=await import(t),a=o.default||o;if(!a||typeof a!="object")throw new Error("Invalid config: default export must be an object");if(!a.schema)throw new Error("Invalid config: missing 'schema' path");if(!a.out)throw new Error("Invalid config: missing 'out' directory");let i={...a};return process.env.MIZZLE_REGION&&(i.region=process.env.MIZZLE_REGION),process.env.MIZZLE_ENDPOINT&&(i.endpoint=process.env.MIZZLE_ENDPOINT),process.env.MIZZLE_SCHEMA&&(i.schema=process.env.MIZZLE_SCHEMA),process.env.MIZZLE_OUT&&(i.out=process.env.MIZZLE_OUT),i}catch(o){if(o instanceof Error&&o.message.startsWith("Invalid config"))throw o;let a=o instanceof Error?o.message:String(o);throw new Error(`Failed to load config: ${a}`)}}var f={COLUMNS:Symbol.for("mizzle:Columns"),INDEXES:Symbol.for("mizzle:Indexes"),SORT_KEY:Symbol.for("mizzle:SortKey"),TABLE_NAME:Symbol.for("mizzle:TableName"),PARTITION_KEY:Symbol.for("mizzle:PartitionKey")},u={ENTITY_NAME:Symbol.for("mizzle:EntityName"),ENTITY_STRATEGY:Symbol.for("mizzle:EntityStrategy"),PHYSICAL_TABLE:Symbol.for("mizzle:PhysicalTable"),COLUMNS:Symbol.for("mizzle:Columns"),ENTITY_KIND:Symbol.for("mizzle:EntityKind")};var v=class{[f.TABLE_NAME]="";[f.INDEXES]=void 0;[f.PARTITION_KEY]={};[f.SORT_KEY]=void 0;static Symbol=f;constructor(e,n){this[f.TABLE_NAME]=e,this[f.PARTITION_KEY]=n.pk.build(this),this[f.SORT_KEY]=n.sk?n.sk.build(this):void 0,this[f.INDEXES]=n.indexes}},O=class{[u.ENTITY_NAME]="";[u.PHYSICAL_TABLE]={};[u.COLUMNS]={};[u.ENTITY_STRATEGY]={};static Symbol=u;constructor(e,n,t,o){this[u.ENTITY_NAME]=e,this[u.PHYSICAL_TABLE]=n,this[u.COLUMNS]=t,this[u.ENTITY_STRATEGY]=o}};var M={COLUMNS:Symbol.for("mizzle:Columns"),INDEXES:Symbol.for("mizzle:Indexes"),SORT_KEY:Symbol.for("mizzle:SortKey"),TABLE_NAME:Symbol.for("mizzle:TableName"),PARTITION_KEY:Symbol.for("mizzle:PartitionKey")},_={ENTITY_NAME:Symbol.for("mizzle:EntityName"),ENTITY_STRATEGY:Symbol.for("mizzle:EntityStrategy"),PHYSICAL_TABLE:Symbol.for("mizzle:PhysicalTable"),COLUMNS:Symbol.for("mizzle:Columns"),ENTITY_KIND:Symbol.for("mizzle:EntityKind")};import ie from"fast-glob";import{stat as re}from"fs/promises";import{resolve as se}from"path";async function w(e){let n=Array.isArray(e.schema)?e.schema:[e.schema],t=[],o=[],a=new Set,i=async r=>{let s=se(process.cwd(),r);if(!a.has(s)){a.add(s);try{let l=await import(s);for(let m in l){let c=l[m];!c||typeof c!="object"||(c instanceof v||c[M.TABLE_NAME]!==void 0?t.push(c):(c instanceof O||c[_.ENTITY_NAME]!==void 0)&&o.push(c))}}catch(l){console.warn(`Failed to import schema file: ${s}`,l)}}};for(let r of n){let s=r,l=!1;try{let c=await re(r);c.isDirectory()?s=`${r}/**/*.{ts,js,tsx,jsx}`:c.isFile()&&(l=!0)}catch{}if(l){await i(r);continue}let m=await ie(s,{absolute:!0});for(let c of m)await i(c)}return{tables:t,entities:o}}import{join as $}from"path";import{writeFile as le,readFile as ce,mkdir as me,readdir as fe}from"fs/promises";import{existsSync as k}from"fs";var j="snapshot.json";async function P(e,n){k(e)||await me(e,{recursive:!0});let t=$(e,j);await le(t,JSON.stringify(n,null,2),"utf-8")}async function Y(e){let n=$(e,j);if(!k(n))return null;let t=await ce(n,"utf-8");return JSON.parse(t)}function E(e){let n={};for(let t of e.tables){let o=e.entities.filter(i=>i[u.PHYSICAL_TABLE]===t),a=pe(t,o);n[a.TableName]=a}return{version:"1",tables:n}}async function R(e){if(!k(e))return"0000";let n=await fe(e),t=-1;for(let o of n){if(!o.endsWith(".ts"))continue;let a=o.match(/^(\d{4})_/);if(a){let i=parseInt(a[1],10);i>t&&(t=i)}}return t===-1?"0000":(t+1).toString().padStart(4,"0")}function pe(e,n){let t=e[f.TABLE_NAME],o=new Map,a=e[f.PARTITION_KEY];o.set(a.name,a.getDynamoType());let i=e[f.SORT_KEY];i&&o.set(i.name,i.getDynamoType());let r=[{AttributeName:a.name,KeyType:"HASH"}];i&&r.push({AttributeName:i.name,KeyType:"RANGE"});let s=[],l=[],m=e[f.INDEXES]||{};for(let[d,y]of Object.entries(m)){let B=y.type,p=y.config;if(B==="gsi"){if(p.pk){let T=K(p.pk,e,n);o.set(p.pk,T)}if(p.sk){let T=K(p.sk,e,n);o.set(p.sk,T)}let x={IndexName:d,KeySchema:[{AttributeName:p.pk,KeyType:"HASH"}],Projection:{ProjectionType:"ALL"}};p.sk&&x.KeySchema.push({AttributeName:p.sk,KeyType:"RANGE"}),s.push(x)}else if(B==="lsi"){if(p.sk){let T=K(p.sk,e,n);o.set(p.sk,T)}let x={IndexName:d,KeySchema:[{AttributeName:a.name,KeyType:"HASH"},{AttributeName:p.sk,KeyType:"RANGE"}],Projection:{ProjectionType:"ALL"}};l.push(x)}}let c=Array.from(o.entries()).map(([d,y])=>({AttributeName:d,AttributeType:y})).sort((d,y)=>d.AttributeName.localeCompare(y.AttributeName));s.sort((d,y)=>(d.IndexName||"").localeCompare(y.IndexName||"")),l.sort((d,y)=>(d.IndexName||"").localeCompare(y.IndexName||""));let S={TableName:t,AttributeDefinitions:c,KeySchema:r};return s.length>0&&(S.GlobalSecondaryIndexes=s),l.length>0&&(S.LocalSecondaryIndexes=l),S}function K(e,n,t){let o=n[f.PARTITION_KEY];if(o.name===e)return o.getDynamoType();let a=n[f.SORT_KEY];if(a&&a.name===e)return a.getDynamoType();for(let i of t){let r=i[u.COLUMNS];if(r){let s=r[e];if(s)return s.getDynamoType()}}throw new Error(`Could not resolve type for column '${e}' in table '${n[f.TABLE_NAME]}'. Ensure it is defined in an Entity.`)}function I(e,n){let t=[],o=E(e).tables,a=n.tables||{},i=new Set([...Object.keys(o),...Object.keys(a)]);for(let r of i){let s=o[r],l=a[r];s&&!l?t.push({type:"create",table:s}):!s&&l?t.push({type:"delete",tableName:r}):s&&l&&(ue(s,l)||t.push({type:"update",tableName:r,changes:["Changed"]}))}return t}function ue(e,n){let t=o=>{let a={...o};return a.AttributeDefinitions=[...a.AttributeDefinitions||[]].sort((i,r)=>(i.AttributeName||"").localeCompare(r.AttributeName||"")),a.GlobalSecondaryIndexes&&(a.GlobalSecondaryIndexes=[...a.GlobalSecondaryIndexes].sort((i,r)=>(i.IndexName||"").localeCompare(r.IndexName||""))),a.LocalSecondaryIndexes&&(a.LocalSecondaryIndexes=[...a.LocalSecondaryIndexes].sort((i,r)=>(i.IndexName||"").localeCompare(r.IndexName||""))),a};return JSON.stringify(t(e))===JSON.stringify(t(n))}import{join as de}from"path";import{writeFile as ye,mkdir as ge}from"fs/promises";import{existsSync as he}from"fs";import{text as be,isCancel as Se,cancel as Te,intro as Ne,outro as U}from"@clack/prompts";async function F(e){Ne("Mizzle Generate");let{config:n}=e,t=e.discoverSchema||w;try{let o=await t(n),a=n.out,i=await Y(a)||{version:"0",tables:{}},r=E(o),s=I(o,i);if(s.length===0){U("No changes detected.");return}console.log(`Detected ${s.length} changes.`);let l=await R(a),m=e.name;if(m||(m=await be({message:"Enter migration name",placeholder:"init",initialValue:"migration",validate(y){if(y.length===0)return"Name is required"}})),Se(m)){Te("Operation cancelled.");return}let c=`${l}_${m}.ts`;he(a)||await ge(a,{recursive:!0});let S=de(a,c),d=Ee(s);await ye(S,d),console.log(`Created migration: ${c}`),await P(a,r),U("Updated snapshot.json")}catch(o){console.error("Error generating migration:",o),process.exit(1)}}function Ee(e){let n=[],t=[];for(let o of e)o.type==="create"?(n.push(`// Create Table: ${o.table.TableName}`),n.push(`await db.createTable("${o.table.TableName}", ${JSON.stringify(o.table,null,2)});
3
+ `),t.unshift(`// Drop Table: ${o.table.TableName}`),t.unshift(`await db.deleteTable("${o.table.TableName}");
4
+ `)):o.type==="delete"?(n.push(`// Drop Table: ${o.tableName}`),n.push(`await db.deleteTable("${o.tableName}");
5
+ `),t.unshift(`// Create Table: ${o.tableName}`),t.unshift(`// TODO: Restore table definition for rollback
6
+ `)):o.type==="update"&&(n.push(`// Update Table: ${o.tableName}`),t.unshift(`// Revert Update Table: ${o.tableName}`));return`import { Mizzle } from "@auri/mizzle";
7
+
8
+ export async function up(db: Mizzle) {
9
+ ${n.join(" ")}
10
+ }
11
+
12
+ export async function down(db: Mizzle) {
13
+ ${t.join(" ")}
14
+ }
15
+ `}import{existsSync as xe,writeFileSync as we}from"fs";import{join as Ie}from"path";import{intro as Ae,outro as Ce,text as A,isCancel as C,cancel as z}from"@clack/prompts";async function G(){let e=Ie(process.cwd(),"mizzle.config.ts");if(xe(e)){console.log("mizzle.config.ts already exists in the current directory. Aborting.");return}Ae("Mizzle Initialization");let n=await A({message:"Where is your schema file or directory located?",placeholder:"./src/schema.ts",initialValue:"./src/schema.ts",validate:r=>{if(!r)return"Schema path is required"}});if(C(n)){z("Operation cancelled.");return}let t=await A({message:"Where should Mizzle store migrations and snapshots?",placeholder:"./migrations",initialValue:"./migrations",validate:r=>{if(!r)return"Output directory is required"}});if(C(t)){z("Operation cancelled.");return}let o=await A({message:"Which AWS region do you want to use?",placeholder:"us-east-1",initialValue:"us-east-1"});if(C(o)){z("Operation cancelled.");return}let a=await A({message:"Do you want to use a custom endpoint (e.g., for local development)?",placeholder:"http://localhost:8000 (optional)"});if(C(a)){z("Operation cancelled.");return}let i=`import { defineConfig } from "@auri/mizzle";
16
+
17
+ export default defineConfig({
18
+ schema: "${n}",
19
+ out: "${t}",
20
+ region: "${o}",
21
+ ${a?`endpoint: "${a}",`:'// endpoint: "http://localhost:8000",'}
22
+ });
23
+ `;we(e,i),Ce("mizzle.config.ts created successfully!")}import{ListTablesCommand as ze,DescribeTableCommand as De}from"@aws-sdk/client-dynamodb";async function D(e){let n={},t,o=[];do{let a=await e.send(new ze({ExclusiveStartTableName:t}));a.TableNames&&o.push(...a.TableNames),t=a.LastEvaluatedTableName}while(t);for(let a of o)try{let i=await e.send(new De({TableName:a}));if(i.Table){let r=Le(i.Table);n[a]=r}}catch(i){console.warn(`Failed to describe table ${a}:`,i)}return{version:"remote",tables:n}}function Le(e){let n={TableName:e.TableName,AttributeDefinitions:e.AttributeDefinitions?.map(t=>({AttributeName:t.AttributeName,AttributeType:t.AttributeType})).sort((t,o)=>t.AttributeName.localeCompare(o.AttributeName))||[],KeySchema:e.KeySchema?.map(t=>({AttributeName:t.AttributeName,KeyType:t.KeyType}))||[]};return e.GlobalSecondaryIndexes&&e.GlobalSecondaryIndexes.length>0&&(n.GlobalSecondaryIndexes=e.GlobalSecondaryIndexes.map(t=>({IndexName:t.IndexName,KeySchema:t.KeySchema?.map(o=>({AttributeName:o.AttributeName,KeyType:o.KeyType})),Projection:t.Projection?{ProjectionType:t.Projection.ProjectionType}:void 0})).sort((t,o)=>t.IndexName.localeCompare(o.IndexName))),e.LocalSecondaryIndexes&&e.LocalSecondaryIndexes.length>0&&(n.LocalSecondaryIndexes=e.LocalSecondaryIndexes.map(t=>({IndexName:t.IndexName,KeySchema:t.KeySchema?.map(o=>({AttributeName:o.AttributeName,KeyType:o.KeyType})),Projection:t.Projection?{ProjectionType:t.Projection.ProjectionType}:void 0})).sort((t,o)=>t.IndexName.localeCompare(o.IndexName))),n}import{CreateTableCommand as ve}from"@aws-sdk/client-dynamodb";import{confirm as Oe,isCancel as Me,cancel as _e,intro as Ke,outro as H,spinner as ke}from"@clack/prompts";async function Z(e){Ke("Mizzle Push");let{config:n,force:t}=e,o=e.discoverSchema||w,a=e.client||h(n);try{let i=await o(n),r=await D(a),s=I(i,r);if(s.length===0){H("Remote is up to date.");return}console.log(`Pushing ${s.length} changes to remote...`);let l=t;if(l||(l=await Oe({message:"Do you want to apply these changes?"})),Me(l)||!l){_e("Operation cancelled.");return}let m=ke();m.start("Pushing changes...");for(let c of s)c.type==="create"?(m.message(`Creating table: ${c.table.TableName}`),await a.send(new ve({TableName:c.table.TableName,AttributeDefinitions:c.table.AttributeDefinitions,KeySchema:c.table.KeySchema,GlobalSecondaryIndexes:c.table.GlobalSecondaryIndexes,LocalSecondaryIndexes:c.table.LocalSecondaryIndexes,BillingMode:"PAY_PER_REQUEST"}))):c.type==="delete"?console.log(`Untracked table found: ${c.tableName} (Skipping deletion)`):c.type==="update"&&m.message(`Updating table: ${c.tableName} (Not fully implemented)`);m.stop("Push complete."),H("Done")}catch(i){console.error("Error pushing changes:",i),process.exit(1)}}import"@aws-sdk/client-dynamodb";import{intro as Pe,outro as V,spinner as Ye}from"@clack/prompts";async function W(e){Pe("Mizzle List Tables");let n=e.client||h(e.config),t=Ye();t.start("Fetching remote tables...");try{let o=await D(n);t.stop("Fetched remote tables.");let a=Object.values(o.tables);if(a.length===0){console.log("No tables found in the remote environment."),V("Done");return}console.log(`Found ${a.length} tables:`);for(let i of a){console.log(`- ${i.TableName}`);let r=i.KeySchema.find(l=>l.KeyType==="HASH")?.AttributeName,s=i.KeySchema.find(l=>l.KeyType==="RANGE")?.AttributeName;console.log(` PK: ${r}, SK: ${s||"(none)"}`),i.GlobalSecondaryIndexes&&i.GlobalSecondaryIndexes.length>0&&console.log(` GSIs: ${i.GlobalSecondaryIndexes.map(l=>l.IndexName).join(", ")}`)}V("Done")}catch(o){t.stop("Failed to fetch tables."),console.error("Error listing tables:",o),process.exit(1)}}import{ListTablesCommand as Re,DeleteTableCommand as Be}from"@aws-sdk/client-dynamodb";import{intro as $e,outro as L,multiselect as je,confirm as Ue,isCancel as q,cancel as Fe,spinner as Ge}from"@clack/prompts";async function J(e){$e("Mizzle Drop Tables");let n=e.client||h(e.config);try{let t=new Re({}),a=(await n.send(t)).TableNames||[];if(a.length===0){console.log("No tables found in the remote environment."),L("Done");return}let i=await je({message:"Select tables to DELETE (This action is irreversible!)",options:a.map(m=>({value:m,label:m}))});if(q(i)){Fe("Operation cancelled.");return}let r=i;if(r.length===0){console.log("No tables selected."),L("Done");return}let s=await Ue({message:`Are you SURE you want to delete ${r.length} table(s)?`});if(q(s)||!s){console.log("Operation cancelled."),L("Done");return}let l=Ge();l.start("Deleting tables...");for(let m of r)l.message(`Deleting ${m}...`),await n.send(new Be({TableName:m}));l.stop("All selected tables deleted."),L("Done")}catch(t){console.error("Error dropping tables:",t),process.exit(1)}}var g=new He;g.name("mizzle").description("Mizzle Migration CLI").version("0.0.1");g.command("init").description("Initialize Mizzle configuration").action(async()=>{try{await G()}catch(e){let n=e instanceof Error?e.message:String(e);b.log.error(n),process.exit(1)}});g.command("generate").description("Generate a new migration snapshot and script").option("-n, --name <name>","Migration name").action(async e=>{try{let n=await N();await F({config:n,name:e.name})}catch(n){let t=n instanceof Error?n.message:String(n);b.log.error(t),process.exit(1)}});g.command("push").description("Directly apply schema changes to the target DynamoDB environment").option("-y, --yes","Skip confirmation").action(async e=>{try{let n=await N();await Z({config:n,force:e.yes})}catch(n){let t=n instanceof Error?n.message:String(n);b.log.error(t),process.exit(1)}});g.command("list").description("List all existing DynamoDB tables in the environment").action(async()=>{try{let e=await N();await W({config:e})}catch(e){let n=e instanceof Error?e.message:String(e);b.log.error(n),process.exit(1)}});g.command("drop").description("Interactive command to select and delete DynamoDB tables").action(async()=>{try{let e=await N();await J({config:e})}catch(e){let n=e instanceof Error?e.message:String(e);b.log.error(n),process.exit(1)}});g.parse();
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@aurios/mizzling",
3
+ "version": "1.0.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "bin": {
7
+ "mizzling": "./dist/index.js"
8
+ },
9
+ "author": {
10
+ "name": "Lucas",
11
+ "url": "https://github.com/realfakenerd"
12
+ },
13
+ "repository": {
14
+ "url": "https://github.com/realfakenerd/mizzle"
15
+ },
16
+ "scripts": {
17
+ "check": "tsc --noEmit",
18
+ "build": "tsup",
19
+ "lint": "eslint \"src/**/*.ts\""
20
+ },
21
+ "dependencies": {
22
+ "@aws-sdk/client-dynamodb": "3.962.0",
23
+ "@aws-sdk/credential-provider-ini": "3.962.0",
24
+ "@aws-sdk/lib-dynamodb": "3.962.0",
25
+ "@clack/prompts": "^0.11.0",
26
+ "@mizzle/shared": "workspace:*",
27
+ "@smithy/node-http-handler": "^4.4.7",
28
+ "commander": "^14.0.2",
29
+ "fast-glob": "^3.3.3"
30
+ },
31
+ "devDependencies": {
32
+ "@mizzle/eslint-config": "workspace:*",
33
+ "@mizzle/tsconfig": "workspace:*",
34
+ "@aurios/mizzle": "workspace:*",
35
+ "tsup": "^8.5.1"
36
+ }
37
+ }
@@ -0,0 +1,71 @@
1
+ import { type MizzleConfig, getClient } from "../config";
2
+ import { DynamoDBClient, ListTablesCommand, DeleteTableCommand } from "@aws-sdk/client-dynamodb";
3
+ import { intro, outro, multiselect, confirm, isCancel, cancel, spinner } from "@clack/prompts";
4
+
5
+ interface DropOptions {
6
+ config: MizzleConfig;
7
+ client?: DynamoDBClient;
8
+ }
9
+
10
+ export async function dropCommand(options: DropOptions) {
11
+ intro("Mizzle Drop Tables");
12
+
13
+ const client = options.client || getClient(options.config);
14
+
15
+ try {
16
+ // 1. Fetch tables
17
+ const listCmd = new ListTablesCommand({});
18
+ const listRes = await client.send(listCmd);
19
+ const tableNames = listRes.TableNames || [];
20
+
21
+ if (tableNames.length === 0) {
22
+ console.log("No tables found in the remote environment.");
23
+ outro("Done");
24
+ return;
25
+ }
26
+
27
+ // 2. Select tables
28
+ const selectedTables = await multiselect({
29
+ message: "Select tables to DELETE (This action is irreversible!)",
30
+ options: tableNames.map(name => ({ value: name, label: name }))
31
+ });
32
+
33
+ if (isCancel(selectedTables)) {
34
+ cancel("Operation cancelled.");
35
+ return;
36
+ }
37
+
38
+ const tablesToDelete = selectedTables as string[];
39
+ if (tablesToDelete.length === 0) {
40
+ console.log("No tables selected.");
41
+ outro("Done");
42
+ return;
43
+ }
44
+
45
+ // 3. Confirm
46
+ const confirmed = await confirm({
47
+ message: `Are you SURE you want to delete ${tablesToDelete.length} table(s)?`
48
+ });
49
+
50
+ if (isCancel(confirmed) || !confirmed) {
51
+ console.log("Operation cancelled.");
52
+ outro("Done");
53
+ return;
54
+ }
55
+
56
+ // 4. Delete
57
+ const s = spinner();
58
+ s.start("Deleting tables...");
59
+
60
+ for (const tableName of tablesToDelete) {
61
+ s.message(`Deleting ${tableName}...`);
62
+ await client.send(new DeleteTableCommand({ TableName: tableName }));
63
+ }
64
+
65
+ s.stop("All selected tables deleted.");
66
+ outro("Done");
67
+ } catch (error) {
68
+ console.error("Error dropping tables:", error);
69
+ process.exit(1);
70
+ }
71
+ }
@@ -0,0 +1,116 @@
1
+ import { type MizzleConfig } from "../config";
2
+ import { discoverSchema } from "../discovery";
3
+ import { loadSnapshot, saveSnapshot, generateSnapshot, getNextMigrationVersion } from "@aurios/mizzle/snapshot";
4
+ import { compareSchema, type SchemaChange } from "@aurios/mizzle/diff";
5
+ import { join } from "path";
6
+ import { writeFile, mkdir } from "fs/promises";
7
+ import { existsSync } from "fs";
8
+ import { text, isCancel, cancel, intro, outro } from "@clack/prompts";
9
+
10
+ interface GenerateOptions {
11
+ config: MizzleConfig;
12
+ name?: string;
13
+ discoverSchema?: typeof discoverSchema;
14
+ }
15
+
16
+ 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);
76
+ }
77
+ }
78
+
79
+ function generateMigrationScript(changes: SchemaChange[]): string {
80
+ const upSteps: string[] = [];
81
+ const downSteps: string[] = [];
82
+
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)});
87
+ `);
88
+
89
+ downSteps.unshift(`// Drop Table: ${change.table.TableName}`);
90
+ downSteps.unshift(`await db.deleteTable("${change.table.TableName}");
91
+ `);
92
+ } else if (change.type === "delete") {
93
+ upSteps.push(`// Drop Table: ${change.tableName}`);
94
+ upSteps.push(`await db.deleteTable("${change.tableName}");
95
+ `);
96
+
97
+ downSteps.unshift(`// Create Table: ${change.tableName}`);
98
+ downSteps.unshift(`// TODO: Restore table definition for rollback
99
+ `);
100
+ } else if (change.type === "update") {
101
+ upSteps.push(`// Update Table: ${change.tableName}`);
102
+ downSteps.unshift(`// Revert Update Table: ${change.tableName}`);
103
+ }
104
+ }
105
+
106
+ return `import { Mizzle } from "@aurios/mizzle";
107
+
108
+ export async function up(db: Mizzle) {
109
+ ${upSteps.join(" ")}
110
+ }
111
+
112
+ export async function down(db: Mizzle) {
113
+ ${downSteps.join(" ")}
114
+ }
115
+ `;
116
+ }
@@ -0,0 +1,77 @@
1
+ import { existsSync, writeFileSync } from "fs";
2
+ import { join } from "path";
3
+ import { intro, outro, text, isCancel, cancel } from "@clack/prompts";
4
+
5
+ export async function initCommand() {
6
+ const configPath = join(process.cwd(), "mizzle.config.ts");
7
+
8
+ if (existsSync(configPath)) {
9
+ console.log("mizzle.config.ts already exists in the current directory. Aborting.");
10
+ return;
11
+ }
12
+
13
+ intro("Mizzle Initialization");
14
+
15
+ const schema = await text({
16
+ message: "Where is your schema file or directory located?",
17
+ placeholder: "./src/schema.ts",
18
+ initialValue: "./src/schema.ts",
19
+ validate: (value) => {
20
+ if (!value) return "Schema path is required";
21
+ },
22
+ });
23
+
24
+ if (isCancel(schema)) {
25
+ cancel("Operation cancelled.");
26
+ return;
27
+ }
28
+
29
+ const out = await text({
30
+ message: "Where should Mizzle store migrations and snapshots?",
31
+ placeholder: "./migrations",
32
+ initialValue: "./migrations",
33
+ validate: (value) => {
34
+ if (!value) return "Output directory is required";
35
+ },
36
+ });
37
+
38
+ if (isCancel(out)) {
39
+ cancel("Operation cancelled.");
40
+ return;
41
+ }
42
+
43
+ const region = await text({
44
+ message: "Which AWS region do you want to use?",
45
+ placeholder: "us-east-1",
46
+ initialValue: "us-east-1",
47
+ });
48
+
49
+ if (isCancel(region)) {
50
+ cancel("Operation cancelled.");
51
+ return;
52
+ }
53
+
54
+ const endpoint = await text({
55
+ message: "Do you want to use a custom endpoint (e.g., for local development)?",
56
+ placeholder: "http://localhost:8000 (optional)",
57
+ });
58
+
59
+ if (isCancel(endpoint)) {
60
+ cancel("Operation cancelled.");
61
+ return;
62
+ }
63
+
64
+ const configContent = `import { defineConfig } from "@aurios/mizzle";
65
+
66
+ export default defineConfig({
67
+ schema: "${schema}",
68
+ out: "${out}",
69
+ region: "${region}",
70
+ ${endpoint ? `endpoint: "${endpoint}",` : "// endpoint: \"http://localhost:8000\","}
71
+ });
72
+ `;
73
+
74
+ writeFileSync(configPath, configContent);
75
+
76
+ outro(`mizzle.config.ts created successfully!`);
77
+ }
@@ -0,0 +1,48 @@
1
+ import { type MizzleConfig, getClient } from "../config";
2
+ import { getRemoteSnapshot } from "@aurios/mizzle/introspection";
3
+ import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
4
+ import { intro, outro, spinner } from "@clack/prompts";
5
+
6
+ interface ListOptions {
7
+ config: MizzleConfig;
8
+ client?: DynamoDBClient;
9
+ }
10
+
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);
47
+ }
48
+ }
@@ -0,0 +1,75 @@
1
+ import { type MizzleConfig, getClient } from "../config";
2
+ import { discoverSchema } from "../discovery";
3
+ import { compareSchema } from "@aurios/mizzle/diff";
4
+ import { getRemoteSnapshot } from "@aurios/mizzle/introspection";
5
+ import { DynamoDBClient, CreateTableCommand, DeleteTableCommand } from "@aws-sdk/client-dynamodb";
6
+ import { confirm, isCancel, cancel, intro, outro, spinner } from "@clack/prompts";
7
+
8
+ interface PushOptions {
9
+ config: MizzleConfig;
10
+ force?: boolean;
11
+ discoverSchema?: typeof discoverSchema;
12
+ client?: DynamoDBClient;
13
+ }
14
+
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);
21
+
22
+ try {
23
+ const schema = await discover(config);
24
+ const remoteSnapshot = await getRemoteSnapshot(client);
25
+
26
+ const changes = compareSchema(schema, remoteSnapshot);
27
+
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
+
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
+
42
+ if (isCancel(shouldContinue) || !shouldContinue) {
43
+ cancel("Operation cancelled.");
44
+ return;
45
+ }
46
+
47
+ const s = spinner();
48
+ s.start("Pushing changes...");
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);
74
+ }
75
+ }
package/src/config.ts ADDED
@@ -0,0 +1,163 @@
1
+ import { join } from "path";
2
+ import { existsSync } from "fs";
3
+ import { DynamoDBClient, type DynamoDBClientConfig } from "@aws-sdk/client-dynamodb";
4
+ import { fromIni } from "@aws-sdk/credential-provider-ini";
5
+ import { NodeHttpHandler } from "@smithy/node-http-handler";
6
+ import http from "http";
7
+ import https from "https";
8
+
9
+ /**
10
+ * Configuration for Mizzle ORM.
11
+ */
12
+ export interface MizzleConfig {
13
+ /**
14
+ * Path or glob pattern(s) to the schema files.
15
+ */
16
+ schema: string | string[];
17
+ /**
18
+ * Directory where generated migrations and snapshots will be stored.
19
+ */
20
+ out: string;
21
+ /**
22
+ * AWS Region to connect to. Defaults to "us-east-1" if not specified.
23
+ * Can be overridden by MIZZLE_REGION environment variable.
24
+ */
25
+ region?: string;
26
+ /**
27
+ * Optional custom endpoint for DynamoDB (e.g., for local development).
28
+ * Can be overridden by MIZZLE_ENDPOINT environment variable.
29
+ */
30
+ endpoint?: string;
31
+ /**
32
+ * Explicit AWS credentials. If provided, these will be used instead of the
33
+ * default credential provider chain or profile.
34
+ */
35
+ credentials?: {
36
+ /**
37
+ * AWS Access Key ID.
38
+ */
39
+ accessKeyId: string;
40
+ /**
41
+ * AWS Secret Access Key.
42
+ */
43
+ secretAccessKey: string;
44
+ /**
45
+ * Optional AWS Session Token.
46
+ */
47
+ sessionToken?: string;
48
+ };
49
+ /**
50
+ * AWS Profile name to use for credentials.
51
+ */
52
+ profile?: string;
53
+ /**
54
+ * Maximum number of retry attempts for DynamoDB requests.
55
+ */
56
+ maxAttempts?: number;
57
+ }
58
+
59
+ /**
60
+ * Helper function to define the configuration with type safety.
61
+ *
62
+ * @param config The Mizzle configuration object.
63
+ * @returns The same configuration object, validated by TypeScript.
64
+ */
65
+ export function defineConfig(config: MizzleConfig): MizzleConfig {
66
+ return config;
67
+ }
68
+
69
+ /**
70
+ * Creates a configured DynamoDBClient instance based on the provided configuration.
71
+ *
72
+ * It prioritizes credentials in the following order:
73
+ * 1. Explicitly provided `credentials` object.
74
+ * 2. Explicitly provided AWS `profile`.
75
+ * 3. Default "local" credentials if the endpoint is localhost/127.0.0.1.
76
+ * 4. Default AWS SDK credential provider chain (environment variables, IAM roles, etc.).
77
+ *
78
+ * @param config The Mizzle configuration.
79
+ * @returns A configured DynamoDBClient instance.
80
+ */
81
+ export function getClient(config: MizzleConfig): DynamoDBClient {
82
+ const agentOptions = {
83
+ keepAlive: true,
84
+ maxSockets: Infinity,
85
+ };
86
+
87
+ const clientConfig: DynamoDBClientConfig = {
88
+ region: config.region || "us-east-1",
89
+ endpoint: config.endpoint,
90
+ maxAttempts: config.maxAttempts,
91
+ requestHandler: new NodeHttpHandler({
92
+ httpAgent: new http.Agent(agentOptions),
93
+ httpsAgent: new https.Agent(agentOptions),
94
+ }),
95
+ };
96
+
97
+ if (config.credentials) {
98
+ clientConfig.credentials = config.credentials;
99
+ } else if (config.profile) {
100
+ clientConfig.credentials = fromIni({ profile: config.profile });
101
+ } else if (
102
+ config.endpoint &&
103
+ (config.endpoint.includes("localhost") || config.endpoint.includes("127.0.0.1"))
104
+ ) {
105
+ clientConfig.credentials = {
106
+ accessKeyId: "local",
107
+ secretAccessKey: "local",
108
+ };
109
+ }
110
+
111
+ return new DynamoDBClient(clientConfig);
112
+ }
113
+
114
+ /**
115
+ * Loads the Mizzle configuration from a file (defaulting to mizzle.config.ts).
116
+ *
117
+ * Environment variables (MIZZLE_REGION, MIZZLE_ENDPOINT, MIZZLE_SCHEMA, MIZZLE_OUT)
118
+ * will override values provided in the configuration file.
119
+ *
120
+ * @param configName The name of the config file to load.
121
+ * @returns A promise that resolves to the loaded and overridden configuration.
122
+ * @throws Error if the configuration file is missing or invalid.
123
+ */
124
+ export async function loadConfig(configName = "mizzle.config.ts"): Promise<MizzleConfig> {
125
+ const envConfig = process.env.MIZZLE_CONFIG;
126
+ const configPath = envConfig || join(process.cwd(), configName);
127
+
128
+ if (!existsSync(configPath)) {
129
+ throw new Error(`Could not find ${configName} in current directory.`);
130
+ }
131
+
132
+ try {
133
+ const imported = await import(configPath);
134
+ const config = imported.default || imported;
135
+
136
+ if (!config || typeof config !== "object") {
137
+ throw new Error("Invalid config: default export must be an object");
138
+ }
139
+
140
+ if (!config.schema) {
141
+ throw new Error("Invalid config: missing 'schema' path");
142
+ }
143
+
144
+ if (!config.out) {
145
+ throw new Error("Invalid config: missing 'out' directory");
146
+ }
147
+
148
+ const finalConfig = { ...config } as MizzleConfig;
149
+
150
+ if (process.env.MIZZLE_REGION) finalConfig.region = process.env.MIZZLE_REGION;
151
+ if (process.env.MIZZLE_ENDPOINT) finalConfig.endpoint = process.env.MIZZLE_ENDPOINT;
152
+ if (process.env.MIZZLE_SCHEMA) finalConfig.schema = process.env.MIZZLE_SCHEMA;
153
+ if (process.env.MIZZLE_OUT) finalConfig.out = process.env.MIZZLE_OUT;
154
+
155
+ return finalConfig;
156
+ } catch (error) {
157
+ if (error instanceof Error && error.message.startsWith("Invalid config")) {
158
+ throw error;
159
+ }
160
+ const message = error instanceof Error ? error.message : String(error);
161
+ throw new Error(`Failed to load config: ${message}`);
162
+ }
163
+ }
@@ -0,0 +1,63 @@
1
+ import { type MizzleConfig } from "./config";
2
+ import { PhysicalTable, Entity } from "@aurios/mizzle/table";
3
+ import { TABLE_SYMBOLS, ENTITY_SYMBOLS } from "@mizzle/shared";
4
+ import fg from "fast-glob";
5
+ import { stat } from "fs/promises";
6
+ import { resolve } from "path";
7
+
8
+ export async function discoverSchema(config: MizzleConfig): Promise<{ tables: PhysicalTable[], entities: Entity[] }> {
9
+ const schemaPatterns = Array.isArray(config.schema) ? config.schema : [config.schema];
10
+ const tables: PhysicalTable[] = [];
11
+ const entities: Entity[] = [];
12
+ const scannedFiles = new Set<string>();
13
+
14
+ const processFile = async (file: string) => {
15
+ const absolutePath = resolve(process.cwd(), file);
16
+ if (scannedFiles.has(absolutePath)) return;
17
+ scannedFiles.add(absolutePath);
18
+
19
+ 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
+ }
30
+ }
31
+ } catch (e) {
32
+ console.warn(`Failed to import schema file: ${absolutePath}`, e);
33
+ }
34
+ };
35
+
36
+ for (const pattern of schemaPatterns) {
37
+ let searchPattern = pattern;
38
+ let isDirectFile = false;
39
+
40
+ 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
+ }
47
+ } catch {
48
+ // Not a file/dir, assume it's a glob pattern
49
+ }
50
+
51
+ if (isDirectFile) {
52
+ await processFile(pattern);
53
+ continue;
54
+ }
55
+
56
+ const files = await fg(searchPattern, { absolute: true });
57
+ for (const file of files) {
58
+ await processFile(file);
59
+ }
60
+ }
61
+
62
+ return { tables, entities };
63
+ }
package/src/index.ts ADDED
@@ -0,0 +1,88 @@
1
+ import { Command } from "commander";
2
+ import * as p from "@clack/prompts";
3
+ import { loadConfig } from "./config";
4
+ import { generateCommand } from "./commands/generate";
5
+ import { initCommand } from "./commands/init";
6
+ import { pushCommand } from "./commands/push";
7
+ import { listCommand } from "./commands/list";
8
+ import { dropCommand } from "./commands/drop";
9
+
10
+ const program = new Command();
11
+
12
+ program
13
+ .name("mizzle")
14
+ .description("Mizzle Migration CLI")
15
+ .version("0.0.1");
16
+
17
+ program
18
+ .command("init")
19
+ .description("Initialize Mizzle configuration")
20
+ .action(async () => {
21
+ try {
22
+ await initCommand();
23
+ } catch (e) {
24
+ const message = e instanceof Error ? e.message : String(e);
25
+ p.log.error(message);
26
+ process.exit(1);
27
+ }
28
+ });
29
+
30
+ program
31
+ .command("generate")
32
+ .description("Generate a new migration snapshot and script")
33
+ .option("-n, --name <name>", "Migration name")
34
+ .action(async (options) => {
35
+ try {
36
+ const config = await loadConfig();
37
+ await generateCommand({ config, name: options.name });
38
+ } catch (e) {
39
+ const message = e instanceof Error ? e.message : String(e);
40
+ p.log.error(message);
41
+ process.exit(1);
42
+ }
43
+ });
44
+
45
+ program
46
+ .command("push")
47
+ .description("Directly apply schema changes to the target DynamoDB environment")
48
+ .option("-y, --yes", "Skip confirmation")
49
+ .action(async (options) => {
50
+ try {
51
+ const config = await loadConfig();
52
+ await pushCommand({ config, force: options.yes });
53
+ } catch (e) {
54
+ const message = e instanceof Error ? e.message : String(e);
55
+ p.log.error(message);
56
+ process.exit(1);
57
+ }
58
+ });
59
+
60
+ program
61
+ .command("list")
62
+ .description("List all existing DynamoDB tables in the environment")
63
+ .action(async () => {
64
+ try {
65
+ const config = await loadConfig();
66
+ await listCommand({ config });
67
+ } catch (e) {
68
+ const message = e instanceof Error ? e.message : String(e);
69
+ p.log.error(message);
70
+ process.exit(1);
71
+ }
72
+ });
73
+
74
+ program
75
+ .command("drop")
76
+ .description("Interactive command to select and delete DynamoDB tables")
77
+ .action(async () => {
78
+ try {
79
+ const config = await loadConfig();
80
+ await dropCommand({ config });
81
+ } catch (e) {
82
+ const message = e instanceof Error ? e.message : String(e);
83
+ p.log.error(message);
84
+ process.exit(1);
85
+ }
86
+ });
87
+
88
+ program.parse();
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "@mizzle/tsconfig/base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "baseUrl": ".",
6
+ "paths": {
7
+ "@mizzle/shared": ["../shared/src/index.ts"],
8
+ "mizzle": ["../mizzle/src/index.ts"],
9
+ "mizzle/*": ["../mizzle/src/*"]
10
+ }
11
+ },
12
+ "include": ["src"]
13
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts'],
5
+ format: ['esm'],
6
+ clean: true,
7
+ noExternal: ['@mizzle/shared', 'mizzle'],
8
+ minify: true,
9
+ banner: {
10
+ js: '#!/usr/bin/env node',
11
+ },
12
+ });