@bounded-sh/observe 0.1.0 → 0.1.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/README.md +32 -3
- package/dist/edge.d.ts +44 -0
- package/dist/edge.js +107 -0
- package/dist/manifest.js +36 -1
- package/dist/pipeline.js +7 -0
- package/dist/register.js +10 -0
- package/dist/types.d.ts +10 -0
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -116,9 +116,38 @@ microseconds (measured in `test/perf.test.ts`; U2 target <1ms p99).
|
|
|
116
116
|
- **Node 18 / 20 / 22 server apps** (fetch + http/https interception).
|
|
117
117
|
- **Vercel / Next.js Node runtime**: supported (call `init()` in
|
|
118
118
|
`instrumentation.ts` or the server entry).
|
|
119
|
-
- **Edge runtimes** (Vercel Edge,
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
- **Edge runtimes** (Cloudflare Workers, Vercel Edge, Deno Deploy): the root
|
|
120
|
+
entry cannot load there (it needs `node:async_hooks`, `node:crypto`, and
|
|
121
|
+
http/https patching) — use the **`@bounded-sh/observe/edge`** subpath
|
|
122
|
+
instead (below).
|
|
123
|
+
|
|
124
|
+
## Edge emitter (`@bounded-sh/observe/edge`)
|
|
125
|
+
|
|
126
|
+
For code that already sits at a chokepoint (a Worker proxy, an API gateway)
|
|
127
|
+
and wants to REPORT events rather than intercept egress. No queue, no timers,
|
|
128
|
+
no patching — one fire-and-forget envelope-v2 POST per call:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import { emitEvent } from "@bounded-sh/observe/edge";
|
|
132
|
+
|
|
133
|
+
emitEvent(
|
|
134
|
+
{ ingestUrl: env.BOUNDED_INGEST_URL, sensorToken: env.BOUNDED_SENSOR_TOKEN },
|
|
135
|
+
{
|
|
136
|
+
class: "action",
|
|
137
|
+
actor: { id: tenantId, kind: "service", grade: "attested" },
|
|
138
|
+
dest: { host: "api.anthropic.com", pathTemplate: "/v1/messages", method: "POST" },
|
|
139
|
+
status: 200, dur_ms: 0, bytes: { i: 0, o: 0 },
|
|
140
|
+
rec: { rail: "llm-gateway", action: "acme.tenant.aiRun", registryVersion: "acme-proxy",
|
|
141
|
+
fields: { actualCents, "usage.input_tokens": inTok, "usage.output_tokens": outTok } },
|
|
142
|
+
},
|
|
143
|
+
ctx, // optional Workers ExecutionContext — the POST rides ctx.waitUntil
|
|
144
|
+
);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Posture: NO-OP unless both `ingestUrl` and `sensorToken` are present (delete
|
|
148
|
+
the secret = kill switch); `postEvent` never rejects; the POST is bounded by
|
|
149
|
+
`timeoutMs` (default 2000 ms); `org`/`sensor` are never sent — the ingest
|
|
150
|
+
stamps both server-authoritatively from the sensor key.
|
|
122
151
|
|
|
123
152
|
Notes: `http/https` path records duration to response *headers* and takes
|
|
124
153
|
`bytes.o` from `Content-Length` (0 when chunked); response-field recognition
|
package/dist/edge.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { EVENT_SCHEMA_VERSION, MAX_BATCH_EVENTS, type ObserveEventV1 } from "./envelope";
|
|
2
|
+
export { EVENT_SCHEMA_VERSION, MAX_BATCH_EVENTS };
|
|
3
|
+
export type { ObserveEventV1, EventClass, EventActor, EventDest, EventBytes, EventRec, RecFields, RecFieldValue, } from "./envelope";
|
|
4
|
+
/** Everything the emitter needs; a missing url/token makes it a NO-OP. */
|
|
5
|
+
export interface EdgeEmitterConfig {
|
|
6
|
+
/** Observe-ingest base URL (POST <ingestUrl>/v1/events). */
|
|
7
|
+
ingestUrl?: string | null;
|
|
8
|
+
/** Org sensor bearer token (obs1.<keyId>.<sig>). */
|
|
9
|
+
sensorToken?: string | null;
|
|
10
|
+
/** Upper bound on one emission POST (default EDGE_EMIT_TIMEOUT_MS). Short
|
|
11
|
+
* by design: a slow ingest must never pin a waitUntil task for long. */
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
/** Custom fetch (tests, service bindings). Defaults to global fetch. */
|
|
14
|
+
fetchImpl?: typeof fetch;
|
|
15
|
+
}
|
|
16
|
+
/** Default upper bound on one emission POST. */
|
|
17
|
+
export declare const EDGE_EMIT_TIMEOUT_MS = 2000;
|
|
18
|
+
/** What callers build: the frozen v2 envelope minus the pieces this module
|
|
19
|
+
* stamps (`v`, `ts` defaulted) or that MUST stay absent (`org`/`sensor` —
|
|
20
|
+
* server-authoritative from the key; `scrubbed` — server-set). */
|
|
21
|
+
export type EdgeObserveEvent = Omit<ObserveEventV1, "v" | "org" | "sensor" | "ts" | "scrubbed"> & {
|
|
22
|
+
ts?: number;
|
|
23
|
+
};
|
|
24
|
+
/** The bytes that go on the wire: `v` stamped, `ts` defaulted, org/sensor absent. */
|
|
25
|
+
export type WireObserveEvent = Omit<ObserveEventV1, "org" | "sensor" | "scrubbed">;
|
|
26
|
+
/** True iff the config is complete (the emitter will actually fire). */
|
|
27
|
+
export declare function edgeEmitConfigured(cfg: EdgeEmitterConfig | null | undefined): boolean;
|
|
28
|
+
/** Stamp `v` and default `ts` on a caller-built event. Pure. */
|
|
29
|
+
export declare function buildEvent(event: EdgeObserveEvent): WireObserveEvent;
|
|
30
|
+
/**
|
|
31
|
+
* POST one event (or a small batch, capped at MAX_BATCH_EVENTS) to
|
|
32
|
+
* `<ingestUrl>/v1/events`. Resolves `true` iff ingest 2xx'd; NEVER rejects.
|
|
33
|
+
* Await it inside your own `ctx.waitUntil` task when composing (e.g.
|
|
34
|
+
* lookup-then-emit); use `emitEvent` for the common one-shot case.
|
|
35
|
+
*/
|
|
36
|
+
export declare function postEvent(cfg: EdgeEmitterConfig | null | undefined, event: EdgeObserveEvent | EdgeObserveEvent[]): Promise<boolean>;
|
|
37
|
+
/**
|
|
38
|
+
* Fire ONE event (or batch), fully detached: no-op on missing config, rides
|
|
39
|
+
* `ctx.waitUntil` when a Workers execution context is given (floated
|
|
40
|
+
* otherwise), and guaranteed to never throw into the request path.
|
|
41
|
+
*/
|
|
42
|
+
export declare function emitEvent(cfg: EdgeEmitterConfig | null | undefined, event: EdgeObserveEvent | EdgeObserveEvent[], ctx?: {
|
|
43
|
+
waitUntil(p: Promise<unknown>): void;
|
|
44
|
+
}): void;
|
package/dist/edge.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// edge.ts -- Worker/edge-native emitter ("@bounded-sh/observe/edge").
|
|
4
|
+
//
|
|
5
|
+
// The package root is a LONG-LIVED-PROCESS shim: it patches fetch/http, runs
|
|
6
|
+
// a batch queue and flush timers, and hard-depends on Node builtins
|
|
7
|
+
// (node:async_hooks, node:crypto). Edge runtimes (Cloudflare Workers, Deno
|
|
8
|
+
// Deploy, Vercel Edge) cannot import it. This entry is the opposite posture:
|
|
9
|
+
// ONE fire-and-forget envelope-v2 event per call, no queue, no timers, no
|
|
10
|
+
// interception, no imports beyond the frozen ./envelope mirror — safe to
|
|
11
|
+
// bundle anywhere `fetch` and `AbortSignal.timeout` exist.
|
|
12
|
+
//
|
|
13
|
+
// Contract (same fail-open posture as the platform emitter in
|
|
14
|
+
// observe-shared/src/emitter.ts and the shim reporter):
|
|
15
|
+
// * NO-OP unless both {ingestUrl, sensorToken} are present — a consumer
|
|
16
|
+
// without config costs nothing and emits nothing. Deleting the sensor
|
|
17
|
+
// token secret is the kill switch.
|
|
18
|
+
// * `org` and `sensor` are NEVER sent: the ingest stamps both
|
|
19
|
+
// server-authoritatively from the sensor key (a mismatched body `org`
|
|
20
|
+
// would be rejected; omitting it is the one-correct-way).
|
|
21
|
+
// * postEvent NEVER rejects; every failure (missing config, network error,
|
|
22
|
+
// timeout, non-2xx) resolves `false`. Observing is reporting, not
|
|
23
|
+
// protection — an ingest outage must cost the caller nothing beyond
|
|
24
|
+
// `timeoutMs`.
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.EDGE_EMIT_TIMEOUT_MS = exports.MAX_BATCH_EVENTS = exports.EVENT_SCHEMA_VERSION = void 0;
|
|
28
|
+
exports.edgeEmitConfigured = edgeEmitConfigured;
|
|
29
|
+
exports.buildEvent = buildEvent;
|
|
30
|
+
exports.postEvent = postEvent;
|
|
31
|
+
exports.emitEvent = emitEvent;
|
|
32
|
+
const envelope_1 = require("./envelope");
|
|
33
|
+
Object.defineProperty(exports, "EVENT_SCHEMA_VERSION", { enumerable: true, get: function () { return envelope_1.EVENT_SCHEMA_VERSION; } });
|
|
34
|
+
Object.defineProperty(exports, "MAX_BATCH_EVENTS", { enumerable: true, get: function () { return envelope_1.MAX_BATCH_EVENTS; } });
|
|
35
|
+
/** Default upper bound on one emission POST. */
|
|
36
|
+
exports.EDGE_EMIT_TIMEOUT_MS = 2_000;
|
|
37
|
+
/** True iff the config is complete (the emitter will actually fire). */
|
|
38
|
+
function edgeEmitConfigured(cfg) {
|
|
39
|
+
return !!(cfg && cfg.ingestUrl && cfg.sensorToken);
|
|
40
|
+
}
|
|
41
|
+
/** Stamp `v` and default `ts` on a caller-built event. Pure. */
|
|
42
|
+
function buildEvent(event) {
|
|
43
|
+
const { ts, ...rest } = event;
|
|
44
|
+
return {
|
|
45
|
+
v: envelope_1.EVENT_SCHEMA_VERSION,
|
|
46
|
+
ts: typeof ts === "number" ? ts : Date.now(),
|
|
47
|
+
...rest,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* POST one event (or a small batch, capped at MAX_BATCH_EVENTS) to
|
|
52
|
+
* `<ingestUrl>/v1/events`. Resolves `true` iff ingest 2xx'd; NEVER rejects.
|
|
53
|
+
* Await it inside your own `ctx.waitUntil` task when composing (e.g.
|
|
54
|
+
* lookup-then-emit); use `emitEvent` for the common one-shot case.
|
|
55
|
+
*/
|
|
56
|
+
async function postEvent(cfg, event) {
|
|
57
|
+
try {
|
|
58
|
+
if (!edgeEmitConfigured(cfg))
|
|
59
|
+
return false; // NO-OP (inert-by-flag)
|
|
60
|
+
const list = Array.isArray(event) ? event.slice(0, envelope_1.MAX_BATCH_EVENTS) : [event];
|
|
61
|
+
if (list.length === 0)
|
|
62
|
+
return false;
|
|
63
|
+
const events = list.map(buildEvent);
|
|
64
|
+
const url = String(cfg.ingestUrl).replace(/\/$/, "") + "/v1/events";
|
|
65
|
+
const doFetch = cfg.fetchImpl ?? fetch;
|
|
66
|
+
const timeoutMs = typeof cfg.timeoutMs === "number" && cfg.timeoutMs > 0 ? cfg.timeoutMs : exports.EDGE_EMIT_TIMEOUT_MS;
|
|
67
|
+
const res = await doFetch(url, {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: {
|
|
70
|
+
"content-type": "application/json",
|
|
71
|
+
authorization: `Bearer ${cfg.sensorToken}`,
|
|
72
|
+
},
|
|
73
|
+
body: JSON.stringify({ events }),
|
|
74
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
75
|
+
});
|
|
76
|
+
return res.ok;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false; // fire-and-forget: an emit failure is invisible to the caller
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Fire ONE event (or batch), fully detached: no-op on missing config, rides
|
|
84
|
+
* `ctx.waitUntil` when a Workers execution context is given (floated
|
|
85
|
+
* otherwise), and guaranteed to never throw into the request path.
|
|
86
|
+
*/
|
|
87
|
+
function emitEvent(cfg, event, ctx) {
|
|
88
|
+
try {
|
|
89
|
+
if (!edgeEmitConfigured(cfg))
|
|
90
|
+
return; // NO-OP (inert-by-flag)
|
|
91
|
+
const task = postEvent(cfg, event);
|
|
92
|
+
if (ctx && typeof ctx.waitUntil === "function") {
|
|
93
|
+
try {
|
|
94
|
+
ctx.waitUntil(task);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
void task;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
void task;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
/* can never throw into the hot path */
|
|
106
|
+
}
|
|
107
|
+
}
|
package/dist/manifest.js
CHANGED
|
@@ -95,10 +95,43 @@ const CRYPTO_RECOGNIZERS = [
|
|
|
95
95
|
...rpcRecognizers("solana", SOLANA_RPC_HOSTS, SOLANA_RPC_METHODS),
|
|
96
96
|
...rpcRecognizers("evm", EVM_RPC_HOSTS, EVM_RPC_METHODS),
|
|
97
97
|
];
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// Wallet-watch recognizers (rails: solana, evm) — the "Crypto wallets" data
|
|
100
|
+
// source. These match a SYNTHETIC destination host "wallet-watch.bounded.sh"
|
|
101
|
+
// that no real egress can ever reach (the same pattern the data plane uses for
|
|
102
|
+
// data.bounded.sh): events are minted SERVER-SIDE by org-host's wallet-watch
|
|
103
|
+
// poller (bounded-org-host/src/wallet-watch.ts) from public onchain history,
|
|
104
|
+
// with `rec` PRE-STAMPED to these action/rail pairs. The shim never emits these
|
|
105
|
+
// (the host is not a real API), so listing them here only makes ingest's
|
|
106
|
+
// server-side re-recognition table AGREE with the poller's stamp (no
|
|
107
|
+
// serverMismatch). observe-only, like the rest of the crypto rails.
|
|
108
|
+
//
|
|
109
|
+
// The `responseFields` document the SAFE scalars the poller carries in
|
|
110
|
+
// rec.fields (all public onchain data). NOTE: the tracked address rides
|
|
111
|
+
// rec.fields.wallet (NOT `address` — the L2 key denylist hard-denies /address/)
|
|
112
|
+
// and, raw, on the envelope's onBehalfOf attribution slot.
|
|
113
|
+
const WALLET_WATCH_RECOGNIZERS = [
|
|
114
|
+
{
|
|
115
|
+
host: "wallet-watch.bounded.sh",
|
|
116
|
+
method: "POST",
|
|
117
|
+
path: "/solana/tx",
|
|
118
|
+
rail: "solana",
|
|
119
|
+
action: "solana.wallet.tx",
|
|
120
|
+
responseFields: ["wallet", "chain", "direction", "amountLamports", "txSig", "counterparty"],
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
host: "wallet-watch.bounded.sh",
|
|
124
|
+
method: "POST",
|
|
125
|
+
path: "/evm/tx",
|
|
126
|
+
rail: "evm",
|
|
127
|
+
action: "evm.wallet.tx",
|
|
128
|
+
responseFields: ["wallet", "chain", "direction", "valueWei", "txHash", "counterparty"],
|
|
129
|
+
},
|
|
130
|
+
];
|
|
98
131
|
/** Built-in default manifest: metadata-only + curated safe-field recognizers.
|
|
99
132
|
* SAFE fields only: amounts (cents), opaque IDs, model names, token counts. */
|
|
100
133
|
exports.BUILTIN_MANIFEST = {
|
|
101
|
-
version: "builtin-0.
|
|
134
|
+
version: "builtin-0.4.0",
|
|
102
135
|
recognizers: [
|
|
103
136
|
// --- Stripe (rail: stripe). Amounts are already in cents. -------------
|
|
104
137
|
{
|
|
@@ -590,6 +623,8 @@ exports.BUILTIN_MANIFEST = {
|
|
|
590
623
|
},
|
|
591
624
|
// --- Crypto RPC (rails: solana, evm) — rpcMethod-matched, observe-only. --
|
|
592
625
|
...CRYPTO_RECOGNIZERS,
|
|
626
|
+
// --- Wallet-watch (rails: solana, evm) — synthetic dest, server-emitted. -
|
|
627
|
+
...WALLET_WATCH_RECOGNIZERS,
|
|
593
628
|
],
|
|
594
629
|
counters: [],
|
|
595
630
|
mutes: [],
|
package/dist/pipeline.js
CHANGED
|
@@ -169,6 +169,13 @@ class Pipeline {
|
|
|
169
169
|
this.reporter.push({ wire, fields });
|
|
170
170
|
return;
|
|
171
171
|
}
|
|
172
|
+
// Bounded-only capture (the app-dev / skill default, "bounded stuff only"):
|
|
173
|
+
// send ONLY recognized-by-fixtures events + explicit counter routes. Drop
|
|
174
|
+
// every UNrecognized route here — no shape sample, no heuristic counter — so
|
|
175
|
+
// nothing about traffic Bounded doesn't already recognize ever leaves the
|
|
176
|
+
// process. BOUNDED_CAPTURE_SCOPE=all re-enables discovery (shapes/counters).
|
|
177
|
+
if (state_1.state.config?.captureScope === "recognized")
|
|
178
|
+
return;
|
|
172
179
|
// Unrecognized: heuristic counter tally for GETs (exact from call #1).
|
|
173
180
|
if (method === "GET") {
|
|
174
181
|
this.counters.add(route, raw.ts, raw.status, raw.durMs, raw.bytesIn, raw.bytesOut, true);
|
package/dist/register.js
CHANGED
|
@@ -32,6 +32,9 @@
|
|
|
32
32
|
// BOUNDED_VERDICT_URL https://<org>.bounded.sh/api/verdict (arms escort)
|
|
33
33
|
// BOUNDED_SENSOR_TOKEN obs1.<keyId>.<sig> (verdict auth + report auth)
|
|
34
34
|
// BOUNDED_ORG org slug (manifest polling only)
|
|
35
|
+
// BOUNDED_CAPTURE_SCOPE bounded|recognized => send only recognized events
|
|
36
|
+
// + counters (app-dev default); all => full capture
|
|
37
|
+
// incl. unknown-route shape discovery (unset => all)
|
|
35
38
|
// BOUNDED_INGEST_BASE default prod observe-ingest
|
|
36
39
|
// BOUNDED_COLLECTION escort collection (default stripeRefunds)
|
|
37
40
|
// BOUNDED_FAIL_MODE closed|open (default closed; SPEC U18) [alias BOUNDED_FAILMODE]
|
|
@@ -105,11 +108,18 @@ function bootstrapFromEnv(env = process.env) {
|
|
|
105
108
|
const flushIntervalMs = Number.isFinite(flushMsRaw) && flushMsRaw > 0 ? flushMsRaw : undefined;
|
|
106
109
|
const timeoutRaw = Number(env.BOUNDED_VERDICT_TIMEOUT_MS);
|
|
107
110
|
const timeoutMs = Number.isFinite(timeoutRaw) && timeoutRaw > 0 ? Math.min(timeoutRaw, 30_000) : undefined;
|
|
111
|
+
// Capture scope: "bounded"/"recognized" => send only recognized-by-fixtures
|
|
112
|
+
// events + counter routes (the app-dev / skill default). Anything else (or
|
|
113
|
+
// unset) => "all" (full capture incl. unknown-route shape discovery).
|
|
114
|
+
const scopeRaw = String(env.BOUNDED_CAPTURE_SCOPE || "").toLowerCase();
|
|
115
|
+
const captureScope = scopeRaw === "recognized" || scopeRaw === "bounded" ? "recognized"
|
|
116
|
+
: scopeRaw === "all" ? "all" : undefined;
|
|
108
117
|
const cfg = {
|
|
109
118
|
ingestBase: env.BOUNDED_INGEST_BASE || DEFAULT_INGEST,
|
|
110
119
|
token: env.BOUNDED_SENSOR_TOKEN || "obs1.unset.unset",
|
|
111
120
|
org: env.BOUNDED_ORG || undefined,
|
|
112
121
|
debug: env.BOUNDED_DEBUG === "1",
|
|
122
|
+
...(captureScope ? { captureScope } : {}),
|
|
113
123
|
...(flushIntervalMs ? { flushIntervalMs } : {}),
|
|
114
124
|
};
|
|
115
125
|
if (verdictUrl) {
|
package/dist/types.d.ts
CHANGED
|
@@ -83,6 +83,16 @@ export interface ObserveConfig {
|
|
|
83
83
|
counterPromoteThreshold?: number;
|
|
84
84
|
/** Max new shape samples emitted per minute (default 30). */
|
|
85
85
|
shapesPerMinute?: number;
|
|
86
|
+
/**
|
|
87
|
+
* What to send. "all" (default): recognized events + counters + UNrecognized
|
|
88
|
+
* shape samples (needed for the classification loop). "recognized" (the
|
|
89
|
+
* app-dev / skill default, "bounded stuff only"): recognized-by-fixtures
|
|
90
|
+
* events + explicit counter routes ONLY — unknown external routes are neither
|
|
91
|
+
* sampled nor counted, so nothing about traffic Bounded doesn't already
|
|
92
|
+
* recognize ever leaves the process. Turn on full capture with
|
|
93
|
+
* BOUNDED_CAPTURE_SCOPE=all when you want to discover/classify new routes.
|
|
94
|
+
*/
|
|
95
|
+
captureScope?: "recognized" | "all";
|
|
86
96
|
/** Manifest override (tests/dev). Skips the built-in + polling when set. */
|
|
87
97
|
manifest?: unknown;
|
|
88
98
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bounded-sh/observe",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Bounded observe shim for Node: intercepts fetch + http/https egress, attributes actors, reports metadata-only events to the Bounded observe ingest. Never breaks the app.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -19,6 +19,11 @@
|
|
|
19
19
|
"types": "./dist/register.d.ts",
|
|
20
20
|
"require": "./dist/register.js",
|
|
21
21
|
"import": "./dist/register.js"
|
|
22
|
+
},
|
|
23
|
+
"./edge": {
|
|
24
|
+
"types": "./dist/edge.d.ts",
|
|
25
|
+
"require": "./dist/edge.js",
|
|
26
|
+
"import": "./dist/edge.js"
|
|
22
27
|
}
|
|
23
28
|
},
|
|
24
29
|
"files": [
|