@nage-api/core 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 +141 -0
- package/dist/bootstrap/bootstrap.d.ts +48 -0
- package/dist/bootstrap/bootstrap.js +255 -0
- package/dist/bootstrap/drain.d.ts +48 -0
- package/dist/bootstrap/drain.js +113 -0
- package/dist/bootstrap/lifecycle.d.ts +30 -0
- package/dist/bootstrap/lifecycle.js +64 -0
- package/dist/bootstrap/process-guards.d.ts +42 -0
- package/dist/bootstrap/process-guards.js +103 -0
- package/dist/bootstrap/query-parser.d.ts +34 -0
- package/dist/bootstrap/query-parser.js +37 -0
- package/dist/bootstrap/shutdown.d.ts +55 -0
- package/dist/bootstrap/shutdown.js +182 -0
- package/dist/constants.d.ts +32 -0
- package/dist/constants.js +48 -0
- package/dist/context/active-context.d.ts +23 -0
- package/dist/context/active-context.js +34 -0
- package/dist/context/request-context.middleware.d.ts +31 -0
- package/dist/context/request-context.middleware.js +95 -0
- package/dist/context/request-context.service.d.ts +29 -0
- package/dist/context/request-context.service.js +67 -0
- package/dist/decorators/owner.decorator.d.ts +18 -0
- package/dist/decorators/owner.decorator.js +31 -0
- package/dist/decorators/public.decorator.d.ts +13 -0
- package/dist/decorators/public.decorator.js +23 -0
- package/dist/decorators/version.decorators.d.ts +34 -0
- package/dist/decorators/version.decorators.js +40 -0
- package/dist/errors/catalog.d.ts +149 -0
- package/dist/errors/catalog.js +289 -0
- package/dist/errors/index.d.ts +3 -0
- package/dist/errors/index.js +22 -0
- package/dist/errors/nage.error.d.ts +43 -0
- package/dist/errors/nage.error.js +45 -0
- package/dist/guards/api-version.guard.d.ts +20 -0
- package/dist/guards/api-version.guard.js +73 -0
- package/dist/http/all-exceptions.filter.d.ts +25 -0
- package/dist/http/all-exceptions.filter.js +256 -0
- package/dist/http/envelope.d.ts +25 -0
- package/dist/http/envelope.js +44 -0
- package/dist/http/no-envelope.decorator.d.ts +11 -0
- package/dist/http/no-envelope.decorator.js +16 -0
- package/dist/http/request-timeout.decorators.d.ts +23 -0
- package/dist/http/request-timeout.decorators.js +29 -0
- package/dist/http/request-timeout.interceptor.d.ts +28 -0
- package/dist/http/request-timeout.interceptor.js +75 -0
- package/dist/http/response.interceptor.d.ts +19 -0
- package/dist/http/response.interceptor.js +73 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +135 -0
- package/dist/job/job.factory.d.ts +29 -0
- package/dist/job/job.factory.js +50 -0
- package/dist/logging/json.logger.d.ts +23 -0
- package/dist/logging/json.logger.js +136 -0
- package/dist/logging/nest-logger.adapter.d.ts +20 -0
- package/dist/logging/nest-logger.adapter.js +46 -0
- package/dist/module/core.module.d.ts +40 -0
- package/dist/module/core.module.js +112 -0
- package/dist/security/audit.d.ts +42 -0
- package/dist/security/audit.js +399 -0
- package/dist/security/index.d.ts +15 -0
- package/dist/security/index.js +50 -0
- package/dist/security/legacy-scan.d.ts +24 -0
- package/dist/security/legacy-scan.js +98 -0
- package/dist/security/random.d.ts +40 -0
- package/dist/security/random.js +87 -0
- package/dist/security/rate-limit.decorators.d.ts +24 -0
- package/dist/security/rate-limit.decorators.js +25 -0
- package/dist/security/rate-limit.guard.d.ts +44 -0
- package/dist/security/rate-limit.guard.js +130 -0
- package/dist/security/rate-limit.store.d.ts +30 -0
- package/dist/security/rate-limit.store.js +63 -0
- package/dist/security/redaction.d.ts +54 -0
- package/dist/security/redaction.js +146 -0
- package/dist/security/tls.d.ts +29 -0
- package/dist/security/tls.js +48 -0
- package/dist/tokens.d.ts +60 -0
- package/dist/tokens.js +89 -0
- package/dist/version.d.ts +5 -0
- package/dist/version.js +8 -0
- package/package.json +77 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Redaction (PLAN.md §12, §18).
|
|
4
|
+
*
|
|
5
|
+
* Structured logs are only safe if the structure cannot smuggle a credential
|
|
6
|
+
* out. Three places need scrubbing, and all three are here so they cannot drift:
|
|
7
|
+
* arbitrary field values, request headers, and URLs carrying tokens in the query
|
|
8
|
+
* string.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.REDACTED_QUERY_PARAMS = exports.REDACTED_HEADERS = exports.CIRCULAR = exports.TRUNCATED = exports.REDACTED = void 0;
|
|
12
|
+
exports.redactionSet = redactionSet;
|
|
13
|
+
exports.redact = redact;
|
|
14
|
+
exports.redactHeaders = redactHeaders;
|
|
15
|
+
exports.redactUrl = redactUrl;
|
|
16
|
+
const constants_js_1 = require("../constants.js");
|
|
17
|
+
exports.REDACTED = '[redacted]';
|
|
18
|
+
/**
|
|
19
|
+
* Substituted where the walk stops.
|
|
20
|
+
*
|
|
21
|
+
* The depth bound used to `return value`, which meant a credential nested deeper
|
|
22
|
+
* than the bound was written out verbatim — the redaction failed *open* at
|
|
23
|
+
* exactly the point where the structure was most likely to be something the
|
|
24
|
+
* caller had not thought about. Whatever cannot be scrubbed is not logged.
|
|
25
|
+
*/
|
|
26
|
+
exports.TRUNCATED = '[truncated]';
|
|
27
|
+
/** A structure that refers to itself. Emitted instead of recursing forever. */
|
|
28
|
+
exports.CIRCULAR = '[circular]';
|
|
29
|
+
/** How deep the walk goes before it substitutes `TRUNCATED`. */
|
|
30
|
+
const MAX_DEPTH = 8;
|
|
31
|
+
/** Headers that carry credentials and are never logged verbatim. */
|
|
32
|
+
exports.REDACTED_HEADERS = [
|
|
33
|
+
'authorization',
|
|
34
|
+
'proxy-authorization',
|
|
35
|
+
'cookie',
|
|
36
|
+
'set-cookie',
|
|
37
|
+
'x-api-key',
|
|
38
|
+
'x-auth-token',
|
|
39
|
+
'x-csrf-token',
|
|
40
|
+
];
|
|
41
|
+
/** Query parameters that carry credentials, e.g. `?access_token=…`. */
|
|
42
|
+
exports.REDACTED_QUERY_PARAMS = [
|
|
43
|
+
'token',
|
|
44
|
+
'access_token',
|
|
45
|
+
'refresh_token',
|
|
46
|
+
'api_key',
|
|
47
|
+
'apikey',
|
|
48
|
+
'password',
|
|
49
|
+
'secret',
|
|
50
|
+
'code',
|
|
51
|
+
'otp',
|
|
52
|
+
];
|
|
53
|
+
/** Build a lookup set from the defaults plus any configured extras. */
|
|
54
|
+
function redactionSet(extra = []) {
|
|
55
|
+
return new Set([...constants_js_1.DEFAULT_REDACTED_FIELDS, ...extra].map((field) => field.toLowerCase()));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Replace the value of any matching key, at any depth.
|
|
59
|
+
*
|
|
60
|
+
* Three things this has to survive, because a logger that throws inside a
|
|
61
|
+
* request handler turns a log line into a 500:
|
|
62
|
+
*
|
|
63
|
+
* - **Depth.** Bounded, and the bound substitutes `TRUNCATED` rather than
|
|
64
|
+
* passing the subtree through — see that constant.
|
|
65
|
+
* - **Cycles.** A request object, a Sequelize instance and an Axios error all
|
|
66
|
+
* refer back to themselves. Tracked per branch, so a value that legitimately
|
|
67
|
+
* appears twice side by side is still logged twice.
|
|
68
|
+
* - **Values `JSON.stringify` mangles.** A `Date` has no own enumerable
|
|
69
|
+
* properties, so walking it produced `{}` and lost the timestamp entirely; a
|
|
70
|
+
* `Buffer` expanded into one key per byte, turning a 1MB upload into a log
|
|
71
|
+
* line nothing will ever read.
|
|
72
|
+
*/
|
|
73
|
+
function redact(value, fields, depth = 0, seen = new Set()) {
|
|
74
|
+
if (value === null || typeof value !== 'object')
|
|
75
|
+
return value;
|
|
76
|
+
if (depth >= MAX_DEPTH)
|
|
77
|
+
return exports.TRUNCATED;
|
|
78
|
+
if (seen.has(value))
|
|
79
|
+
return exports.CIRCULAR;
|
|
80
|
+
// Left whole: the caller's own serializer knows what to do with these, and
|
|
81
|
+
// walking them destroys or explodes them.
|
|
82
|
+
if (value instanceof Date)
|
|
83
|
+
return value;
|
|
84
|
+
if (value instanceof Error)
|
|
85
|
+
return value;
|
|
86
|
+
if (isBufferLike(value))
|
|
87
|
+
return `[binary ${String(value.byteLength)} bytes]`;
|
|
88
|
+
const branch = new Set(seen).add(value);
|
|
89
|
+
if (Array.isArray(value)) {
|
|
90
|
+
return value.map((item) => redact(item, fields, depth + 1, branch));
|
|
91
|
+
}
|
|
92
|
+
const result = {};
|
|
93
|
+
for (const key of Object.keys(value)) {
|
|
94
|
+
if (fields.has(key.toLowerCase())) {
|
|
95
|
+
result[key] = exports.REDACTED;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
result[key] = redact(value[key], fields, depth + 1, branch);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
// A getter that throws — an ORM instance with a lazy association, a proxy
|
|
103
|
+
// whose target is gone. One unreadable field must not lose the whole line.
|
|
104
|
+
result[key] = `[unreadable: ${error instanceof Error ? error.message : 'unknown'}]`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
function isBufferLike(value) {
|
|
110
|
+
return value instanceof ArrayBuffer || ArrayBuffer.isView(value);
|
|
111
|
+
}
|
|
112
|
+
/** Scrub credential-bearing request headers before they reach a log line. */
|
|
113
|
+
function redactHeaders(headers, extra = []) {
|
|
114
|
+
if (headers === undefined)
|
|
115
|
+
return {};
|
|
116
|
+
const sensitive = new Set([...exports.REDACTED_HEADERS, ...extra].map((header) => header.toLowerCase()));
|
|
117
|
+
const result = {};
|
|
118
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
119
|
+
result[name] = sensitive.has(name.toLowerCase()) ? exports.REDACTED : value;
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Scrub credentials out of a URL's query string.
|
|
125
|
+
*
|
|
126
|
+
* Request URLs are logged on every error; a token passed as a query parameter
|
|
127
|
+
* (which the legacy WebSocket handshake did) would otherwise be written to disk
|
|
128
|
+
* in clear text on every failure.
|
|
129
|
+
*/
|
|
130
|
+
function redactUrl(url) {
|
|
131
|
+
if (url?.includes('?') !== true)
|
|
132
|
+
return url;
|
|
133
|
+
const [path, query] = url.split('?', 2);
|
|
134
|
+
if (query === undefined)
|
|
135
|
+
return url;
|
|
136
|
+
const sensitive = new Set(exports.REDACTED_QUERY_PARAMS);
|
|
137
|
+
const scrubbed = query
|
|
138
|
+
.split('&')
|
|
139
|
+
.map((pair) => {
|
|
140
|
+
const key = pair.split('=', 1)[0] ?? '';
|
|
141
|
+
return sensitive.has(key.toLowerCase()) ? `${key}=${exports.REDACTED}` : pair;
|
|
142
|
+
})
|
|
143
|
+
.join('&');
|
|
144
|
+
return `${path ?? ''}?${scrubbed}`;
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=redaction.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TLS policy for outbound connections (PLAN.md §12).
|
|
3
|
+
*
|
|
4
|
+
* The legacy framework set `rejectUnauthorized: false` in two places — the SQL
|
|
5
|
+
* connection and the SMTP client — which silently accepts any certificate and
|
|
6
|
+
* makes the connection interceptable. Drivers and integrations translate a
|
|
7
|
+
* declared `TlsMode` through this helper instead of hand-rolling options, so
|
|
8
|
+
* "verification is off" is a value in the config that `doctor` can see rather
|
|
9
|
+
* than a literal buried in a driver.
|
|
10
|
+
*/
|
|
11
|
+
import type { NodeEnvironment, TlsMode } from '@nage-api/contracts';
|
|
12
|
+
/** Node TLS options a driver can pass straight to its client. */
|
|
13
|
+
export interface TlsOptions {
|
|
14
|
+
readonly enabled: boolean;
|
|
15
|
+
readonly rejectUnauthorized: boolean;
|
|
16
|
+
/** Verify the certificate's hostname as well as its chain. */
|
|
17
|
+
readonly checkServerIdentity: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare const DEFAULT_TLS_MODE: TlsMode;
|
|
20
|
+
/** Translate a declared mode into concrete client options. */
|
|
21
|
+
export declare function resolveTlsOptions(mode?: TlsMode): TlsOptions;
|
|
22
|
+
/**
|
|
23
|
+
* Refuse an unverified mode where it is indefensible.
|
|
24
|
+
*
|
|
25
|
+
* Development gets a warning and carries on — over-strict defaults that break
|
|
26
|
+
* local work get disabled wholesale, which is worse (§26).
|
|
27
|
+
*/
|
|
28
|
+
export declare function assertTlsAllowed(mode: TlsMode, environment: NodeEnvironment, location?: string): void;
|
|
29
|
+
//# sourceMappingURL=tls.d.ts.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* TLS policy for outbound connections (PLAN.md §12).
|
|
4
|
+
*
|
|
5
|
+
* The legacy framework set `rejectUnauthorized: false` in two places — the SQL
|
|
6
|
+
* connection and the SMTP client — which silently accepts any certificate and
|
|
7
|
+
* makes the connection interceptable. Drivers and integrations translate a
|
|
8
|
+
* declared `TlsMode` through this helper instead of hand-rolling options, so
|
|
9
|
+
* "verification is off" is a value in the config that `doctor` can see rather
|
|
10
|
+
* than a literal buried in a driver.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.DEFAULT_TLS_MODE = void 0;
|
|
14
|
+
exports.resolveTlsOptions = resolveTlsOptions;
|
|
15
|
+
exports.assertTlsAllowed = assertTlsAllowed;
|
|
16
|
+
const catalog_js_1 = require("../errors/catalog.js");
|
|
17
|
+
exports.DEFAULT_TLS_MODE = 'verify-full';
|
|
18
|
+
/** Translate a declared mode into concrete client options. */
|
|
19
|
+
function resolveTlsOptions(mode = exports.DEFAULT_TLS_MODE) {
|
|
20
|
+
switch (mode) {
|
|
21
|
+
case 'disable':
|
|
22
|
+
return { enabled: false, rejectUnauthorized: false, checkServerIdentity: false };
|
|
23
|
+
case 'require':
|
|
24
|
+
// Encrypted, but the chain is not verified — acceptable only on a network
|
|
25
|
+
// you already trust, such as a private VPC with a self-signed CA.
|
|
26
|
+
return { enabled: true, rejectUnauthorized: false, checkServerIdentity: false };
|
|
27
|
+
case 'no-verify':
|
|
28
|
+
return { enabled: true, rejectUnauthorized: false, checkServerIdentity: false };
|
|
29
|
+
case 'verify-full':
|
|
30
|
+
return { enabled: true, rejectUnauthorized: true, checkServerIdentity: true };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Refuse an unverified mode where it is indefensible.
|
|
35
|
+
*
|
|
36
|
+
* Development gets a warning and carries on — over-strict defaults that break
|
|
37
|
+
* local work get disabled wholesale, which is worse (§26).
|
|
38
|
+
*/
|
|
39
|
+
function assertTlsAllowed(mode, environment, location = 'database.ssl') {
|
|
40
|
+
const unverified = mode === 'no-verify' || mode === 'disable';
|
|
41
|
+
if (unverified && environment === 'production') {
|
|
42
|
+
throw new catalog_js_1.ConfigurationError({
|
|
43
|
+
detail: `${location} is "${mode}" in production; certificate verification must not be disabled`,
|
|
44
|
+
meta: { location, mode, environment },
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=tls.js.map
|
package/dist/tokens.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DI tokens (PLAN.md §7.3).
|
|
3
|
+
*
|
|
4
|
+
* Feature packages must not import each other; they collaborate through the
|
|
5
|
+
* interfaces in `@nage-api/contracts` and these tokens. A token carries its port
|
|
6
|
+
* type as a phantom parameter so `inject<T>()` stays type-safe without any cast
|
|
7
|
+
* at the call site.
|
|
8
|
+
*/
|
|
9
|
+
import type { ContextStore, KeyValueStore, LoggerPort, NageCoreConfig, RateLimitStore, RepositoryPort, UnitOfWork } from '@nage-api/contracts';
|
|
10
|
+
import type { LifecycleState } from './bootstrap/lifecycle.js';
|
|
11
|
+
/** A DI token that remembers what it resolves to. */
|
|
12
|
+
export type Token<TPort> = symbol & {
|
|
13
|
+
readonly __port?: TPort;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Create a typed injection token.
|
|
17
|
+
*
|
|
18
|
+
* Unmemoised on purpose: every caller is a module-level constant, so identity
|
|
19
|
+
* comes from the single evaluation of that constant. A *derived* token — one
|
|
20
|
+
* computed from a name at more than one call site — must go through
|
|
21
|
+
* `namedToken` instead.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createToken<TPort>(description: string): Token<TPort>;
|
|
24
|
+
/** The validated core configuration the application booted with. */
|
|
25
|
+
export declare const NAGE_CONFIG: Token<NageCoreConfig>;
|
|
26
|
+
/** Structured logger; correlation ids are attached by the implementation. */
|
|
27
|
+
export declare const NAGE_LOGGER: Token<LoggerPort>;
|
|
28
|
+
/** Ambient per-request context (requestId / user / tenant). */
|
|
29
|
+
export declare const NAGE_REQUEST_CONTEXT: Token<ContextStore>;
|
|
30
|
+
/**
|
|
31
|
+
* Counter store behind the global throttle. Defaults to the in-process store,
|
|
32
|
+
* which counts per process; a multi-instance deployment supplies a shared one
|
|
33
|
+
* through `NageCoreModule.forRoot(config, { rateLimitStore })` (§12).
|
|
34
|
+
*
|
|
35
|
+
* Exported so an application can inject the store it supplied. Providing this
|
|
36
|
+
* token from the application module does **not** override the default — the
|
|
37
|
+
* binding lives inside `NageCoreModule` and that is where the guard resolves it.
|
|
38
|
+
*/
|
|
39
|
+
export declare const NAGE_RATE_LIMIT_STORE: Token<RateLimitStore>;
|
|
40
|
+
/**
|
|
41
|
+
* Whether this process is starting, live, or draining after a shutdown signal.
|
|
42
|
+
*
|
|
43
|
+
* Published so `/health/ready` can fail the instant a signal arrives (§21): a
|
|
44
|
+
* draining process that still reports ready keeps being sent traffic it is about
|
|
45
|
+
* to disconnect.
|
|
46
|
+
*/
|
|
47
|
+
export declare const NAGE_LIFECYCLE: Token<LifecycleState>;
|
|
48
|
+
/** Transaction boundary provided by the selected data driver (Phase 5). */
|
|
49
|
+
export declare const NAGE_UNIT_OF_WORK: Token<UnitOfWork>;
|
|
50
|
+
/**
|
|
51
|
+
* Repository token for one model. Drivers register against it, application
|
|
52
|
+
* services inject it — neither side imports the other.
|
|
53
|
+
*
|
|
54
|
+
* Same model name, same token, however many times it is asked for: that identity
|
|
55
|
+
* *is* the decoupling. See `namedToken`.
|
|
56
|
+
*/
|
|
57
|
+
export declare function repositoryToken<TEntity>(model: string): Token<RepositoryPort<TEntity>>;
|
|
58
|
+
/** Named key/value store — sessions, throttling counters, caches (Phase 7-8). */
|
|
59
|
+
export declare function keyValueStoreToken<TValue>(name: string): Token<KeyValueStore<TValue>>;
|
|
60
|
+
//# sourceMappingURL=tokens.d.ts.map
|
package/dist/tokens.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* DI tokens (PLAN.md §7.3).
|
|
4
|
+
*
|
|
5
|
+
* Feature packages must not import each other; they collaborate through the
|
|
6
|
+
* interfaces in `@nage-api/contracts` and these tokens. A token carries its port
|
|
7
|
+
* type as a phantom parameter so `inject<T>()` stays type-safe without any cast
|
|
8
|
+
* at the call site.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.NAGE_UNIT_OF_WORK = exports.NAGE_LIFECYCLE = exports.NAGE_RATE_LIMIT_STORE = exports.NAGE_REQUEST_CONTEXT = exports.NAGE_LOGGER = exports.NAGE_CONFIG = void 0;
|
|
12
|
+
exports.createToken = createToken;
|
|
13
|
+
exports.repositoryToken = repositoryToken;
|
|
14
|
+
exports.keyValueStoreToken = keyValueStoreToken;
|
|
15
|
+
/**
|
|
16
|
+
* Create a typed injection token.
|
|
17
|
+
*
|
|
18
|
+
* Unmemoised on purpose: every caller is a module-level constant, so identity
|
|
19
|
+
* comes from the single evaluation of that constant. A *derived* token — one
|
|
20
|
+
* computed from a name at more than one call site — must go through
|
|
21
|
+
* `namedToken` instead.
|
|
22
|
+
*/
|
|
23
|
+
function createToken(description) {
|
|
24
|
+
return Symbol(description);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Tokens derived from a name, memoised by that name.
|
|
28
|
+
*
|
|
29
|
+
* `Symbol()` returns a fresh symbol every call, so a factory that wrapped it
|
|
30
|
+
* directly produced a *different* token each time it was asked for the same
|
|
31
|
+
* model — which quietly breaks the one property these tokens exist for. A driver
|
|
32
|
+
* registering `repositoryToken('Invoice')` and a service injecting
|
|
33
|
+
* `repositoryToken('Invoice')` would be talking about two different symbols, and
|
|
34
|
+
* Nest would report the provider as missing however correctly both sides were
|
|
35
|
+
* written.
|
|
36
|
+
*
|
|
37
|
+
* Scoped to this module rather than `Symbol.for`, which would put the keys in a
|
|
38
|
+
* process-global registry shared with every other library.
|
|
39
|
+
*/
|
|
40
|
+
const namedTokens = new Map();
|
|
41
|
+
function namedToken(description) {
|
|
42
|
+
const existing = namedTokens.get(description);
|
|
43
|
+
if (existing !== undefined)
|
|
44
|
+
return existing;
|
|
45
|
+
const token = createToken(description);
|
|
46
|
+
namedTokens.set(description, token);
|
|
47
|
+
return token;
|
|
48
|
+
}
|
|
49
|
+
/** The validated core configuration the application booted with. */
|
|
50
|
+
exports.NAGE_CONFIG = createToken('NAGE_CONFIG');
|
|
51
|
+
/** Structured logger; correlation ids are attached by the implementation. */
|
|
52
|
+
exports.NAGE_LOGGER = createToken('NAGE_LOGGER');
|
|
53
|
+
/** Ambient per-request context (requestId / user / tenant). */
|
|
54
|
+
exports.NAGE_REQUEST_CONTEXT = createToken('NAGE_REQUEST_CONTEXT');
|
|
55
|
+
/**
|
|
56
|
+
* Counter store behind the global throttle. Defaults to the in-process store,
|
|
57
|
+
* which counts per process; a multi-instance deployment supplies a shared one
|
|
58
|
+
* through `NageCoreModule.forRoot(config, { rateLimitStore })` (§12).
|
|
59
|
+
*
|
|
60
|
+
* Exported so an application can inject the store it supplied. Providing this
|
|
61
|
+
* token from the application module does **not** override the default — the
|
|
62
|
+
* binding lives inside `NageCoreModule` and that is where the guard resolves it.
|
|
63
|
+
*/
|
|
64
|
+
exports.NAGE_RATE_LIMIT_STORE = createToken('NAGE_RATE_LIMIT_STORE');
|
|
65
|
+
/**
|
|
66
|
+
* Whether this process is starting, live, or draining after a shutdown signal.
|
|
67
|
+
*
|
|
68
|
+
* Published so `/health/ready` can fail the instant a signal arrives (§21): a
|
|
69
|
+
* draining process that still reports ready keeps being sent traffic it is about
|
|
70
|
+
* to disconnect.
|
|
71
|
+
*/
|
|
72
|
+
exports.NAGE_LIFECYCLE = createToken('NAGE_LIFECYCLE');
|
|
73
|
+
/** Transaction boundary provided by the selected data driver (Phase 5). */
|
|
74
|
+
exports.NAGE_UNIT_OF_WORK = createToken('NAGE_UNIT_OF_WORK');
|
|
75
|
+
/**
|
|
76
|
+
* Repository token for one model. Drivers register against it, application
|
|
77
|
+
* services inject it — neither side imports the other.
|
|
78
|
+
*
|
|
79
|
+
* Same model name, same token, however many times it is asked for: that identity
|
|
80
|
+
* *is* the decoupling. See `namedToken`.
|
|
81
|
+
*/
|
|
82
|
+
function repositoryToken(model) {
|
|
83
|
+
return namedToken(`NAGE_REPOSITORY:${model}`);
|
|
84
|
+
}
|
|
85
|
+
/** Named key/value store — sessions, throttling counters, caches (Phase 7-8). */
|
|
86
|
+
function keyValueStoreToken(name) {
|
|
87
|
+
return namedToken(`NAGE_KV_STORE:${name}`);
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Package version marker, useful in `nage doctor` / `nage info` output. */
|
|
2
|
+
export declare const NAGE_CORE_VERSION = "0.0.0";
|
|
3
|
+
/** Minimum Node.js runtime supported by the framework (PLAN.md §21). */
|
|
4
|
+
export declare const NAGE_MIN_NODE_VERSION = "22.0.0";
|
|
5
|
+
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NAGE_MIN_NODE_VERSION = exports.NAGE_CORE_VERSION = void 0;
|
|
4
|
+
/** Package version marker, useful in `nage doctor` / `nage info` output. */
|
|
5
|
+
exports.NAGE_CORE_VERSION = '0.0.0';
|
|
6
|
+
/** Minimum Node.js runtime supported by the framework (PLAN.md §21). */
|
|
7
|
+
exports.NAGE_MIN_NODE_VERSION = '22.0.0';
|
|
8
|
+
//# sourceMappingURL=version.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nage-api/core",
|
|
3
|
+
"version": "1.0.0-beta.2",
|
|
4
|
+
"description": "Cross-cutting primitives for @nage-api applications — context, response envelope, error catalog, bootstrap",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"!dist/.tsbuildinfo",
|
|
20
|
+
"!dist/**/*.map",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"compression": "1.8.1",
|
|
28
|
+
"helmet": "8.3.0",
|
|
29
|
+
"@nage-api/contracts": "1.0.0-beta.2"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"@nestjs/common": "^11.0.0",
|
|
33
|
+
"@nestjs/core": "^11.0.0",
|
|
34
|
+
"class-transformer": "^0.5.1",
|
|
35
|
+
"class-validator": "^0.15.0",
|
|
36
|
+
"reflect-metadata": "^0.2.0",
|
|
37
|
+
"rxjs": "^7.8.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependenciesMeta": {
|
|
40
|
+
"class-transformer": {
|
|
41
|
+
"optional": true
|
|
42
|
+
},
|
|
43
|
+
"class-validator": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@nestjs/common": "11.1.29",
|
|
49
|
+
"@nestjs/core": "11.1.29",
|
|
50
|
+
"@nestjs/platform-express": "11.1.29",
|
|
51
|
+
"@nestjs/testing": "11.1.29",
|
|
52
|
+
"@swc/core": "1.15.47",
|
|
53
|
+
"@types/compression": "1.8.1",
|
|
54
|
+
"@types/node": "22.20.1",
|
|
55
|
+
"@types/supertest": "7.2.1",
|
|
56
|
+
"@vitest/coverage-v8": "4.1.10",
|
|
57
|
+
"class-transformer": "0.5.1",
|
|
58
|
+
"class-validator": "0.15.1",
|
|
59
|
+
"reflect-metadata": "0.2.2",
|
|
60
|
+
"rimraf": "6.1.3",
|
|
61
|
+
"rxjs": "7.8.2",
|
|
62
|
+
"supertest": "7.2.2",
|
|
63
|
+
"typescript": "5.9.3",
|
|
64
|
+
"unplugin-swc": "1.5.11",
|
|
65
|
+
"vitest": "4.1.10",
|
|
66
|
+
"@nage-api/testing": "1.0.0-beta.2"
|
|
67
|
+
},
|
|
68
|
+
"engines": {
|
|
69
|
+
"node": ">=22.0.0"
|
|
70
|
+
},
|
|
71
|
+
"scripts": {
|
|
72
|
+
"build": "tsc -b tsconfig.build.json",
|
|
73
|
+
"clean": "rimraf dist .turbo",
|
|
74
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
75
|
+
"test": "vitest run"
|
|
76
|
+
}
|
|
77
|
+
}
|