CLI tool for schema management and migrations for Superfunctions libraries
npm install @superfunctions/cliCommand-line tool for managing Superfunctions library schemas and migrations.
``bash
npm install @superfunctions/cli --save-dev
---
For Downstream Users (Using Superfunctions Libraries)
$3
#### 1. Initialize Your Libraries
Configure your libraries once in your application code:
`typescript
// src/conduct.ts
import { createConductBackend } from 'conduct';
import { adapter } from './db';export const conduct = createConductBackend({
database: adapter,
storage: s3Storage,
// Schema customizations
additionalFields: {
project: {
department: { type: 'string', required: false },
priority: { type: 'number', required: false },
}
},
plugins: [
conductAuditPlugin(),
]
});
``typescript
// src/auth.ts
import { createAuthFn } from '@superfunctions/authfn';
import { adapter } from './db';export const auth = createAuthFn({
database: adapter,
additionalFields: {
user: {
role: { type: 'string', required: true },
department: { type: 'string', required: false },
}
},
plugins: [
authFnTwoFactorPlugin(),
],
});
`#### 2. Configure CLI
Create
superfunctions.config.js:Option A: Explicit file paths (recommended for production)
`javascript
import { defineConfig } from '@superfunctions/cli';export default defineConfig({
adapter: {
type: 'drizzle',
drizzle: {
dialect: 'postgres',
connectionString: process.env.DATABASE_URL,
}
},
// Point to files that initialize libraries
libraries: [
'./src/conduct.ts',
'./src/auth.ts',
],
migrationsDir: './migrations',
});
`Option B: Auto-discovery (good for small projects/prototyping)
`javascript
import { defineConfig } from '@superfunctions/cli';export default defineConfig({
adapter: {
type: 'drizzle',
drizzle: {
dialect: 'postgres',
connectionString: process.env.DATABASE_URL,
}
},
// Auto-discover initialization files
autoDiscover: true, // Scans src/, lib/, server/, app/ directories
migrationsDir: './migrations',
});
`Option C: Custom auto-discovery patterns
`javascript
import { defineConfig } from '@superfunctions/cli';export default defineConfig({
adapter: { / ... / },
// Custom patterns
autoDiscover: {
patterns: [
'src/*/.ts',
'api/*/.ts',
],
exclude: [
'*/.test.ts',
'*/.spec.ts',
'/examples/',
]
},
migrationsDir: './migrations',
});
`#### 3. Generate Migrations
`bash
npx superfunctions generate
`Output:
`
š Parsing library initialization files...
ā
./src/conduct.ts: Found 1 initialization(s)
ā
./src/auth.ts: Found 1 initialization(s)š¦ Found 2 library initialization(s):
- conduct (createConductBackend)
- authfn (createAuthFn)
š Processing conduct...
ā
Generated schema (v1, 8 tables)
š Processing authfn...
ā
Generated schema (v1, 4 tables)
š¾ Writing migration files...
ā
./migrations/1234567890_conduct_v1.sql
ā
./migrations/1234567891_authfn_v1.sql
⨠Generated 2 migration file(s)
š” Next steps:
1. Review the generated migration files
2. Apply migrations using your ORM tool:
npx drizzle-kit push
`#### 4. Apply Migrations
Use your ORM tool to apply migrations:
`bash
npx drizzle-kit push
or
npx prisma migrate deploy
or
npx kysely migrate latest
`$3
ā
Single source of truth: Configure libraries once in your app code
ā
No duplication: CLI reads from your actual initialization
ā
Type safety: Full IDE autocomplete and type checking
ā
Less boilerplate: No separate
*.config.ts files needed ---
For Library Authors (Building with @superfunctions/db)
If you're building a library that uses
@superfunctions/db adapter, you need to export a getSchema function for CLI integration.$3
`typescript
// your-library/src/schema/index.ts
import type { TableSchema } from '@superfunctions/db';export interface YourLibraryConfig {
// Your library's config structure
additionalFields?: Record>;
plugins?: Plugin[];
// ... other options
}
/**
* Generate schema from config
* This is called by @superfunctions/cli to generate migrations
*/
export function getSchema(config: YourLibraryConfig = {}): {
version: number;
schemas: TableSchema[];
} {
const baseSchema = getBaseSchema(config);
const pluginSchemas = getPluginSchemas(config);
return {
version: 1, // Increment when schema changes
schemas: [
...baseSchema,
...pluginSchemas,
],
};
}
function getBaseSchema(config: YourLibraryConfig): TableSchema[] {
return [
{
modelName: 'your_table',
fields: {
id: { type: 'number', required: true },
name: { type: 'string', required: true },
...config.additionalFields?.your_table, // Apply custom fields
},
},
// ... more tables
];
}
function getPluginSchemas(config: YourLibraryConfig): TableSchema[] {
if (!config.plugins || config.plugins.length === 0) {
return [];
}
const schemas: TableSchema[] = [];
for (const plugin of config.plugins) {
if (plugin.schema) {
// Plugins can contribute additional tables
schemas.push(...Object.values(plugin.schema));
}
}
return schemas;
}
`$3
`typescript
// your-library/src/index.ts// Runtime exports
export { createYourLibrary } from './main.js';
export type { YourLibraryConfig } from './types.js';
// CLI integration export
export { getSchema } from './schema/index.js';
export type { YourLibraryConfig as YourLibrarySchemaConfig } from './schema/index.js';
`$3
Add
superfunctions metadata to your package.json:`json
{
"name": "@your-org/your-library",
"version": "1.0.0",
"superfunctions": {
"initFunction": "createYourLibrary",
"schemaVersion": 1
},
"exports": {
".": "./dist/index.js"
}
}
`Fields:
-
initFunction (required): Name of your library's initialization function
- schemaVersion (optional): Current schema version numberThe CLI will automatically discover your library by scanning
node_modules for packages with this metadata. No registration with the CLI repo needed!$3
Plugins can add their own tables:
`typescript
// Plugin example
export function yourLibraryAuditPlugin() {
return {
name: 'audit',
schema: {
audit_log: {
modelName: 'audit_logs',
fields: {
id: { type: 'number', required: true },
entity_type: { type: 'string', required: true },
entity_id: { type: 'string', required: true },
action: { type: 'string', required: true },
user_id: { type: 'string', required: false },
timestamp: { type: 'date', required: true },
changes: { type: 'json', required: false },
},
},
},
// ... plugin logic
};
}
`When users enable the plugin in their config:
`typescript
createYourLibrary({
plugins: [
yourLibraryAuditPlugin(),
]
});
`The CLI will automatically include the audit_logs table in the generated migrations.
---
CLI Commands
$3
Generate migration files from schema diffs.
`bash
Generate for all libraries
npx superfunctions generateGenerate for specific library
npx superfunctions generate conductDry run (preview without writing)
npx superfunctions generate --dry-run
`$3
Check current schema versions and migration status.
`bash
npx superfunctions status
`Output:
`
š Schema StatusLibrary: conduct
Current version: 1
Latest version: 1
Status: ā
Up-to-date
Library: authfn
Current version: 0
Latest version: 1
Status: ā ļø Needs migration
`$3
Validate configuration file structure.
`bash
npx superfunctions validate
`---
How It Works
$3
1. Parse initialization files: CLI reads files specified in
libraries array
2. Extract configs: Uses TypeScript AST parser to extract library initialization calls and their configs
3. Generate schemas: For each library, calls libraryPackage.getSchema(extractedConfig)
4. Introspect database: Connects to database and reads current schema
5. Diff schemas: Compares desired schema with current state
6. Generate migrations: Creates ORM-specific SQL migration files
7. Track versions: Stores schema versions in database for tracking$3
The CLI can extract configs from:
ā
Object literals
`typescript
createLibrary({
additionalFields: { ... },
plugins: [ ... ]
});
`ā
Variable references
`typescript
const config = { ... };
createLibrary(config);
`ā
Spread operators
`typescript
const baseConfig = { ... };
createLibrary({ ...baseConfig, plugins: [ ... ] });
`ā
Plugin function calls
`typescript
plugins: [
conductAuditPlugin(),
conductMetricsPlugin({ ... })
]
`ā ļø Runtime values are marked as
undefined
`typescript
// These cannot be evaluated statically:
connectionString: process.env.DATABASE_URL // ā undefined
name: ${prefix}_table // ā undefined
`Workaround: Keep database/storage/auth in runtime config, keep schema customizations as static objects.
---
Adapter Support
$3
`javascript
export default defineConfig({
adapter: {
type: 'drizzle',
drizzle: {
dialect: 'postgres', // or 'mysql', 'sqlite'
connectionString: process.env.DATABASE_URL,
}
},
});
`Generated migrations work with
drizzle-kit push.$3
`javascript
export default defineConfig({
adapter: {
type: 'prisma',
prisma: {
// Prisma client instance will be used
}
},
});
`Generated migrations work with
npx prisma migrate deploy.$3
`javascript
export default defineConfig({
adapter: {
type: 'kysely',
kysely: {
dialect: 'postgres', // or 'mysql', 'sqlite'
connectionString: process.env.DATABASE_URL,
}
},
});
`Generated migrations work with
npx kysely migrate latest.---
Troubleshooting
$3
Problem: CLI can't find library initialization calls in your files.
Solution:
- Make sure files are specified correctly in
libraries array
- Verify function names match registry (e.g., createConductBackend, not initConduct)
- Check that initialization calls are at the top level (not inside functions/conditions)$3
Problem: Config uses complex expressions that can't be statically analyzed.
Solution:
`typescript
// Instead of:
const conduct = createConductBackend(getConfig());// Do this:
export const conductConfig = {
additionalFields: { ... },
plugins: [ ... ]
};
export const conduct = createConductBackend({
database: adapter,
...conductConfig
});
`$3
Problem: Library hasn't integrated with CLI yet.
Solution: Contact library maintainer or see "For Library Authors" section to add support.
---
FAQ
$3
Problem with separate configs: Users had to maintain two configs - one for CLI (
conduct.config.ts) and one for runtime (app.ts). This creates duplication and drift risk.Solution: Configure once in your app code. CLI parses those files directly.
$3
Yes, if needed:
`typescript
// conduct.config.ts
export const conductConfig = {
additionalFields: { ... },
plugins: [ ... ]
};// app.ts
import { conductConfig } from './conduct.config';
export const conduct = createConductBackend({
database: adapter,
...conductConfig
});
`Point CLI to
app.ts - it will resolve the import and extract the config.$3
The CLI scans
node_modules for packages with superfunctions metadata in their package.json. This allows any library to integrate without needing changes to the CLI itself.$3
better-auth: Single library, single config
superfunctions: Multiple libraries, need to identify which is which
Solution: Point to specific files containing each library's initialization. CLI uses function names (
createConductBackend, createAuthFn) to identify libraries.$3
CLI will detect all instances and generate schemas for each. Make sure they use different namespaces to avoid conflicts.
$3
Yes, uses TypeScript compiler API to parse both
.ts and .js files.$3
Works with JavaScript too. Just point to
.js files in libraries array.$3
Use explicit paths (
libraries) when:
- ā
Production applications
- ā
Monorepos with multiple packages
- ā
Complex project structures
- ā
Need predictable behavior
- ā
Want fast CLI performanceUse auto-discovery (
autoDiscover) when:
- ā
Small/simple projects
- ā
Prototyping
- ā
Don't want to maintain file list
- ā
Standard project structure (src/, lib/, etc.)Performance note: Auto-discovery scans your codebase which can be slow in large projects. Explicit paths are always faster.
---
Examples
$3
`
my-app/
āāā superfunctions.config.js
āāā src/
ā āāā db.ts # Database adapter
ā āāā conduct.ts # Conduct initialization
ā āāā auth.ts # AuthFn initialization
ā āāā server.ts # Main app
āāā migrations/
ā āāā 1234567890_conduct_v1.sql
ā āāā 1234567891_authfn_v1.sql
āāā package.json
`superfunctions.config.js:
`javascript
import { defineConfig } from '@superfunctions/cli';export default defineConfig({
adapter: {
type: 'drizzle',
drizzle: {
dialect: 'postgres',
connectionString: process.env.DATABASE_URL,
}
},
libraries: [
'./src/conduct.ts',
'./src/auth.ts',
],
migrationsDir: './migrations',
});
`src/db.ts:
`typescript
import { createDrizzleAdapter } from '@superfunctions/db/drizzle';
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';const client = postgres(process.env.DATABASE_URL!);
const db = drizzle(client);
export const adapter = createDrizzleAdapter({ db });
`src/conduct.ts:
`typescript
import { createConductBackend } from 'conduct';
import { adapter } from './db';export const conduct = createConductBackend({
database: adapter,
storage: { / ... / },
additionalFields: {
project: {
department: { type: 'string', required: false },
owner: { type: 'string', required: true },
}
},
plugins: [
conductAuditPlugin(),
]
});
`src/auth.ts:
`typescript
import { createAuthFn } from '@superfunctions/authfn';
import { adapter } from './db';export const auth = createAuthFn({
database: adapter,
additionalFields: {
user: {
role: { type: 'string', required: true },
department: { type: 'string', required: false },
}
},
plugins: [
authFnTwoFactorPlugin(),
],
});
`src/server.ts:
`typescript
import express from 'express';
import { toExpressRouter } from '@superfunctions/http-express';
import { conduct } from './conduct';
import { auth } from './auth';const app = express();
app.use('/api/conduct', toExpressRouter(conduct));
app.use('/api/auth', toExpressRouter(auth));
app.listen(3000);
``---
MIT