@lenne.tech/nest-server 11.31.0 → 11.31.2
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/CLAUDE.md +2 -0
- package/FRAMEWORK-API.md +1 -1
- package/dist/core/modules/ai/core-ai.controller.d.ts +1 -1
- package/dist/core/modules/ai/core-ai.controller.js +4 -5
- package/dist/core/modules/ai/core-ai.controller.js.map +1 -1
- package/dist/core/modules/ai/core-ai.resolver.d.ts +1 -1
- package/dist/core/modules/ai/core-ai.resolver.js +7 -6
- package/dist/core/modules/ai/core-ai.resolver.js.map +1 -1
- package/dist/core/modules/ai/models/core-ai-conversation.model.d.ts +6 -0
- package/dist/core/modules/ai/models/core-ai-conversation.model.js +13 -1
- package/dist/core/modules/ai/models/core-ai-conversation.model.js.map +1 -1
- package/dist/core/modules/ai/services/core-ai-conversation.service.d.ts +8 -2
- package/dist/core/modules/ai/services/core-ai-conversation.service.js +53 -2
- package/dist/core/modules/ai/services/core-ai-conversation.service.js.map +1 -1
- package/dist/core/modules/migrate/cli/migrate-cli.d.ts +13 -1
- package/dist/core/modules/migrate/cli/migrate-cli.js +16 -1
- package/dist/core/modules/migrate/cli/migrate-cli.js.map +1 -1
- package/dist/core/modules/migrate/migration-runner.d.ts +4 -0
- package/dist/core/modules/migrate/migration-runner.js +46 -6
- package/dist/core/modules/migrate/migration-runner.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/migration-guides/11.31.0-to-11.31.1.md +89 -0
- package/migration-guides/11.31.1-to-11.31.2.md +127 -0
- package/package.json +1 -1
- package/src/core/modules/ai/core-ai.controller.ts +12 -9
- package/src/core/modules/ai/core-ai.resolver.ts +14 -9
- package/src/core/modules/ai/models/core-ai-conversation.model.ts +18 -1
- package/src/core/modules/ai/services/core-ai-conversation.service.ts +110 -4
- package/src/core/modules/migrate/README.md +56 -0
- package/src/core/modules/migrate/cli/migrate-cli.ts +27 -3
- package/src/core/modules/migrate/migration-runner.ts +123 -6
|
@@ -33,6 +33,59 @@ export interface MigrationFile {
|
|
|
33
33
|
*/
|
|
34
34
|
export const DEFAULT_MIGRATION_FILE_PATTERN = /(?:(?<!\.d)\.ts|\.js)$/;
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Matches the trailing `.ts`/`.js` extension stripped by {@link migrationId}.
|
|
38
|
+
* Hoisted to module level so `migrationId` allocates no per-call RegExp wrapper.
|
|
39
|
+
*/
|
|
40
|
+
const MIGRATION_EXTENSION_PATTERN = /\.(ts|js)$/;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Migration identity = the timestamped file stem WITHOUT its `.ts`/`.js` extension.
|
|
44
|
+
*
|
|
45
|
+
* A migration keeps its identity when a project switches its production image from
|
|
46
|
+
* ts-node (`1699-foo.ts`) to compiled JavaScript (`1699-foo.js`) — the recommended
|
|
47
|
+
* prod setup, since the image prunes ts-node. Comparing raw filenames would make
|
|
48
|
+
* every compiled `.js` migration look "pending" against a state recorded under `.ts`
|
|
49
|
+
* names and re-run already-applied migrations (data corruption on existing DBs).
|
|
50
|
+
* Normalising both sides makes the transition safe with no state rewrite.
|
|
51
|
+
*
|
|
52
|
+
* @param title Migration title, usually the filename (e.g. `1699000000000-foo.ts`)
|
|
53
|
+
* @returns The extension-agnostic identity (e.g. `1699000000000-foo`)
|
|
54
|
+
* @example
|
|
55
|
+
* migrationId('1699000000000-foo.ts'); // '1699000000000-foo'
|
|
56
|
+
* migrationId('1699000000000-foo.js'); // '1699000000000-foo'
|
|
57
|
+
* migrationId('1699000000000-foo'); // '1699000000000-foo' (already extensionless)
|
|
58
|
+
*/
|
|
59
|
+
export function migrationId(title: string): string {
|
|
60
|
+
return title.replace(MIGRATION_EXTENSION_PATTERN, '');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Parse the `NSC__MIGRATE__STRICT` environment variable into a boolean.
|
|
65
|
+
*
|
|
66
|
+
* Truthy values: `1`, `true`, `yes` (case-insensitive, surrounding whitespace ignored —
|
|
67
|
+
* a real risk with Docker/compose env files). Every other value means `false` (tolerate,
|
|
68
|
+
* the safe default); non-empty unrecognized values additionally emit a warning so a typo
|
|
69
|
+
* like `NSC__MIGRATE__STRICT=on` does not silently disable the control the operator
|
|
70
|
+
* intended to enable.
|
|
71
|
+
*
|
|
72
|
+
* Shared by the CLI (`migrate-cli.ts`) and the {@link MigrationRunner} constructor, so
|
|
73
|
+
* the env var behaves identically for CLI and programmatic runners.
|
|
74
|
+
*/
|
|
75
|
+
export function parseStrictEnv(value: string | undefined = process.env.NSC__MIGRATE__STRICT): boolean {
|
|
76
|
+
if (value === undefined) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
const normalized = value.trim().toLowerCase();
|
|
80
|
+
if (['1', 'true', 'yes'].includes(normalized)) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
if (normalized !== '' && !['0', 'false', 'no'].includes(normalized)) {
|
|
84
|
+
console.warn(`[migrate] Unrecognized NSC__MIGRATE__STRICT value "${value}" — treating as false (tolerate).`);
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
36
89
|
/**
|
|
37
90
|
* Migration runner configuration
|
|
38
91
|
*/
|
|
@@ -43,6 +96,23 @@ export interface MigrationRunnerOptions {
|
|
|
43
96
|
pattern?: RegExp;
|
|
44
97
|
/** State store for tracking migrations */
|
|
45
98
|
stateStore: MongoStateStore;
|
|
99
|
+
/**
|
|
100
|
+
* Fail hard when a migration recorded in the state has no file on disk.
|
|
101
|
+
*
|
|
102
|
+
* Default: resolved from the `NSC__MIGRATE__STRICT` environment variable (see
|
|
103
|
+
* {@link parseStrictEnv}), falling back to `false` (tolerate) — recorded migrations
|
|
104
|
+
* whose files were deleted are ignored: `up()` skips them (with a warning) and the
|
|
105
|
+
* server still starts. Migrations are tracked in git and can be restored, so old
|
|
106
|
+
* migration files can be pruned without blocking boot. Set `true` to enforce
|
|
107
|
+
* state/disk integrity (missing file → error in `up()` and `migrate list`).
|
|
108
|
+
*
|
|
109
|
+
* Applies to `up()` and `status()`/`migrate list` only — `down()` ALWAYS fails hard
|
|
110
|
+
* on a missing rollback file, because rollback is an explicit operator action and
|
|
111
|
+
* never a boot path (an exit 0 with no rollback performed would mislead scripts).
|
|
112
|
+
*
|
|
113
|
+
* @see `--strict` CLI flag and `NSC__MIGRATE__STRICT` env var in `cli/migrate-cli.ts`
|
|
114
|
+
*/
|
|
115
|
+
strict?: boolean;
|
|
46
116
|
}
|
|
47
117
|
|
|
48
118
|
/**
|
|
@@ -72,12 +142,21 @@ export class MigrationRunner {
|
|
|
72
142
|
private pattern: RegExp;
|
|
73
143
|
|
|
74
144
|
constructor(options: MigrationRunnerOptions) {
|
|
75
|
-
|
|
145
|
+
// Resolve the strict default from the environment so NSC__MIGRATE__STRICT works
|
|
146
|
+
// identically for programmatic runners and the CLI (which parses it itself).
|
|
147
|
+
this.options = { ...options, strict: options.strict ?? parseStrictEnv() };
|
|
76
148
|
this.pattern = options.pattern || DEFAULT_MIGRATION_FILE_PATTERN;
|
|
77
149
|
}
|
|
78
150
|
|
|
79
151
|
/**
|
|
80
152
|
* Load all migration files from the migrations directory
|
|
153
|
+
*
|
|
154
|
+
* Files are deduplicated by {@link migrationId}: when both `foo.ts` and `foo.js`
|
|
155
|
+
* are present (overlapping `outDir`, source + build output copied into one image),
|
|
156
|
+
* they are ONE migration — loading both would execute it twice in a single `up()`
|
|
157
|
+
* run (the exact double-execution the identity concept exists to prevent). The
|
|
158
|
+
* `.js` file wins deterministically (`.js` sorts before `.ts`), matching the
|
|
159
|
+
* compiled-production intent; the duplicate is skipped with a warning.
|
|
81
160
|
*/
|
|
82
161
|
private async loadMigrationFiles(): Promise<MigrationFile[]> {
|
|
83
162
|
const files = fs
|
|
@@ -86,8 +165,17 @@ export class MigrationRunner {
|
|
|
86
165
|
.sort(); // Sort alphabetically (timestamp-based filenames will be in order)
|
|
87
166
|
|
|
88
167
|
const migrations: MigrationFile[] = [];
|
|
168
|
+
const seenIds = new Set<string>();
|
|
89
169
|
|
|
90
170
|
for (const file of files) {
|
|
171
|
+
const id = migrationId(file);
|
|
172
|
+
if (seenIds.has(id)) {
|
|
173
|
+
console.warn(
|
|
174
|
+
`[migrate] duplicate files for migration "${id}" — skipping ${file} (a same-named file was already loaded)`,
|
|
175
|
+
);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
91
179
|
const filePath = path.join(this.options.migrationsDirectory, file);
|
|
92
180
|
|
|
93
181
|
const module = require(filePath);
|
|
@@ -101,6 +189,7 @@ export class MigrationRunner {
|
|
|
101
189
|
const timestampMatch = file.match(/^(\d+)-/);
|
|
102
190
|
const timestamp = timestampMatch ? parseInt(timestampMatch[1], 10) : Date.now();
|
|
103
191
|
|
|
192
|
+
seenIds.add(id);
|
|
104
193
|
migrations.push({
|
|
105
194
|
down: module.down,
|
|
106
195
|
filePath,
|
|
@@ -121,9 +210,24 @@ export class MigrationRunner {
|
|
|
121
210
|
|
|
122
211
|
const allMigrations = await this.loadMigrationFiles();
|
|
123
212
|
const state = await this.options.stateStore.loadAsync();
|
|
124
|
-
const
|
|
213
|
+
const recorded = state.migrations || [];
|
|
214
|
+
const presentIds = new Set(allMigrations.map((m) => migrationId(m.title)));
|
|
215
|
+
|
|
216
|
+
// Recorded migrations whose file is gone. Tolerated by default (git-tracked and
|
|
217
|
+
// restorable); in strict mode this is a hard error so integrity drift is caught.
|
|
218
|
+
const missing = recorded.filter((m) => !presentIds.has(migrationId(m.title)));
|
|
219
|
+
if (missing.length > 0) {
|
|
220
|
+
const list = missing.map((m) => m.title).join(', ');
|
|
221
|
+
if (this.options.strict) {
|
|
222
|
+
throw new Error(`Strict mode: ${missing.length} recorded migration file(s) missing: ${list}`);
|
|
223
|
+
}
|
|
224
|
+
console.warn(
|
|
225
|
+
`[migrate] ${missing.length} recorded migration file(s) missing — tolerated (strict=false): ${list}`,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
125
228
|
|
|
126
|
-
const
|
|
229
|
+
const completedIds = new Set(recorded.map((m) => migrationId(m.title)));
|
|
230
|
+
const pendingMigrations = allMigrations.filter((m) => !completedIds.has(migrationId(m.title)));
|
|
127
231
|
|
|
128
232
|
if (pendingMigrations.length === 0) {
|
|
129
233
|
console.log('No pending migrations');
|
|
@@ -181,10 +285,14 @@ export class MigrationRunner {
|
|
|
181
285
|
|
|
182
286
|
const lastMigration = completedMigrations[completedMigrations.length - 1];
|
|
183
287
|
const allMigrations = await this.loadMigrationFiles();
|
|
184
|
-
const
|
|
288
|
+
const rollbackId = migrationId(lastMigration.title);
|
|
289
|
+
const migrationToRollback = allMigrations.find((m) => migrationId(m.title) === rollbackId);
|
|
185
290
|
|
|
186
291
|
if (!migrationToRollback) {
|
|
187
|
-
|
|
292
|
+
// Always a hard error, independent of `strict`: down() is an explicit operator
|
|
293
|
+
// action and never a boot path — the boot-tolerance rationale does not apply,
|
|
294
|
+
// and an exit 0 with no rollback performed would mislead scripted rollbacks.
|
|
295
|
+
throw new Error(`Migration file not found: ${lastMigration.title} — restore the file from git to roll back.`);
|
|
188
296
|
}
|
|
189
297
|
|
|
190
298
|
if (!migrationToRollback.down) {
|
|
@@ -216,18 +324,27 @@ export class MigrationRunner {
|
|
|
216
324
|
|
|
217
325
|
/**
|
|
218
326
|
* Get migration status
|
|
327
|
+
*
|
|
328
|
+
* `missing` lists recorded migrations whose file is gone from disk (identity-based,
|
|
329
|
+
* so a `.ts`-recorded migration with a compiled `.js` on disk is NOT missing) — the
|
|
330
|
+
* same integrity drift `up()` warns about, surfaced here so `migrate list` can show
|
|
331
|
+
* it before a deploy or file pruning.
|
|
219
332
|
*/
|
|
220
333
|
async status(): Promise<{
|
|
221
334
|
completed: string[];
|
|
335
|
+
missing: string[];
|
|
222
336
|
pending: string[];
|
|
223
337
|
}> {
|
|
224
338
|
const allMigrations = await this.loadMigrationFiles();
|
|
225
339
|
const state = await this.options.stateStore.loadAsync();
|
|
226
340
|
const completedMigrations = (state.migrations || []).map((m) => m.title);
|
|
341
|
+
const completedIds = new Set(completedMigrations.map(migrationId));
|
|
342
|
+
const presentIds = new Set(allMigrations.map((m) => migrationId(m.title)));
|
|
227
343
|
|
|
228
344
|
return {
|
|
229
345
|
completed: completedMigrations,
|
|
230
|
-
|
|
346
|
+
missing: completedMigrations.filter((title) => !presentIds.has(migrationId(title))),
|
|
347
|
+
pending: allMigrations.filter((m) => !completedIds.has(migrationId(m.title))).map((m) => m.title),
|
|
231
348
|
};
|
|
232
349
|
}
|
|
233
350
|
|