@kuckit/sdk 1.0.1 → 1.0.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.
- package/dist/config/define-config.d.ts +3 -0
- package/dist/config/define-config.js +3 -0
- package/dist/config/index.d.ts +5 -0
- package/dist/config/index.js +5 -0
- package/dist/config/loader.d.ts +3 -0
- package/dist/config/loader.js +3 -0
- package/dist/config/types.d.ts +2 -0
- package/dist/config/types.js +1 -0
- package/dist/config-BeiJJZGf.js +1 -0
- package/dist/container-D0DK003A.js +50 -0
- package/dist/container-D0DK003A.js.map +1 -0
- package/dist/container-Ngzcb6LI.d.ts +49 -0
- package/dist/core/container.d.ts +3 -0
- package/dist/core/container.js +4 -0
- package/dist/core/core.module.d.ts +3 -0
- package/dist/core/core.module.js +3 -0
- package/dist/core.module-Ckt9iPWn.d.ts +22 -0
- package/dist/core.module-Ctm2stcL.js +48 -0
- package/dist/core.module-Ctm2stcL.js.map +1 -0
- package/dist/define-config-GYI_W9K6.js +34 -0
- package/dist/define-config-GYI_W9K6.js.map +1 -0
- package/dist/define-config-yzb59aTs.d.ts +34 -0
- package/dist/define-module-B83hUzJz.js +58 -0
- package/dist/define-module-B83hUzJz.js.map +1 -0
- package/dist/define-module-Cw7q13EE.d.ts +56 -0
- package/dist/index-CDDzqahH.d.ts +1 -0
- package/dist/index-Dfcz46Ma.d.ts +2 -0
- package/dist/index-Dw5cCt-A.d.ts +1 -0
- package/dist/index.d.ts +15 -616
- package/dist/index.js +12 -633
- package/dist/loader-Ct4ZivZz.js +177 -0
- package/dist/loader-Ct4ZivZz.js.map +1 -0
- package/dist/loader-DCNm6pZB.d.ts +73 -0
- package/dist/loader-YEqdtcKI.d.ts +29 -0
- package/dist/loader-lCPWCMYx.js +75 -0
- package/dist/loader-lCPWCMYx.js.map +1 -0
- package/dist/modules/define-module.d.ts +4 -0
- package/dist/modules/define-module.js +3 -0
- package/dist/modules/index.d.ts +7 -0
- package/dist/modules/index.js +8 -0
- package/dist/modules/loader.d.ts +4 -0
- package/dist/modules/loader.js +6 -0
- package/dist/modules/registry.d.ts +4 -0
- package/dist/modules/registry.js +3 -0
- package/dist/modules/types.d.ts +3 -0
- package/dist/modules/types.js +1 -0
- package/dist/modules-BDQBjAbp.js +1 -0
- package/dist/registry-BPYpBtYx.js +83 -0
- package/dist/registry-BPYpBtYx.js.map +1 -0
- package/dist/registry-CL_5erME.js +132 -0
- package/dist/registry-CL_5erME.js.map +1 -0
- package/dist/registry-CfpVCPcW.d.ts +68 -0
- package/dist/registry-DrTkgmtH.d.ts +90 -0
- package/dist/schema/index.d.ts +3 -0
- package/dist/schema/index.js +4 -0
- package/dist/schema/registry.d.ts +2 -0
- package/dist/schema/registry.js +3 -0
- package/dist/schema-BuA2HF_H.js +1 -0
- package/dist/types-ByO301S-.d.ts +112 -0
- package/dist/types-DKCy16X1.d.ts +51 -0
- package/dist/types-DxaDmkQo.d.ts +87 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +1 -0
- package/package.json +14 -28
- package/src/index.ts +0 -62
package/dist/index.js
CHANGED
|
@@ -1,638 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as registerCoreModule } from "./core.module-Ctm2stcL.js";
|
|
2
|
+
import { n as disposeContainer, t as createKuckitContainer } from "./container-D0DK003A.js";
|
|
3
|
+
import { t as defineKuckitModule } from "./define-module-B83hUzJz.js";
|
|
4
|
+
import { i as resetModuleRegistry, n as getModuleRegistry, r as getModulesWithCapability, t as ModuleRegistry } from "./registry-CL_5erME.js";
|
|
5
|
+
import { n as getSchemaRegistry, r as resetSchemaRegistry, t as SchemaRegistry } from "./registry-BPYpBtYx.js";
|
|
6
|
+
import "./schema-BuA2HF_H.js";
|
|
7
|
+
import { n as loadKuckitModules, t as createModuleShutdownHandler } from "./loader-Ct4ZivZz.js";
|
|
8
|
+
import "./modules-BDQBjAbp.js";
|
|
9
|
+
import { t as defineConfig } from "./define-config-GYI_W9K6.js";
|
|
10
|
+
import { i as tryLoadKuckitConfig, n as hasUnifiedConfig, r as loadKuckitConfig, t as findConfigFile } from "./loader-lCPWCMYx.js";
|
|
11
|
+
import "./config-BeiJJZGf.js";
|
|
12
|
+
import { asClass, asFunction, asValue } from "awilix";
|
|
2
13
|
import * as Domain from "@kuckit/domain";
|
|
3
|
-
import { SystemClock } from "@kuckit/domain";
|
|
4
|
-
import { google } from "@ai-sdk/google";
|
|
5
|
-
import { auth } from "@kuckit/auth";
|
|
6
|
-
import { InMemoryCacheStore, InMemoryEventBus, InMemoryRateLimiterStore, createDb, createDbPool, makeErrorHandler, makeRequestLogger, makeStructuredLogger } from "@kuckit/infrastructure";
|
|
7
|
-
import { existsSync } from "node:fs";
|
|
8
|
-
import { dirname, resolve } from "node:path";
|
|
9
14
|
import * as Application from "@kuckit/application";
|
|
10
15
|
import * as Contracts from "@kuckit/contracts";
|
|
11
16
|
|
|
12
|
-
//#region src/core/core.module.ts
|
|
13
|
-
/**
|
|
14
|
-
* Register core infrastructure services into the container
|
|
15
|
-
*
|
|
16
|
-
* This registers:
|
|
17
|
-
* - Database pool and connection
|
|
18
|
-
* - Clock
|
|
19
|
-
* - Logger (structured, Loki/Prometheus compatible)
|
|
20
|
-
* - AI provider
|
|
21
|
-
* - Auth
|
|
22
|
-
* - Event bus
|
|
23
|
-
* - Cache store
|
|
24
|
-
* - Rate limiter store
|
|
25
|
-
* - Request-scoped logger
|
|
26
|
-
*/
|
|
27
|
-
const registerCoreModule = (container) => {
|
|
28
|
-
container.register({
|
|
29
|
-
dbPool: asFunction$1(({ config }) => createDbPool(config.databaseUrl)).singleton(),
|
|
30
|
-
db: asFunction$1(({ dbPool }) => createDb(dbPool)).singleton(),
|
|
31
|
-
clock: asValue$1(new SystemClock()),
|
|
32
|
-
logger: asFunction$1(({ config }) => makeStructuredLogger({
|
|
33
|
-
enableFile: config.enableFileLogging,
|
|
34
|
-
logDir: config.logDir,
|
|
35
|
-
minLevel: config.logLevel
|
|
36
|
-
})).singleton(),
|
|
37
|
-
errorHandler: asFunction$1(({ logger }) => makeErrorHandler(logger)).singleton(),
|
|
38
|
-
auth: asValue$1(auth),
|
|
39
|
-
aiProvider: asFunction$1(() => google("gemini-2.5-flash")).singleton(),
|
|
40
|
-
eventBus: asFunction$1(({ logger }) => new InMemoryEventBus(logger)).singleton(),
|
|
41
|
-
cacheStore: asClass$1(InMemoryCacheStore).singleton(),
|
|
42
|
-
rateLimiterStore: asClass$1(InMemoryRateLimiterStore).singleton(),
|
|
43
|
-
requestLogger: asFunction$1(({ logger, requestId, session }) => makeRequestLogger({
|
|
44
|
-
requestId,
|
|
45
|
-
userId: session?.user?.id,
|
|
46
|
-
baseLogger: logger
|
|
47
|
-
})).scoped()
|
|
48
|
-
});
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
//#endregion
|
|
52
|
-
//#region src/core/container.ts
|
|
53
|
-
/**
|
|
54
|
-
* Factory function to create a Kuckit DI container
|
|
55
|
-
*
|
|
56
|
-
* This is the primary entry point for applications using the SDK.
|
|
57
|
-
* It creates an Awilix container with core infrastructure services pre-registered.
|
|
58
|
-
*
|
|
59
|
-
* @example
|
|
60
|
-
* ```ts
|
|
61
|
-
* const container = await createKuckitContainer({
|
|
62
|
-
* config: {
|
|
63
|
-
* databaseUrl: process.env.DATABASE_URL!,
|
|
64
|
-
* enableFileLogging: true,
|
|
65
|
-
* logDir: './logs',
|
|
66
|
-
* logLevel: 'INFO',
|
|
67
|
-
* env: 'production',
|
|
68
|
-
* },
|
|
69
|
-
* extraRegistrations: (container) => {
|
|
70
|
-
* // Register your custom services
|
|
71
|
-
* container.register({
|
|
72
|
-
* myService: asClass(MyService).singleton(),
|
|
73
|
-
* })
|
|
74
|
-
* },
|
|
75
|
-
* })
|
|
76
|
-
* ```
|
|
77
|
-
*/
|
|
78
|
-
const createKuckitContainer = async (opts) => {
|
|
79
|
-
const container = createContainer({ injectionMode: InjectionMode.PROXY });
|
|
80
|
-
container.register({ config: asValue$1(opts.config) });
|
|
81
|
-
registerCoreModule(container);
|
|
82
|
-
if (opts.extraRegistrations) await opts.extraRegistrations(container);
|
|
83
|
-
return container;
|
|
84
|
-
};
|
|
85
|
-
/**
|
|
86
|
-
* Cleanup container resources gracefully
|
|
87
|
-
*
|
|
88
|
-
* Call this when shutting down your application to close database connections
|
|
89
|
-
* and other resources.
|
|
90
|
-
*/
|
|
91
|
-
const disposeContainer = async (container) => {
|
|
92
|
-
const { dbPool } = container.cradle;
|
|
93
|
-
if (dbPool && typeof dbPool === "object" && "end" in dbPool && typeof dbPool.end === "function") await dbPool.end();
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
//#endregion
|
|
97
|
-
//#region src/modules/define-module.ts
|
|
98
|
-
/**
|
|
99
|
-
* Helper to define a Kuckit module with type safety
|
|
100
|
-
*
|
|
101
|
-
* This is the primary way to create a module for the Kuckit SDK.
|
|
102
|
-
* It provides type inference for the configuration and validates
|
|
103
|
-
* the module structure at compile time.
|
|
104
|
-
*
|
|
105
|
-
* @example
|
|
106
|
-
* ```ts
|
|
107
|
-
* // In your module's main file (e.g., src/module.ts)
|
|
108
|
-
* import { defineKuckitModule, asClass, asFunction } from '@kuckit/sdk'
|
|
109
|
-
*
|
|
110
|
-
* interface BillingModuleConfig {
|
|
111
|
-
* currency: string
|
|
112
|
-
* taxRate: number
|
|
113
|
-
* }
|
|
114
|
-
*
|
|
115
|
-
* export const kuckitModule = defineKuckitModule<BillingModuleConfig>({
|
|
116
|
-
* id: 'acme.billing',
|
|
117
|
-
* displayName: 'Billing',
|
|
118
|
-
* description: 'Invoice and payment processing',
|
|
119
|
-
* version: '1.0.0',
|
|
120
|
-
*
|
|
121
|
-
* register(ctx) {
|
|
122
|
-
* ctx.container.register({
|
|
123
|
-
* invoiceRepository: asClass(DrizzleInvoiceRepository).scoped(),
|
|
124
|
-
* paymentService: asFunction(makePaymentService).singleton(),
|
|
125
|
-
* })
|
|
126
|
-
* },
|
|
127
|
-
*
|
|
128
|
-
* registerApi(ctx) {
|
|
129
|
-
* ctx.addApiRegistration({
|
|
130
|
-
* type: 'rpc-router',
|
|
131
|
-
* name: 'billing',
|
|
132
|
-
* router: createBillingRouter(ctx.container),
|
|
133
|
-
* })
|
|
134
|
-
* },
|
|
135
|
-
*
|
|
136
|
-
* onBootstrap(ctx) {
|
|
137
|
-
* const logger = ctx.container.resolve('logger')
|
|
138
|
-
* logger.info(`Billing module initialized with currency: ${ctx.config.currency}`)
|
|
139
|
-
* },
|
|
140
|
-
* })
|
|
141
|
-
* ```
|
|
142
|
-
*
|
|
143
|
-
* @param mod - The module definition object
|
|
144
|
-
* @returns The same module definition (for type inference)
|
|
145
|
-
*/
|
|
146
|
-
const defineKuckitModule = (mod) => {
|
|
147
|
-
if (!mod.id) throw new Error("Module must have an id");
|
|
148
|
-
if (typeof mod.id !== "string" || mod.id.trim() === "") throw new Error("Module id must be a non-empty string");
|
|
149
|
-
return mod;
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
//#endregion
|
|
153
|
-
//#region src/modules/registry.ts
|
|
154
|
-
/**
|
|
155
|
-
* Validates that a capability string matches expected patterns
|
|
156
|
-
*/
|
|
157
|
-
const isValidCapability = (cap) => {
|
|
158
|
-
if ([
|
|
159
|
-
"nav.item",
|
|
160
|
-
"settings.page",
|
|
161
|
-
"dashboard.widget",
|
|
162
|
-
"api.webhook",
|
|
163
|
-
"api.public",
|
|
164
|
-
"slot.provider"
|
|
165
|
-
].includes(cap)) return true;
|
|
166
|
-
if (cap.startsWith("custom.") && cap.length > 7) return true;
|
|
167
|
-
return false;
|
|
168
|
-
};
|
|
169
|
-
/**
|
|
170
|
-
* Registry for loaded Kuckit modules
|
|
171
|
-
*
|
|
172
|
-
* Tracks all loaded modules and their capabilities for querying.
|
|
173
|
-
*/
|
|
174
|
-
var ModuleRegistry = class {
|
|
175
|
-
modules = /* @__PURE__ */ new Map();
|
|
176
|
-
frozen = false;
|
|
177
|
-
/**
|
|
178
|
-
* Register a loaded module
|
|
179
|
-
* @throws Error if registry is frozen or module ID already exists
|
|
180
|
-
*/
|
|
181
|
-
register(module) {
|
|
182
|
-
if (this.frozen) throw new Error(`ModuleRegistry is frozen. Cannot register module "${module.id}" after finalization.`);
|
|
183
|
-
if (this.modules.has(module.id)) throw new Error(`Module with ID "${module.id}" is already registered.`);
|
|
184
|
-
const capabilities = module.capabilities ?? [];
|
|
185
|
-
for (const cap of capabilities) if (!isValidCapability(cap)) throw new Error(`Invalid capability "${cap}" in module "${module.id}". Capabilities must be built-in (nav.item, settings.page, etc.) or custom.* prefixed.`);
|
|
186
|
-
this.modules.set(module.id, {
|
|
187
|
-
id: module.id,
|
|
188
|
-
displayName: module.displayName,
|
|
189
|
-
description: module.description,
|
|
190
|
-
version: module.version,
|
|
191
|
-
capabilities
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Get all registered modules
|
|
196
|
-
*/
|
|
197
|
-
getAll() {
|
|
198
|
-
return Array.from(this.modules.values());
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Get a module by ID
|
|
202
|
-
*/
|
|
203
|
-
getById(id) {
|
|
204
|
-
return this.modules.get(id);
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* Check if a module is registered
|
|
208
|
-
*/
|
|
209
|
-
has(id) {
|
|
210
|
-
return this.modules.has(id);
|
|
211
|
-
}
|
|
212
|
-
/**
|
|
213
|
-
* Get all modules that have a specific capability
|
|
214
|
-
*/
|
|
215
|
-
getWithCapability(capability) {
|
|
216
|
-
return this.getAll().filter((mod) => mod.capabilities.includes(capability));
|
|
217
|
-
}
|
|
218
|
-
/**
|
|
219
|
-
* Check if a specific module has a capability
|
|
220
|
-
*/
|
|
221
|
-
hasCapability(moduleId, capability) {
|
|
222
|
-
const module = this.modules.get(moduleId);
|
|
223
|
-
return module ? module.capabilities.includes(capability) : false;
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* Get all unique capabilities across all modules
|
|
227
|
-
*/
|
|
228
|
-
getAllCapabilities() {
|
|
229
|
-
const caps = /* @__PURE__ */ new Set();
|
|
230
|
-
for (const mod of this.modules.values()) for (const cap of mod.capabilities) caps.add(cap);
|
|
231
|
-
return Array.from(caps);
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Freeze the registry to prevent further modifications
|
|
235
|
-
*/
|
|
236
|
-
freeze() {
|
|
237
|
-
this.frozen = true;
|
|
238
|
-
}
|
|
239
|
-
/**
|
|
240
|
-
* Check if registry is frozen
|
|
241
|
-
*/
|
|
242
|
-
isFrozen() {
|
|
243
|
-
return this.frozen;
|
|
244
|
-
}
|
|
245
|
-
/**
|
|
246
|
-
* Get the number of registered modules
|
|
247
|
-
*/
|
|
248
|
-
get size() {
|
|
249
|
-
return this.modules.size;
|
|
250
|
-
}
|
|
251
|
-
/**
|
|
252
|
-
* Clear all modules (only works if not frozen)
|
|
253
|
-
*/
|
|
254
|
-
clear() {
|
|
255
|
-
if (this.frozen) throw new Error("ModuleRegistry is frozen. Cannot clear modules.");
|
|
256
|
-
this.modules.clear();
|
|
257
|
-
}
|
|
258
|
-
};
|
|
259
|
-
let globalRegistry = null;
|
|
260
|
-
/**
|
|
261
|
-
* Get the global module registry
|
|
262
|
-
* Creates one if it doesn't exist
|
|
263
|
-
*/
|
|
264
|
-
function getModuleRegistry() {
|
|
265
|
-
if (!globalRegistry) globalRegistry = new ModuleRegistry();
|
|
266
|
-
return globalRegistry;
|
|
267
|
-
}
|
|
268
|
-
/**
|
|
269
|
-
* Get all modules that have a specific capability
|
|
270
|
-
* Convenience function that uses the global registry
|
|
271
|
-
*/
|
|
272
|
-
function getModulesWithCapability(capability) {
|
|
273
|
-
return getModuleRegistry().getWithCapability(capability);
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* Reset the global registry (mainly for testing)
|
|
277
|
-
*/
|
|
278
|
-
function resetModuleRegistry() {
|
|
279
|
-
globalRegistry = null;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
//#endregion
|
|
283
|
-
//#region src/schema/registry.ts
|
|
284
|
-
/**
|
|
285
|
-
* Registry for module-owned database schemas
|
|
286
|
-
*
|
|
287
|
-
* Allows modules to register their Drizzle schemas during the register() hook.
|
|
288
|
-
* The CLI uses this registry to aggregate schemas for drizzle-kit operations.
|
|
289
|
-
*/
|
|
290
|
-
var SchemaRegistry = class {
|
|
291
|
-
schemas = /* @__PURE__ */ new Map();
|
|
292
|
-
/**
|
|
293
|
-
* Register a schema from a module
|
|
294
|
-
* @param moduleId - Module identifier (e.g., 'acme.billing')
|
|
295
|
-
* @param tableName - Table name identifier (e.g., 'invoices')
|
|
296
|
-
* @param schema - Drizzle PgTable schema
|
|
297
|
-
*/
|
|
298
|
-
register(moduleId, tableName, schema) {
|
|
299
|
-
const key = `${moduleId}:${tableName}`;
|
|
300
|
-
if (this.schemas.has(key)) throw new Error(`Schema "${tableName}" already registered by module "${moduleId}"`);
|
|
301
|
-
this.schemas.set(key, {
|
|
302
|
-
moduleId,
|
|
303
|
-
tableName,
|
|
304
|
-
schema
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
/**
|
|
308
|
-
* Get all registered schemas
|
|
309
|
-
*/
|
|
310
|
-
getAll() {
|
|
311
|
-
return new Map(this.schemas);
|
|
312
|
-
}
|
|
313
|
-
/**
|
|
314
|
-
* Get schemas registered by a specific module
|
|
315
|
-
*/
|
|
316
|
-
getByModule(moduleId) {
|
|
317
|
-
return Array.from(this.schemas.values()).filter((entry) => entry.moduleId === moduleId);
|
|
318
|
-
}
|
|
319
|
-
/**
|
|
320
|
-
* Get all schemas as a flat object for drizzle-kit
|
|
321
|
-
* Keys are table names, values are PgTable schemas
|
|
322
|
-
*/
|
|
323
|
-
getAllSchemas() {
|
|
324
|
-
const result = {};
|
|
325
|
-
for (const entry of this.schemas.values()) result[entry.tableName] = entry.schema;
|
|
326
|
-
return result;
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Check if any schemas are registered
|
|
330
|
-
*/
|
|
331
|
-
hasSchemas() {
|
|
332
|
-
return this.schemas.size > 0;
|
|
333
|
-
}
|
|
334
|
-
/**
|
|
335
|
-
* Get the count of registered schemas
|
|
336
|
-
*/
|
|
337
|
-
get size() {
|
|
338
|
-
return this.schemas.size;
|
|
339
|
-
}
|
|
340
|
-
/**
|
|
341
|
-
* Clear all schemas (mainly for testing)
|
|
342
|
-
*/
|
|
343
|
-
clear() {
|
|
344
|
-
this.schemas.clear();
|
|
345
|
-
}
|
|
346
|
-
};
|
|
347
|
-
let globalSchemaRegistry = null;
|
|
348
|
-
/**
|
|
349
|
-
* Get the global schema registry
|
|
350
|
-
* Creates one if it doesn't exist
|
|
351
|
-
*/
|
|
352
|
-
function getSchemaRegistry() {
|
|
353
|
-
if (!globalSchemaRegistry) globalSchemaRegistry = new SchemaRegistry();
|
|
354
|
-
return globalSchemaRegistry;
|
|
355
|
-
}
|
|
356
|
-
/**
|
|
357
|
-
* Reset the global schema registry (mainly for testing)
|
|
358
|
-
*/
|
|
359
|
-
function resetSchemaRegistry() {
|
|
360
|
-
globalSchemaRegistry = null;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
//#endregion
|
|
364
|
-
//#region src/modules/loader.ts
|
|
365
|
-
/**
|
|
366
|
-
* Validates that a module has the correct shape
|
|
367
|
-
*/
|
|
368
|
-
const validateModule = (candidate, packageName) => {
|
|
369
|
-
if (!candidate || typeof candidate !== "object") throw new Error(`Invalid Kuckit module from "${packageName}": expected an object with module definition`);
|
|
370
|
-
const mod = candidate;
|
|
371
|
-
if (!mod.id || typeof mod.id !== "string") throw new Error(`Invalid Kuckit module from "${packageName}": missing or invalid 'id' property`);
|
|
372
|
-
for (const hookName of [
|
|
373
|
-
"register",
|
|
374
|
-
"registerApi",
|
|
375
|
-
"onBootstrap",
|
|
376
|
-
"onShutdown"
|
|
377
|
-
]) if (mod[hookName] !== void 0 && typeof mod[hookName] !== "function") throw new Error(`Invalid Kuckit module from "${packageName}": '${hookName}' must be a function`);
|
|
378
|
-
return mod;
|
|
379
|
-
};
|
|
380
|
-
/**
|
|
381
|
-
* Load and initialize Kuckit modules
|
|
382
|
-
*
|
|
383
|
-
* This function orchestrates the module loading lifecycle:
|
|
384
|
-
*
|
|
385
|
-
* 1. **Import Phase**: Dynamic import each module package
|
|
386
|
-
* 2. **Register Phase**: Run register() hooks (DI bindings)
|
|
387
|
-
* 3. **API Phase**: Run registerApi() hooks (API routes)
|
|
388
|
-
* 4. **Wire Phase**: Call onApiRegistrations callback
|
|
389
|
-
* 5. **Bootstrap Phase**: Run onBootstrap() hooks
|
|
390
|
-
* 6. **Complete Phase**: Call onComplete callback
|
|
391
|
-
*
|
|
392
|
-
* @example
|
|
393
|
-
* ```ts
|
|
394
|
-
* await loadKuckitModules({
|
|
395
|
-
* container,
|
|
396
|
-
* env: 'production',
|
|
397
|
-
* modules: [
|
|
398
|
-
* { package: '@kuckit/users-module' },
|
|
399
|
-
* { package: '@acme/billing-module', config: { currency: 'USD' } },
|
|
400
|
-
* { package: './src/modules/custom', disabled: process.env.DISABLE_CUSTOM === 'true' },
|
|
401
|
-
* ],
|
|
402
|
-
* onApiRegistrations: (registrations) => {
|
|
403
|
-
* // Wire up oRPC routers, REST routes, etc.
|
|
404
|
-
* for (const reg of registrations) {
|
|
405
|
-
* if (reg.type === 'rpc-router') {
|
|
406
|
-
* rootRouter.merge(reg.name, reg.router)
|
|
407
|
-
* }
|
|
408
|
-
* }
|
|
409
|
-
* },
|
|
410
|
-
* onComplete: () => {
|
|
411
|
-
* console.log('All modules loaded!')
|
|
412
|
-
* },
|
|
413
|
-
* })
|
|
414
|
-
* ```
|
|
415
|
-
*/
|
|
416
|
-
const loadKuckitModules = async (opts) => {
|
|
417
|
-
const { container, env, modules, onApiRegistrations, onComplete } = opts;
|
|
418
|
-
const apiRegistrations = [];
|
|
419
|
-
const loadedModules = [];
|
|
420
|
-
const registry = getModuleRegistry();
|
|
421
|
-
for (const spec of modules) {
|
|
422
|
-
if (spec.disabled) continue;
|
|
423
|
-
let validated;
|
|
424
|
-
if (spec.module) validated = validateModule(spec.module, spec.module.id ?? "direct-module");
|
|
425
|
-
else if (spec.package) {
|
|
426
|
-
let imported;
|
|
427
|
-
try {
|
|
428
|
-
imported = await import(spec.package);
|
|
429
|
-
} catch (error) {
|
|
430
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
431
|
-
throw new Error(`Failed to import module "${spec.package}": ${message}`);
|
|
432
|
-
}
|
|
433
|
-
validated = validateModule(imported.kuckitModule ?? imported.default, spec.package);
|
|
434
|
-
} else throw new Error("ModuleSpec must have either \"module\" or \"package\" property");
|
|
435
|
-
registry.register(validated);
|
|
436
|
-
loadedModules.push({
|
|
437
|
-
...validated,
|
|
438
|
-
_config: spec.config
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
for (const mod of loadedModules) if (mod.register) {
|
|
442
|
-
const schemaRegistry = getSchemaRegistry();
|
|
443
|
-
const ctx = {
|
|
444
|
-
container,
|
|
445
|
-
env,
|
|
446
|
-
config: mod._config ?? {},
|
|
447
|
-
registerSchema: (tableName, schema) => {
|
|
448
|
-
schemaRegistry.register(mod.id, tableName, schema);
|
|
449
|
-
}
|
|
450
|
-
};
|
|
451
|
-
try {
|
|
452
|
-
await mod.register(ctx);
|
|
453
|
-
} catch (error) {
|
|
454
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
455
|
-
throw new Error(`Module "${mod.id}" register() failed: ${message}`);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
for (const mod of loadedModules) if (mod.registerApi) {
|
|
459
|
-
const schemaRegistry = getSchemaRegistry();
|
|
460
|
-
const ctx = {
|
|
461
|
-
container,
|
|
462
|
-
env,
|
|
463
|
-
config: mod._config ?? {},
|
|
464
|
-
registerSchema: (tableName, schema) => {
|
|
465
|
-
schemaRegistry.register(mod.id, tableName, schema);
|
|
466
|
-
},
|
|
467
|
-
addApiRegistration: (reg) => {
|
|
468
|
-
apiRegistrations.push(reg);
|
|
469
|
-
}
|
|
470
|
-
};
|
|
471
|
-
try {
|
|
472
|
-
await mod.registerApi(ctx);
|
|
473
|
-
} catch (error) {
|
|
474
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
475
|
-
throw new Error(`Module "${mod.id}" registerApi() failed: ${message}`);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
if (onApiRegistrations && apiRegistrations.length > 0) await onApiRegistrations(apiRegistrations);
|
|
479
|
-
for (const mod of loadedModules) if (mod.onBootstrap) {
|
|
480
|
-
const schemaRegistry = getSchemaRegistry();
|
|
481
|
-
const ctx = {
|
|
482
|
-
container,
|
|
483
|
-
env,
|
|
484
|
-
config: mod._config ?? {},
|
|
485
|
-
registerSchema: (tableName, schema) => {
|
|
486
|
-
schemaRegistry.register(mod.id, tableName, schema);
|
|
487
|
-
}
|
|
488
|
-
};
|
|
489
|
-
try {
|
|
490
|
-
await mod.onBootstrap(ctx);
|
|
491
|
-
} catch (error) {
|
|
492
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
493
|
-
throw new Error(`Module "${mod.id}" onBootstrap() failed: ${message}`);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
if (onComplete) await onComplete();
|
|
497
|
-
};
|
|
498
|
-
/**
|
|
499
|
-
* Create a shutdown handler for loaded modules
|
|
500
|
-
*
|
|
501
|
-
* Returns a function that calls onShutdown() on all modules in reverse order.
|
|
502
|
-
* Call this during graceful shutdown.
|
|
503
|
-
*
|
|
504
|
-
* @example
|
|
505
|
-
* ```ts
|
|
506
|
-
* const shutdown = createModuleShutdownHandler(loadedModules, container, env)
|
|
507
|
-
*
|
|
508
|
-
* process.on('SIGTERM', async () => {
|
|
509
|
-
* await shutdown()
|
|
510
|
-
* process.exit(0)
|
|
511
|
-
* })
|
|
512
|
-
* ```
|
|
513
|
-
*/
|
|
514
|
-
const createModuleShutdownHandler = (modules, container, env) => {
|
|
515
|
-
return async () => {
|
|
516
|
-
for (const mod of [...modules].reverse()) if (mod.onShutdown) {
|
|
517
|
-
const schemaRegistry = getSchemaRegistry();
|
|
518
|
-
const ctx = {
|
|
519
|
-
container,
|
|
520
|
-
env,
|
|
521
|
-
config: {},
|
|
522
|
-
registerSchema: (tableName, schema) => {
|
|
523
|
-
schemaRegistry.register(mod.id, tableName, schema);
|
|
524
|
-
}
|
|
525
|
-
};
|
|
526
|
-
try {
|
|
527
|
-
await mod.onShutdown(ctx);
|
|
528
|
-
} catch (error) {
|
|
529
|
-
console.error(`Module "${mod.id}" onShutdown() failed:`, error);
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
};
|
|
533
|
-
};
|
|
534
|
-
|
|
535
|
-
//#endregion
|
|
536
|
-
//#region src/config/define-config.ts
|
|
537
|
-
/**
|
|
538
|
-
* Define a Kuckit configuration with full type safety.
|
|
539
|
-
*
|
|
540
|
-
* This helper provides IntelliSense support and type checking for your configuration.
|
|
541
|
-
*
|
|
542
|
-
* @example
|
|
543
|
-
* ```typescript
|
|
544
|
-
* // kuckit.config.ts
|
|
545
|
-
* import { defineConfig } from '@kuckit/sdk'
|
|
546
|
-
*
|
|
547
|
-
* export default defineConfig({
|
|
548
|
-
* modules: [
|
|
549
|
-
* { package: '@kuckit/users-module' },
|
|
550
|
-
* { package: '@acme/billing-module', config: { currency: 'USD' } },
|
|
551
|
-
* ],
|
|
552
|
-
*
|
|
553
|
-
* server: {
|
|
554
|
-
* apiPrefix: '/api',
|
|
555
|
-
* },
|
|
556
|
-
*
|
|
557
|
-
* client: {
|
|
558
|
-
* routeInjection: true,
|
|
559
|
-
* },
|
|
560
|
-
* })
|
|
561
|
-
* ```
|
|
562
|
-
*/
|
|
563
|
-
function defineConfig(config) {
|
|
564
|
-
return config;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
//#endregion
|
|
568
|
-
//#region src/config/loader.ts
|
|
569
|
-
const CONFIG_FILES = [
|
|
570
|
-
"kuckit.config.ts",
|
|
571
|
-
"kuckit.config.js",
|
|
572
|
-
"kuckit.config.mjs"
|
|
573
|
-
];
|
|
574
|
-
/**
|
|
575
|
-
* Find the config file path by searching from cwd upward
|
|
576
|
-
*/
|
|
577
|
-
function findConfigFile(cwd = process.cwd()) {
|
|
578
|
-
let dir = cwd;
|
|
579
|
-
while (dir !== dirname(dir)) {
|
|
580
|
-
for (const file of CONFIG_FILES) {
|
|
581
|
-
const configPath = resolve(dir, file);
|
|
582
|
-
if (existsSync(configPath)) return configPath;
|
|
583
|
-
}
|
|
584
|
-
dir = dirname(dir);
|
|
585
|
-
}
|
|
586
|
-
for (const file of CONFIG_FILES) {
|
|
587
|
-
const configPath = resolve(dir, file);
|
|
588
|
-
if (existsSync(configPath)) return configPath;
|
|
589
|
-
}
|
|
590
|
-
return null;
|
|
591
|
-
}
|
|
592
|
-
/**
|
|
593
|
-
* Check if a unified config file exists
|
|
594
|
-
*/
|
|
595
|
-
function hasUnifiedConfig(cwd = process.cwd()) {
|
|
596
|
-
return findConfigFile(cwd) !== null;
|
|
597
|
-
}
|
|
598
|
-
/**
|
|
599
|
-
* Load Kuckit configuration from file
|
|
600
|
-
*
|
|
601
|
-
* Uses jiti for TypeScript support at runtime.
|
|
602
|
-
* Falls back to dynamic import for JS/MJS files.
|
|
603
|
-
*
|
|
604
|
-
* @param cwd - Directory to start searching from (default: process.cwd())
|
|
605
|
-
* @throws Error if config file not found or invalid
|
|
606
|
-
*/
|
|
607
|
-
async function loadKuckitConfig(cwd = process.cwd()) {
|
|
608
|
-
const configPath = findConfigFile(cwd);
|
|
609
|
-
if (!configPath) throw new Error(`No Kuckit config file found. Create a kuckit.config.ts at your project root.\nSearched from: ${cwd}`);
|
|
610
|
-
let config;
|
|
611
|
-
if (configPath.endsWith(".ts")) {
|
|
612
|
-
const { createJiti } = await import("jiti");
|
|
613
|
-
const loaded = await createJiti(cwd, { interopDefault: true }).import(configPath);
|
|
614
|
-
config = loaded.default ?? loaded;
|
|
615
|
-
} else {
|
|
616
|
-
const loaded = await import(configPath);
|
|
617
|
-
config = loaded.default ?? loaded;
|
|
618
|
-
}
|
|
619
|
-
if (!config || typeof config !== "object") throw new Error(`Invalid Kuckit config at ${configPath}: expected an object`);
|
|
620
|
-
if (!Array.isArray(config.modules)) throw new Error(`Invalid Kuckit config at ${configPath}: 'modules' must be an array`);
|
|
621
|
-
return {
|
|
622
|
-
...config,
|
|
623
|
-
_configPath: configPath
|
|
624
|
-
};
|
|
625
|
-
}
|
|
626
|
-
/**
|
|
627
|
-
* Try to load config, returning null if not found
|
|
628
|
-
*/
|
|
629
|
-
async function tryLoadKuckitConfig(cwd = process.cwd()) {
|
|
630
|
-
try {
|
|
631
|
-
return await loadKuckitConfig(cwd);
|
|
632
|
-
} catch {
|
|
633
|
-
return null;
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
//#endregion
|
|
638
17
|
export { Application, Contracts, Domain, ModuleRegistry, SchemaRegistry, asClass, asFunction, asValue, createKuckitContainer, createModuleShutdownHandler, defineConfig, defineKuckitModule, disposeContainer, findConfigFile, getModuleRegistry, getModulesWithCapability, getSchemaRegistry, hasUnifiedConfig, loadKuckitConfig, loadKuckitModules, registerCoreModule, resetModuleRegistry, resetSchemaRegistry, tryLoadKuckitConfig };
|