@warlock.js/core 4.5.0 → 4.6.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 +27 -0
- package/esm/ai/src/agent/agent.mjs +126 -7
- package/esm/ai/src/agent/agent.mjs.map +1 -1
- package/esm/ai/src/agent/signature.mjs +57 -0
- package/esm/ai/src/agent/signature.mjs.map +1 -0
- package/esm/ai/src/agent/snapshot.mjs +101 -0
- package/esm/ai/src/agent/snapshot.mjs.map +1 -0
- package/esm/ai/src/ai.mjs +37 -1
- package/esm/ai/src/ai.mjs.map +1 -1
- package/esm/ai/src/errors/agent-drift-error.mjs +31 -0
- package/esm/ai/src/errors/agent-drift-error.mjs.map +1 -0
- package/esm/ai/src/errors/index.mjs +2 -0
- package/esm/ai/src/errors/planner-drift-error.mjs +33 -0
- package/esm/ai/src/errors/planner-drift-error.mjs.map +1 -0
- package/esm/ai/src/image/image-cost.mjs +55 -0
- package/esm/ai/src/image/image-cost.mjs.map +1 -0
- package/esm/ai/src/image/image.mjs +112 -0
- package/esm/ai/src/image/image.mjs.map +1 -0
- package/esm/ai/src/image/index.mjs +4 -0
- package/esm/ai/src/index.mjs +20 -0
- package/esm/ai/src/planner/planner-run.mjs +161 -6
- package/esm/ai/src/planner/planner-run.mjs.map +1 -1
- package/esm/ai/src/planner/planner.mjs +25 -1
- package/esm/ai/src/planner/planner.mjs.map +1 -1
- package/esm/ai/src/planner/snapshot.mjs +95 -0
- package/esm/ai/src/planner/snapshot.mjs.map +1 -0
- package/esm/ai/src/rag/hybrid/bm25.mjs +51 -0
- package/esm/ai/src/rag/hybrid/bm25.mjs.map +1 -0
- package/esm/ai/src/rag/hybrid/hybrid-rank.mjs +29 -0
- package/esm/ai/src/rag/hybrid/hybrid-rank.mjs.map +1 -0
- package/esm/ai/src/rag/hybrid/rrf.mjs +30 -0
- package/esm/ai/src/rag/hybrid/rrf.mjs.map +1 -0
- package/esm/ai/src/rag/index.mjs +11 -0
- package/esm/ai/src/rag/loaders/errors.mjs +25 -0
- package/esm/ai/src/rag/loaders/errors.mjs.map +1 -0
- package/esm/ai/src/rag/loaders/index.mjs +7 -0
- package/esm/ai/src/rag/loaders/load-html.mjs +138 -0
- package/esm/ai/src/rag/loaders/load-html.mjs.map +1 -0
- package/esm/ai/src/rag/loaders/load-pdf.mjs +150 -0
- package/esm/ai/src/rag/loaders/load-pdf.mjs.map +1 -0
- package/esm/ai/src/rag/loaders/load-text.mjs +60 -0
- package/esm/ai/src/rag/loaders/load-text.mjs.map +1 -0
- package/esm/ai/src/rag/loaders/load-web.mjs +89 -0
- package/esm/ai/src/rag/loaders/load-web.mjs.map +1 -0
- package/esm/ai/src/rag/store/pg-vector-store.mjs +328 -0
- package/esm/ai/src/rag/store/pg-vector-store.mjs.map +1 -0
- package/esm/ai/src/rag/transforms/multi-query.mjs +41 -0
- package/esm/ai/src/rag/transforms/multi-query.mjs.map +1 -0
- package/esm/ai/src/speech/index.mjs +3 -0
- package/esm/ai/src/speech/speech.mjs +122 -0
- package/esm/ai/src/speech/speech.mjs.map +1 -0
- package/esm/ai/src/supervisor/entries.mjs +2 -2
- package/esm/ai/src/supervisor/entries.mjs.map +1 -1
- package/esm/ai/src/transcribe/audio-input.mjs +84 -0
- package/esm/ai/src/transcribe/audio-input.mjs.map +1 -0
- package/esm/ai/src/transcribe/index.mjs +4 -0
- package/esm/ai/src/transcribe/transcribe.mjs +127 -0
- package/esm/ai/src/transcribe/transcribe.mjs.map +1 -0
- package/esm/ai-openai/src/image.mjs +5 -0
- package/esm/ai-openai/src/index.mjs +3 -0
- package/esm/ai-openai/src/sdk.mjs +3 -0
- package/esm/ai-openai/src/speech.mjs +5 -0
- package/esm/ai-openai/src/transcription.mjs +6 -0
- package/esm/ai-openai/src/utils/index.mjs +1 -0
- package/esm/ai-openai/src/utils/to-openai-messages.mjs +3 -0
- package/esm/cli/commands/doctor/checks/config.check.mjs +42 -0
- package/esm/cli/commands/doctor/checks/config.check.mjs.map +1 -0
- package/esm/cli/commands/doctor/checks/connectors.check.mjs +28 -0
- package/esm/cli/commands/doctor/checks/connectors.check.mjs.map +1 -0
- package/esm/cli/commands/doctor/checks/health.check.mjs +35 -0
- package/esm/cli/commands/doctor/checks/health.check.mjs.map +1 -0
- package/esm/cli/commands/doctor/checks/index.mjs +25 -0
- package/esm/cli/commands/doctor/checks/index.mjs.map +1 -0
- package/esm/cli/commands/doctor/checks/optional-peers.check.mjs +90 -0
- package/esm/cli/commands/doctor/checks/optional-peers.check.mjs.map +1 -0
- package/esm/cli/commands/doctor/checks/release-hygiene.check.mjs +69 -0
- package/esm/cli/commands/doctor/checks/release-hygiene.check.mjs.map +1 -0
- package/esm/cli/commands/doctor/checks/routes.check.mjs +30 -0
- package/esm/cli/commands/doctor/checks/routes.check.mjs.map +1 -0
- package/esm/cli/commands/doctor/doctor-command.action.mjs +28 -0
- package/esm/cli/commands/doctor/doctor-command.action.mjs.map +1 -0
- package/esm/cli/commands/doctor/format-report.mjs +44 -0
- package/esm/cli/commands/doctor/format-report.mjs.map +1 -0
- package/esm/cli/commands/doctor/run-checks.mjs +50 -0
- package/esm/cli/commands/doctor/run-checks.mjs.map +1 -0
- package/esm/cli/commands/doctor.command.mjs +29 -0
- package/esm/cli/commands/doctor.command.mjs.map +1 -0
- package/esm/cli/commands/generate/templates/stubs.mjs +6 -8
- package/esm/cli/commands/generate/templates/stubs.mjs.map +1 -1
- package/esm/cli/commands/routes/format-routes-table.mjs +129 -0
- package/esm/cli/commands/routes/format-routes-table.mjs.map +1 -0
- package/esm/cli/commands/routes/route-row.mjs +92 -0
- package/esm/cli/commands/routes/route-row.mjs.map +1 -0
- package/esm/cli/commands/routes/routes-command.action.mjs +43 -0
- package/esm/cli/commands/routes/routes-command.action.mjs.map +1 -0
- package/esm/cli/commands/routes.command.mjs +51 -0
- package/esm/cli/commands/routes.command.mjs.map +1 -0
- package/esm/cli/commands/seed.command.mjs +5 -0
- package/esm/cli/commands/seed.command.mjs.map +1 -1
- package/esm/cli/framework-cli-commands.mjs +4 -0
- package/esm/cli/framework-cli-commands.mjs.map +1 -1
- package/esm/config/config-getter.mjs +5 -5
- package/esm/config/config-getter.mjs.map +1 -1
- package/esm/config/config-loader.mjs +2 -2
- package/esm/config/config-loader.mjs.map +1 -1
- package/esm/connectors/access-connector.mjs +2 -2
- package/esm/connectors/access-connector.mjs.map +1 -1
- package/esm/connectors/ai-connector.mjs +2 -2
- package/esm/connectors/ai-connector.mjs.map +1 -1
- package/esm/connectors/cache-connector.mjs +2 -2
- package/esm/connectors/cache-connector.mjs.map +1 -1
- package/esm/connectors/database-connector.mjs +2 -2
- package/esm/connectors/database-connector.mjs.map +1 -1
- package/esm/connectors/herald-connector.mjs +2 -2
- package/esm/connectors/herald-connector.mjs.map +1 -1
- package/esm/connectors/http-connector.d.mts.map +1 -1
- package/esm/connectors/http-connector.mjs +9 -7
- package/esm/connectors/http-connector.mjs.map +1 -1
- package/esm/connectors/logger-connector.mjs +2 -2
- package/esm/connectors/logger-connector.mjs.map +1 -1
- package/esm/connectors/mail-connector.mjs +2 -2
- package/esm/connectors/mail-connector.mjs.map +1 -1
- package/esm/connectors/notifications-connector.mjs +2 -2
- package/esm/connectors/notifications-connector.mjs.map +1 -1
- package/esm/connectors/socket-connector.mjs +3 -3
- package/esm/connectors/socket-connector.mjs.map +1 -1
- package/esm/database/index.d.mts +2 -1
- package/esm/database/index.mjs +1 -0
- package/esm/database/seed-command-action.mjs +44 -4
- package/esm/database/seed-command-action.mjs.map +1 -1
- package/esm/database/seeds/seed-records-table-migration.mjs +36 -0
- package/esm/database/seeds/seed-records-table-migration.mjs.map +1 -0
- package/esm/database/seeds/seeder.d.mts +22 -5
- package/esm/database/seeds/seeder.d.mts.map +1 -1
- package/esm/database/seeds/seeder.errors.d.mts +22 -0
- package/esm/database/seeds/seeder.errors.d.mts.map +1 -0
- package/esm/database/seeds/seeder.errors.mjs +29 -0
- package/esm/database/seeds/seeder.errors.mjs.map +1 -0
- package/esm/database/seeds/seeder.mjs.map +1 -1
- package/esm/database/seeds/seeders.manager.mjs +90 -11
- package/esm/database/seeds/seeders.manager.mjs.map +1 -1
- package/esm/database/seeds/types.d.mts +117 -1
- package/esm/database/seeds/types.d.mts.map +1 -1
- package/esm/database/seeds/utils.mjs +6 -1
- package/esm/database/seeds/utils.mjs.map +1 -1
- package/esm/dev-server/module-loader.d.mts +12 -0
- package/esm/dev-server/module-loader.d.mts.map +1 -1
- package/esm/dev-server/module-loader.mjs +30 -2
- package/esm/dev-server/module-loader.mjs.map +1 -1
- package/esm/http/config.mjs +2 -2
- package/esm/http/config.mjs.map +1 -1
- package/esm/http/createHttpApplication.mjs +2 -2
- package/esm/http/createHttpApplication.mjs.map +1 -1
- package/esm/http/health.d.mts +14 -0
- package/esm/http/health.d.mts.map +1 -1
- package/esm/http/health.mjs +16 -0
- package/esm/http/health.mjs.map +1 -1
- package/esm/http/middleware/idempotency.middleware.mjs +5 -5
- package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
- package/esm/http/middleware/inject-request-context.mjs +2 -2
- package/esm/http/middleware/inject-request-context.mjs.map +1 -1
- package/esm/http/middleware/maintenance.middleware.mjs +4 -4
- package/esm/http/middleware/maintenance.middleware.mjs.map +1 -1
- package/esm/http/plugins.mjs +9 -9
- package/esm/http/plugins.mjs.map +1 -1
- package/esm/http/response.mjs +5 -5
- package/esm/http/response.mjs.map +1 -1
- package/esm/http/server.mjs +4 -4
- package/esm/http/server.mjs.map +1 -1
- package/esm/index.d.mts +4 -3
- package/esm/index.mjs +2 -1
- package/esm/repositories/adapters/cascade/cascade-query-builder.d.mts +28 -1
- package/esm/repositories/adapters/cascade/cascade-query-builder.d.mts.map +1 -1
- package/esm/repositories/adapters/cascade/cascade-query-builder.mjs +39 -0
- package/esm/repositories/adapters/cascade/cascade-query-builder.mjs.map +1 -1
- package/esm/repositories/contracts/index.d.mts +1 -1
- package/esm/repositories/contracts/query-builder.contract.d.mts +87 -1
- package/esm/repositories/contracts/query-builder.contract.d.mts.map +1 -1
- package/esm/repositories/index.d.mts +1 -1
- package/esm/repositories/repository.manager.d.mts +99 -1
- package/esm/repositories/repository.manager.d.mts.map +1 -1
- package/esm/repositories/repository.manager.mjs +128 -0
- package/esm/repositories/repository.manager.mjs.map +1 -1
- package/esm/router/router.d.mts +9 -0
- package/esm/router/router.d.mts.map +1 -1
- package/esm/router/router.mjs +21 -1
- package/esm/router/router.mjs.map +1 -1
- package/esm/utils/paths.mjs +2 -2
- package/esm/utils/paths.mjs.map +1 -1
- package/esm/validation/validateAll.mjs +2 -2
- package/esm/validation/validateAll.mjs.map +1 -1
- package/llms-full.txt +427 -45
- package/llms.txt +4 -2
- package/package.json +9 -9
- package/skills/README.md +65 -0
- package/skills/health-checks/SKILL.md +13 -0
- package/skills/use-repository/SKILL.md +38 -1
- package/skills/warlock-doctor/SKILL.md +135 -0
- package/skills/warlock-routes/SKILL.md +91 -0
- package/skills/write-seeder/SKILL.md +143 -45
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region ../@warlock.js/core/src/database/seeds/seeder.errors.ts
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when a seeder's `dependsOn` references a seeder name that is not
|
|
4
|
+
* registered with the manager. Surfaces the offending dependent + the missing
|
|
5
|
+
* dependency so the misconfiguration is obvious without spelunking.
|
|
6
|
+
*/
|
|
7
|
+
var UnknownSeederDependencyError = class extends Error {
|
|
8
|
+
constructor(seeder, dependency) {
|
|
9
|
+
super(`Seeder "${seeder}" depends on "${dependency}", but no seeder with that name is registered.`);
|
|
10
|
+
this.seeder = seeder;
|
|
11
|
+
this.dependency = dependency;
|
|
12
|
+
this.name = "UnknownSeederDependencyError";
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Thrown when the `dependsOn` graph contains a cycle, which would make a
|
|
17
|
+
* deterministic run order impossible. Carries the cycle path for diagnosis.
|
|
18
|
+
*/
|
|
19
|
+
var SeederDependencyCycleError = class extends Error {
|
|
20
|
+
constructor(cycle) {
|
|
21
|
+
super(`Seeder dependency cycle detected: ${cycle.join(" -> ")}`);
|
|
22
|
+
this.cycle = cycle;
|
|
23
|
+
this.name = "SeederDependencyCycleError";
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
export { SeederDependencyCycleError, UnknownSeederDependencyError };
|
|
29
|
+
//# sourceMappingURL=seeder.errors.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seeder.errors.mjs","names":[],"sources":["../../../../../../../../@warlock.js/core/src/database/seeds/seeder.errors.ts"],"sourcesContent":["/**\n * Thrown when a seeder's `dependsOn` references a seeder name that is not\n * registered with the manager. Surfaces the offending dependent + the missing\n * dependency so the misconfiguration is obvious without spelunking.\n */\nexport class UnknownSeederDependencyError extends Error {\n public constructor(\n public readonly seeder: string,\n public readonly dependency: string,\n ) {\n super(\n `Seeder \"${seeder}\" depends on \"${dependency}\", but no seeder with that name is registered.`,\n );\n this.name = \"UnknownSeederDependencyError\";\n }\n}\n\n/**\n * Thrown when the `dependsOn` graph contains a cycle, which would make a\n * deterministic run order impossible. Carries the cycle path for diagnosis.\n */\nexport class SeederDependencyCycleError extends Error {\n public constructor(public readonly cycle: string[]) {\n super(`Seeder dependency cycle detected: ${cycle.join(\" -> \")}`);\n this.name = \"SeederDependencyCycleError\";\n }\n}\n"],"mappings":";;;;;;AAKA,IAAa,+BAAb,cAAkD,MAAM;CACtD,AAAO,YACL,AAAgB,QAChB,AAAgB,YAChB;EACA,MACE,WAAW,OAAO,gBAAgB,WAAW,+CAC/C;EALgB;EACA;EAKhB,KAAK,OAAO;CACd;AACF;;;;;AAMA,IAAa,6BAAb,cAAgD,MAAM;CACpD,AAAO,YAAY,AAAgB,OAAiB;EAClD,MAAM,qCAAqC,MAAM,KAAK,MAAM,GAAG;EAD9B;EAEjC,KAAK,OAAO;CACd;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seeder.mjs","names":[],"sources":["../../../../../../../../@warlock.js/core/src/database/seeds/seeder.ts"],"sourcesContent":["import { SeedResult } from \"./types\";\n\nexport type Seeder = {\n /**\n * Seed name\n */\n name: string;\n /**\n * Whether to enable it\n */\n enabled?: boolean;\n /**\n * Seed description\n */\n description?: string;\n /**\n * List of dependent seeds to run first\n */\n dependsOn?: string[];\n /**\n * Whether to run it once\n */\n once?: boolean;\n /**\n * Seed execution order\n */\n order?: number;\n /**\n * Batch size\n */\n batchSize?: number;\n /**\n * Run seed\n */\n run(): Promise<SeedResult | void>;\n};\n\n/**\n * Create a new seeder\n */\nexport function seeder(seeder: Seeder) {\n return seeder;\n}\n"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"seeder.mjs","names":[],"sources":["../../../../../../../../@warlock.js/core/src/database/seeds/seeder.ts"],"sourcesContent":["import { SeedContext, SeedResult } from \"./types\";\n\nexport type Seeder = {\n /**\n * Seed name\n */\n name: string;\n /**\n * Whether to enable it\n */\n enabled?: boolean;\n /**\n * Seed description\n */\n description?: string;\n /**\n * List of dependent seeds to run first\n */\n dependsOn?: string[];\n /**\n * Whether to run it once\n */\n once?: boolean;\n /**\n * Seed execution order\n */\n order?: number;\n /**\n * Batch size.\n *\n * Surfaced to `run()` as {@link SeedContext.batchSize} (defaulting to this\n * value, or `0` when unset) so a seed can forward it to\n * `Model.createMany(rows, { batchSize })`.\n */\n batchSize?: number;\n /**\n * Run seed.\n *\n * Receives a {@link SeedContext} exposing:\n * - `track` — registers the records this seed creates so they can be undone\n * by `warlock seed --drop`. Auto-derives `recordsCreated` from the track\n * count; an explicitly returned {@link SeedResult} is an optional fallback.\n * - `now` — the injectable clock to read instead of an ambient `new Date()` /\n * `dayjs()`, so historical seeds and the seeds-log share one overridable\n * clock.\n * - `batchSize` — the suggested bulk-insert batch size (from this seeder's\n * `batchSize`, default `0`).\n *\n * The `ctx` parameter is optional at the call site — an existing zero-arg\n * `run()` keeps working unchanged.\n */\n run(ctx: SeedContext): Promise<SeedResult | void>;\n};\n\n/**\n * Create a new seeder\n */\nexport function seeder(seeder: Seeder) {\n return seeder;\n}\n"],"mappings":";;;;AAyDA,SAAgB,OAAO,QAAgB;CACrC,OAAO;AACT"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SeederDependencyCycleError, UnknownSeederDependencyError } from "./seeder.errors.mjs";
|
|
2
|
+
import { seedRecordsTableName, seedsTableName } from "./utils.mjs";
|
|
3
|
+
import { SeedRecordsTableMigration } from "./seed-records-table-migration.mjs";
|
|
2
4
|
import { SeedsTableMigration } from "./seeds-table-migration.mjs";
|
|
3
5
|
import { DatabaseWriterValidationError, dataSourceRegistry, migrationRunner, transaction } from "@warlock.js/cascade";
|
|
4
6
|
import { colors } from "@mongez/copper";
|
|
@@ -9,6 +11,7 @@ var SeedersManager = class {
|
|
|
9
11
|
this.options = options;
|
|
10
12
|
this.seeders = [];
|
|
11
13
|
this.datasource = options?.datasource ?? dataSourceRegistry.get();
|
|
14
|
+
this.now = options?.clock ?? (() => /* @__PURE__ */ new Date());
|
|
12
15
|
}
|
|
13
16
|
/**
|
|
14
17
|
* Register new seeder
|
|
@@ -22,6 +25,7 @@ var SeedersManager = class {
|
|
|
22
25
|
*/
|
|
23
26
|
async init() {
|
|
24
27
|
if (!await this.driver.blueprint.tableExists("_seeds")) await migrationRunner.run(SeedsTableMigration);
|
|
28
|
+
if (!await this.driver.blueprint.tableExists("_seed_records")) await migrationRunner.run(SeedRecordsTableMigration);
|
|
25
29
|
}
|
|
26
30
|
/**
|
|
27
31
|
* Run seeders
|
|
@@ -42,10 +46,23 @@ var SeedersManager = class {
|
|
|
42
46
|
try {
|
|
43
47
|
console.log(`🔄 Running ${colors.green(seeder.name)}...`);
|
|
44
48
|
const startTime = Date.now();
|
|
45
|
-
const
|
|
49
|
+
const refs = [];
|
|
50
|
+
const track = this.createTracker(seeder, refs);
|
|
51
|
+
const runSeeder = async () => {
|
|
52
|
+
await this.driver.deleteMany(seedRecordsTableName, { seeder: seeder.name });
|
|
53
|
+
const result = await seeder.run({
|
|
54
|
+
track,
|
|
55
|
+
now: this.now,
|
|
56
|
+
batchSize: seeder.batchSize ?? 0
|
|
57
|
+
});
|
|
58
|
+
if (refs.length > 0) await this.driver.insertMany(seedRecordsTableName, refs.map((ref) => ({ ...ref })));
|
|
59
|
+
return result;
|
|
60
|
+
};
|
|
61
|
+
const result = withTransaction ? await transaction(runSeeder) : await runSeeder();
|
|
46
62
|
const duration = Date.now() - startTime;
|
|
47
|
-
|
|
48
|
-
|
|
63
|
+
const recordsCreated = refs.length > 0 ? refs.length : result?.recordsCreated ?? 0;
|
|
64
|
+
await this.storeSeedsResults(seeder, { recordsCreated });
|
|
65
|
+
console.log(`✅ ${colors.green(seeder.name)} completed (${duration}ms, ${recordsCreated} records)\n`);
|
|
49
66
|
successCount++;
|
|
50
67
|
} catch (error) {
|
|
51
68
|
const err = error;
|
|
@@ -63,6 +80,35 @@ var SeedersManager = class {
|
|
|
63
80
|
console.log("=".repeat(50));
|
|
64
81
|
}
|
|
65
82
|
/**
|
|
83
|
+
* Build a `track` helper bound to the given seeder. Tracked refs are pushed
|
|
84
|
+
* into `refs` and persisted (within the run transaction) by the caller.
|
|
85
|
+
*
|
|
86
|
+
* Every overload returns its first argument so it can be chained inline.
|
|
87
|
+
*/
|
|
88
|
+
createTracker(seeder, refs) {
|
|
89
|
+
const push = (table, recordId) => {
|
|
90
|
+
refs.push({
|
|
91
|
+
seeder: seeder.name,
|
|
92
|
+
table,
|
|
93
|
+
recordId,
|
|
94
|
+
runAt: this.now()
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
function track(arg, id) {
|
|
98
|
+
if (typeof arg === "string") {
|
|
99
|
+
push(arg, id);
|
|
100
|
+
return arg;
|
|
101
|
+
}
|
|
102
|
+
if (Array.isArray(arg)) {
|
|
103
|
+
for (const model of arg) push(model.getTableName(), model.id);
|
|
104
|
+
return arg;
|
|
105
|
+
}
|
|
106
|
+
push(arg.getTableName(), arg.id);
|
|
107
|
+
return arg;
|
|
108
|
+
}
|
|
109
|
+
return track;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
66
112
|
* Sort seeds
|
|
67
113
|
*/
|
|
68
114
|
sort() {
|
|
@@ -72,13 +118,45 @@ var SeedersManager = class {
|
|
|
72
118
|
return this;
|
|
73
119
|
}
|
|
74
120
|
/**
|
|
75
|
-
* Prepare seeders to order by the seeder order
|
|
76
|
-
*
|
|
77
|
-
*
|
|
121
|
+
* Prepare seeders to order by the seeder order, then resolve `dependsOn`.
|
|
122
|
+
*
|
|
123
|
+
* Dependencies are honored via a topological sort layered over the existing
|
|
124
|
+
* numeric `order` tie-break: among seeders whose dependencies are already
|
|
125
|
+
* satisfied, the lowest `order` (then registration order) runs first. Throws
|
|
126
|
+
* {@link UnknownSeederDependencyError} for a missing dependency and
|
|
127
|
+
* {@link SeederDependencyCycleError} for a cycle.
|
|
78
128
|
*/
|
|
79
129
|
prepareSeeders() {
|
|
80
130
|
this.seeders = this.seeders.filter((seeder) => seeder.enabled !== false);
|
|
81
131
|
this.sort();
|
|
132
|
+
this.seeders = this.resolveDependencies(this.seeders);
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Topologically sort `seeders` by their `dependsOn` names. The input is
|
|
137
|
+
* assumed pre-sorted by `order` (see {@link sort}); that ordering is used as
|
|
138
|
+
* the tie-break so dependency-free siblings keep their `order` sequence.
|
|
139
|
+
*/
|
|
140
|
+
resolveDependencies(seeders) {
|
|
141
|
+
const byName = /* @__PURE__ */ new Map();
|
|
142
|
+
for (const seeder of seeders) byName.set(seeder.name, seeder);
|
|
143
|
+
for (const seeder of seeders) for (const dependency of seeder.dependsOn ?? []) if (!byName.has(dependency)) throw new UnknownSeederDependencyError(seeder.name, dependency);
|
|
144
|
+
const sorted = [];
|
|
145
|
+
const state = /* @__PURE__ */ new Map();
|
|
146
|
+
const visit = (seeder, path) => {
|
|
147
|
+
const status = state.get(seeder.name);
|
|
148
|
+
if (status === "visited") return;
|
|
149
|
+
if (status === "visiting") {
|
|
150
|
+
const cycleStart = path.indexOf(seeder.name);
|
|
151
|
+
throw new SeederDependencyCycleError(path.slice(cycleStart >= 0 ? cycleStart : 0).concat(seeder.name));
|
|
152
|
+
}
|
|
153
|
+
state.set(seeder.name, "visiting");
|
|
154
|
+
for (const dependency of seeder.dependsOn ?? []) visit(byName.get(dependency), [...path, seeder.name]);
|
|
155
|
+
state.set(seeder.name, "visited");
|
|
156
|
+
sorted.push(seeder);
|
|
157
|
+
};
|
|
158
|
+
for (const seeder of seeders) visit(seeder, []);
|
|
159
|
+
return sorted;
|
|
82
160
|
}
|
|
83
161
|
/**
|
|
84
162
|
* Store seed results in db
|
|
@@ -86,19 +164,20 @@ var SeedersManager = class {
|
|
|
86
164
|
async storeSeedsResults(seeder, result) {
|
|
87
165
|
const oldResult = await this.getMetadata(seeder);
|
|
88
166
|
console.log(`📊 Total records created: ${result.recordsCreated}`);
|
|
167
|
+
const now = this.now();
|
|
89
168
|
if (oldResult) {
|
|
90
169
|
await this.driver.queryBuilder(seedsTableName).where("name", seeder.name).update({
|
|
91
170
|
runCount: oldResult.runCount + 1,
|
|
92
|
-
lastRunAt:
|
|
171
|
+
lastRunAt: now,
|
|
93
172
|
totalRecordsCreated: oldResult.totalRecordsCreated + result.recordsCreated,
|
|
94
173
|
lastRunRecordsCreated: result.recordsCreated
|
|
95
174
|
});
|
|
96
175
|
console.log(`📊 Total records created so far: ${oldResult.totalRecordsCreated + result.recordsCreated}`);
|
|
97
176
|
} else await this.driver.insert(seedsTableName, {
|
|
98
177
|
name: seeder.name,
|
|
99
|
-
createdAt:
|
|
100
|
-
firstRunAt:
|
|
101
|
-
lastRunAt:
|
|
178
|
+
createdAt: now,
|
|
179
|
+
firstRunAt: now,
|
|
180
|
+
lastRunAt: now,
|
|
102
181
|
runCount: 1,
|
|
103
182
|
totalRecordsCreated: result.recordsCreated,
|
|
104
183
|
lastRunRecordsCreated: result.recordsCreated
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seeders.manager.mjs","names":[],"sources":["../../../../../../../../@warlock.js/core/src/database/seeds/seeders.manager.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport {\n type DataSource,\n type DriverContract,\n DatabaseWriterValidationError,\n dataSourceRegistry,\n migrationRunner,\n transaction,\n} from \"@warlock.js/cascade\";\nimport { Seeder } from \"./seeder\";\nimport { SeedsTableMigration } from \"./seeds-table-migration\";\nimport { SeederMetadata, SeedResult } from \"./types\";\nimport { seedsTableName } from \"./utils\";\n\nexport type SeedersManagerOptions = {\n datasource?: DataSource;\n};\n\nexport class SeedersManager {\n public seeders: Seeder[] = [];\n\n protected datasource?: DataSource;\n\n public constructor(protected options?: SeedersManagerOptions) {\n this.datasource = options?.datasource ?? dataSourceRegistry.get();\n }\n\n /**\n * Register new seeder\n */\n public register(...seeders: Seeder[]) {\n this.seeders.push(...seeders);\n return this;\n }\n\n /**\n * Initialize before running\n */\n protected async init() {\n if (!(await this.driver.blueprint.tableExists(seedsTableName))) {\n await migrationRunner.run(SeedsTableMigration);\n }\n }\n\n /**\n * Run seeders\n */\n public async run(withTransaction = true) {\n await this.init();\n\n this.prepareSeeders();\n\n console.log(`🌱 Running ${this.seeders.length} seeder(s)...\\n`);\n\n let successCount = 0;\n let skippedCount = 0;\n let failedCount = 0;\n\n for (const seeder of this.seeders) {\n // Check if already executed\n if (seeder.once && (await this.seederIsExecutedBefore(seeder))) {\n console.log(`⏭️ Skipping ${colors.yellow(seeder.name)} (already executed)`);\n skippedCount++;\n continue;\n }\n\n try {\n console.log(`🔄 Running ${colors.green(seeder.name)}...`);\n const startTime = Date.now();\n\n const result = withTransaction\n ? await transaction(async () => seeder.run())\n : await seeder.run();\n\n const duration = Date.now() - startTime;\n if (result) {\n await this.storeSeedsResults(seeder, result);\n }\n\n console.log(\n `✅ ${colors.green(seeder.name)} completed (${duration}ms, ${result?.recordsCreated ?? 0} records)\\n`,\n );\n successCount++;\n } catch (error: any) {\n const err = error as Error;\n console.error(`❌ ${colors.red(seeder.name)} failed:`, err.message);\n console.log(err);\n failedCount++;\n\n if (error instanceof DatabaseWriterValidationError) {\n console.log(error.errors);\n }\n\n // Re-throw to stop execution (or continue to next seed based on your preference)\n throw error;\n }\n }\n\n // Summary\n console.log(\"\\n\" + \"=\".repeat(50));\n console.log(`✅ Success: ${successCount}`);\n\n if (skippedCount > 0) console.log(`⏭️ Skipped: ${skippedCount}`);\n\n if (failedCount > 0) console.log(`❌ Failed: ${failedCount}`);\n\n console.log(\"=\".repeat(50));\n }\n\n /**\n * Sort seeds\n */\n public sort() {\n this.seeders = this.seeders.sort((a, b) => {\n const orderA = a.order ?? Number.MAX_SAFE_INTEGER;\n const orderB = b.order ?? Number.MAX_SAFE_INTEGER;\n return orderA - orderB;\n });\n\n return this;\n }\n\n /**\n * Prepare seeders to order by the seeder order\n * Also keep an eye on the dependsOn for each seeder to make sure\n * they are ordered correctly\n */\n public prepareSeeders() {\n this.seeders = this.seeders.filter((seeder) => seeder.enabled !== false);\n\n this.sort();\n\n // TODO: Handle dependsOn resolution\n // This is more complex - needs topological sort\n }\n\n /**\n * Store seed results in db\n */\n public async storeSeedsResults(seeder: Seeder, result: SeedResult) {\n const oldResult = await this.getMetadata(seeder);\n\n // log with an emoji icon at beginning of text no of total craeted records\n console.log(`📊 Total records created: ${result.recordsCreated}`);\n\n if (oldResult) {\n // Update existing record - use query builder with WHERE clause\n await this.driver\n .queryBuilder(seedsTableName)\n .where(\"name\", seeder.name)\n .update({\n runCount: oldResult.runCount + 1,\n lastRunAt: new Date(),\n totalRecordsCreated: oldResult.totalRecordsCreated + result.recordsCreated,\n lastRunRecordsCreated: result.recordsCreated,\n });\n\n // now display total created records so far\n console.log(\n `📊 Total records created so far: ${oldResult.totalRecordsCreated + result.recordsCreated}`,\n );\n } else {\n // Insert new record\n await this.driver.insert(seedsTableName, {\n name: seeder.name,\n createdAt: new Date(),\n firstRunAt: new Date(),\n lastRunAt: new Date(),\n runCount: 1,\n totalRecordsCreated: result.recordsCreated,\n lastRunRecordsCreated: result.recordsCreated,\n });\n }\n }\n\n /**\n * Get seed info from database\n */\n protected getMetadata(seeder: Seeder): Promise<SeederMetadata> {\n const driver = this.datasource.driver;\n\n return driver.queryBuilder(seedsTableName).where(\"name\", seeder.name).first();\n }\n\n /**\n * Get driver instance\n */\n protected get driver(): DriverContract {\n return this.datasource.driver;\n }\n\n /**\n * Check if seeder has been executed before\n */\n protected async seederIsExecutedBefore(seeder: Seeder) {\n return !!(await this.getMetadata(seeder));\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"seeders.manager.mjs","names":[],"sources":["../../../../../../../../@warlock.js/core/src/database/seeds/seeders.manager.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport {\n type DataSource,\n type DriverContract,\n DatabaseWriterValidationError,\n dataSourceRegistry,\n migrationRunner,\n transaction,\n} from \"@warlock.js/cascade\";\nimport { Seeder } from \"./seeder\";\nimport { SeederDependencyCycleError, UnknownSeederDependencyError } from \"./seeder.errors\";\nimport { SeedRecordsTableMigration } from \"./seed-records-table-migration\";\nimport { SeedsTableMigration } from \"./seeds-table-migration\";\nimport {\n SeedClock,\n SeederMetadata,\n SeedRecordRef,\n SeedResult,\n Track,\n TrackableModel,\n} from \"./types\";\nimport { seedRecordsTableName, seedsTableName } from \"./utils\";\n\nexport type SeedersManagerOptions = {\n datasource?: DataSource;\n /**\n * Clock the manager reads for the `now` it hands each seeder AND for its own\n * metadata timestamps (`createdAt` / `firstRunAt` / `lastRunAt` / a tracked\n * ref's `runAt`). Defaults to `() => new Date()`. Override it to make a\n * historical back-fill or a test deterministic — every timestamp a run\n * produces then comes from this one clock.\n */\n clock?: SeedClock;\n};\n\nexport class SeedersManager {\n public seeders: Seeder[] = [];\n\n protected datasource?: DataSource;\n\n /**\n * The single clock every timestamp in a run reads from. Defaults to\n * `() => new Date()`; overridable via {@link SeedersManagerOptions.clock}.\n */\n protected readonly now: SeedClock;\n\n public constructor(protected options?: SeedersManagerOptions) {\n this.datasource = options?.datasource ?? dataSourceRegistry.get();\n this.now = options?.clock ?? (() => new Date());\n }\n\n /**\n * Register new seeder\n */\n public register(...seeders: Seeder[]) {\n this.seeders.push(...seeders);\n return this;\n }\n\n /**\n * Initialize before running\n */\n protected async init() {\n if (!(await this.driver.blueprint.tableExists(seedsTableName))) {\n await migrationRunner.run(SeedsTableMigration);\n }\n\n if (!(await this.driver.blueprint.tableExists(seedRecordsTableName))) {\n await migrationRunner.run(SeedRecordsTableMigration);\n }\n }\n\n /**\n * Run seeders\n */\n public async run(withTransaction = true) {\n await this.init();\n\n this.prepareSeeders();\n\n console.log(`🌱 Running ${this.seeders.length} seeder(s)...\\n`);\n\n let successCount = 0;\n let skippedCount = 0;\n let failedCount = 0;\n\n for (const seeder of this.seeders) {\n // Check if already executed\n if (seeder.once && (await this.seederIsExecutedBefore(seeder))) {\n console.log(`⏭️ Skipping ${colors.yellow(seeder.name)} (already executed)`);\n skippedCount++;\n continue;\n }\n\n try {\n console.log(`🔄 Running ${colors.green(seeder.name)}...`);\n const startTime = Date.now();\n\n const refs: SeedRecordRef[] = [];\n const track = this.createTracker(seeder, refs);\n\n const runSeeder = async () => {\n // Keep LAST-RUN refs only: drop the prior refs for this seeder before\n // writing the new ones, so `--drop` undo stays bounded.\n await this.driver.deleteMany(seedRecordsTableName, { seeder: seeder.name });\n\n const result = await seeder.run({\n track,\n now: this.now,\n batchSize: seeder.batchSize ?? 0,\n });\n\n if (refs.length > 0) {\n await this.driver.insertMany(\n seedRecordsTableName,\n refs.map((ref) => ({ ...ref })),\n );\n }\n\n return result;\n };\n\n const result = withTransaction ? await transaction(runSeeder) : await runSeeder();\n\n const duration = Date.now() - startTime;\n\n // Auto-derive recordsCreated from the track count; a returned\n // SeedResult is an optional fallback when nothing was tracked.\n const recordsCreated = refs.length > 0 ? refs.length : (result?.recordsCreated ?? 0);\n\n await this.storeSeedsResults(seeder, { recordsCreated });\n\n console.log(\n `✅ ${colors.green(seeder.name)} completed (${duration}ms, ${recordsCreated} records)\\n`,\n );\n successCount++;\n } catch (error: any) {\n const err = error as Error;\n console.error(`❌ ${colors.red(seeder.name)} failed:`, err.message);\n console.log(err);\n failedCount++;\n\n if (error instanceof DatabaseWriterValidationError) {\n console.log(error.errors);\n }\n\n // Re-throw to stop execution (or continue to next seed based on your preference)\n throw error;\n }\n }\n\n // Summary\n console.log(\"\\n\" + \"=\".repeat(50));\n console.log(`✅ Success: ${successCount}`);\n\n if (skippedCount > 0) console.log(`⏭️ Skipped: ${skippedCount}`);\n\n if (failedCount > 0) console.log(`❌ Failed: ${failedCount}`);\n\n console.log(\"=\".repeat(50));\n }\n\n /**\n * Build a `track` helper bound to the given seeder. Tracked refs are pushed\n * into `refs` and persisted (within the run transaction) by the caller.\n *\n * Every overload returns its first argument so it can be chained inline.\n */\n protected createTracker(seeder: Seeder, refs: SeedRecordRef[]): Track {\n const push = (table: string, recordId: number | string) => {\n refs.push({ seeder: seeder.name, table, recordId, runAt: this.now() });\n };\n\n function track<T extends TrackableModel>(model: T): T;\n function track<T extends TrackableModel>(models: T[]): T[];\n function track(table: string, id: number | string): string;\n function track(\n arg: TrackableModel | TrackableModel[] | string,\n id?: number | string,\n ): TrackableModel | TrackableModel[] | string {\n if (typeof arg === \"string\") {\n push(arg, id as number | string);\n\n return arg;\n }\n\n if (Array.isArray(arg)) {\n for (const model of arg) {\n push(model.getTableName(), model.id);\n }\n\n return arg;\n }\n\n push(arg.getTableName(), arg.id);\n\n return arg;\n }\n\n return track;\n }\n\n /**\n * Sort seeds\n */\n public sort() {\n this.seeders = this.seeders.sort((a, b) => {\n const orderA = a.order ?? Number.MAX_SAFE_INTEGER;\n const orderB = b.order ?? Number.MAX_SAFE_INTEGER;\n return orderA - orderB;\n });\n\n return this;\n }\n\n /**\n * Prepare seeders to order by the seeder order, then resolve `dependsOn`.\n *\n * Dependencies are honored via a topological sort layered over the existing\n * numeric `order` tie-break: among seeders whose dependencies are already\n * satisfied, the lowest `order` (then registration order) runs first. Throws\n * {@link UnknownSeederDependencyError} for a missing dependency and\n * {@link SeederDependencyCycleError} for a cycle.\n */\n public prepareSeeders() {\n this.seeders = this.seeders.filter((seeder) => seeder.enabled !== false);\n\n this.sort();\n\n this.seeders = this.resolveDependencies(this.seeders);\n\n return this;\n }\n\n /**\n * Topologically sort `seeders` by their `dependsOn` names. The input is\n * assumed pre-sorted by `order` (see {@link sort}); that ordering is used as\n * the tie-break so dependency-free siblings keep their `order` sequence.\n */\n protected resolveDependencies(seeders: Seeder[]): Seeder[] {\n const byName = new Map<string, Seeder>();\n\n for (const seeder of seeders) {\n byName.set(seeder.name, seeder);\n }\n\n // Validate dependencies up-front so an unknown name is a clear error rather\n // than a silently dropped edge.\n for (const seeder of seeders) {\n for (const dependency of seeder.dependsOn ?? []) {\n if (!byName.has(dependency)) {\n throw new UnknownSeederDependencyError(seeder.name, dependency);\n }\n }\n }\n\n const sorted: Seeder[] = [];\n const state = new Map<string, \"visiting\" | \"visited\">();\n\n const visit = (seeder: Seeder, path: string[]) => {\n const status = state.get(seeder.name);\n\n if (status === \"visited\") {\n return;\n }\n\n if (status === \"visiting\") {\n const cycleStart = path.indexOf(seeder.name);\n const cycle = path.slice(cycleStart >= 0 ? cycleStart : 0).concat(seeder.name);\n\n throw new SeederDependencyCycleError(cycle);\n }\n\n state.set(seeder.name, \"visiting\");\n\n for (const dependency of seeder.dependsOn ?? []) {\n // Presence already validated above; the `!` is safe.\n visit(byName.get(dependency)!, [...path, seeder.name]);\n }\n\n state.set(seeder.name, \"visited\");\n sorted.push(seeder);\n };\n\n // Iterate in the incoming (order-sorted, registration-stable) sequence so\n // dependency-free seeders preserve their `order` tie-break.\n for (const seeder of seeders) {\n visit(seeder, []);\n }\n\n return sorted;\n }\n\n /**\n * Store seed results in db\n */\n public async storeSeedsResults(seeder: Seeder, result: SeedResult) {\n const oldResult = await this.getMetadata(seeder);\n\n // log with an emoji icon at beginning of text no of total craeted records\n console.log(`📊 Total records created: ${result.recordsCreated}`);\n\n // Read the single overridable clock once so every timestamp in this write\n // is identical (and pinned in a deterministic / historical run).\n const now = this.now();\n\n if (oldResult) {\n // Update existing record - use query builder with WHERE clause\n await this.driver\n .queryBuilder(seedsTableName)\n .where(\"name\", seeder.name)\n .update({\n runCount: oldResult.runCount + 1,\n lastRunAt: now,\n totalRecordsCreated: oldResult.totalRecordsCreated + result.recordsCreated,\n lastRunRecordsCreated: result.recordsCreated,\n });\n\n // now display total created records so far\n console.log(\n `📊 Total records created so far: ${oldResult.totalRecordsCreated + result.recordsCreated}`,\n );\n } else {\n // Insert new record\n await this.driver.insert(seedsTableName, {\n name: seeder.name,\n createdAt: now,\n firstRunAt: now,\n lastRunAt: now,\n runCount: 1,\n totalRecordsCreated: result.recordsCreated,\n lastRunRecordsCreated: result.recordsCreated,\n });\n }\n }\n\n /**\n * Get seed info from database\n */\n protected getMetadata(seeder: Seeder): Promise<SeederMetadata> {\n const driver = this.datasource.driver;\n\n return driver.queryBuilder(seedsTableName).where(\"name\", seeder.name).first();\n }\n\n /**\n * Get driver instance\n */\n protected get driver(): DriverContract {\n return this.datasource.driver;\n }\n\n /**\n * Check if seeder has been executed before\n */\n protected async seederIsExecutedBefore(seeder: Seeder) {\n return !!(await this.getMetadata(seeder));\n }\n}\n"],"mappings":"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"}
|
|
@@ -11,6 +11,122 @@ type SeederMetadata = {
|
|
|
11
11
|
totalRecordsCreated: number;
|
|
12
12
|
lastRunRecordsCreated: number;
|
|
13
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* A minimal Cascade-model-like shape that `track` can read.
|
|
16
|
+
*
|
|
17
|
+
* The cascade `Model` exposes both `getTableName()` and an `id` getter;
|
|
18
|
+
* this structural type keeps `track` decoupled from the concrete `Model`
|
|
19
|
+
* class so seeds can also hand in lightweight test doubles.
|
|
20
|
+
*/
|
|
21
|
+
type TrackableModel = {
|
|
22
|
+
/**
|
|
23
|
+
* Owning table / collection name.
|
|
24
|
+
*/
|
|
25
|
+
getTableName(): string;
|
|
26
|
+
/**
|
|
27
|
+
* Primary key value.
|
|
28
|
+
*/
|
|
29
|
+
id: number | string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* A single tracked record reference, persisted to the `seed_records` table.
|
|
33
|
+
*/
|
|
34
|
+
type SeedRecordRef = {
|
|
35
|
+
/**
|
|
36
|
+
* Owning seeder name.
|
|
37
|
+
*/
|
|
38
|
+
seeder: string;
|
|
39
|
+
/**
|
|
40
|
+
* Table / collection the record lives in.
|
|
41
|
+
*/
|
|
42
|
+
table: string;
|
|
43
|
+
/**
|
|
44
|
+
* Primary key value of the tracked record.
|
|
45
|
+
*/
|
|
46
|
+
recordId: number | string;
|
|
47
|
+
/**
|
|
48
|
+
* Wall-clock time the reference was recorded.
|
|
49
|
+
*/
|
|
50
|
+
runAt: Date;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Tracker handed to a seeder's `run()` so it can register the records it
|
|
54
|
+
* creates. Tracked references are written in the same transaction the manager
|
|
55
|
+
* wraps `run()` in, and are read back by `warlock seed --drop` to undo a seed.
|
|
56
|
+
*
|
|
57
|
+
* Every overload RETURNS its first argument so it can be chained inline:
|
|
58
|
+
*
|
|
59
|
+
* ```ts
|
|
60
|
+
* const user = track(await User.create({ ... }));
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
type Track = {
|
|
64
|
+
/**
|
|
65
|
+
* Track a single model. Reads `model.getTableName()` + `model.id`.
|
|
66
|
+
*
|
|
67
|
+
* @returns the same model, so the call can be chained inline.
|
|
68
|
+
*/
|
|
69
|
+
<T extends TrackableModel>(model: T): T;
|
|
70
|
+
/**
|
|
71
|
+
* Track many models in one call.
|
|
72
|
+
*
|
|
73
|
+
* @returns the same array, so the call can be chained inline.
|
|
74
|
+
*/
|
|
75
|
+
<T extends TrackableModel>(models: T[]): T[];
|
|
76
|
+
/**
|
|
77
|
+
* Raw escape hatch — track an arbitrary table + id without a model.
|
|
78
|
+
*
|
|
79
|
+
* @returns the passed table name, so the call can be chained inline.
|
|
80
|
+
*/
|
|
81
|
+
(table: string, id: number | string): string;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Injectable clock. Returns the "current" time a seed run should treat as
|
|
85
|
+
* `now`.
|
|
86
|
+
*
|
|
87
|
+
* Defaults to `() => new Date()`. Override it (via the
|
|
88
|
+
* {@link SeedContext.now} the manager builds) to make historical seeds and the
|
|
89
|
+
* manager's own metadata timestamps deterministic — both read from this single
|
|
90
|
+
* clock, so a test or a back-fill can pin every timestamp to one value.
|
|
91
|
+
*/
|
|
92
|
+
type SeedClock = () => Date;
|
|
93
|
+
/**
|
|
94
|
+
* Context passed to a seeder's `run()`.
|
|
95
|
+
*/
|
|
96
|
+
type SeedContext = {
|
|
97
|
+
/**
|
|
98
|
+
* Tracker used to register created records for later undo via
|
|
99
|
+
* `warlock seed --drop`.
|
|
100
|
+
*/
|
|
101
|
+
track: Track;
|
|
102
|
+
/**
|
|
103
|
+
* The clock the seed should read instead of an ambient `new Date()` /
|
|
104
|
+
* `dayjs()`. The {@link SeedersManager} builds it from its `clock` option
|
|
105
|
+
* (default `() => new Date()`) and reads the SAME clock for its own metadata
|
|
106
|
+
* timestamps, so historical seeds and the seeds-log stay consistent.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```ts
|
|
110
|
+
* run: async ({ track, now }) => {
|
|
111
|
+
* track(await User.create({ created_at: now() }));
|
|
112
|
+
* }
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
now: SeedClock;
|
|
116
|
+
/**
|
|
117
|
+
* Suggested batch size for bulk inserts, defaulted from the seeder's
|
|
118
|
+
* `batchSize`. Surfaced here so a seed can forward it to
|
|
119
|
+
* `Model.createMany(rows, { batchSize })` without re-reading its own config.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```ts
|
|
123
|
+
* run: async ({ track, batchSize }) => {
|
|
124
|
+
* track(await User.createMany(rows, { batchSize }));
|
|
125
|
+
* }
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
batchSize: number;
|
|
129
|
+
};
|
|
14
130
|
//#endregion
|
|
15
|
-
export { SeedResult, SeederMetadata };
|
|
131
|
+
export { SeedClock, SeedContext, SeedRecordRef, SeedResult, SeederMetadata, Track, TrackableModel };
|
|
16
132
|
//# sourceMappingURL=types.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/core/src/database/seeds/types.ts"],"mappings":";KAAY,UAAA;EACV,cAAc;AAAA;AAAA,KAGJ,cAAA;EACV,IAAA;EACA,SAAA;EACA,UAAA;EACA,SAAA;EACA,QAAA;EACA,mBAAA;EACA,qBAAA;AAAA"}
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/core/src/database/seeds/types.ts"],"mappings":";KAAY,UAAA;EACV,cAAc;AAAA;AAAA,KAGJ,cAAA;EACV,IAAA;EACA,SAAA;EACA,UAAA;EACA,SAAA;EACA,QAAA;EACA,mBAAA;EACA,qBAAA;AAAA;;;;;;;AAAqB;KAUX,cAAA;EAAc;;;EAIxB,YAAA;EAUU;;;EANV,EAAE;AAAA;;;;KAMQ,aAAA;EAgBC;AAAA;AAcb;EA1BE,MAAA;EA0Be;;;EAtBf,KAAA;EAkCW;;;EA9BX,QAAA;EA8B0C;;;EA1B1C,KAAA,EAAO,IAAI;AAAA;;;;;;;;;;AAgCwB;AAYrC;KA9BY,KAAA;;;AA8BsB;AAKlC;;aA7Ba,cAAA,EAAgB,KAAA,EAAO,CAAA,GAAI,CAAA;EAgDxB;;;;;EAAA,WA1CH,cAAA,EAAgB,MAAA,EAAQ,CAAA,KAAM,CAAA;EAuDhC;AAAA;;;;EAAA,CAjDR,KAAA,UAAe,EAAA;AAAA;;;;;;;;;;KAYN,SAAA,SAAkB,IAAI;;;;KAKtB,WAAA;;;;;EAKV,KAAA,EAAO,KAAA;;;;;;;;;;;;;;EAcP,GAAA,EAAK,SAAS;;;;;;;;;;;;;EAad,SAAA;AAAA"}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
//#region ../@warlock.js/core/src/database/seeds/utils.ts
|
|
2
2
|
const seedsTableName = "_seeds";
|
|
3
|
+
/**
|
|
4
|
+
* Table that stores per-record references created by seeders, written via the
|
|
5
|
+
* `track` helper. Backs `warlock seed --drop`.
|
|
6
|
+
*/
|
|
7
|
+
const seedRecordsTableName = "_seed_records";
|
|
3
8
|
|
|
4
9
|
//#endregion
|
|
5
|
-
export { seedsTableName };
|
|
10
|
+
export { seedRecordsTableName, seedsTableName };
|
|
6
11
|
//# sourceMappingURL=utils.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.mjs","names":[],"sources":["../../../../../../../../@warlock.js/core/src/database/seeds/utils.ts"],"sourcesContent":["export const seedsTableName = \"_seeds\";\n"],"mappings":";AAAA,MAAa,iBAAiB"}
|
|
1
|
+
{"version":3,"file":"utils.mjs","names":[],"sources":["../../../../../../../../@warlock.js/core/src/database/seeds/utils.ts"],"sourcesContent":["export const seedsTableName = \"_seeds\";\n\n/**\n * Table that stores per-record references created by seeders, written via the\n * `track` helper. Backs `warlock seed --drop`.\n */\nexport const seedRecordsTableName = \"_seed_records\";\n"],"mappings":";AAAA,MAAa,iBAAiB;;;;;AAM9B,MAAa,uBAAuB"}
|
|
@@ -5,6 +5,11 @@ import { FileManager } from "./file-manager.mjs";
|
|
|
5
5
|
declare global {
|
|
6
6
|
var __currentModuleFile: FileManager | undefined;
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Thrown when a special file (route/event/main/locale) fails to import or
|
|
10
|
+
* register. Carries the file that failed and the original cause so a broken
|
|
11
|
+
* module surfaces with its origin instead of vanishing into a 404.
|
|
12
|
+
*/
|
|
8
13
|
/**
|
|
9
14
|
* Loads application modules through the ESM loader hook. The hook stamps
|
|
10
15
|
* `?v=N` on every import, so each reload is a fresh module without any
|
|
@@ -17,6 +22,13 @@ declare class ModuleLoader {
|
|
|
17
22
|
/**
|
|
18
23
|
* Eagerly load every special file at boot, in the canonical order so
|
|
19
24
|
* later phases (e.g. routes) see state earlier phases registered.
|
|
25
|
+
*
|
|
26
|
+
* A failing module no longer vanishes silently: each failure is collected
|
|
27
|
+
* (so one broken file doesn't hide the others) and, once every file has been
|
|
28
|
+
* attempted, an aggregate error is thrown. The boot callers
|
|
29
|
+
* (`DevelopmentServer.start`, the HTTP test bootstrap) already wrap this in a
|
|
30
|
+
* try/catch that logs and aborts the boot — so a broken route module aborts
|
|
31
|
+
* boot loudly rather than booting into a surface that 404s.
|
|
20
32
|
*/
|
|
21
33
|
loadAll(): Promise<void>;
|
|
22
34
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-loader.d.mts","names":[],"sources":["../../../../../../../@warlock.js/core/src/dev-server/module-loader.ts"],"mappings":";;;;QAMQ,MAAA;EAAA,IACF,mBAAA,EAAqB,WAAW;AAAA
|
|
1
|
+
{"version":3,"file":"module-loader.d.mts","names":[],"sources":["../../../../../../../@warlock.js/core/src/dev-server/module-loader.ts"],"mappings":";;;;QAMQ,MAAA;EAAA,IACF,mBAAA,EAAqB,WAAW;AAAA;;;;;;;;;;;cAgCzB,YAAA;EAAA,iBAGkB,qBAAA;EAAA,iBAFZ,aAAA;cAEY,qBAAA,EAAuB,qBAAA;EAgIlB;;;;;;;;;;;EAnHrB,OAAA,IAAW,OAAA;EA6BhB;;;;EADK,UAAA,cACX,IAAA,EAAM,WAAA,EACN,IAAA,WACC,OAAA,CAAQ,CAAA;EAgDE;;;;;EAAA,YAAA,CAAa,IAAA,EAAM,WAAA,GAAc,OAAA;EAkB5B;;;;EAAX,UAAA,CAAW,IAAA,EAAM,WAAA;EAiCD;AAAA;;EAfhB,oBAAA,CAAqB,IAAA,EAAM,WAAA;;;;;;UAe1B,eAAA;AAAA"}
|
|
@@ -4,6 +4,21 @@ import { pathToFileURL } from "node:url";
|
|
|
4
4
|
|
|
5
5
|
//#region ../@warlock.js/core/src/dev-server/module-loader.ts
|
|
6
6
|
/**
|
|
7
|
+
* Thrown when a special file (route/event/main/locale) fails to import or
|
|
8
|
+
* register. Carries the file that failed and the original cause so a broken
|
|
9
|
+
* module surfaces with its origin instead of vanishing into a 404.
|
|
10
|
+
*/
|
|
11
|
+
var ModuleLoadError = class extends Error {
|
|
12
|
+
constructor(type, file, cause) {
|
|
13
|
+
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
14
|
+
super(`Failed to load ${type}: ${file.relativePath} - ${reason}`);
|
|
15
|
+
this.type = type;
|
|
16
|
+
this.file = file;
|
|
17
|
+
this.cause = cause;
|
|
18
|
+
this.name = "ModuleLoadError";
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
7
22
|
* The four "special" file kinds that the dev server eagerly imports at boot.
|
|
8
23
|
* Order matters: locales first (used by everything), events before main so
|
|
9
24
|
* listeners are registered when main runs, routes last so handlers are bound.
|
|
@@ -27,9 +42,22 @@ var ModuleLoader = class {
|
|
|
27
42
|
/**
|
|
28
43
|
* Eagerly load every special file at boot, in the canonical order so
|
|
29
44
|
* later phases (e.g. routes) see state earlier phases registered.
|
|
45
|
+
*
|
|
46
|
+
* A failing module no longer vanishes silently: each failure is collected
|
|
47
|
+
* (so one broken file doesn't hide the others) and, once every file has been
|
|
48
|
+
* attempted, an aggregate error is thrown. The boot callers
|
|
49
|
+
* (`DevelopmentServer.start`, the HTTP test bootstrap) already wrap this in a
|
|
50
|
+
* try/catch that logs and aborts the boot — so a broken route module aborts
|
|
51
|
+
* boot loudly rather than booting into a surface that 404s.
|
|
30
52
|
*/
|
|
31
53
|
async loadAll() {
|
|
32
|
-
|
|
54
|
+
const failures = [];
|
|
55
|
+
for (const type of SPECIAL_TYPES) for (const file of this.specialFilesCollector.getFilesByType(type)) try {
|
|
56
|
+
await this.loadModule(file, type);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
failures.push(error instanceof ModuleLoadError ? error : new ModuleLoadError(type, file, error));
|
|
59
|
+
}
|
|
60
|
+
if (failures.length > 0) throw new AggregateError(failures, `${failures.length} module(s) failed to load during boot:\n` + failures.map((failure) => ` - ${failure.message}`).join("\n"));
|
|
33
61
|
}
|
|
34
62
|
/**
|
|
35
63
|
* Import a file through the loader hook. For routes, scopes the import in
|
|
@@ -50,7 +78,7 @@ var ModuleLoader = class {
|
|
|
50
78
|
} catch (error) {
|
|
51
79
|
if (error.code === "ERR_MODULE_NOT_FOUND") devLogError(formatModuleNotFoundError(error));
|
|
52
80
|
else devLogError(`Failed to load ${type}: ${file.relativePath} - ${error?.message || error}`, error);
|
|
53
|
-
|
|
81
|
+
throw error instanceof ModuleLoadError ? error : new ModuleLoadError(type, file, error);
|
|
54
82
|
} finally {
|
|
55
83
|
globalThis.__currentModuleFile = void 0;
|
|
56
84
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-loader.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/dev-server/module-loader.ts"],"sourcesContent":["import { pathToFileURL } from \"node:url\";\
|
|
1
|
+
{"version":3,"file":"module-loader.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/dev-server/module-loader.ts"],"sourcesContent":["import { pathToFileURL } from \"node:url\";\nimport { router } from \"../router/router\";\nimport { devLogError, formatModuleNotFoundError } from \"./dev-logger\";\nimport type { CleanupFunction, FileManager } from \"./file-manager\";\nimport type { SpecialFileType, SpecialFilesCollector } from \"./special-files-collector\";\n\ndeclare global {\n var __currentModuleFile: FileManager | undefined;\n}\n\n/**\n * Thrown when a special file (route/event/main/locale) fails to import or\n * register. Carries the file that failed and the original cause so a broken\n * module surfaces with its origin instead of vanishing into a 404.\n */\nexport class ModuleLoadError extends Error {\n public constructor(\n public readonly type: string,\n public readonly file: FileManager,\n public readonly cause: unknown,\n ) {\n const reason = cause instanceof Error ? cause.message : String(cause);\n super(`Failed to load ${type}: ${file.relativePath} - ${reason}`);\n this.name = \"ModuleLoadError\";\n }\n}\n\n/**\n * The four \"special\" file kinds that the dev server eagerly imports at boot.\n * Order matters: locales first (used by everything), events before main so\n * listeners are registered when main runs, routes last so handlers are bound.\n */\nconst SPECIAL_TYPES: readonly SpecialFileType[] = [\"locale\", \"event\", \"main\", \"route\"] as const;\n\n/**\n * Loads application modules through the ESM loader hook. The hook stamps\n * `?v=N` on every import, so each reload is a fresh module without any\n * userland cache-busting plumbing.\n */\nexport class ModuleLoader {\n private readonly loadedModules = new Map<string, unknown>();\n\n constructor(private readonly specialFilesCollector: SpecialFilesCollector) {}\n\n /**\n * Eagerly load every special file at boot, in the canonical order so\n * later phases (e.g. routes) see state earlier phases registered.\n *\n * A failing module no longer vanishes silently: each failure is collected\n * (so one broken file doesn't hide the others) and, once every file has been\n * attempted, an aggregate error is thrown. The boot callers\n * (`DevelopmentServer.start`, the HTTP test bootstrap) already wrap this in a\n * try/catch that logs and aborts the boot — so a broken route module aborts\n * boot loudly rather than booting into a surface that 404s.\n */\n public async loadAll(): Promise<void> {\n const failures: ModuleLoadError[] = [];\n\n for (const type of SPECIAL_TYPES) {\n for (const file of this.specialFilesCollector.getFilesByType(type)) {\n try {\n await this.loadModule(file, type);\n } catch (error) {\n failures.push(\n error instanceof ModuleLoadError ? error : new ModuleLoadError(type, file, error),\n );\n }\n }\n }\n\n if (failures.length > 0) {\n throw new AggregateError(\n failures,\n `${failures.length} module(s) failed to load during boot:\\n` +\n failures.map((failure) => ` - ${failure.message}`).join(\"\\n\"),\n );\n }\n }\n\n /**\n * Import a file through the loader hook. For routes, scopes the import in\n * `router.withSourceFile()` so the added routes carry their origin.\n */\n public async loadModule<T = unknown>(\n file: FileManager,\n type: string,\n ): Promise<T | undefined> {\n if (file.relativePath.endsWith(\".env\")) return undefined;\n\n globalThis.__currentModuleFile = file;\n\n try {\n const fileUrl = pathToFileURL(file.absolutePath).href;\n\n const load = async () => {\n const module = (await import(fileUrl)) as Record<string, unknown>;\n this.loadedModules.set(file.absolutePath, module);\n this.registerCleanup(file, module);\n return module as T;\n };\n\n return type === \"route\"\n ? await router.withSourceFile(file.relativePath, load)\n : await load();\n } catch (error: any) {\n if (error.code === \"ERR_MODULE_NOT_FOUND\") {\n devLogError(formatModuleNotFoundError(error));\n } else {\n // Pass the error through so devLogError prints the stack. With dev\n // source maps enabled, frames remap to the original `.ts` line/col\n // (e.g. the actual call site of an undefined symbol) instead of\n // vanishing — a one-line \"x is not defined\" is undebuggable.\n devLogError(\n `Failed to load ${type}: ${file.relativePath} - ${error?.message || error}`,\n error,\n );\n }\n\n // RETHROW after logging. Previously this returned undefined, so a route\n // file that threw on import or registration disappeared with no boot\n // error — the whole surface 404'd silently. Boot callers abort on the\n // throw; the HMR batch-reload handler catches it, keeps the server\n // alive, and shows it loudly.\n throw error instanceof ModuleLoadError ? error : new ModuleLoadError(type, file, error);\n } finally {\n globalThis.__currentModuleFile = undefined;\n }\n }\n\n /**\n * Reload a previously-loaded special file. The version-bump that makes the\n * import fresh is done by the caller (layer-executor) before invoking this.\n * Routes are removed from the registry first so re-registration is clean.\n */\n public async reloadModule(file: FileManager): Promise<void> {\n const moduleType = this.specialFilesCollector.getFileType(file.relativePath);\n if (!moduleType) return;\n\n this.runCleanup(file);\n\n if (moduleType === \"route\") {\n router.removeRoutesBySourceFile(file.relativePath);\n }\n\n this.loadedModules.delete(file.absolutePath);\n await this.loadModule(file, moduleType);\n }\n\n /**\n * Run every cleanup hook the file registered (or `$cleanup` on its exports)\n * and reset the list. Called once per HMR reload.\n */\n public runCleanup(file: FileManager): void {\n for (const hook of file.cleanup) {\n try {\n if (typeof hook === \"function\") {\n hook();\n } else if (hook && typeof (hook as { unsubscribe?: () => void }).unsubscribe === \"function\") {\n (hook as { unsubscribe: () => void }).unsubscribe();\n }\n } catch {\n // Cleanup errors shouldn't break HMR.\n }\n }\n file.resetCleanup();\n }\n\n /**\n * Clean up after a file whose source was deleted from disk.\n */\n public cleanupDeletedModule(file: FileManager): void {\n this.loadedModules.delete(file.absolutePath);\n\n if (file.type === \"route\") {\n router.removeRoutesBySourceFile(file.relativePath);\n }\n\n this.runCleanup(file);\n }\n\n /**\n * Scan a freshly-loaded module for cleanup handlers and register them on\n * the FileManager so they run before the next reload. Priority: explicit\n * `export function cleanup()` → `.$cleanup` on any exported value.\n */\n private registerCleanup(file: FileManager, module: Record<string, unknown>): void {\n if (typeof module.cleanup === \"function\") {\n file.addCleanup(module.cleanup as CleanupFunction);\n return;\n }\n\n const cleanups: CleanupFunction[] = [];\n\n for (const exportedValue of Object.values(module)) {\n const cleanup = (exportedValue as { $cleanup?: unknown })?.$cleanup;\n if (typeof cleanup === \"function\") {\n cleanups.push(cleanup.bind(exportedValue) as CleanupFunction);\n }\n }\n\n if (cleanups.length > 0) {\n file.addCleanup(cleanups);\n }\n }\n}\n"],"mappings":"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"}
|
package/esm/http/config.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import config from "@mongez/config";
|
|
2
2
|
import { get } from "@mongez/reinforcements";
|
|
3
3
|
|
|
4
4
|
//#region ../@warlock.js/core/src/http/config.ts
|
|
@@ -18,7 +18,7 @@ const defaultHttpConfigurations = {
|
|
|
18
18
|
* Get http configurations for the given key
|
|
19
19
|
*/
|
|
20
20
|
function httpConfig(key) {
|
|
21
|
-
return
|
|
21
|
+
return config.get(`http.${key}`, get(defaultHttpConfigurations, key));
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
//#endregion
|
package/esm/http/config.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.mjs","names":[
|
|
1
|
+
{"version":3,"file":"config.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/http/config.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { get } from \"@mongez/reinforcements\";\nimport type { HttpConfigurations } from \"./types\";\n\n/**\n * Default http configurations\n */\nexport const defaultHttpConfigurations: HttpConfigurations = {\n port: 3000,\n host: \"0.0.0.0\",\n middleware: {\n all: [],\n only: {\n middleware: [],\n },\n except: {\n middleware: [],\n },\n },\n};\n\n/**\n * Get http configurations for the given key\n */\nexport function httpConfig(key: string): any {\n return config.get(`http.${key}`, get(defaultHttpConfigurations, key));\n}\n"],"mappings":";;;;;;;AAOA,MAAa,4BAAgD;CAC3D,MAAM;CACN,MAAM;CACN,YAAY;EACV,KAAK,CAAC;EACN,MAAM,EACJ,YAAY,CAAC,EACf;EACA,QAAQ,EACN,YAAY,CAAC,EACf;CACF;AACF;;;;AAKA,SAAgB,WAAW,KAAkB;CAC3C,OAAO,OAAO,IAAI,QAAQ,OAAO,IAAI,2BAA2B,GAAG,CAAC;AACtE"}
|
|
@@ -4,7 +4,7 @@ import { router } from "../router/router.mjs";
|
|
|
4
4
|
import "../router/index.mjs";
|
|
5
5
|
import { registerHttpPlugins } from "./plugins.mjs";
|
|
6
6
|
import { getHttpServer, startHttpServer } from "./server.mjs";
|
|
7
|
-
import
|
|
7
|
+
import config from "@mongez/config";
|
|
8
8
|
import { log } from "@warlock.js/logger";
|
|
9
9
|
|
|
10
10
|
//#region ../@warlock.js/core/src/http/createHttpApplication.ts
|
|
@@ -19,7 +19,7 @@ async function createHttpApplication() {
|
|
|
19
19
|
port,
|
|
20
20
|
host: httpConfig("host")
|
|
21
21
|
});
|
|
22
|
-
const baseUrl =
|
|
22
|
+
const baseUrl = config.get("app.baseUrl");
|
|
23
23
|
setBaseUrl(baseUrl);
|
|
24
24
|
log.success("http", "server", `Server is listening on ${baseUrl}`);
|
|
25
25
|
} catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createHttpApplication.mjs","names":[
|
|
1
|
+
{"version":3,"file":"createHttpApplication.mjs","names":[],"sources":["../../../../../../../@warlock.js/core/src/http/createHttpApplication.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { log } from \"@warlock.js/logger\";\nimport { router } from \"../router\";\nimport { setBaseUrl } from \"../utils/urls\";\nimport { httpConfig } from \"./config\";\nimport { registerHttpPlugins } from \"./plugins\";\nimport { getHttpServer, startHttpServer } from \"./server\";\n\nexport async function createHttpApplication() {\n const server = startHttpServer();\n\n await registerHttpPlugins(server);\n\n router.scan(server);\n\n const port = httpConfig(\"port\");\n\n try {\n log.info(\"http\", \"server\", \"Connecting to the server\");\n // 👇🏻 We can use the url of the server\n await server.listen({\n port,\n host: httpConfig(\"host\"),\n });\n\n const baseUrl = config.get(\"app.baseUrl\");\n\n // update base url\n setBaseUrl(baseUrl);\n\n log.success(\"http\", \"server\", `Server is listening on ${baseUrl}`);\n } catch (error) {\n log.error(\"http\", \"server\", error);\n\n process.exit(1); // stop the process, exit with error\n }\n}\n\nexport async function stopHttpApplication() {\n log.info(\"http\", \"server\", \"Stopping the server\");\n const server = getHttpServer();\n\n await server?.close();\n\n log.success(\"http\", \"server\", \"Server is stopped\");\n}\n"],"mappings":";;;;;;;;;;AAQA,eAAsB,wBAAwB;CAC5C,MAAM,SAAS,gBAAgB;CAE/B,MAAM,oBAAoB,MAAM;CAEhC,OAAO,KAAK,MAAM;CAElB,MAAM,OAAO,WAAW,MAAM;CAE9B,IAAI;EACF,IAAI,KAAK,QAAQ,UAAU,0BAA0B;EAErD,MAAM,OAAO,OAAO;GAClB;GACA,MAAM,WAAW,MAAM;EACzB,CAAC;EAED,MAAM,UAAU,OAAO,IAAI,aAAa;EAGxC,WAAW,OAAO;EAElB,IAAI,QAAQ,QAAQ,UAAU,0BAA0B,SAAS;CACnE,SAAS,OAAO;EACd,IAAI,MAAM,QAAQ,UAAU,KAAK;EAEjC,QAAQ,KAAK,CAAC;CAChB;AACF;AAEA,eAAsB,sBAAsB;CAC1C,IAAI,KAAK,QAAQ,UAAU,qBAAqB;CAGhD,MAFe,cAEJ,CAAC,EAAE,MAAM;CAEpB,IAAI,QAAQ,QAAQ,UAAU,mBAAmB;AACnD"}
|
package/esm/http/health.d.mts
CHANGED
|
@@ -40,6 +40,20 @@ declare class HealthRegistry {
|
|
|
40
40
|
* use it to decide whether to ROUTE to the instance.
|
|
41
41
|
*/
|
|
42
42
|
readiness(): Promise<HealthStatus>;
|
|
43
|
+
/**
|
|
44
|
+
* Register a readiness check asserting the app has at least one route
|
|
45
|
+
* registered. A booted HTTP app that ends up with zero routes almost always
|
|
46
|
+
* means a route module failed to register silently, so the instance should
|
|
47
|
+
* report not-ready (503) rather than 404 every request.
|
|
48
|
+
*
|
|
49
|
+
* The route count is passed in as a getter so this registry stays decoupled
|
|
50
|
+
* from the router (no import cycle). The HTTP connector wires this in after
|
|
51
|
+
* scanning routes; apps with no HTTP surface simply never register it.
|
|
52
|
+
*
|
|
53
|
+
* @param getRouteCount Returns the number of currently-registered routes.
|
|
54
|
+
* @param name Override the check name (defaults to `"routes"`).
|
|
55
|
+
*/
|
|
56
|
+
addRoutesRegisteredCheck(getRouteCount: () => number, name?: string): void;
|
|
43
57
|
/**
|
|
44
58
|
* Run one check. A thrown error counts as a failed check rather than crashing
|
|
45
59
|
* the probe. The failure is surfaced in the readiness `checks` map and the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"health.d.mts","names":[],"sources":["../../../../../../../@warlock.js/core/src/http/health.ts"],"mappings":";;AAMA;;;KAAY,WAAA,mBAA8B,OAAO;AAAA;AAMjD;;;AANiD,KAMrC,YAAA;EACV,MAAA;EACA,MAAA,GAAS,MAAM;AAAA;;AAAA;AACf;;;;;cASI,cAAA;EAAA,iBACa,MAAA;EA6BgB;;;EAxB1B,QAAA,CAAS,IAAA,UAAc,KAAA,EAAO,WAAA;EAArB;;;EAOT,WAAA,CAAY,IAAA;EAAA;;;;EAQZ,QAAA,IAAY,YAAA;EASe
|
|
1
|
+
{"version":3,"file":"health.d.mts","names":[],"sources":["../../../../../../../@warlock.js/core/src/http/health.ts"],"mappings":";;AAMA;;;KAAY,WAAA,mBAA8B,OAAO;AAAA;AAMjD;;;AANiD,KAMrC,YAAA;EACV,MAAA;EACA,MAAA,GAAS,MAAM;AAAA;;AAAA;AACf;;;;;cASI,cAAA;EAAA,iBACa,MAAA;EA6BgB;;;EAxB1B,QAAA,CAAS,IAAA,UAAc,KAAA,EAAO,WAAA;EAArB;;;EAOT,WAAA,CAAY,IAAA;EAAA;;;;EAQZ,QAAA,IAAY,YAAA;EASe;;;;;EAArB,SAAA,IAAa,OAAA,CAAQ,YAAA;EA2CZ;AAYxB;;;;AAA0C;;;;;;;;EAtBjC,wBAAA,CAAyB,aAAA,gBAA6B,IAAA;;;;;;;UAU/C,QAAA;AAAA;;;;cAYH,MAAA,EAAM,cAAuB"}
|