@nage-api/compat 1.0.0-beta.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/LICENSE +202 -0
- package/README.md +215 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +29 -0
- package/dist/codemods/cli-main.d.ts +32 -0
- package/dist/codemods/cli-main.js +128 -0
- package/dist/codemods/config-inventory.d.ts +20 -0
- package/dist/codemods/config-inventory.js +118 -0
- package/dist/codemods/env-rename.d.ts +36 -0
- package/dist/codemods/env-rename.js +104 -0
- package/dist/codemods/import-paths.d.ts +45 -0
- package/dist/codemods/import-paths.js +247 -0
- package/dist/codemods/job-generics.d.ts +17 -0
- package/dist/codemods/job-generics.js +74 -0
- package/dist/codemods/node-fs.d.ts +11 -0
- package/dist/codemods/node-fs.js +35 -0
- package/dist/codemods/registry.d.ts +26 -0
- package/dist/codemods/registry.js +42 -0
- package/dist/codemods/res-envelope.d.ts +29 -0
- package/dist/codemods/res-envelope.js +111 -0
- package/dist/codemods/runner.d.ts +36 -0
- package/dist/codemods/runner.js +44 -0
- package/dist/codemods/types.d.ts +69 -0
- package/dist/codemods/types.js +40 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +63 -0
- package/dist/legacy/compat.module.d.ts +14 -0
- package/dist/legacy/compat.module.js +39 -0
- package/dist/legacy/envelope.d.ts +62 -0
- package/dist/legacy/envelope.js +86 -0
- package/dist/legacy/legacy-envelope.decorator.d.ts +17 -0
- package/dist/legacy/legacy-envelope.decorator.js +23 -0
- package/dist/legacy/legacy-envelope.interceptor.d.ts +25 -0
- package/dist/legacy/legacy-envelope.interceptor.js +67 -0
- package/dist/legacy/options.d.ts +14 -0
- package/dist/legacy/options.js +7 -0
- package/dist/legacy/responses.d.ts +48 -0
- package/dist/legacy/responses.js +52 -0
- package/package.json +61 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The legacy response body, rebuilt (PLAN.md §23.1).
|
|
3
|
+
*
|
|
4
|
+
* §23.1 asks for "the old `Result`/`Created` helpers and the old envelope behind
|
|
5
|
+
* a flag" so a controller can be ported before its clients are. That needs a
|
|
6
|
+
* definition of the old body, and this repository does not contain one:
|
|
7
|
+
* `nest-core-v2` is not a dependency here and `src/core/core.responses.ts` is
|
|
8
|
+
* not readable from it. What is below is **reconstructed** from the two call
|
|
9
|
+
* sites PLAN.md quotes — `Result(res, { data, message })` (§4.1) and
|
|
10
|
+
* `Result(res, { records: rows, offset, limit, count })` (§22, and the
|
|
11
|
+
* before/after pair in `docs/migration.md`) — plus §16.1's observation that the
|
|
12
|
+
* old envelope carried `timestamp` on 200s and not on errors.
|
|
13
|
+
*
|
|
14
|
+
* So the default builder spreads the payload and stamps a timestamp, which is
|
|
15
|
+
* the only behaviour both quoted call sites agree on. Anything more specific
|
|
16
|
+
* would be invention. `legacyBody` is the seam: diff the output against your own
|
|
17
|
+
* `core.responses.ts` and, where it differs, pass your own builder rather than
|
|
18
|
+
* patching this one.
|
|
19
|
+
*
|
|
20
|
+
* One thing is deliberately **not** reproduced: the legacy `ErrorResponse`
|
|
21
|
+
* echoed `error.message || error` straight to the client (§5.2), which is how
|
|
22
|
+
* SQL text and upstream bodies reached callers. `legacyErrorBody` takes an
|
|
23
|
+
* already-sanitised `ErrorPayload`, so the shape comes back and the leak does
|
|
24
|
+
* not.
|
|
25
|
+
*/
|
|
26
|
+
import type { ErrorPayload } from '@nage-api/contracts';
|
|
27
|
+
/** What the handler (or the `Result` caller) produced, plus the clock. */
|
|
28
|
+
export interface LegacyBodyInput {
|
|
29
|
+
readonly payload: unknown;
|
|
30
|
+
/** ISO-8601; injected rather than read so a test can assert an exact body. */
|
|
31
|
+
readonly timestamp: string;
|
|
32
|
+
}
|
|
33
|
+
export type LegacyBodyBuilder = (input: LegacyBodyInput) => unknown;
|
|
34
|
+
/** The legacy list body: counters beside the records, not under `meta`. */
|
|
35
|
+
export interface LegacyListBody {
|
|
36
|
+
readonly records: readonly unknown[];
|
|
37
|
+
readonly offset: number;
|
|
38
|
+
readonly limit: number;
|
|
39
|
+
readonly count: number;
|
|
40
|
+
readonly timestamp: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Reconstructed legacy success body.
|
|
44
|
+
*
|
|
45
|
+
* A page is flattened back out: `records` and the three counters return to the
|
|
46
|
+
* top level, undoing the `meta.pagination` nesting §16.1 introduced. That is the
|
|
47
|
+
* whole point of the shim — the service can already return `Paginated<T>` while
|
|
48
|
+
* the client still reads the fields where it always read them.
|
|
49
|
+
*/
|
|
50
|
+
export declare const defaultLegacyBody: LegacyBodyBuilder;
|
|
51
|
+
/**
|
|
52
|
+
* Reconstructed legacy error body.
|
|
53
|
+
*
|
|
54
|
+
* `code` is carried through even though the legacy body had no such field: a
|
|
55
|
+
* client that ignores it is unaffected, and one that starts reading it is
|
|
56
|
+
* already migrating. Nothing else is added, because an error body that grows
|
|
57
|
+
* during a migration is a body two systems disagree about.
|
|
58
|
+
*/
|
|
59
|
+
export declare function legacyErrorBody(error: ErrorPayload, options?: {
|
|
60
|
+
readonly timestamp?: string;
|
|
61
|
+
}): Record<string, unknown>;
|
|
62
|
+
//# sourceMappingURL=envelope.d.ts.map
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The legacy response body, rebuilt (PLAN.md §23.1).
|
|
4
|
+
*
|
|
5
|
+
* §23.1 asks for "the old `Result`/`Created` helpers and the old envelope behind
|
|
6
|
+
* a flag" so a controller can be ported before its clients are. That needs a
|
|
7
|
+
* definition of the old body, and this repository does not contain one:
|
|
8
|
+
* `nest-core-v2` is not a dependency here and `src/core/core.responses.ts` is
|
|
9
|
+
* not readable from it. What is below is **reconstructed** from the two call
|
|
10
|
+
* sites PLAN.md quotes — `Result(res, { data, message })` (§4.1) and
|
|
11
|
+
* `Result(res, { records: rows, offset, limit, count })` (§22, and the
|
|
12
|
+
* before/after pair in `docs/migration.md`) — plus §16.1's observation that the
|
|
13
|
+
* old envelope carried `timestamp` on 200s and not on errors.
|
|
14
|
+
*
|
|
15
|
+
* So the default builder spreads the payload and stamps a timestamp, which is
|
|
16
|
+
* the only behaviour both quoted call sites agree on. Anything more specific
|
|
17
|
+
* would be invention. `legacyBody` is the seam: diff the output against your own
|
|
18
|
+
* `core.responses.ts` and, where it differs, pass your own builder rather than
|
|
19
|
+
* patching this one.
|
|
20
|
+
*
|
|
21
|
+
* One thing is deliberately **not** reproduced: the legacy `ErrorResponse`
|
|
22
|
+
* echoed `error.message || error` straight to the client (§5.2), which is how
|
|
23
|
+
* SQL text and upstream bodies reached callers. `legacyErrorBody` takes an
|
|
24
|
+
* already-sanitised `ErrorPayload`, so the shape comes back and the leak does
|
|
25
|
+
* not.
|
|
26
|
+
*/
|
|
27
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
|
+
exports.defaultLegacyBody = void 0;
|
|
29
|
+
exports.legacyErrorBody = legacyErrorBody;
|
|
30
|
+
/** True for a `{ records, pagination }` page, the shape a ported service returns. */
|
|
31
|
+
function isPage(value) {
|
|
32
|
+
if (value === null || typeof value !== 'object')
|
|
33
|
+
return false;
|
|
34
|
+
const candidate = value;
|
|
35
|
+
if (!Array.isArray(candidate.records))
|
|
36
|
+
return false;
|
|
37
|
+
if (candidate.pagination === null || typeof candidate.pagination !== 'object')
|
|
38
|
+
return false;
|
|
39
|
+
return typeof candidate.pagination.limit === 'number';
|
|
40
|
+
}
|
|
41
|
+
/** A plain object, as opposed to an array, a date, a class instance or null. */
|
|
42
|
+
function isPlainObject(value) {
|
|
43
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
44
|
+
return false;
|
|
45
|
+
const prototype = Object.getPrototypeOf(value);
|
|
46
|
+
return prototype === Object.prototype || prototype === null;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Reconstructed legacy success body.
|
|
50
|
+
*
|
|
51
|
+
* A page is flattened back out: `records` and the three counters return to the
|
|
52
|
+
* top level, undoing the `meta.pagination` nesting §16.1 introduced. That is the
|
|
53
|
+
* whole point of the shim — the service can already return `Paginated<T>` while
|
|
54
|
+
* the client still reads the fields where it always read them.
|
|
55
|
+
*/
|
|
56
|
+
const defaultLegacyBody = ({ payload, timestamp }) => {
|
|
57
|
+
if (isPage(payload)) {
|
|
58
|
+
const { offset, limit, count } = payload.pagination;
|
|
59
|
+
return { records: payload.records, offset, limit, count, timestamp };
|
|
60
|
+
}
|
|
61
|
+
// Spread only a plain object. Spreading an array loses it to numeric keys, and
|
|
62
|
+
// spreading a class instance drops everything on its prototype — both of which
|
|
63
|
+
// silently produce `{ timestamp }` and nothing else.
|
|
64
|
+
if (isPlainObject(payload))
|
|
65
|
+
return { ...payload, timestamp };
|
|
66
|
+
return { data: payload ?? null, timestamp };
|
|
67
|
+
};
|
|
68
|
+
exports.defaultLegacyBody = defaultLegacyBody;
|
|
69
|
+
/**
|
|
70
|
+
* Reconstructed legacy error body.
|
|
71
|
+
*
|
|
72
|
+
* `code` is carried through even though the legacy body had no such field: a
|
|
73
|
+
* client that ignores it is unaffected, and one that starts reading it is
|
|
74
|
+
* already migrating. Nothing else is added, because an error body that grows
|
|
75
|
+
* during a migration is a body two systems disagree about.
|
|
76
|
+
*/
|
|
77
|
+
function legacyErrorBody(error, options = {}) {
|
|
78
|
+
return {
|
|
79
|
+
success: false,
|
|
80
|
+
message: error.message,
|
|
81
|
+
code: error.code,
|
|
82
|
+
...(error.details === undefined ? {} : { details: error.details }),
|
|
83
|
+
...(options.timestamp === undefined ? {} : { timestamp: options.timestamp }),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=envelope.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opt one route (or one controller) back onto the legacy response body.
|
|
3
|
+
*
|
|
4
|
+
* The flag §23.1 asks for is per-route rather than per-application, and that is
|
|
5
|
+
* a deliberate limit rather than a shortcut. `NageCoreModule` registers
|
|
6
|
+
* `ResponseInterceptor` unconditionally, so a global "serve the old envelope"
|
|
7
|
+
* switch would have to win a race with it that is decided by module import
|
|
8
|
+
* order — a correctness property no reader can see at the call site. Riding on
|
|
9
|
+
* `@NoEnvelope()` instead makes the core interceptor stand down for exactly the
|
|
10
|
+
* routes this one handles, whatever order the modules were imported in.
|
|
11
|
+
*
|
|
12
|
+
* It also matches how §23.3 phase 5 says to migrate: controller by controller.
|
|
13
|
+
*/
|
|
14
|
+
/** Read by `LegacyEnvelopeInterceptor`; not part of the public contract. */
|
|
15
|
+
export declare const LEGACY_ENVELOPE_METADATA = "nage:compat:legacy-envelope";
|
|
16
|
+
export declare const LegacyEnvelope: () => MethodDecorator & ClassDecorator;
|
|
17
|
+
//# sourceMappingURL=legacy-envelope.decorator.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Opt one route (or one controller) back onto the legacy response body.
|
|
4
|
+
*
|
|
5
|
+
* The flag §23.1 asks for is per-route rather than per-application, and that is
|
|
6
|
+
* a deliberate limit rather than a shortcut. `NageCoreModule` registers
|
|
7
|
+
* `ResponseInterceptor` unconditionally, so a global "serve the old envelope"
|
|
8
|
+
* switch would have to win a race with it that is decided by module import
|
|
9
|
+
* order — a correctness property no reader can see at the call site. Riding on
|
|
10
|
+
* `@NoEnvelope()` instead makes the core interceptor stand down for exactly the
|
|
11
|
+
* routes this one handles, whatever order the modules were imported in.
|
|
12
|
+
*
|
|
13
|
+
* It also matches how §23.3 phase 5 says to migrate: controller by controller.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.LegacyEnvelope = exports.LEGACY_ENVELOPE_METADATA = void 0;
|
|
17
|
+
const common_1 = require("@nestjs/common");
|
|
18
|
+
const core_1 = require("@nage-api/core");
|
|
19
|
+
/** Read by `LegacyEnvelopeInterceptor`; not part of the public contract. */
|
|
20
|
+
exports.LEGACY_ENVELOPE_METADATA = 'nage:compat:legacy-envelope';
|
|
21
|
+
const LegacyEnvelope = () => (0, common_1.applyDecorators)((0, core_1.NoEnvelope)(), (0, common_1.SetMetadata)(exports.LEGACY_ENVELOPE_METADATA, true));
|
|
22
|
+
exports.LegacyEnvelope = LegacyEnvelope;
|
|
23
|
+
//# sourceMappingURL=legacy-envelope.decorator.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serves the legacy body for routes marked `@LegacyEnvelope()` (PLAN.md §23.1).
|
|
3
|
+
*
|
|
4
|
+
* The controller has already been ported — it returns data and never touches
|
|
5
|
+
* `res` — while its clients still read `records`/`offset`/`limit`/`count` at the
|
|
6
|
+
* top level. This maps one to the other, so the two halves of a migration can
|
|
7
|
+
* ship weeks apart.
|
|
8
|
+
*
|
|
9
|
+
* Errors are **not** remapped. `AllExceptionsFilter` is global and this package
|
|
10
|
+
* does not replace it, so a failing legacy route answers with the new
|
|
11
|
+
* `{ success: false, error, meta }` shape. Restoring the old error body would
|
|
12
|
+
* mean a second filter with its own copy of the status mapping, and the old
|
|
13
|
+
* shape is the one that leaked (§5.2). See the README.
|
|
14
|
+
*/
|
|
15
|
+
import { type CallHandler, type ExecutionContext, type NestInterceptor } from '@nestjs/common';
|
|
16
|
+
import { Reflector } from '@nestjs/core';
|
|
17
|
+
import type { Observable } from 'rxjs';
|
|
18
|
+
import { type CompatOptions } from './options.js';
|
|
19
|
+
export declare class LegacyEnvelopeInterceptor implements NestInterceptor {
|
|
20
|
+
#private;
|
|
21
|
+
private readonly reflector;
|
|
22
|
+
constructor(reflector: Reflector, options?: CompatOptions);
|
|
23
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=legacy-envelope.interceptor.d.ts.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Serves the legacy body for routes marked `@LegacyEnvelope()` (PLAN.md §23.1).
|
|
4
|
+
*
|
|
5
|
+
* The controller has already been ported — it returns data and never touches
|
|
6
|
+
* `res` — while its clients still read `records`/`offset`/`limit`/`count` at the
|
|
7
|
+
* top level. This maps one to the other, so the two halves of a migration can
|
|
8
|
+
* ship weeks apart.
|
|
9
|
+
*
|
|
10
|
+
* Errors are **not** remapped. `AllExceptionsFilter` is global and this package
|
|
11
|
+
* does not replace it, so a failing legacy route answers with the new
|
|
12
|
+
* `{ success: false, error, meta }` shape. Restoring the old error body would
|
|
13
|
+
* mean a second filter with its own copy of the status mapping, and the old
|
|
14
|
+
* shape is the one that leaked (§5.2). See the README.
|
|
15
|
+
*/
|
|
16
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
17
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
18
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
19
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
20
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
21
|
+
};
|
|
22
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
23
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
24
|
+
};
|
|
25
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
26
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.LegacyEnvelopeInterceptor = void 0;
|
|
30
|
+
const common_1 = require("@nestjs/common");
|
|
31
|
+
const core_1 = require("@nestjs/core");
|
|
32
|
+
const operators_1 = require("rxjs/operators");
|
|
33
|
+
const envelope_js_1 = require("./envelope.js");
|
|
34
|
+
const legacy_envelope_decorator_js_1 = require("./legacy-envelope.decorator.js");
|
|
35
|
+
const options_js_1 = require("./options.js");
|
|
36
|
+
let LegacyEnvelopeInterceptor = class LegacyEnvelopeInterceptor {
|
|
37
|
+
reflector;
|
|
38
|
+
#options;
|
|
39
|
+
constructor(reflector, options) {
|
|
40
|
+
this.reflector = reflector;
|
|
41
|
+
this.#options = options ?? {};
|
|
42
|
+
}
|
|
43
|
+
intercept(context, next) {
|
|
44
|
+
if (context.getType() !== 'http')
|
|
45
|
+
return next.handle();
|
|
46
|
+
const legacy = this.reflector.getAllAndOverride(legacy_envelope_decorator_js_1.LEGACY_ENVELOPE_METADATA, [
|
|
47
|
+
context.getHandler(),
|
|
48
|
+
context.getClass(),
|
|
49
|
+
]);
|
|
50
|
+
if (legacy !== true)
|
|
51
|
+
return next.handle();
|
|
52
|
+
const build = this.#options.body ?? envelope_js_1.defaultLegacyBody;
|
|
53
|
+
const now = this.#options.now ?? (() => new Date());
|
|
54
|
+
return next
|
|
55
|
+
.handle()
|
|
56
|
+
.pipe((0, operators_1.map)((payload) => build({ payload, timestamp: now().toISOString() })));
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
exports.LegacyEnvelopeInterceptor = LegacyEnvelopeInterceptor;
|
|
60
|
+
exports.LegacyEnvelopeInterceptor = LegacyEnvelopeInterceptor = __decorate([
|
|
61
|
+
(0, common_1.Injectable)(),
|
|
62
|
+
__param(0, (0, common_1.Inject)(core_1.Reflector)),
|
|
63
|
+
__param(1, (0, common_1.Optional)()),
|
|
64
|
+
__param(1, (0, common_1.Inject)(options_js_1.NAGE_COMPAT_OPTIONS)),
|
|
65
|
+
__metadata("design:paramtypes", [core_1.Reflector, Object])
|
|
66
|
+
], LegacyEnvelopeInterceptor);
|
|
67
|
+
//# sourceMappingURL=legacy-envelope.interceptor.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Wiring for the legacy shim; see `envelope.ts` for why `body` is a seam. */
|
|
2
|
+
import type { LegacyBodyBuilder } from './envelope.js';
|
|
3
|
+
export interface CompatOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Builds the legacy body. The default is reconstructed from PLAN.md's quoted
|
|
6
|
+
* call sites; supply your own once you have diffed it against the
|
|
7
|
+
* `core.responses.ts` your application actually shipped.
|
|
8
|
+
*/
|
|
9
|
+
readonly body?: LegacyBodyBuilder;
|
|
10
|
+
/** Injected so a test can assert an exact body; defaults to now. */
|
|
11
|
+
readonly now?: () => Date;
|
|
12
|
+
}
|
|
13
|
+
export declare const NAGE_COMPAT_OPTIONS: import("@nage-api/core").Token<CompatOptions>;
|
|
14
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Wiring for the legacy shim; see `envelope.ts` for why `body` is a seam. */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.NAGE_COMPAT_OPTIONS = void 0;
|
|
5
|
+
const core_1 = require("@nage-api/core");
|
|
6
|
+
exports.NAGE_COMPAT_OPTIONS = (0, core_1.createToken)('NAGE_COMPAT_OPTIONS');
|
|
7
|
+
//# sourceMappingURL=options.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The legacy manual response builders (PLAN.md §23.1).
|
|
3
|
+
*
|
|
4
|
+
* These exist for exactly one job: letting a controller that still declares
|
|
5
|
+
* `@Res() res: Response` compile and behave inside a `@nage-api` application, so its
|
|
6
|
+
* import can be repointed in one commit and the `@Res()` removal happens in
|
|
7
|
+
* another. They are the first step of §23.3 phase 5, not a destination — a route
|
|
8
|
+
* that writes the response itself is invisible to `ResponseInterceptor` and to
|
|
9
|
+
* `AllExceptionsFilter`, which is the defect §16.1 replaced.
|
|
10
|
+
*
|
|
11
|
+
* The response argument is typed as `LegacyResponse` rather than Express's
|
|
12
|
+
* `Response`. Two reasons: this package should not pull Express into an
|
|
13
|
+
* application that runs Fastify, and a two-method interface is a thing a test can
|
|
14
|
+
* pass without a server.
|
|
15
|
+
*/
|
|
16
|
+
import type { ErrorPayload } from '@nage-api/contracts';
|
|
17
|
+
import { type LegacyBodyBuilder } from './envelope.js';
|
|
18
|
+
/** The part of an HTTP response object these helpers touch. */
|
|
19
|
+
export interface LegacyResponse {
|
|
20
|
+
status(code: number): LegacyResponse;
|
|
21
|
+
json(body: unknown): unknown;
|
|
22
|
+
}
|
|
23
|
+
export interface LegacyResponseOptions {
|
|
24
|
+
/** Override when your `core.responses.ts` built a different body. */
|
|
25
|
+
readonly body?: LegacyBodyBuilder;
|
|
26
|
+
/** Injected so a test can assert an exact body; defaults to now. */
|
|
27
|
+
readonly now?: () => Date;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* `Result(res, payload)` — 200 with the legacy body.
|
|
31
|
+
*
|
|
32
|
+
* Returns whatever `res.json` returns so `return Result(res, x)` keeps working
|
|
33
|
+
* unchanged; the value is Nest's, not ours, and is ignored in `@Res()` mode.
|
|
34
|
+
*/
|
|
35
|
+
export declare function Result(res: LegacyResponse, payload: unknown, options?: LegacyResponseOptions): unknown;
|
|
36
|
+
/** `Created(res, payload)` — the same body at 201. */
|
|
37
|
+
export declare function Created(res: LegacyResponse, payload: unknown, options?: LegacyResponseOptions): unknown;
|
|
38
|
+
/**
|
|
39
|
+
* `ErrorResponse(res, error)` — the legacy error shape from a *safe* payload.
|
|
40
|
+
*
|
|
41
|
+
* The legacy helper of this name took the caught error and echoed
|
|
42
|
+
* `error.message || error`, which is how SQL text and upstream response bodies
|
|
43
|
+
* reached clients (§5.2). This one takes an `ErrorPayload` — the sanitised
|
|
44
|
+
* `{ code, message, details? }` the exception filter produces — so the call site
|
|
45
|
+
* has to have decided what is safe to say before it can call this at all.
|
|
46
|
+
*/
|
|
47
|
+
export declare function ErrorResponse(res: LegacyResponse, error: ErrorPayload, status: number, options?: LegacyResponseOptions): unknown;
|
|
48
|
+
//# sourceMappingURL=responses.d.ts.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The legacy manual response builders (PLAN.md §23.1).
|
|
4
|
+
*
|
|
5
|
+
* These exist for exactly one job: letting a controller that still declares
|
|
6
|
+
* `@Res() res: Response` compile and behave inside a `@nage-api` application, so its
|
|
7
|
+
* import can be repointed in one commit and the `@Res()` removal happens in
|
|
8
|
+
* another. They are the first step of §23.3 phase 5, not a destination — a route
|
|
9
|
+
* that writes the response itself is invisible to `ResponseInterceptor` and to
|
|
10
|
+
* `AllExceptionsFilter`, which is the defect §16.1 replaced.
|
|
11
|
+
*
|
|
12
|
+
* The response argument is typed as `LegacyResponse` rather than Express's
|
|
13
|
+
* `Response`. Two reasons: this package should not pull Express into an
|
|
14
|
+
* application that runs Fastify, and a two-method interface is a thing a test can
|
|
15
|
+
* pass without a server.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.Result = Result;
|
|
19
|
+
exports.Created = Created;
|
|
20
|
+
exports.ErrorResponse = ErrorResponse;
|
|
21
|
+
const envelope_js_1 = require("./envelope.js");
|
|
22
|
+
function stamp(options) {
|
|
23
|
+
return (options.now?.() ?? new Date()).toISOString();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* `Result(res, payload)` — 200 with the legacy body.
|
|
27
|
+
*
|
|
28
|
+
* Returns whatever `res.json` returns so `return Result(res, x)` keeps working
|
|
29
|
+
* unchanged; the value is Nest's, not ours, and is ignored in `@Res()` mode.
|
|
30
|
+
*/
|
|
31
|
+
function Result(res, payload, options = {}) {
|
|
32
|
+
const build = options.body ?? envelope_js_1.defaultLegacyBody;
|
|
33
|
+
return res.status(200).json(build({ payload, timestamp: stamp(options) }));
|
|
34
|
+
}
|
|
35
|
+
/** `Created(res, payload)` — the same body at 201. */
|
|
36
|
+
function Created(res, payload, options = {}) {
|
|
37
|
+
const build = options.body ?? envelope_js_1.defaultLegacyBody;
|
|
38
|
+
return res.status(201).json(build({ payload, timestamp: stamp(options) }));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* `ErrorResponse(res, error)` — the legacy error shape from a *safe* payload.
|
|
42
|
+
*
|
|
43
|
+
* The legacy helper of this name took the caught error and echoed
|
|
44
|
+
* `error.message || error`, which is how SQL text and upstream response bodies
|
|
45
|
+
* reached clients (§5.2). This one takes an `ErrorPayload` — the sanitised
|
|
46
|
+
* `{ code, message, details? }` the exception filter produces — so the call site
|
|
47
|
+
* has to have decided what is safe to say before it can call this at all.
|
|
48
|
+
*/
|
|
49
|
+
function ErrorResponse(res, error, status, options = {}) {
|
|
50
|
+
return res.status(status).json((0, envelope_js_1.legacyErrorBody)(error, { timestamp: stamp(options) }));
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=responses.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nage-api/compat",
|
|
3
|
+
"version": "1.0.0-beta.2",
|
|
4
|
+
"description": "Legacy envelope shim and migration codemods for moving a nest-core-v2 app onto @nage-api",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"bin": {
|
|
11
|
+
"nage-codemod": "./dist/cli.js"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"!dist/.tsbuildinfo",
|
|
23
|
+
"!dist/**/*.map",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@nage-api/contracts": "1.0.0-beta.2",
|
|
31
|
+
"@nage-api/core": "1.0.0-beta.2"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@nestjs/common": "^11.0.0",
|
|
35
|
+
"@nestjs/core": "^11.0.0",
|
|
36
|
+
"reflect-metadata": "^0.2.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@nestjs/common": "11.1.29",
|
|
40
|
+
"@nestjs/core": "11.1.29",
|
|
41
|
+
"@nestjs/testing": "11.1.29",
|
|
42
|
+
"@swc/core": "1.15.47",
|
|
43
|
+
"@types/node": "22.20.1",
|
|
44
|
+
"@vitest/coverage-v8": "4.1.10",
|
|
45
|
+
"reflect-metadata": "0.2.2",
|
|
46
|
+
"rimraf": "6.1.3",
|
|
47
|
+
"rxjs": "7.8.2",
|
|
48
|
+
"typescript": "5.9.3",
|
|
49
|
+
"unplugin-swc": "1.5.11",
|
|
50
|
+
"vitest": "4.1.10"
|
|
51
|
+
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=22.0.0"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsc -b tsconfig.build.json",
|
|
57
|
+
"clean": "rimraf dist .turbo",
|
|
58
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
59
|
+
"test": "vitest run"
|
|
60
|
+
}
|
|
61
|
+
}
|