@nodii/telemetry 0.0.2 → 0.3.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/README.md +223 -1
- package/dist/active-span.d.ts +12 -0
- package/dist/active-span.d.ts.map +1 -0
- package/dist/active-span.js +20 -0
- package/dist/active-span.js.map +1 -0
- package/dist/agent-invocable.d.ts +24 -0
- package/dist/agent-invocable.d.ts.map +1 -0
- package/dist/agent-invocable.js +64 -0
- package/dist/agent-invocable.js.map +1 -0
- package/dist/audit-types.d.ts +15 -0
- package/dist/audit-types.d.ts.map +1 -0
- package/dist/audit-types.js +39 -0
- package/dist/audit-types.js.map +1 -0
- package/dist/audit.d.ts +226 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +255 -0
- package/dist/audit.js.map +1 -0
- package/dist/configure.d.ts +34 -0
- package/dist/configure.d.ts.map +1 -0
- package/dist/configure.js +23 -0
- package/dist/configure.js.map +1 -0
- package/dist/context.d.ts +40 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +70 -0
- package/dist/context.js.map +1 -0
- package/dist/drizzle/audit-schema.d.ts +884 -0
- package/dist/drizzle/audit-schema.d.ts.map +1 -0
- package/dist/drizzle/audit-schema.js +268 -0
- package/dist/drizzle/audit-schema.js.map +1 -0
- package/dist/drizzle/index.d.ts +3 -0
- package/dist/drizzle/index.d.ts.map +1 -0
- package/dist/drizzle/index.js +5 -0
- package/dist/drizzle/index.js.map +1 -0
- package/dist/index.d.ts +21 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -5
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +39 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +114 -0
- package/dist/init.js.map +1 -0
- package/dist/intent.d.ts +21 -0
- package/dist/intent.d.ts.map +1 -0
- package/dist/intent.js +123 -0
- package/dist/intent.js.map +1 -0
- package/dist/logger.d.ts +19 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +99 -0
- package/dist/logger.js.map +1 -0
- package/dist/metrics.d.ts +53 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +152 -0
- package/dist/metrics.js.map +1 -0
- package/dist/otlp-adapter.d.ts +44 -0
- package/dist/otlp-adapter.d.ts.map +1 -0
- package/dist/otlp-adapter.js +351 -0
- package/dist/otlp-adapter.js.map +1 -0
- package/dist/outbox-emit.d.ts +34 -0
- package/dist/outbox-emit.d.ts.map +1 -0
- package/dist/outbox-emit.js +58 -0
- package/dist/outbox-emit.js.map +1 -0
- package/dist/redaction.d.ts +51 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/redaction.js +173 -0
- package/dist/redaction.js.map +1 -0
- package/dist/sdk-counters.d.ts +14 -0
- package/dist/sdk-counters.d.ts.map +1 -0
- package/dist/sdk-counters.js +42 -0
- package/dist/sdk-counters.js.map +1 -0
- package/dist/tracer.d.ts +63 -0
- package/dist/tracer.d.ts.map +1 -0
- package/dist/tracer.js +214 -0
- package/dist/tracer.js.map +1 -0
- package/dist/types.d.ts +230 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +66 -0
- package/dist/types.js.map +1 -0
- package/dist/uuid.d.ts +5 -0
- package/dist/uuid.d.ts.map +1 -0
- package/dist/uuid.js +34 -0
- package/dist/uuid.js.map +1 -0
- package/package.json +44 -7
package/dist/audit.js
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// Audit signal class — the canonical adapter per `02-audit-doctrine.md`
|
|
2
|
+
// § 3 (row shape) + § 6.0 (Patterns A/B/C tx-bound) + § 13 (opt-in chain).
|
|
3
|
+
//
|
|
4
|
+
// "If a tenant could legitimately need to see this row, it's audit, not
|
|
5
|
+
// log." — D17 cultural rule, doctrine § 1.
|
|
6
|
+
//
|
|
7
|
+
// The library ships the adapter (the write path); the audit emission
|
|
8
|
+
// policy (D33 three paths: method-level interceptor, saga lifecycle,
|
|
9
|
+
// agent-layer double-emission) and the `<service>_audit_event` schema
|
|
10
|
+
// live in `02-audit-doctrine.md`, not here. The Drizzle schema fragment
|
|
11
|
+
// for the table ships from `@nodii/telemetry/drizzle`.
|
|
12
|
+
//
|
|
13
|
+
// Three emission patterns per § 6.0:
|
|
14
|
+
// - Pattern A — explicit tx pass: `audit.emit(tx, args)`
|
|
15
|
+
// - Pattern B — Hono `c.set("txn", tx)` middleware: also `audit.emit(tx, args)`
|
|
16
|
+
// - Pattern C — out-of-band: `audit.emit(args)` (writer owns its tx)
|
|
17
|
+
import { AUDIT_ACTOR_KINDS, AUDIT_OUTCOMES, } from "./audit-types";
|
|
18
|
+
import { getContext } from "./context";
|
|
19
|
+
import { requireTelemetry } from "./init";
|
|
20
|
+
import { uuidv7 } from "./uuid";
|
|
21
|
+
export { AUDIT_ACTOR_KINDS, AUDIT_OUTCOMES };
|
|
22
|
+
/**
|
|
23
|
+
* Thrown by `audit.emit` when no `AuditWriter` has been wired. v0.1.2
|
|
24
|
+
* replaces the v0.1.0–v0.1.1 silent-Noop failure mode (R1 violation,
|
|
25
|
+
* drift `4cc80f62`). Services MUST wire a writer via `_setAuditWriter(...)`
|
|
26
|
+
* before the first `audit.emit` fires.
|
|
27
|
+
*/
|
|
28
|
+
export class TelemetryAuditWriterMissing extends Error {
|
|
29
|
+
constructor() {
|
|
30
|
+
super("@nodii/telemetry: audit.emit called before an AuditWriter was wired. " +
|
|
31
|
+
"Drift 4cc80f62 (R1, breaking) removed the v0.1.0–v0.1.1 Noop default " +
|
|
32
|
+
"because it silently dropped audit rows. Wire a real writer via " +
|
|
33
|
+
"`_setAuditWriter(...)` (typically from `@nodii/db-rls`'s adapter " +
|
|
34
|
+
"writing to <serviceName>_audit_event per 02-audit-doctrine.md § 6.0).");
|
|
35
|
+
this.name = "TelemetryAuditWriterMissing";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
let writer = null;
|
|
39
|
+
let chain = null;
|
|
40
|
+
/**
|
|
41
|
+
* SDK-internal: services or the `@nodii/db-rls` adapter sets the writer.
|
|
42
|
+
* Public callers MUST NOT call this; the override exists for the adapter
|
|
43
|
+
* layer.
|
|
44
|
+
*/
|
|
45
|
+
export function _setAuditWriter(w) {
|
|
46
|
+
writer = w;
|
|
47
|
+
}
|
|
48
|
+
/** Test-only reset. Clears the wired writer; the next `audit.emit` will throw. */
|
|
49
|
+
export function _resetAuditWriterForTests() {
|
|
50
|
+
writer = null;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* SDK-internal: `configureTelemetry({audit: {chain}})` calls this to wire
|
|
54
|
+
* the opt-in chain adapter. Setting `null` removes it.
|
|
55
|
+
*/
|
|
56
|
+
export function _setAuditChain(adapter) {
|
|
57
|
+
chain = adapter;
|
|
58
|
+
}
|
|
59
|
+
/** Test-only reset. Removes the wired chain adapter. */
|
|
60
|
+
export function _resetAuditChainForTests() {
|
|
61
|
+
chain = null;
|
|
62
|
+
}
|
|
63
|
+
/** SDK-internal accessor for tests + chain-layer integration probes. */
|
|
64
|
+
export function _getAuditChain() {
|
|
65
|
+
return chain;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Type guard distinguishing the legacy `AuditEvent` from the rich `AuditArgs`.
|
|
69
|
+
* The discriminator is `target_kind`: present on the legacy shape, absent on
|
|
70
|
+
* the rich shape (which uses `resourceType`). Both shapes carry `action`.
|
|
71
|
+
*/
|
|
72
|
+
function isLegacyEvent(arg) {
|
|
73
|
+
return ("target_kind" in arg &&
|
|
74
|
+
typeof arg.target_kind === "string" &&
|
|
75
|
+
!("resourceType" in arg));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Map the legacy shape onto the rich `AuditArgs` so the rest of the
|
|
79
|
+
* pipeline only handles one input form. Mapping per doctrine § 3:
|
|
80
|
+
* target_kind → resource_type
|
|
81
|
+
* target_id → resource_id
|
|
82
|
+
* payload → metadata
|
|
83
|
+
* Other rich fields are left undefined for `resolveRow` to fill from ctx.
|
|
84
|
+
*/
|
|
85
|
+
function legacyToRich(event) {
|
|
86
|
+
return {
|
|
87
|
+
action: event.action,
|
|
88
|
+
resourceType: event.target_kind,
|
|
89
|
+
resourceId: event.target_id,
|
|
90
|
+
metadata: event.payload ?? {},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Derive the canonical `actor_kind` from a `NodiiContext`. Falls back to
|
|
95
|
+
* `'unknown'` per doctrine § 4.2 — the SDK MUST emit `unknown` when no
|
|
96
|
+
* actor stamp can be derived; this is a security-event marker, not a
|
|
97
|
+
* silent default.
|
|
98
|
+
*
|
|
99
|
+
* Mapping rules per § 4 + auth-sdk § 5.6:
|
|
100
|
+
* ctx.actor_type='user' → 'tenant_user' (callers override to 'employee_user' when platform-admin)
|
|
101
|
+
* ctx.actor_type='agent' → 'agent'
|
|
102
|
+
* ctx.actor_type='service' → 'service'
|
|
103
|
+
* ctx.actor_type='system' → 'system'
|
|
104
|
+
* ctx absent → 'unknown' (security marker)
|
|
105
|
+
*/
|
|
106
|
+
function deriveActorKind(ctx) {
|
|
107
|
+
if (!ctx)
|
|
108
|
+
return "unknown";
|
|
109
|
+
switch (ctx.actor_type) {
|
|
110
|
+
case "user":
|
|
111
|
+
return "tenant_user";
|
|
112
|
+
case "agent":
|
|
113
|
+
return "agent";
|
|
114
|
+
case "service":
|
|
115
|
+
return "service";
|
|
116
|
+
case "system":
|
|
117
|
+
return "system";
|
|
118
|
+
default: {
|
|
119
|
+
// Exhaustiveness check + future-proofing if ActorType grows.
|
|
120
|
+
const _exhaustive = ctx.actor_type;
|
|
121
|
+
void _exhaustive;
|
|
122
|
+
return "unknown";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Derive the canonical `actor_id` from `NodiiContext`. For `actor_type='user'`
|
|
128
|
+
* use `user_id`; for `actor_type='agent'` use `on_behalf_of` (the human who
|
|
129
|
+
* authorised the dispatch — per doctrine § 4.1 agent extension); for service
|
|
130
|
+
* + system contexts no `actor_id` is derivable and the caller must supply
|
|
131
|
+
* one if needed.
|
|
132
|
+
*/
|
|
133
|
+
function deriveActorId(ctx) {
|
|
134
|
+
if (!ctx)
|
|
135
|
+
return null;
|
|
136
|
+
switch (ctx.actor_type) {
|
|
137
|
+
case "user":
|
|
138
|
+
return ctx.user_id;
|
|
139
|
+
case "agent":
|
|
140
|
+
return ctx.on_behalf_of;
|
|
141
|
+
case "service":
|
|
142
|
+
case "system":
|
|
143
|
+
return null;
|
|
144
|
+
default:
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Build the canonical `AuditEventRow` from `AuditArgs` + the active
|
|
150
|
+
* `NodiiContext`. Resolution rules (in order):
|
|
151
|
+
* 1. `tenant_id`: args.tenantId → ctx.tenant_id → throw
|
|
152
|
+
* 2. `actor_kind`: args.actorKind → derive from ctx → 'unknown'
|
|
153
|
+
* 3. `actor_id`: args.actorId → derive from ctx → null
|
|
154
|
+
* 4. `request_id`, `intent_id`: args → ctx → null
|
|
155
|
+
* 5. Other fields: pass through args (null when undefined)
|
|
156
|
+
*
|
|
157
|
+
* Throws `Error` when neither args nor ctx carry a `tenant_id` — the
|
|
158
|
+
* audit row cannot be RLS-bound without one, so silently dropping it
|
|
159
|
+
* would be a doctrine § 3 + R1 violation.
|
|
160
|
+
*/
|
|
161
|
+
function resolveRow(args) {
|
|
162
|
+
const ctx = getContext();
|
|
163
|
+
const tenantId = args.tenantId ?? ctx?.tenant_id ?? null;
|
|
164
|
+
if (!tenantId) {
|
|
165
|
+
throw new Error("audit.emit: tenant_id is required — pass `tenantId` in args or " +
|
|
166
|
+
"invoke inside a `withContext({tenant_id: ...})` scope. The audit " +
|
|
167
|
+
"row cannot be RLS-bound without it (02-audit-doctrine.md § 3).");
|
|
168
|
+
}
|
|
169
|
+
const actorKind = args.actorKind ?? deriveActorKind(ctx);
|
|
170
|
+
const actorId = args.actorId ?? deriveActorId(ctx);
|
|
171
|
+
return {
|
|
172
|
+
id: uuidv7(),
|
|
173
|
+
tenant_id: tenantId,
|
|
174
|
+
at: new Date(),
|
|
175
|
+
actor_kind: actorKind,
|
|
176
|
+
actor_id: actorId,
|
|
177
|
+
actor_external_id: args.actorExternalId ?? null,
|
|
178
|
+
actor_display: args.actorDisplay ?? null,
|
|
179
|
+
actor_ip: args.actorIp ?? null,
|
|
180
|
+
actor_user_agent: args.actorUserAgent ?? null,
|
|
181
|
+
actor: args.actor ?? null,
|
|
182
|
+
action: args.action,
|
|
183
|
+
resource_type: args.resourceType,
|
|
184
|
+
resource_id: args.resourceId ?? null,
|
|
185
|
+
resource_label: args.resourceLabel ?? null,
|
|
186
|
+
secondary_resource_type: args.secondaryResourceType ?? null,
|
|
187
|
+
secondary_resource_id: args.secondaryResourceId ?? null,
|
|
188
|
+
outcome: args.outcome ?? "success",
|
|
189
|
+
http_status: args.httpStatus ?? null,
|
|
190
|
+
grpc_code: args.grpcCode ?? null,
|
|
191
|
+
error_code: args.errorCode ?? null,
|
|
192
|
+
error_message: args.errorMessage ?? null,
|
|
193
|
+
error_details: args.errorDetails ?? null,
|
|
194
|
+
before: args.before ?? null,
|
|
195
|
+
after: args.after ?? null,
|
|
196
|
+
patch: args.patch ?? null,
|
|
197
|
+
changed_fields: args.changedFields ?? null,
|
|
198
|
+
reason_code: args.reasonCode ?? null,
|
|
199
|
+
reason_text: args.reasonText ?? null,
|
|
200
|
+
request_id: args.requestId ?? ctx?.request_id ?? null,
|
|
201
|
+
correlation_id: args.correlationId ?? null,
|
|
202
|
+
trace_id: args.traceId ?? null,
|
|
203
|
+
span_id: args.spanId ?? null,
|
|
204
|
+
intent_id: args.intentId ?? ctx?.intent_id ?? null,
|
|
205
|
+
saga_id: args.sagaId ?? null,
|
|
206
|
+
step_id: args.stepId ?? null,
|
|
207
|
+
idempotency_key: args.idempotencyKey ?? null,
|
|
208
|
+
queue_name: args.queueName ?? null,
|
|
209
|
+
job_id: args.jobId ?? null,
|
|
210
|
+
parent_audit_event_id: args.parentAuditEventId ?? null,
|
|
211
|
+
read_count: args.readCount ?? null,
|
|
212
|
+
first_read_at: args.firstReadAt ?? null,
|
|
213
|
+
last_read_at: args.lastReadAt ?? null,
|
|
214
|
+
// Chain columns left null unless an adapter is wired (see emitImpl).
|
|
215
|
+
prev_hash: null,
|
|
216
|
+
row_hash: null,
|
|
217
|
+
tenant_audit_seq: null,
|
|
218
|
+
metadata: args.metadata ?? {},
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Shared write path. Resolves the row, optionally runs the chain adapter,
|
|
223
|
+
* and hands the row + tx to the writer. Throws `TelemetryAuditWriterMissing`
|
|
224
|
+
* when no writer is wired (drift `4cc80f62` enforcement).
|
|
225
|
+
*/
|
|
226
|
+
async function emitImpl(args, tx) {
|
|
227
|
+
requireTelemetry();
|
|
228
|
+
if (writer === null) {
|
|
229
|
+
throw new TelemetryAuditWriterMissing();
|
|
230
|
+
}
|
|
231
|
+
const row = resolveRow(args);
|
|
232
|
+
if (chain !== null) {
|
|
233
|
+
const augmented = await chain.augment(row, tx);
|
|
234
|
+
row.prev_hash = augmented.prev_hash;
|
|
235
|
+
row.row_hash = augmented.row_hash;
|
|
236
|
+
row.tenant_audit_seq = augmented.tenant_audit_seq;
|
|
237
|
+
}
|
|
238
|
+
await writer(row, tx);
|
|
239
|
+
}
|
|
240
|
+
async function emit(a, b) {
|
|
241
|
+
if (b !== undefined) {
|
|
242
|
+
// emit(tx, args) — Pattern A / B.
|
|
243
|
+
return emitImpl(b, a);
|
|
244
|
+
}
|
|
245
|
+
const arg = a;
|
|
246
|
+
if (isLegacyEvent(arg)) {
|
|
247
|
+
return emitImpl(legacyToRich(arg));
|
|
248
|
+
}
|
|
249
|
+
// emit(args) — Pattern C.
|
|
250
|
+
return emitImpl(arg);
|
|
251
|
+
}
|
|
252
|
+
export const audit = {
|
|
253
|
+
emit,
|
|
254
|
+
};
|
|
255
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type AuditChainAdapter } from "./audit";
|
|
2
|
+
/**
|
|
3
|
+
* Arguments to `configureTelemetry`. Currently only `audit.chain` is
|
|
4
|
+
* supported; the shape exists so additional knobs (e.g. log redaction
|
|
5
|
+
* overrides, custom exporters) can be added without an API break.
|
|
6
|
+
*/
|
|
7
|
+
export interface ConfigureTelemetryOptions {
|
|
8
|
+
audit?: {
|
|
9
|
+
/**
|
|
10
|
+
* Opt-in tamper-evident chain layer per `02-audit-doctrine.md § 13` +
|
|
11
|
+
* § 15.3. When wired, every `audit.emit` row receives `prev_hash`,
|
|
12
|
+
* `row_hash`, and `tenant_audit_seq` from the adapter before the row
|
|
13
|
+
* lands in the writer.
|
|
14
|
+
*
|
|
15
|
+
* Pass `null` to remove a previously-wired adapter (useful for tests
|
|
16
|
+
* + services that toggle chain coverage via feature flag).
|
|
17
|
+
*
|
|
18
|
+
* The library does NOT inspect the adapter beyond calling `augment`.
|
|
19
|
+
* Implementations (typically `@nodii/audit-chain`) own the hashing,
|
|
20
|
+
* the per-tenant monotonic sequence (via
|
|
21
|
+
* `pg_advisory_xact_lock(hash(tenant_id || 'audit'))`), and any
|
|
22
|
+
* verifier-worker reconciliation.
|
|
23
|
+
*/
|
|
24
|
+
chain?: AuditChainAdapter | null;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Wire optional telemetry adapters. Currently the only knob is
|
|
29
|
+
* `audit.chain`. Idempotent: calling repeatedly with `audit.chain` set
|
|
30
|
+
* replaces the previously-wired adapter; calling with `audit` undefined
|
|
31
|
+
* leaves the existing wiring intact.
|
|
32
|
+
*/
|
|
33
|
+
export declare function configureTelemetry(opts: ConfigureTelemetryOptions): void;
|
|
34
|
+
//# sourceMappingURL=configure.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configure.d.ts","sourceRoot":"","sources":["../src/configure.ts"],"names":[],"mappings":"AAWA,OAAO,EAAkB,KAAK,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjE;;;;GAIG;AACH,MAAM,WAAW,yBAAyB;IACxC,KAAK,CAAC,EAAE;QACN;;;;;;;;;;;;;;WAcG;QACH,KAAK,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;KAClC,CAAC;CACH;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,yBAAyB,GAAG,IAAI,CAIxE"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Top-level telemetry configuration knobs that aren't part of
|
|
2
|
+
// `initTelemetry()`'s argument shape — typically opt-in adapter layers
|
|
3
|
+
// (audit chain, future: tracing exporters, metric reducers).
|
|
4
|
+
//
|
|
5
|
+
// `configureTelemetry(...)` is idempotent and order-independent w.r.t.
|
|
6
|
+
// `initTelemetry()`: a service can call it before `initTelemetry()` (set
|
|
7
|
+
// up the chain adapter at module load) or after (wire it during boot
|
|
8
|
+
// once secrets/clients are resolved). The library reads the wired
|
|
9
|
+
// adapter inside `audit.emit` at write time — there is no init-time
|
|
10
|
+
// snapshot.
|
|
11
|
+
import { _setAuditChain } from "./audit";
|
|
12
|
+
/**
|
|
13
|
+
* Wire optional telemetry adapters. Currently the only knob is
|
|
14
|
+
* `audit.chain`. Idempotent: calling repeatedly with `audit.chain` set
|
|
15
|
+
* replaces the previously-wired adapter; calling with `audit` undefined
|
|
16
|
+
* leaves the existing wiring intact.
|
|
17
|
+
*/
|
|
18
|
+
export function configureTelemetry(opts) {
|
|
19
|
+
if (opts.audit !== undefined && "chain" in opts.audit) {
|
|
20
|
+
_setAuditChain(opts.audit.chain ?? null);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=configure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configure.js","sourceRoot":"","sources":["../src/configure.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,uEAAuE;AACvE,6DAA6D;AAC7D,EAAE;AACF,uEAAuE;AACvE,yEAAyE;AACzE,qEAAqE;AACrE,kEAAkE;AAClE,oEAAoE;AACpE,YAAY;AAEZ,OAAO,EAAE,cAAc,EAA0B,MAAM,SAAS,CAAC;AA4BjE;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAA+B;IAChE,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,OAAO,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACtD,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;IAC3C,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { NodiiContext } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Returns the active `NodiiContext`, or null if no middleware adapter
|
|
4
|
+
* has set one for this async scope (cron-flow before the
|
|
5
|
+
* `system-process` adapter runs; raw scripts; tests without harness).
|
|
6
|
+
*/
|
|
7
|
+
export declare function getContext(): NodiiContext | null;
|
|
8
|
+
/**
|
|
9
|
+
* Like `getContext()` but throws if no context is active. Use this in
|
|
10
|
+
* code that is allowed to assume an adapter ran — typically inside a
|
|
11
|
+
* request handler.
|
|
12
|
+
*/
|
|
13
|
+
export declare function requireContext(): NodiiContext;
|
|
14
|
+
/**
|
|
15
|
+
* Run `fn` inside a child scope where `ctx` is the active context. Per
|
|
16
|
+
* D41: never mutates the parent scope — child finishes and the parent
|
|
17
|
+
* scope's context is unaffected.
|
|
18
|
+
*/
|
|
19
|
+
export declare function withContext<T>(ctx: NodiiContext, fn: () => T): T;
|
|
20
|
+
/**
|
|
21
|
+
* SDK-internal use only. The middleware adapters (§ 5.9) call this on
|
|
22
|
+
* request entry to set the active context. Service code MUST NOT call
|
|
23
|
+
* this directly — use `withContext()` for child scopes only. Not part
|
|
24
|
+
* of the public barrel; subpath adapters import it via the package
|
|
25
|
+
* internal path.
|
|
26
|
+
*/
|
|
27
|
+
export declare function setContextForCurrentScope(ctx: NodiiContext): void;
|
|
28
|
+
/**
|
|
29
|
+
* Update the active context's `intent_id` only. Used by the intent
|
|
30
|
+
* lifecycle (§ 5.6) when `intent.begin()` / `intent.complete()` /
|
|
31
|
+
* `intent.abandon()` activate or deactivate an intent. Returns the new
|
|
32
|
+
* context so callers can verify; throws if no context is active (intent
|
|
33
|
+
* lifecycle must run inside an established `NodiiContext`).
|
|
34
|
+
*
|
|
35
|
+
* This is the ONLY mutation API per D41. It exists because intent
|
|
36
|
+
* activation is a per-scope assignment that the intent lifecycle owns,
|
|
37
|
+
* not a service-code mutation.
|
|
38
|
+
*/
|
|
39
|
+
export declare function _setIntentIdInCurrentScope(intentId: string | null): NodiiContext;
|
|
40
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAI5C;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,YAAY,GAAG,IAAI,CAEhD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,YAAY,CAQ7C;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,GAAG,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAEhE;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAEjE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,MAAM,GAAG,IAAI,GACtB,YAAY,CAUd"}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Context propagation per D40 / D41.
|
|
2
|
+
//
|
|
3
|
+
// SDK middleware (§ 5.9 of the feature_doc) is the ONLY writer of
|
|
4
|
+
// `NodiiContext`. Service code reads via `getContext()`; child scopes
|
|
5
|
+
// only via `withContext({...}, fn)`. There is no public mutate-in-place
|
|
6
|
+
// API — that's an invariant of D41.
|
|
7
|
+
//
|
|
8
|
+
// Implementation: Node's `AsyncLocalStorage`, which works under both
|
|
9
|
+
// Node and Bun. The store holds a `NodiiContext` directly.
|
|
10
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
11
|
+
const als = new AsyncLocalStorage();
|
|
12
|
+
/**
|
|
13
|
+
* Returns the active `NodiiContext`, or null if no middleware adapter
|
|
14
|
+
* has set one for this async scope (cron-flow before the
|
|
15
|
+
* `system-process` adapter runs; raw scripts; tests without harness).
|
|
16
|
+
*/
|
|
17
|
+
export function getContext() {
|
|
18
|
+
return als.getStore() ?? null;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Like `getContext()` but throws if no context is active. Use this in
|
|
22
|
+
* code that is allowed to assume an adapter ran — typically inside a
|
|
23
|
+
* request handler.
|
|
24
|
+
*/
|
|
25
|
+
export function requireContext() {
|
|
26
|
+
const ctx = als.getStore();
|
|
27
|
+
if (!ctx) {
|
|
28
|
+
throw new Error("No NodiiContext active. SDK middleware (or `withContext`) must run before this code path.");
|
|
29
|
+
}
|
|
30
|
+
return ctx;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Run `fn` inside a child scope where `ctx` is the active context. Per
|
|
34
|
+
* D41: never mutates the parent scope — child finishes and the parent
|
|
35
|
+
* scope's context is unaffected.
|
|
36
|
+
*/
|
|
37
|
+
export function withContext(ctx, fn) {
|
|
38
|
+
return als.run(ctx, fn);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* SDK-internal use only. The middleware adapters (§ 5.9) call this on
|
|
42
|
+
* request entry to set the active context. Service code MUST NOT call
|
|
43
|
+
* this directly — use `withContext()` for child scopes only. Not part
|
|
44
|
+
* of the public barrel; subpath adapters import it via the package
|
|
45
|
+
* internal path.
|
|
46
|
+
*/
|
|
47
|
+
export function setContextForCurrentScope(ctx) {
|
|
48
|
+
als.enterWith(ctx);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Update the active context's `intent_id` only. Used by the intent
|
|
52
|
+
* lifecycle (§ 5.6) when `intent.begin()` / `intent.complete()` /
|
|
53
|
+
* `intent.abandon()` activate or deactivate an intent. Returns the new
|
|
54
|
+
* context so callers can verify; throws if no context is active (intent
|
|
55
|
+
* lifecycle must run inside an established `NodiiContext`).
|
|
56
|
+
*
|
|
57
|
+
* This is the ONLY mutation API per D41. It exists because intent
|
|
58
|
+
* activation is a per-scope assignment that the intent lifecycle owns,
|
|
59
|
+
* not a service-code mutation.
|
|
60
|
+
*/
|
|
61
|
+
export function _setIntentIdInCurrentScope(intentId) {
|
|
62
|
+
const cur = als.getStore();
|
|
63
|
+
if (!cur) {
|
|
64
|
+
throw new Error("Cannot set intent_id: no NodiiContext active. SDK middleware must run before `intent.begin()`.");
|
|
65
|
+
}
|
|
66
|
+
const next = { ...cur, intent_id: intentId };
|
|
67
|
+
als.enterWith(next);
|
|
68
|
+
return next;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,qCAAqC;AACrC,EAAE;AACF,kEAAkE;AAClE,sEAAsE;AACtE,wEAAwE;AACxE,oCAAoC;AACpC,EAAE;AACF,qEAAqE;AACrE,2DAA2D;AAE3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAGrD,MAAM,GAAG,GAAG,IAAI,iBAAiB,EAAgB,CAAC;AAElD;;;;GAIG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO,GAAG,CAAC,QAAQ,EAAE,IAAI,IAAI,CAAC;AAChC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC;IAC3B,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,2FAA2F,CAC5F,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAI,GAAiB,EAAE,EAAW;IAC3D,OAAO,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CAAC,GAAiB;IACzD,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;AACrB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,0BAA0B,CACxC,QAAuB;IAEvB,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC;IAC3B,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,gGAAgG,CACjG,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAiB,EAAE,GAAG,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;IAC3D,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACpB,OAAO,IAAI,CAAC;AACd,CAAC"}
|