diff --git a/.gitignore b/.gitignore index 5dca41bc..f1a594a2 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,6 @@ playwright-report/ # Vendor Reference Code activepieces-reference/ n8n-reference/ + +# TypeScript Build Info +*.tsbuildinfo diff --git a/apps/api/package.json b/apps/api/package.json index 085a4ea6..115701e1 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -41,6 +41,7 @@ "@nexiom/cache": "workspace:*", "@nexiom/connections": "workspace:*", "@nexiom/database": "workspace:*", + "@nexiom/dbmanager": "workspace:*", "@nexiom/identity": "workspace:*", "bcryptjs": "^3.0.3", "better-auth": "^1.4.10", diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index 9a9ef140..90c160cd 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -21,6 +21,7 @@ import { TriggerModule } from '../modules/trigger/trigger.module'; import { EmailModule } from '../modules/email/email.module'; import { StorageResolverModule } from '../modules/storage-resolver/storage-resolver.module'; import { CacheModule } from '@nexiom/cache'; +import { DbManagerModule } from '../modules/dbmanager/dbmanager.module'; @Module({ imports: [ @@ -82,6 +83,7 @@ import { CacheModule } from '@nexiom/cache'; ConnectionsModule, TriggerModule, StorageResolverModule, + DbManagerModule, ], controllers: [AppController], providers: [AppService], diff --git a/apps/api/src/db/create-inbound-gateway.ts b/apps/api/src/db/create-inbound-gateway.ts deleted file mode 100644 index 5705afa6..00000000 --- a/apps/api/src/db/create-inbound-gateway.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * inbound_gateway table creation script. - * - * Run directly via the DatabaseManager.execSql pattern — no Drizzle generate/migrate. - * - * The table lives in the workspace schema (ws_{workspaceId}). - * Since workspace = connection in Nexiom, there is no connection_id column needed. - * - * Usage (from db-cli or a one-off script): - * import { createInboundGateway } from './create-inbound-gateway'; - * await createInboundGateway(client, 'ws_abc123'); - */ -import type { Client } from 'pg'; -import { createHash } from 'node:crypto'; - -/** - * Strict allowlist: must match the workspace schema pattern ws_{workspaceId} - * where the id portion is lowercase alphanumeric only. - * This prevents creating arbitrary schemas and stops SQL injection via the prefix. - */ -const SAFE_SCHEMA_NAME_RE = /^ws_[a-z0-9]+$/; - -function validateSchemaName(name: string): void { - if (!SAFE_SCHEMA_NAME_RE.test(name)) { - throw new Error( - `Invalid schemaName (sha256 prefix: ${createHash('sha256').update(name).digest('hex').slice(0, 8)}…) — ` + - `expected format: ws_{workspaceId} with only lowercase letters and digits after the prefix.`, - ); - } -} - -export async function createInboundGateway( - client: Client, - schemaName: string, // e.g. 'ws_abc123' -): Promise { - validateSchemaName(schemaName); - - await client.query(` - CREATE SCHEMA IF NOT EXISTS "${schemaName}"; - `); - - await client.query(` - CREATE TABLE IF NOT EXISTS "${schemaName}".inbound_gateway ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - source_event_id TEXT NOT NULL, - trigger_name TEXT NOT NULL, - app_name TEXT NOT NULL, - object_type TEXT, - payload JSONB NOT NULL, - status TEXT NOT NULL DEFAULT 'pending', - trace_id UUID NOT NULL DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - - CONSTRAINT uq_inbound_source_event UNIQUE (source_event_id) - ); - `); - - await client.query(` - CREATE INDEX IF NOT EXISTS idx_inbound_gateway_status - ON "${schemaName}".inbound_gateway (status); - `); - - await client.query(` - CREATE INDEX IF NOT EXISTS idx_inbound_gateway_object_type - ON "${schemaName}".inbound_gateway (object_type) - WHERE object_type IS NOT NULL; - `); - - await client.query(` - CREATE INDEX IF NOT EXISTS idx_inbound_gateway_created_at - ON "${schemaName}".inbound_gateway (created_at DESC); - `); -} diff --git a/apps/api/src/modules/connections/connectors.service.spec.ts b/apps/api/src/modules/connections/connectors.service.spec.ts index 17696f6d..0f9aa935 100644 --- a/apps/api/src/modules/connections/connectors.service.spec.ts +++ b/apps/api/src/modules/connections/connectors.service.spec.ts @@ -7,6 +7,7 @@ import { EncryptionService, AppCredentialError, } from '@nexiom/connections'; +import { DB_MANAGER } from '../dbmanager/dbmanager.module'; import { InternalServerErrorException, NotFoundException, @@ -85,6 +86,7 @@ describe('ConnectorsService', () => { { provide: ConfigService, useValue: mockConfigService }, { provide: EncryptionService, useValue: mockEncryptionService }, { provide: DATABASE_CONNECTION, useValue: mockDb as unknown }, + { provide: DB_MANAGER, useValue: { applyPlan: vi.fn() } }, ], }).compile(); @@ -561,7 +563,7 @@ describe('ConnectorsService', () => { ).rejects.toThrow(InternalServerErrorException); }); - it('should throw InternalServerErrorException and abort if schema creation execute() fails', async () => { + it('should throw InternalServerErrorException and abort if DB manager applyPlan fails', async () => { // Mock appConnection insert succeeding: mockDbInsert.mockReturnValueOnce({ values: vi.fn().mockReturnValue({ @@ -582,11 +584,29 @@ describe('ConnectorsService', () => { }), }); - // Mock the raw schema execute command failing: - mockDb.execute.mockRejectedValueOnce(new Error('schema create failed')); + // We need to re-mock the DB_MANAGER for this specific test + const failingDbManager = { + applyPlan: vi.fn().mockRejectedValue(new Error('applyPlan failed')), + }; + const moduleFail: TestingModule = await Test.createTestingModule({ + providers: [ + ConnectorsService, + { provide: ProviderRegistryService, useValue: mockProviderRegistry }, + { + provide: ConfigService, + useValue: { get: vi.fn().mockReturnValue('mock-region-context') }, + }, + { provide: EncryptionService, useValue: mockEncryptionService }, + { provide: DATABASE_CONNECTION, useValue: mockDb as unknown }, + { provide: DB_MANAGER, useValue: failingDbManager }, + ], + }).compile(); + + const failingService = + moduleFail.get(ConnectorsService); await expect( - service.storeOAuthConnection({ + failingService.storeOAuthConnection({ tenantId: 'tenant-123', providerName: 'salesforce', externalId: 'salesforce-tms', diff --git a/apps/api/src/modules/connections/connectors.service.ts b/apps/api/src/modules/connections/connectors.service.ts index 68548e0e..64bf0dec 100644 --- a/apps/api/src/modules/connections/connectors.service.ts +++ b/apps/api/src/modules/connections/connectors.service.ts @@ -20,8 +20,9 @@ import { DATABASE_CONNECTION, type DrizzleDb, } from '@nexiom/database'; -import { sql } from 'drizzle-orm'; -import * as crypto from 'crypto'; +import { DatabaseManager, SchemaPlan } from '@nexiom/dbmanager'; +import { DB_MANAGER } from '../dbmanager/dbmanager.module'; +import * as crypto from 'node:crypto'; /** Encrypted value blob stored in app_connection.value — mirrors Activepieces BaseOAuth2ConnectionValue */ export interface ConnectionValueBlob { @@ -55,6 +56,7 @@ export class ConnectorsService { constructor( @Inject(DATABASE_CONNECTION) private readonly db: DrizzleDb, + @Inject(DB_MANAGER) private readonly dbManager: DatabaseManager, private readonly providerRegistry: ProviderRegistryService, private readonly configService: ConfigService, ) {} @@ -297,7 +299,7 @@ export class ConnectorsService { } try { - await this.db.transaction(async (tx) => { + const workspaceSchemaName = await this.db.transaction(async (tx) => { // 1. Insert or update the business connection metadata const [connection] = await tx .insert(appConnections) @@ -340,13 +342,13 @@ export class ConnectorsService { const sanitizedProvider = providerName.replaceAll(/[^a-z0-9]/g, ''); const finalProviderToken = sanitizedProvider || 'unknown'; const safeToken = finalProviderToken.substring(0, 40); - const workspaceSchemaName = `ws_${safeToken}_${hashedSuffix}`; + const schemaName = `ws_${safeToken}_${hashedSuffix}`; await tx .insert(connectionStorageRegistry) .values({ connectionId: connection.id, - workspaceId: workspaceSchemaName, + workspaceId: schemaName, databaseHostId: 'primary-cluster', // Can be parameterized later for regional sharding regionContext: finalRegionContext, }) @@ -354,13 +356,16 @@ export class ConnectorsService { target: connectionStorageRegistry.connectionId, }); // Already provisioned - // 3. Actually create the physical PostgreSQL schema on the cluster - // Using sql.raw here is safe because workspaceSchemaName is strictly internally generated - // from a regex-sanitized string and a UUID slice, not user input. - await tx.execute( - sql`CREATE SCHEMA IF NOT EXISTS "${sql.raw(workspaceSchemaName)}"`, - ); + return schemaName; }); + + // 3. Apply the initial schema plan (NAMESPACE_ONLY) outside the transaction + // so the DDL runs on its own connection and does not silently escape the + // Drizzle tx scope (db.$client vs the transaction's dedicated connection). + await this.dbManager.applyPlan( + workspaceSchemaName, + SchemaPlan.NAMESPACE_ONLY, + ); } catch (error) { if (error instanceof HttpException) { throw error; diff --git a/apps/api/src/modules/dbmanager/dbmanager.module.ts b/apps/api/src/modules/dbmanager/dbmanager.module.ts new file mode 100644 index 00000000..8db9bc35 --- /dev/null +++ b/apps/api/src/modules/dbmanager/dbmanager.module.ts @@ -0,0 +1,23 @@ +import { Module, Global } from '@nestjs/common'; +import { SqlDatabaseManager } from '@nexiom/dbmanager'; +import { DbModule } from '../../db/db.module'; +import { DATABASE_CONNECTION } from '@nexiom/database'; +import type { DrizzleDb } from '@nexiom/database'; + +export const DB_MANAGER = 'DATABASE_MANAGER'; + +@Global() +@Module({ + imports: [DbModule], + providers: [ + { + provide: DB_MANAGER, + useFactory: (drizzleDb: DrizzleDb) => { + return new SqlDatabaseManager(drizzleDb); + }, + inject: [DATABASE_CONNECTION], + }, + ], + exports: [DB_MANAGER], +}) +export class DbManagerModule {} diff --git a/apps/api/src/modules/trigger/trigger-executor.service.spec.ts b/apps/api/src/modules/trigger/trigger-executor.service.spec.ts index 07a66f87..423dbe1b 100644 --- a/apps/api/src/modules/trigger/trigger-executor.service.spec.ts +++ b/apps/api/src/modules/trigger/trigger-executor.service.spec.ts @@ -46,6 +46,9 @@ describe('TriggerExecutorService', () => { service = new TriggerExecutorService( db as unknown as import('@nexiom/database').DrizzleDb, redis as unknown as import('ioredis').Redis, + { + applyPlan: vi.fn(), + } as unknown as import('@nexiom/dbmanager').DatabaseManager, ); }); diff --git a/apps/api/src/modules/trigger/trigger-executor.service.ts b/apps/api/src/modules/trigger/trigger-executor.service.ts index 0e725a2d..47641ed3 100644 --- a/apps/api/src/modules/trigger/trigger-executor.service.ts +++ b/apps/api/src/modules/trigger/trigger-executor.service.ts @@ -2,6 +2,8 @@ import { Injectable, Inject, Logger } from '@nestjs/common'; import type { Trigger, TriggerContext } from '@nexiom/connections'; import type { DrizzleDb } from '@nexiom/database'; import { DATABASE_CONNECTION } from '@nexiom/database'; +import { DatabaseManager, SchemaPlan } from '@nexiom/dbmanager'; +import { DB_MANAGER } from '../dbmanager/dbmanager.module'; import type { Redis } from 'ioredis'; import { createHash, randomUUID } from 'node:crypto'; import { RedisBackedTriggerStore } from './redis-trigger-store'; @@ -59,6 +61,7 @@ export class TriggerExecutorService { constructor( @Inject(DATABASE_CONNECTION) private readonly db: DrizzleDb, @Inject('REDIS_CLIENT') private readonly redis: Redis, + @Inject(DB_MANAGER) private readonly dbManager: DatabaseManager, ) {} // ─── Polling ──────────────────────────────────────────────────────────── @@ -138,6 +141,13 @@ export class TriggerExecutorService { async runOnEnable(params: TriggerRunParams): Promise { const context = this.buildContext(params); try { + // 1. Ensure the gateway tables are provisioned lazily + await this.dbManager.applyPlan( + params.workspaceId, + SchemaPlan.GATEWAY_ACTIVE, + ); + + // 2. Invoke the trigger enablement logic (e.g. Subscribe to webhook) await params.trigger.onEnable?.(context); this.logger.log('onEnable completed', { appName: params.appName, @@ -428,7 +438,7 @@ export class TriggerExecutorService { ) { const sorted = Object.keys(record as Record) .filter((k) => !VOLATILE_KEYS.has(k)) - .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)) + .sort((a, b) => (a ?? '').localeCompare(b ?? '')) .reduce>((acc, k) => { acc[k] = (record as Record)[k]; return acc; diff --git a/apps/api/src/modules/trigger/trigger.module.ts b/apps/api/src/modules/trigger/trigger.module.ts index dd2217fb..81be23a5 100644 --- a/apps/api/src/modules/trigger/trigger.module.ts +++ b/apps/api/src/modules/trigger/trigger.module.ts @@ -9,6 +9,8 @@ import { DlqProcessorService } from './dlq-processor.service'; import { WebhooksController } from './webhooks.controller'; import { DATABASE_CONNECTION } from '@nexiom/database'; import type { DrizzleDb } from '@nexiom/database'; +import { DB_MANAGER } from '../dbmanager/dbmanager.module'; +import { DatabaseManager } from '@nexiom/dbmanager'; /** * Wires all trigger-related services. @@ -23,9 +25,9 @@ import type { DrizzleDb } from '@nexiom/database'; PieceRegistryService, { provide: TriggerExecutorService, - useFactory: (db: DrizzleDb, redis: Redis) => - new TriggerExecutorService(db, redis), - inject: [DATABASE_CONNECTION, REDIS_CLIENT], + useFactory: (db: DrizzleDb, redis: Redis, dbManager: DatabaseManager) => + new TriggerExecutorService(db, redis, dbManager), + inject: [DATABASE_CONNECTION, REDIS_CLIENT, DB_MANAGER], }, { provide: PollerService, diff --git a/docs/architecture/schema_lifecycle_and_provisioning.md b/docs/architecture/schema_lifecycle_and_provisioning.md new file mode 100644 index 00000000..8a5523b4 --- /dev/null +++ b/docs/architecture/schema_lifecycle_and_provisioning.md @@ -0,0 +1,59 @@ +# Architecture: Schema Lifecycle & Lazy Provisioning + +This document defines the timing and triggers for database schema creation within the FluxNex 6-layer pipeline. To ensure maximum scalability, we follow a Lazy Provisioning strategy—nothing is created until there is active intent from the user. + +1. Provisioning Lifecycle Summary +| Lifecycle Event | Physical Action | Component Responsible | Status | +| --------------- | --------------- | ----------------------- | ------ | +| App Authentication | Create Schema Namespace (ws_{id}) | api-gateway / dbmanager | Empty Silo | +| Route Activation | Create Kernel Tables (Ingest/Logs) | dbmanager (Atlas) | Active Silo | +| Object Selection | Initialize Unified Replica (replica_entity) | dbmanager (Atlas) | Data Ready | + +2. Event-Driven Provisioning Details +Stage 1: Connection Handshake (The "Namespace" Event) +Trigger: User successfully completes an OAuth or API Key handshake. +Database Action: CREATE SCHEMA IF NOT EXISTS "ws_sf_101". +Registry Action: Insert row into public.connection_storage_registry. +Result: A physical "Wall" is established. The schema is empty, incurring zero index or metadata overhead in the Postgres catalog. +Stage 2: Route Activation (The "Kernel" Event) +Trigger: User creates a Route (e.g., Salesforce → QuickBooks) and clicks "Save & Activate." +Database Action: The dbmanager uses Atlas to deploy the Pipeline Kernel tables into the connection's schema. +Tables Created: * `inbound_gateway`: Stores raw ingestion data. +*(Note: `sync_log`, `sync_cursor`, and `outbound_gateway` are planned additions for future Gateway integration).* +Stage 3: Object Mapping (The "Storage Initialization" Event) +Trigger: User selects a specific object (e.g., "Accounts") in the Mapping UI. +Database Action: The system ensures the structured state store is ready. It executes the SQL to create: +replica_entity: The unified JSONB state store for ALL objects in this connection. +GIN Indexes: Applied to the data column to enable high-speed search across all fields and custom objects. +Optimization: This table is created once per connection and handles all object types (Account, Contact, etc.) through the entity_type column. + +3. The 6-Layer Readiness Matrix +This matrix shows which physical tables must be "ready" before a specific layer worker can process a job. +| Layer | Dependency | Physical Location | Why? | +| ----- | ---------- | ----------------- | ---- | +| L1: Source Gateway | inbound_gateway | ws_source | To log the "evidence" of the raw webhook. | +| L2: Universal Replica | replica_entity | ws_source | To store the parsed source-of-truth. | +| L3: Normalization | normalized_entity | ws_source | To store the platform-standard JSON. | +| L4: Outbound Prep | field_mapping | public | To read the rules defined by the user. | +| L5: Delivery Engine | N/A | N/A | Pure I/O. Does not write to tenant schemas. | +| L6: Dest. Gateway | outbound_gateway | ws_dest | To log the destination API response. | + +4. Engineering Implementation: The "Ensure" Pattern +Because workers are asynchronous, a webhook (L1) might arrive before the dbmanager has finished creating the tables. To prevent crashes, all workers follow the Ensure Pattern: +// packages/engine/src/workers/base.worker.ts +async function processLayerJob(connectionId: string, payload: any) { + // 1. Resolve Schema Name + const schema = await storageResolver.resolve(connectionId); + + // 2. JIT Check (The "Ensure" step) + // If the tables are missing, the worker triggers a fast schema plan apply + await dbmanager.applyPlan(schema, SchemaPlan.GATEWAY_ACTIVE); + + // 3. Perform Business Logic + await db.withSchema(schema).insert(...); +} + +1. Benefits of this Timing +Catalog Leanliness: We avoid the "Postgres Metadata Bloat" that occurs when thousands of unused tables exist in the system. +Speed: App connection is instantaneous because it only creates a namespace. +Isolation: The architecture guarantees that no worker ever attempts to write to a "Shared" schema; it is always scoped to the connection-specific workspace. diff --git a/packages/dbmanager/package.json b/packages/dbmanager/package.json new file mode 100644 index 00000000..0ad7ef38 --- /dev/null +++ b/packages/dbmanager/package.json @@ -0,0 +1,17 @@ +{ + "name": "@nexiom/dbmanager", + "version": "0.0.1", + "description": "Declarative schema manager for Nexiom", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc" + }, + "dependencies": { + "@nexiom/database": "workspace:*" + }, + "devDependencies": { + "@types/node": "^22.10.7", + "typescript": "^5.7.3" + } +} \ No newline at end of file diff --git a/packages/dbmanager/src/impl/sql-database-manager.ts b/packages/dbmanager/src/impl/sql-database-manager.ts new file mode 100644 index 00000000..c9712a15 --- /dev/null +++ b/packages/dbmanager/src/impl/sql-database-manager.ts @@ -0,0 +1,81 @@ +import type { DatabaseManager } from '../interfaces'; +import { SchemaPlan } from '../interfaces'; +import type { DrizzleDb } from '@nexiom/database'; +import { createHash } from 'node:crypto'; + +const SAFE_SCHEMA_NAME_RE = /^ws_[a-z0-9_]+$/; + +export class SqlDatabaseManager implements DatabaseManager { + constructor(private readonly db: DrizzleDb) { } + + private validateSchemaName(name: string): void { + if (!SAFE_SCHEMA_NAME_RE.test(name)) { + throw new Error( + `Invalid schemaName (sha256 prefix: ${createHash('sha256').update(name).digest('hex').slice(0, 8)}…) — ` + + `expected format: ws_{workspaceId} with only lowercase letters, digits, and underscores after the prefix.`, + ); + } + } + + async applyPlan(schemaName: string, plan: SchemaPlan): Promise { + this.validateSchemaName(schemaName); + + // 1. Always ensure namespace exists (Minimum baseline for all plans) + await this.db.$client.query( + `CREATE SCHEMA IF NOT EXISTS "${schemaName}";`, + ); + + if (plan === SchemaPlan.NAMESPACE_ONLY) { + return; + } + + // 2. Ensure Gateway Tables exist + await this.provisionGatewayTables(schemaName); + + if (plan === SchemaPlan.GATEWAY_ACTIVE) { + return; + } + + // 3. (Future) Ensure Replica Tables exist + // TODO: implement provisionReplicaTables(schemaName) before enabling REPLICA_ACTIVE in production + if (plan === SchemaPlan.REPLICA_ACTIVE) { + return; + } + } + + private async provisionGatewayTables(schemaName: string): Promise { + // We execute these independently so that they are idempotent. + // If they error, the whole task fails, preventing partial corruption. + await this.db.$client.query(` + CREATE TABLE IF NOT EXISTS "${schemaName}".inbound_gateway ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + source_event_id TEXT NOT NULL, + trigger_name TEXT NOT NULL, + app_name TEXT NOT NULL, + object_type TEXT, + payload JSONB NOT NULL, + status TEXT NOT NULL DEFAULT 'pending', + trace_id UUID NOT NULL DEFAULT gen_random_uuid(), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + CONSTRAINT uq_inbound_source_event UNIQUE (source_event_id) + ); + `); + + await this.db.$client.query(` + CREATE INDEX IF NOT EXISTS idx_inbound_gateway_status + ON "${schemaName}".inbound_gateway (status); + `); + + await this.db.$client.query(` + CREATE INDEX IF NOT EXISTS idx_inbound_gateway_object_type + ON "${schemaName}".inbound_gateway (object_type) + WHERE object_type IS NOT NULL; + `); + + await this.db.$client.query(` + CREATE INDEX IF NOT EXISTS idx_inbound_gateway_created_at + ON "${schemaName}".inbound_gateway (created_at DESC); + `); + } +} diff --git a/packages/dbmanager/src/index.ts b/packages/dbmanager/src/index.ts new file mode 100644 index 00000000..c44c4a32 --- /dev/null +++ b/packages/dbmanager/src/index.ts @@ -0,0 +1,2 @@ +export * from './interfaces'; +export * from './impl/sql-database-manager'; diff --git a/packages/dbmanager/src/interfaces.ts b/packages/dbmanager/src/interfaces.ts new file mode 100644 index 00000000..8a06a56f --- /dev/null +++ b/packages/dbmanager/src/interfaces.ts @@ -0,0 +1,32 @@ +export enum SchemaPlan { + /** + * Only the empty PostgreSQL schema namespace (e.g., "ws_abc123") + * Provisioned automatically when a connection handshake succeeds. + */ + NAMESPACE_ONLY = 'NAMESPACE_ONLY', + + /** + * The L1 Gateway tables (inbound_gateway, sync_logs) + * Provisioned when a Route/Webhook is activated. + */ + GATEWAY_ACTIVE = 'GATEWAY_ACTIVE', + + /** + * The L2 Unified Replica tables (replica_entity) + * Provisioned when an object mapping is initialized. + */ + REPLICA_ACTIVE = 'REPLICA_ACTIVE', +} + +/** + * Enterprise declarative database manager interface. + * Implements the Hexagonal Architecture "Port" pattern to decouple + * physical schema provisioning from API business logic. + */ +export interface DatabaseManager { + /** + * Idempotently bring the schema up to the desired plan level. + * If the schema already exceeds the plan, it does nothing. + */ + applyPlan(schemaName: string, plan: SchemaPlan): Promise; +} diff --git a/packages/dbmanager/tsconfig.json b/packages/dbmanager/tsconfig.json new file mode 100644 index 00000000..d0ae2158 --- /dev/null +++ b/packages/dbmanager/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "lib": [ + "es2022" + ], + "target": "es2022", + "module": "NodeNext", + "moduleResolution": "NodeNext" + }, + "include": [ + "src/**/*" + ] +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a2c32c5e..e12b1562 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,6 +66,9 @@ importers: '@nexiom/database': specifier: workspace:* version: link:../../packages/database + '@nexiom/dbmanager': + specifier: workspace:* + version: link:../../packages/dbmanager '@nexiom/identity': specifier: workspace:* version: link:../../packages/identity @@ -517,6 +520,19 @@ importers: specifier: ^5.7.3 version: 5.9.3 + packages/dbmanager: + dependencies: + '@nexiom/database': + specifier: workspace:* + version: link:../database + devDependencies: + '@types/node': + specifier: ^22.10.7 + version: 22.19.3 + typescript: + specifier: ^5.7.3 + version: 5.9.3 + packages/eslint-config: dependencies: '@eslint/js':