parseman 0.43.0 → 0.45.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/dist/analysis/diagnose.d.ts +118 -0
- package/dist/analysis/diagnose.d.ts.map +1 -0
- package/dist/analysis/gating.d.ts +9 -9
- package/dist/analysis/gating.d.ts.map +1 -1
- package/dist/analysis/grammar.d.ts.map +1 -1
- package/dist/combinators/attempt.d.ts.map +1 -1
- package/dist/combinators/choice.d.ts.map +1 -1
- package/dist/combinators/dispatch.d.ts +19 -1
- package/dist/combinators/dispatch.d.ts.map +1 -1
- package/dist/combinators/expect.d.ts.map +1 -1
- package/dist/combinators/first-set.d.ts.map +1 -1
- package/dist/combinators/grammar.d.ts +7 -0
- package/dist/combinators/grammar.d.ts.map +1 -1
- package/dist/combinators/map.d.ts +11 -0
- package/dist/combinators/map.d.ts.map +1 -1
- package/dist/combinators/node.d.ts +19 -0
- package/dist/combinators/node.d.ts.map +1 -1
- package/dist/combinators/token.d.ts +3 -2
- package/dist/combinators/token.d.ts.map +1 -1
- package/dist/combinators/trivia-skip.d.ts +1 -0
- package/dist/combinators/trivia-skip.d.ts.map +1 -1
- package/dist/compiler/build-arity.d.ts +28 -0
- package/dist/compiler/build-arity.d.ts.map +1 -1
- package/dist/compiler/codegen.d.ts +80 -60
- package/dist/compiler/codegen.d.ts.map +1 -1
- package/dist/compiler/degradation.d.ts +123 -0
- package/dist/compiler/degradation.d.ts.map +1 -0
- package/dist/compiler/fields.d.ts.map +1 -1
- package/dist/compiler/grammar-coverage-ids.d.ts.map +1 -1
- package/dist/compiler/inline-build.d.ts.map +1 -1
- package/dist/compiler/ir-serialize.d.ts.map +1 -1
- package/dist/compiler/linker.d.ts +1 -1
- package/dist/compiler/linker.d.ts.map +1 -1
- package/dist/compiler/scannable-run.d.ts +1 -1
- package/dist/compiler/scannable-run.d.ts.map +1 -1
- package/dist/compiler/trivia-fast-path.d.ts +4 -3
- package/dist/compiler/trivia-fast-path.d.ts.map +1 -1
- package/dist/coverage.d.ts +13 -0
- package/dist/coverage.d.ts.map +1 -1
- package/dist/cst/reflection.d.ts.map +1 -1
- package/dist/cst/trivia-entries.d.ts +15 -9
- package/dist/cst/trivia-entries.d.ts.map +1 -1
- package/dist/cst/trivia-kinds.d.ts +15 -11
- package/dist/cst/trivia-kinds.d.ts.map +1 -1
- package/dist/functional/run.d.ts +22 -16
- package/dist/functional/run.d.ts.map +1 -1
- package/dist/index.cjs +2131 -1594
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +6 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2131 -1594
- package/dist/index.js.map +4 -4
- package/dist/language-service/index.cjs +201 -132
- package/dist/language-service/index.cjs.map +4 -4
- package/dist/language-service/index.js +201 -132
- package/dist/language-service/index.js.map +4 -4
- package/dist/oracle/digest.d.ts +132 -6
- package/dist/oracle/digest.d.ts.map +1 -1
- package/dist/oracle/index.cjs +136 -305
- package/dist/oracle/index.cjs.map +4 -4
- package/dist/oracle/index.d.ts +43 -31
- package/dist/oracle/index.d.ts.map +1 -1
- package/dist/oracle/index.js +136 -305
- package/dist/oracle/index.js.map +4 -4
- package/dist/plugin/evaluator.d.ts +3 -0
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/index.cjs +2333 -1721
- package/dist/plugin/index.cjs.map +4 -4
- package/dist/plugin/index.d.ts +18 -0
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +2333 -1721
- package/dist/plugin/index.js.map +4 -4
- package/dist/plugin/reducer-resolver.d.ts +41 -0
- package/dist/plugin/reducer-resolver.d.ts.map +1 -0
- package/dist/run/index.cjs +185 -132
- package/dist/run/index.cjs.map +3 -3
- package/dist/run/index.d.ts +1 -1
- package/dist/run/index.d.ts.map +1 -1
- package/dist/run/index.js +185 -132
- package/dist/run/index.js.map +3 -3
- package/dist/spec/index.cjs +1637 -1322
- package/dist/spec/index.cjs.map +4 -4
- package/dist/spec/index.js +1637 -1322
- package/dist/spec/index.js.map +4 -4
- package/dist/spec/model.d.ts.map +1 -1
- package/dist/types.d.ts +31 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +7 -3
- package/dist/combinators/guard.d.ts +0 -8
- package/dist/combinators/guard.d.ts.map +0 -1
- package/dist/cst/offset-model.d.ts +0 -119
- package/dist/cst/offset-model.d.ts.map +0 -1
- package/dist/oracle/corpus.d.ts +0 -32
- package/dist/oracle/corpus.d.ts.map +0 -1
- package/dist/oracle/identity.d.ts +0 -117
- package/dist/oracle/identity.d.ts.map +0 -1
package/dist/oracle/digest.d.ts
CHANGED
|
@@ -5,17 +5,143 @@
|
|
|
5
5
|
* comparing two reports should not have to diff hex to see that the format moved.
|
|
6
6
|
*/
|
|
7
7
|
export declare const DIGEST_FORMAT = 1;
|
|
8
|
+
/**
|
|
9
|
+
* Anything that absorbs a stream of text. Node's `crypto.Hash` satisfies it, and
|
|
10
|
+
* so does a three-line test double.
|
|
11
|
+
*
|
|
12
|
+
* The CALLER supplies this, and therefore owns the algorithm and the result.
|
|
13
|
+
* Deciding what the canonical byte stream IS depends on parseman's node shapes
|
|
14
|
+
* and on nothing else; deciding that you wanted sha256-hex does not.
|
|
15
|
+
*/
|
|
16
|
+
export type DigestTarget = {
|
|
17
|
+
update(chunk: string): void;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Raised when a walk exceeds its visit budget.
|
|
21
|
+
*
|
|
22
|
+
* A distinct class, and deliberately NOT an ordinary `Error` a caller might
|
|
23
|
+
* lump in with a parse failure: this says the TOOL gave up, which is the
|
|
24
|
+
* opposite of a fact about the grammar.
|
|
25
|
+
*/
|
|
26
|
+
export declare class CanonicalBudgetError extends Error {
|
|
27
|
+
readonly name = "CanonicalBudgetError";
|
|
28
|
+
constructor(budget: number, projected?: number);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Default visit budget.
|
|
32
|
+
*
|
|
33
|
+
* A backstop against unbounded work, not a correctness feature. Any walk that
|
|
34
|
+
* finishes under budget writes exactly the bytes it always did, so this cannot
|
|
35
|
+
* move a recorded digest — which is the only reason it can be added to a shipped
|
|
36
|
+
* format at all.
|
|
37
|
+
*
|
|
38
|
+
* It is NOT a fix for the underlying asymmetry. The fix is to dedupe by node
|
|
39
|
+
* IDENTITY instead of by ancestor path, and that rewrites the byte stream for
|
|
40
|
+
* every value with any sharing in it: `{ left: shared, right: shared }` stops
|
|
41
|
+
* being two full writes and becomes a back-reference. That invalidates every
|
|
42
|
+
* digest, every committed baseline, and {@link DIGEST_FORMAT} itself. It is a
|
|
43
|
+
* format decision for the owner, not something to slip in behind a bug fix.
|
|
44
|
+
*
|
|
45
|
+
* ## The tempting middle road does not exist
|
|
46
|
+
*
|
|
47
|
+
* Stated because it looks obviously right and is obviously right about the wrong
|
|
48
|
+
* thing: memoise the canonical BYTES of a subtree by node identity, splice the
|
|
49
|
+
* cached bytes in at each of the two places, and you get the identical output
|
|
50
|
+
* with the work done once. It is sound — a subtree's bytes depend only on the
|
|
51
|
+
* subtree, except for a back-edge leaving it, which is exactly the case
|
|
52
|
+
* {@link projectVisits} declines — and it does not help.
|
|
53
|
+
*
|
|
54
|
+
* It does not help because the walk is ALREADY linear in its own output, and it
|
|
55
|
+
* is the OUTPUT that is exponential: a depth-`d` two-way unroll writes 33·2^d
|
|
56
|
+
* chars, measured, and every one of them has to reach the hash. Caching removes
|
|
57
|
+
* re-derivation, of which there is a constant factor's worth; it cannot remove
|
|
58
|
+
* bytes the format requires. At depth 40 that is 36 TB of canonical text — 7
|
|
59
|
+
* hours of sha256 at the 1.37 Gchar/s this machine manages, with traversal,
|
|
60
|
+
* allocation and splicing all free. Depth 40 has no answer to compute quickly.
|
|
61
|
+
* There is only refusing quickly, which is what the probe does.
|
|
62
|
+
*/
|
|
63
|
+
export declare const DEFAULT_MAX_VISITS = 100000000;
|
|
64
|
+
/** Options shared by every entry point that walks a value. */
|
|
65
|
+
export type CanonicalOptions = {
|
|
66
|
+
/**
|
|
67
|
+
* Maximum object visits before {@link CanonicalBudgetError}. Defaults to
|
|
68
|
+
* {@link DEFAULT_MAX_VISITS}. A walk that finishes under budget is byte-for-byte
|
|
69
|
+
* unaffected, so this can never change a digest.
|
|
70
|
+
*
|
|
71
|
+
* MUST be a non-negative safe integer. `NaN`, `Infinity`, a negative and a
|
|
72
|
+
* fractional value are all REJECTED rather than clamped — see
|
|
73
|
+
* {@link newState} for why a bad budget is a thrown error and not a shrug.
|
|
74
|
+
*/
|
|
75
|
+
maxVisits?: number | undefined;
|
|
76
|
+
};
|
|
8
77
|
/**
|
|
9
78
|
* The canonical token string for a value. Exported because a moved digest is
|
|
10
79
|
* only actionable if you can see WHAT moved: diff two canonical strings and the
|
|
11
80
|
* answer is a line, not a hex mismatch.
|
|
81
|
+
*
|
|
82
|
+
* This MATERIALISES the projection, so it is bounded by the maximum JS string
|
|
83
|
+
* length and by available memory. Use it to explain a move you already know
|
|
84
|
+
* about; take the digest itself with {@link digestInto}, which is bounded by
|
|
85
|
+
* neither.
|
|
86
|
+
*/
|
|
87
|
+
export declare function canonicalize(value: unknown, options?: CanonicalOptions): string;
|
|
88
|
+
/**
|
|
89
|
+
* Stream a value's canonical projection into a caller-owned hash.
|
|
90
|
+
*
|
|
91
|
+
* This is the primitive: deterministic serialization of ONE parse result, which
|
|
92
|
+
* is the part only parseman can do — it is parseman's node shapes that decide
|
|
93
|
+
* which distinctions are semantically meaningful. The caller brings the hash and
|
|
94
|
+
* keeps the result, so the algorithm, the encoding and the digest width are all
|
|
95
|
+
* theirs.
|
|
96
|
+
*
|
|
97
|
+
* ```ts
|
|
98
|
+
* const sha = createHash('sha256')
|
|
99
|
+
* digestInto(sha, tree)
|
|
100
|
+
* const digest = sha.digest('hex')
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* `prefix` is written ahead of the first token with NO separator, for callers
|
|
104
|
+
* that discriminate one digest space from another (parseman's own oracle writes
|
|
105
|
+
* `OK:` or `ERR:`). Pass `''` when there is nothing to discriminate.
|
|
106
|
+
*
|
|
107
|
+
* Equivalent to `hash(prefix + canonicalize(value))`, and preferable in every
|
|
108
|
+
* case where you do not need to READ the projection.
|
|
109
|
+
*
|
|
110
|
+
* ## Two sharp edges, stated because the target is YOURS
|
|
111
|
+
*
|
|
112
|
+
* **A throw leaves the target written-to.** The token stream is pushed at
|
|
113
|
+
* `target` as the walk proceeds, so a {@link CanonicalBudgetError}, a throwing
|
|
114
|
+
* getter, or any other failure mid-walk leaves an ARBITRARY PREFIX of the
|
|
115
|
+
* projection already absorbed. The hash object is then polluted: its digest is
|
|
116
|
+
* neither the value's nor anything else's, and it is not recoverable — a
|
|
117
|
+
* `crypto.Hash` cannot be rewound. On any throw, DISCARD the target and start a
|
|
118
|
+
* fresh one. {@link digestValue} does exactly that by owning its hash for a
|
|
119
|
+
* single call.
|
|
120
|
+
*
|
|
121
|
+
* **Two calls against one target concatenate with NO delimiter.** There is no
|
|
122
|
+
* record separator between calls: `digestInto(t, a); digestInto(t, b)` writes
|
|
123
|
+
* exactly `canonicalize(a) + canonicalize(b)`, with nothing in between. That is
|
|
124
|
+
* ambiguous across the call boundary — the byte stream does not record where one
|
|
125
|
+
* value ended, so a differently-split sequence can produce the identical stream
|
|
126
|
+
* and therefore the identical digest. Concretely, `digestInto(t, 1);
|
|
127
|
+
* digestInto(t, 2)` and the single call `digestInto(t, 2, '#1')` both write
|
|
128
|
+
* `#1#2`. (Within ONE call the projection is unambiguous — every token is
|
|
129
|
+
* self-contained and NUL-joined; it is only the seam between calls that is not.)
|
|
130
|
+
*
|
|
131
|
+
* This is DOCUMENTED rather than fixed. Any delimiter at the seam — written
|
|
132
|
+
* before, after, or between — changes the bytes some existing caller already
|
|
133
|
+
* hashes, and moving a recorded digest is a {@link DIGEST_FORMAT} decision for
|
|
134
|
+
* the owner, not a patch. Callers hashing a SEQUENCE should digest a wrapper
|
|
135
|
+
* value (`digestInto(t, [a, b])`), or write their own unambiguous separator
|
|
136
|
+
* between calls, or use one target per value.
|
|
12
137
|
*/
|
|
13
|
-
export declare function
|
|
14
|
-
/** Full 64-hex sha256 of a string. */
|
|
15
|
-
export declare function hash(payload: string): string;
|
|
138
|
+
export declare function digestInto(target: DigestTarget, value: unknown, prefix?: string, options?: CanonicalOptions): void;
|
|
16
139
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
140
|
+
* Full 64-hex sha256 of a value's canonical projection, streamed.
|
|
141
|
+
*
|
|
142
|
+
* The convenience wrapper over {@link digestInto} for the common case.
|
|
143
|
+
* Identical to `sha256(prefix + canonicalize(value))` for every value the
|
|
144
|
+
* latter can survive, and unbounded where it is not.
|
|
19
145
|
*/
|
|
20
|
-
export declare
|
|
146
|
+
export declare function digestValue(value: unknown, prefix?: string, options?: CanonicalOptions): string;
|
|
21
147
|
//# sourceMappingURL=digest.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../../src/oracle/digest.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../../src/oracle/digest.ts"],"names":[],"mappings":"AAoEA;;;;;GAKG;AACH,eAAO,MAAM,aAAa,IAAI,CAAA;AAsC9B;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B,CAAA;AAkED;;;;;;GAMG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,SAAkB,IAAI,0BAAyB;IAE/C,YAAY,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAW7C;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,kBAAkB,YAAc,CAAA;AAkP7C,8DAA8D;AAC9D,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC/B,CAAA;AA+BD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAI/E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,OAAO,EACd,MAAM,SAAK,EACX,OAAO,CAAC,EAAE,gBAAgB,GACzB,IAAI,CAKN;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,SAAK,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAI3F"}
|
package/dist/oracle/index.cjs
CHANGED
|
@@ -20,24 +20,125 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/oracle/index.ts
|
|
21
21
|
var oracle_exports = {};
|
|
22
22
|
__export(oracle_exports, {
|
|
23
|
+
CanonicalBudgetError: () => CanonicalBudgetError,
|
|
24
|
+
DEFAULT_MAX_VISITS: () => DEFAULT_MAX_VISITS,
|
|
23
25
|
DIGEST_FORMAT: () => DIGEST_FORMAT,
|
|
24
|
-
HARNESS_DIGEST: () => HARNESS_DIGEST,
|
|
25
26
|
canonicalize: () => canonicalize,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
formatComparison: () => formatComparison,
|
|
29
|
-
loadCorpus: () => loadCorpus
|
|
27
|
+
digestInto: () => digestInto,
|
|
28
|
+
digestValue: () => digestValue
|
|
30
29
|
});
|
|
31
30
|
module.exports = __toCommonJS(oracle_exports);
|
|
32
31
|
|
|
33
32
|
// src/oracle/digest.ts
|
|
34
33
|
var import_node_crypto = require("node:crypto");
|
|
35
34
|
var DIGEST_FORMAT = 1;
|
|
35
|
+
var SEPARATOR = String.fromCharCode(0);
|
|
36
36
|
var str = (s) => JSON.stringify(s);
|
|
37
37
|
var tagOf = (v) => {
|
|
38
38
|
const ctor = v.constructor;
|
|
39
39
|
return typeof ctor === "function" && typeof ctor.name === "string" ? ctor.name : "";
|
|
40
40
|
};
|
|
41
|
+
var ArraySink = class {
|
|
42
|
+
tokens = [];
|
|
43
|
+
push(token) {
|
|
44
|
+
this.tokens.push(token);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var StreamSink = class {
|
|
48
|
+
target;
|
|
49
|
+
buffer = [];
|
|
50
|
+
buffered = 0;
|
|
51
|
+
started = false;
|
|
52
|
+
constructor(target) {
|
|
53
|
+
this.target = target;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Write text that is NOT a canonical token and takes no separator — the
|
|
57
|
+
* `OK:` / `ERR:` discriminator, which the string form concatenates onto the
|
|
58
|
+
* front rather than joining.
|
|
59
|
+
*/
|
|
60
|
+
prefix(text) {
|
|
61
|
+
if (text === "") return;
|
|
62
|
+
this.buffer.push(text);
|
|
63
|
+
this.buffered += text.length;
|
|
64
|
+
}
|
|
65
|
+
push(token) {
|
|
66
|
+
if (this.started) {
|
|
67
|
+
this.buffer.push(SEPARATOR);
|
|
68
|
+
this.buffered += 1;
|
|
69
|
+
} else {
|
|
70
|
+
this.started = true;
|
|
71
|
+
}
|
|
72
|
+
this.buffer.push(token);
|
|
73
|
+
this.buffered += token.length;
|
|
74
|
+
if (this.buffered >= FLUSH_CHARS) this.flush();
|
|
75
|
+
}
|
|
76
|
+
flush() {
|
|
77
|
+
if (this.buffer.length === 0) return;
|
|
78
|
+
this.target.update(this.buffer.join(""));
|
|
79
|
+
this.buffer = [];
|
|
80
|
+
this.buffered = 0;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
var FLUSH_CHARS = 1 << 16;
|
|
84
|
+
var CanonicalBudgetError = class extends Error {
|
|
85
|
+
name = "CanonicalBudgetError";
|
|
86
|
+
constructor(budget, projected) {
|
|
87
|
+
super(
|
|
88
|
+
`canonicalize: exceeded ${budget} visits. The value is not merely large \u2014 it is almost certainly a DAG being unrolled. This projection writes a shared subtree once per PATH that reaches it (see "Sharing is not a cycle"), so a node referenced from two places at each of d levels costs 2^d. Deduplicate the shared structure, or raise \`maxVisits\` if the value really is that big.` + (projected === void 0 ? "" : ` Refused EARLY: the walk was projected to visit ${projected} objects, so it could not have finished under this budget however long it ran. See {@link projectVisits}.`)
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
var DEFAULT_MAX_VISITS = 1e8;
|
|
93
|
+
var EXPANSION_PROBE_VISITS = 1e6;
|
|
94
|
+
var EXPANSION_PROBE_NODES = 2e5;
|
|
95
|
+
var EXPANSION_PROBE_MARGIN = 4;
|
|
96
|
+
function projectVisits(root) {
|
|
97
|
+
const memo = /* @__PURE__ */ new Map();
|
|
98
|
+
const path = /* @__PURE__ */ new Set();
|
|
99
|
+
let nodes = 0;
|
|
100
|
+
let usable = true;
|
|
101
|
+
const count = (v) => {
|
|
102
|
+
if (typeof v !== "object" || v === null) return 0;
|
|
103
|
+
if (path.has(v)) {
|
|
104
|
+
usable = false;
|
|
105
|
+
return 1;
|
|
106
|
+
}
|
|
107
|
+
const done = memo.get(v);
|
|
108
|
+
if (done !== void 0) return done;
|
|
109
|
+
if (++nodes > EXPANSION_PROBE_NODES) {
|
|
110
|
+
usable = false;
|
|
111
|
+
return 1;
|
|
112
|
+
}
|
|
113
|
+
path.add(v);
|
|
114
|
+
let total = 1;
|
|
115
|
+
if (Array.isArray(v)) {
|
|
116
|
+
for (const item of v) total += count(item);
|
|
117
|
+
} else if (v instanceof Map) {
|
|
118
|
+
for (const [k, val] of v) total += count(k) + count(val);
|
|
119
|
+
} else if (v instanceof Set) {
|
|
120
|
+
for (const item of v) total += count(item);
|
|
121
|
+
} else if (!(v instanceof Date) && !(v instanceof RegExp)) {
|
|
122
|
+
for (const k of Object.keys(v)) total += count(v[k]);
|
|
123
|
+
}
|
|
124
|
+
path.delete(v);
|
|
125
|
+
memo.set(v, total);
|
|
126
|
+
return total;
|
|
127
|
+
};
|
|
128
|
+
try {
|
|
129
|
+
const total = count(root);
|
|
130
|
+
return usable ? total : void 0;
|
|
131
|
+
} catch {
|
|
132
|
+
return void 0;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function probeExpansion(state) {
|
|
136
|
+
state.probeAt = -1;
|
|
137
|
+
const projected = projectVisits(state.root);
|
|
138
|
+
if (projected !== void 0 && projected / EXPANSION_PROBE_MARGIN > state.budget) {
|
|
139
|
+
throw new CanonicalBudgetError(state.budget, projected);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
41
142
|
function writeScalar(v, out) {
|
|
42
143
|
switch (typeof v) {
|
|
43
144
|
case "undefined":
|
|
@@ -79,11 +180,15 @@ function writeScalar(v, out) {
|
|
|
79
180
|
out.push("z");
|
|
80
181
|
}
|
|
81
182
|
}
|
|
82
|
-
function write(v,
|
|
183
|
+
function write(v, state, depth) {
|
|
184
|
+
const out = state.sink;
|
|
83
185
|
if (typeof v !== "object" || v === null) {
|
|
84
186
|
writeScalar(v, out);
|
|
85
187
|
return;
|
|
86
188
|
}
|
|
189
|
+
if (--state.visits < 0) throw new CanonicalBudgetError(state.budget);
|
|
190
|
+
if (state.visits === state.probeAt) probeExpansion(state);
|
|
191
|
+
const path = state.path;
|
|
87
192
|
const seenAt = path.get(v);
|
|
88
193
|
if (seenAt !== void 0) {
|
|
89
194
|
out.push(`^${depth - seenAt}`);
|
|
@@ -92,18 +197,18 @@ function write(v, path, depth, out) {
|
|
|
92
197
|
path.set(v, depth);
|
|
93
198
|
if (Array.isArray(v)) {
|
|
94
199
|
out.push("[");
|
|
95
|
-
for (const item of v) write(item,
|
|
200
|
+
for (const item of v) write(item, state, depth + 1);
|
|
96
201
|
out.push("]");
|
|
97
202
|
} else if (v instanceof Map) {
|
|
98
203
|
out.push("M");
|
|
99
204
|
for (const [k, val] of v) {
|
|
100
|
-
write(k,
|
|
101
|
-
write(val,
|
|
205
|
+
write(k, state, depth + 1);
|
|
206
|
+
write(val, state, depth + 1);
|
|
102
207
|
}
|
|
103
208
|
out.push("}");
|
|
104
209
|
} else if (v instanceof Set) {
|
|
105
210
|
out.push("S");
|
|
106
|
-
for (const item of v) write(item,
|
|
211
|
+
for (const item of v) write(item, state, depth + 1);
|
|
107
212
|
out.push("}");
|
|
108
213
|
} else if (v instanceof Date) {
|
|
109
214
|
out.push(`D${v.getTime()}`);
|
|
@@ -114,310 +219,36 @@ function write(v, path, depth, out) {
|
|
|
114
219
|
out.push(`{${str(tag === "Object" ? "" : tag)}`);
|
|
115
220
|
for (const k of Object.keys(v).sort()) {
|
|
116
221
|
out.push(`k${str(k)}`);
|
|
117
|
-
write(v[k],
|
|
222
|
+
write(v[k], state, depth + 1);
|
|
118
223
|
}
|
|
119
224
|
out.push("}");
|
|
120
225
|
}
|
|
121
226
|
path.delete(v);
|
|
122
227
|
}
|
|
123
|
-
function
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
function hash(payload2) {
|
|
129
|
-
return (0, import_node_crypto.createHash)("sha256").update(payload2).digest("hex");
|
|
130
|
-
}
|
|
131
|
-
var FINGERPRINT_HEX = 16;
|
|
132
|
-
|
|
133
|
-
// src/oracle/identity.ts
|
|
134
|
-
var defaultProjectError = (thrown) => thrown instanceof Error ? { name: thrown.name, message: thrown.message } : { thrown };
|
|
135
|
-
function payload(surface, entry, options) {
|
|
136
|
-
try {
|
|
137
|
-
return { text: `OK:${canonicalize(surface.parse(entry.source, entry.id))}`, threw: false };
|
|
138
|
-
} catch (thrown) {
|
|
139
|
-
const project = options.projectError ?? defaultProjectError;
|
|
140
|
-
return { text: `ERR:${canonicalize(project(thrown, entry.id, surface.name))}`, threw: true };
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
var fingerprint = (text) => hash(text).slice(0, FINGERPRINT_HEX);
|
|
144
|
-
function digestCorpus(surfaces, corpus, options = {}) {
|
|
145
|
-
if (surfaces.length === 0) throw new Error("digestCorpus: no surfaces \u2014 there is nothing to compare.");
|
|
146
|
-
const names = /* @__PURE__ */ new Set();
|
|
147
|
-
for (const s of surfaces) {
|
|
148
|
-
if (names.has(s.name)) throw new Error(`digestCorpus: duplicate surface name ${JSON.stringify(s.name)}.`);
|
|
149
|
-
names.add(s.name);
|
|
150
|
-
}
|
|
151
|
-
const entries = [...corpus].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
152
|
-
for (let n = 1; n < entries.length; n++) {
|
|
153
|
-
if (entries[n].id === entries[n - 1].id) {
|
|
154
|
-
throw new Error(`digestCorpus: duplicate corpus id ${JSON.stringify(entries[n].id)}.`);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
const perEntry = {};
|
|
158
|
-
const threw = {};
|
|
159
|
-
const texts = [];
|
|
160
|
-
for (const s of surfaces) threw[s.name] = 0;
|
|
161
|
-
for (const entry of entries) {
|
|
162
|
-
const row = {};
|
|
163
|
-
const rowTexts = /* @__PURE__ */ new Map();
|
|
164
|
-
for (const s of surfaces) {
|
|
165
|
-
const result = payload(s, entry, options);
|
|
166
|
-
if (result.threw) threw[s.name]++;
|
|
167
|
-
row[s.name] = fingerprint(result.text);
|
|
168
|
-
rowTexts.set(s.name, result.text);
|
|
169
|
-
}
|
|
170
|
-
perEntry[entry.id] = row;
|
|
171
|
-
texts.push(rowTexts);
|
|
172
|
-
}
|
|
173
|
-
verifyDeterminism(surfaces, entries, texts, options);
|
|
174
|
-
return {
|
|
175
|
-
format: DIGEST_FORMAT,
|
|
176
|
-
harness: HARNESS_DIGEST,
|
|
177
|
-
entries: entries.length,
|
|
178
|
-
surfaces: surfaces.map((s) => ({
|
|
179
|
-
name: s.name,
|
|
180
|
-
// The surface NAME is inside its own aggregate: two surfaces that happen
|
|
181
|
-
// to agree on every entry must still have distinct aggregates, or a
|
|
182
|
-
// report that swapped them would compare equal.
|
|
183
|
-
aggregate: hash(
|
|
184
|
-
[`surface:${s.name}`, ...entries.map((e) => `${e.id}:${perEntry[e.id][s.name]}`)].join("\n")
|
|
185
|
-
),
|
|
186
|
-
threw: threw[s.name]
|
|
187
|
-
})),
|
|
188
|
-
perEntry
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
function verifyDeterminism(surfaces, entries, texts, options) {
|
|
192
|
-
const want = options.determinismSample ?? 32;
|
|
193
|
-
if (want <= 0 || entries.length === 0) return;
|
|
194
|
-
const stride = Math.max(1, Math.floor(entries.length / Math.min(want, entries.length)));
|
|
195
|
-
for (let n = 0; n < entries.length; n += stride) {
|
|
196
|
-
const entry = entries[n];
|
|
197
|
-
for (const s of surfaces) {
|
|
198
|
-
if (payload(s, entry, options).text === texts[n].get(s.name)) continue;
|
|
199
|
-
throw new Error(
|
|
200
|
-
`digestCorpus: surface ${JSON.stringify(s.name)} is NOT DETERMINISTIC on ${JSON.stringify(entry.id)} \u2014 two parses of the same source produced different output. Every digest from this grammar would move on its own, so a comparison against it cannot mean anything. Usual causes: a timestamp or counter in the output, iteration over a keyed-by-object Map, or a node carrying a reference to mutable shared state.`
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
function compareReports(before, after) {
|
|
206
|
-
if (before.harness !== after.harness) {
|
|
207
|
-
return incomparable(
|
|
208
|
-
`harness drift: reports were produced by DIFFERENT versions of the identity harness (${before.harness.slice(0, 12)}\u2026 vs ${after.harness.slice(0, 12)}\u2026). Their digests are not comparable, and treating them as such would either invent a regression or hide one. Re-run BOTH sides on one harness.`
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
if (before.format !== after.format) {
|
|
212
|
-
return incomparable(`digest format ${before.format} vs ${after.format} \u2014 re-run both sides on one version.`);
|
|
213
|
-
}
|
|
214
|
-
const beforeIds = new Set(Object.keys(before.perEntry));
|
|
215
|
-
const afterIds = new Set(Object.keys(after.perEntry));
|
|
216
|
-
const addedEntries = [...afterIds].filter((id) => !beforeIds.has(id)).sort();
|
|
217
|
-
const removedEntries = [...beforeIds].filter((id) => !afterIds.has(id)).sort();
|
|
218
|
-
const shared = [...beforeIds].filter((id) => afterIds.has(id)).sort();
|
|
219
|
-
const byName = /* @__PURE__ */ new Map();
|
|
220
|
-
for (const s of before.surfaces) byName.set(s.name, { ...byName.get(s.name), before: s });
|
|
221
|
-
for (const s of after.surfaces) byName.set(s.name, { ...byName.get(s.name), after: s });
|
|
222
|
-
const surfaces = [...byName.entries()].sort(([a], [b]) => a < b ? -1 : 1).map(([name, pair]) => {
|
|
223
|
-
const moved = pair.before && pair.after ? shared.filter((id) => before.perEntry[id][name] !== after.perEntry[id][name]) : [];
|
|
224
|
-
return {
|
|
225
|
-
name,
|
|
226
|
-
before: pair.before?.aggregate ?? null,
|
|
227
|
-
after: pair.after?.aggregate ?? null,
|
|
228
|
-
equal: pair.before !== void 0 && pair.after !== void 0 && pair.before.aggregate === pair.after.aggregate,
|
|
229
|
-
moved,
|
|
230
|
-
beforeThrew: pair.before?.threw ?? null,
|
|
231
|
-
afterThrew: pair.after?.threw ?? null
|
|
232
|
-
};
|
|
233
|
-
});
|
|
234
|
-
const identical = addedEntries.length === 0 && removedEntries.length === 0 && surfaces.every((s) => s.equal);
|
|
235
|
-
return { verdict: identical ? "identical" : "moved", reason: null, addedEntries, removedEntries, surfaces };
|
|
236
|
-
}
|
|
237
|
-
function incomparable(reason) {
|
|
238
|
-
return { verdict: "incomparable", reason, addedEntries: [], removedEntries: [], surfaces: [] };
|
|
239
|
-
}
|
|
240
|
-
function formatComparison(c, options = {}) {
|
|
241
|
-
if (c.verdict === "incomparable") return `INCOMPARABLE \u2014 ${c.reason ?? "unknown reason"}`;
|
|
242
|
-
const max = options.maxMoved ?? 10;
|
|
243
|
-
const lines = [];
|
|
244
|
-
for (const s of c.surfaces) {
|
|
245
|
-
if (s.before === null) lines.push(` + surface ${s.name} (added)`);
|
|
246
|
-
else if (s.after === null) lines.push(` - surface ${s.name} (removed)`);
|
|
247
|
-
else if (s.equal) lines.push(` = ${s.name} ${s.before.slice(0, 16)}\u2026 threw=${s.afterThrew}`);
|
|
248
|
-
else {
|
|
249
|
-
lines.push(
|
|
250
|
-
` ! ${s.name} ${s.before.slice(0, 16)}\u2026 -> ${s.after.slice(0, 16)}\u2026 threw ${s.beforeThrew} -> ${s.afterThrew} (${s.moved.length} entries moved)`
|
|
251
|
-
);
|
|
252
|
-
for (const id of s.moved.slice(0, max)) lines.push(` ${id}`);
|
|
253
|
-
if (s.moved.length > max) lines.push(` \u2026 and ${s.moved.length - max} more`);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
if (c.addedEntries.length > 0) lines.push(` corpus GAINED ${c.addedEntries.length} entries`);
|
|
257
|
-
if (c.removedEntries.length > 0) {
|
|
258
|
-
lines.push(
|
|
259
|
-
` corpus LOST ${c.removedEntries.length} entries \u2014 a differential over a SMALLER corpus is a weaker gate, not a passing one`
|
|
228
|
+
function newState(sink, options, root) {
|
|
229
|
+
const budget = options?.maxVisits ?? DEFAULT_MAX_VISITS;
|
|
230
|
+
if (!Number.isSafeInteger(budget) || budget < 0) {
|
|
231
|
+
throw new RangeError(
|
|
232
|
+
`canonicalize: maxVisits must be a non-negative safe integer, received ${String(budget)}. A non-finite or fractional budget does not raise the ceiling, it REMOVES it \u2014 the budget is spent with \`--visits < 0\`, which is false forever for NaN and never reached for Infinity \u2014 so the walk this bounds would run unbounded into the OOM the budget exists to prevent.`
|
|
260
233
|
);
|
|
261
234
|
}
|
|
262
|
-
|
|
235
|
+
const probeAt = budget >= EXPANSION_PROBE_VISITS ? budget - EXPANSION_PROBE_VISITS : -1;
|
|
236
|
+
return { path: /* @__PURE__ */ new Map(), sink, budget, root, visits: budget, probeAt };
|
|
263
237
|
}
|
|
264
|
-
function
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
this.x = x;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
const shared = { shared: true };
|
|
272
|
-
const cyclic = { name: "root" };
|
|
273
|
-
cyclic.self = cyclic;
|
|
274
|
-
cyclic.child = { up: cyclic };
|
|
275
|
-
const values = {
|
|
276
|
-
"a/scalars": [0, -0, 1.5, Number.NaN, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY, 10n, true, false, null],
|
|
277
|
-
"b/absent": [{ a: void 0 }, {}, { a: null }, [void 0], []],
|
|
278
|
-
"c/key-order": [{ a: 1, b: 2 }, { b: 2, a: 1 }],
|
|
279
|
-
"d/collections": [/* @__PURE__ */ new Map([["k", 1], ["j", 2]]), /* @__PURE__ */ new Set([1, 2]), /* @__PURE__ */ new Date(0), /ab+c/giu],
|
|
280
|
-
"e/tagged": [new Tagged(1), { x: 1 }],
|
|
281
|
-
"f/sharing": { left: shared, right: shared },
|
|
282
|
-
"g/cycle": cyclic,
|
|
283
|
-
"h/text": ["", "a\0b", "a b", '"quoted"', "\\"],
|
|
284
|
-
"i/callable": [function named() {
|
|
285
|
-
}, /* @__PURE__ */ Symbol("sym")]
|
|
286
|
-
};
|
|
287
|
-
const surfaces = [
|
|
288
|
-
{ name: "value", parse: (_source, id) => values[id] },
|
|
289
|
-
{
|
|
290
|
-
name: "thrower",
|
|
291
|
-
parse: (_source, id) => {
|
|
292
|
-
if (id === "a/scalars") throw new TypeError("canary");
|
|
293
|
-
if (id === "b/absent") throw "a bare string";
|
|
294
|
-
return { id };
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
];
|
|
298
|
-
const corpus = Object.keys(values).map((id) => ({ id, source: id }));
|
|
299
|
-
const perEntry = {};
|
|
300
|
-
const threw = { value: 0, thrower: 0 };
|
|
301
|
-
for (const entry of [...corpus].sort((a, b) => a.id < b.id ? -1 : 1)) {
|
|
302
|
-
const row = {};
|
|
303
|
-
for (const s of surfaces) {
|
|
304
|
-
const result = payload(s, entry, {});
|
|
305
|
-
if (result.threw) threw[s.name]++;
|
|
306
|
-
row[s.name] = fingerprint(result.text);
|
|
307
|
-
}
|
|
308
|
-
perEntry[entry.id] = row;
|
|
309
|
-
}
|
|
310
|
-
const ids = Object.keys(perEntry).sort();
|
|
311
|
-
return {
|
|
312
|
-
format: DIGEST_FORMAT,
|
|
313
|
-
harness: "",
|
|
314
|
-
entries: ids.length,
|
|
315
|
-
surfaces: surfaces.map((s) => ({
|
|
316
|
-
name: s.name,
|
|
317
|
-
aggregate: hash([`surface:${s.name}`, ...ids.map((id) => `${id}:${perEntry[id][s.name]}`)].join("\n")),
|
|
318
|
-
threw: threw[s.name]
|
|
319
|
-
})),
|
|
320
|
-
perEntry
|
|
321
|
-
};
|
|
238
|
+
function canonicalize(value, options) {
|
|
239
|
+
const out = new ArraySink();
|
|
240
|
+
write(value, newState(out, options, value), 0);
|
|
241
|
+
return out.tokens.join(SEPARATOR);
|
|
322
242
|
}
|
|
323
|
-
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
);
|
|
328
|
-
})();
|
|
329
|
-
|
|
330
|
-
// src/oracle/corpus.ts
|
|
331
|
-
var import_node_fs = require("node:fs");
|
|
332
|
-
var import_node_path = require("node:path");
|
|
333
|
-
var DEFAULT_IGNORE = [".git", "node_modules/.cache"];
|
|
334
|
-
function loadCorpus(options) {
|
|
335
|
-
const base = (0, import_node_path.resolve)(options.base);
|
|
336
|
-
const ignoreDirs = options.ignoreDirs ?? DEFAULT_IGNORE;
|
|
337
|
-
const ignoreNames = new Set(ignoreDirs.filter((d) => !d.includes("/")));
|
|
338
|
-
const ignorePaths = ignoreDirs.filter((d) => d.includes("/")).map((d) => d.replace(/^\.?\/+/, "").replace(/\/+$/, ""));
|
|
339
|
-
const maxBytes = options.maxBytes ?? Number.POSITIVE_INFINITY;
|
|
340
|
-
const exts = new Set(options.extensions.map((e) => e.toLowerCase()));
|
|
341
|
-
const missingRoots = [];
|
|
342
|
-
const skippedLarge = [];
|
|
343
|
-
const files = /* @__PURE__ */ new Map();
|
|
344
|
-
const isIgnored = (name, relFromRoot) => {
|
|
345
|
-
if (ignoreNames.has(name)) return true;
|
|
346
|
-
return ignorePaths.some((p) => relFromRoot === p || relFromRoot.endsWith(`/${p}`));
|
|
347
|
-
};
|
|
348
|
-
const walk = (dir, root, visited) => {
|
|
349
|
-
let real;
|
|
350
|
-
try {
|
|
351
|
-
real = (0, import_node_fs.realpathSync)(dir);
|
|
352
|
-
} catch {
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
if (visited.has(real)) return;
|
|
356
|
-
visited.add(real);
|
|
357
|
-
for (const item of (0, import_node_fs.readdirSync)(dir, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
|
|
358
|
-
const full = (0, import_node_path.join)(dir, item.name);
|
|
359
|
-
if (isIgnored(item.name, (0, import_node_path.relative)(root, full).split(import_node_path.sep).join("/"))) continue;
|
|
360
|
-
let isDir = item.isDirectory();
|
|
361
|
-
let isFile = item.isFile();
|
|
362
|
-
let size = 0;
|
|
363
|
-
if (item.isSymbolicLink() || !isDir && !isFile) {
|
|
364
|
-
try {
|
|
365
|
-
const st = (0, import_node_fs.statSync)(full);
|
|
366
|
-
isDir = st.isDirectory();
|
|
367
|
-
isFile = st.isFile();
|
|
368
|
-
size = st.size;
|
|
369
|
-
} catch {
|
|
370
|
-
continue;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
if (isDir) {
|
|
374
|
-
walk(full, root, visited);
|
|
375
|
-
continue;
|
|
376
|
-
}
|
|
377
|
-
if (!isFile) continue;
|
|
378
|
-
const dot = item.name.lastIndexOf(".");
|
|
379
|
-
if (dot < 0 || !exts.has(item.name.slice(dot).toLowerCase())) continue;
|
|
380
|
-
if (size === 0) {
|
|
381
|
-
try {
|
|
382
|
-
size = (0, import_node_fs.statSync)(full).size;
|
|
383
|
-
} catch {
|
|
384
|
-
continue;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
if (size > maxBytes) {
|
|
388
|
-
skippedLarge.push(idOf(base, full));
|
|
389
|
-
continue;
|
|
390
|
-
}
|
|
391
|
-
let realFile;
|
|
392
|
-
try {
|
|
393
|
-
realFile = (0, import_node_fs.realpathSync)(full);
|
|
394
|
-
} catch {
|
|
395
|
-
continue;
|
|
396
|
-
}
|
|
397
|
-
const id = idOf(base, full);
|
|
398
|
-
const existing = files.get(realFile);
|
|
399
|
-
if (existing === void 0 || id < existing) files.set(realFile, id);
|
|
400
|
-
}
|
|
401
|
-
};
|
|
402
|
-
for (const root of options.roots) {
|
|
403
|
-
const full = (0, import_node_path.resolve)(base, root);
|
|
404
|
-
try {
|
|
405
|
-
if (!(0, import_node_fs.statSync)(full).isDirectory()) throw new Error("not a directory");
|
|
406
|
-
} catch {
|
|
407
|
-
if (!options.allowMissingRoots) {
|
|
408
|
-
throw new Error(
|
|
409
|
-
`loadCorpus: root ${JSON.stringify(root)} does not resolve to a directory (${full}). A corpus that quietly shrank produces a different aggregate that looks like a grammar change. Fix the root, or pass allowMissingRoots and record \`missingRoots\` alongside the digest.`
|
|
410
|
-
);
|
|
411
|
-
}
|
|
412
|
-
missingRoots.push(root);
|
|
413
|
-
continue;
|
|
414
|
-
}
|
|
415
|
-
walk(full, full, /* @__PURE__ */ new Set());
|
|
416
|
-
}
|
|
417
|
-
const entries = [...files.values()].sort().map((id) => ({ id, source: (0, import_node_fs.readFileSync)((0, import_node_path.resolve)(base, id), "utf8") }));
|
|
418
|
-
return { entries, missingRoots, skippedLarge: skippedLarge.sort() };
|
|
243
|
+
function digestInto(target, value, prefix = "", options) {
|
|
244
|
+
const sink = new StreamSink(target);
|
|
245
|
+
sink.prefix(prefix);
|
|
246
|
+
write(value, newState(sink, options, value), 0);
|
|
247
|
+
sink.flush();
|
|
419
248
|
}
|
|
420
|
-
function
|
|
421
|
-
|
|
249
|
+
function digestValue(value, prefix = "", options) {
|
|
250
|
+
const sha = (0, import_node_crypto.createHash)("sha256");
|
|
251
|
+
digestInto(sha, value, prefix, options);
|
|
252
|
+
return sha.digest("hex");
|
|
422
253
|
}
|
|
423
254
|
//# sourceMappingURL=index.cjs.map
|