@pithy-sh/core 0.10.0 → 0.12.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/access/access.d.ts +172 -0
- package/dist/access/access.d.ts.map +1 -0
- package/dist/access/access.js +147 -0
- package/dist/access/acting.d.ts +49 -0
- package/dist/access/acting.d.ts.map +1 -0
- package/dist/access/acting.js +21 -0
- package/dist/access/policyCheck.d.ts +33 -0
- package/dist/access/policyCheck.d.ts.map +1 -0
- package/dist/access/policyCheck.js +48 -0
- package/dist/access/usage.d.ts +83 -0
- package/dist/access/usage.d.ts.map +1 -0
- package/dist/access/usage.js +104 -0
- package/dist/access/visibility.d.ts +66 -0
- package/dist/access/visibility.d.ts.map +1 -0
- package/dist/access/visibility.js +86 -0
- package/dist/audit/auditEvent.d.ts +5 -0
- package/dist/audit/auditEvent.d.ts.map +1 -1
- package/dist/audit/auditEvent.js +2 -0
- package/dist/audit/recorder.d.ts +10 -2
- package/dist/audit/recorder.d.ts.map +1 -1
- package/dist/capability/bindings.d.ts +47 -2
- package/dist/capability/bindings.d.ts.map +1 -1
- package/dist/capability/bindings.js +117 -7
- package/dist/capability/capability.d.ts +110 -28
- package/dist/capability/capability.d.ts.map +1 -1
- package/dist/capability/capability.js +2 -0
- package/dist/capability/manifest.d.ts +2 -1
- package/dist/capability/manifest.d.ts.map +1 -1
- package/dist/capability/manifest.js +1 -1
- package/dist/capability/purge.d.ts +80 -0
- package/dist/capability/purge.d.ts.map +1 -0
- package/dist/capability/purge.js +76 -0
- package/dist/capability/settings.d.ts +86 -2
- package/dist/capability/settings.d.ts.map +1 -1
- package/dist/capability/settings.js +80 -1
- package/dist/cli/commandCatalog.generated.d.ts +238 -24
- package/dist/cli/commandCatalog.generated.d.ts.map +1 -1
- package/dist/cli/commandCatalog.generated.js +251 -22
- package/dist/cli/commandFacts.d.ts +2 -1
- package/dist/cli/commandFacts.d.ts.map +1 -1
- package/dist/cli/commandFacts.js +2 -0
- package/dist/controlPlane/capability.d.ts.map +1 -1
- package/dist/controlPlane/capability.js +19 -2
- package/dist/controlPlane/context.d.ts +5 -0
- package/dist/controlPlane/context.d.ts.map +1 -1
- package/dist/controlPlane/context.js +3 -1
- package/dist/controlPlane/data/connection.d.ts +49 -12
- package/dist/controlPlane/data/connection.d.ts.map +1 -1
- package/dist/controlPlane/data/connection.js +56 -2
- package/dist/controlPlane/discovery/adminRoute.d.ts +29 -0
- package/dist/controlPlane/discovery/adminRoute.d.ts.map +1 -1
- package/dist/controlPlane/discovery/adminRoute.js +3 -0
- package/dist/controlPlane/error/errors.d.ts +4 -3
- package/dist/controlPlane/error/errors.d.ts.map +1 -1
- package/dist/controlPlane/http/cors.d.ts +4 -4
- package/dist/controlPlane/http/cors.js +4 -4
- package/dist/controlPlane/http/guard.d.ts +11 -0
- package/dist/controlPlane/http/guard.d.ts.map +1 -1
- package/dist/controlPlane/http/guard.js +17 -1
- package/dist/controlPlane/http/handlers.d.ts +139 -0
- package/dist/controlPlane/http/handlers.d.ts.map +1 -1
- package/dist/controlPlane/http/handlers.js +67 -4
- package/dist/controlPlane/http/readiness.d.ts +33 -0
- package/dist/controlPlane/http/readiness.d.ts.map +1 -0
- package/dist/controlPlane/http/readiness.js +39 -0
- package/dist/controlPlane/http/responses.d.ts +245 -0
- package/dist/controlPlane/http/responses.d.ts.map +1 -1
- package/dist/controlPlane/http/responses.js +101 -4
- package/dist/controlPlane/http/routes.d.ts +2 -0
- package/dist/controlPlane/http/routes.d.ts.map +1 -1
- package/dist/controlPlane/http/routes.js +31 -3
- package/dist/controlPlane/http/verify.d.ts.map +1 -1
- package/dist/controlPlane/http/verify.js +2 -1
- package/dist/controlPlane/migrationState/migrationState.d.ts +102 -0
- package/dist/controlPlane/migrationState/migrationState.d.ts.map +1 -0
- package/dist/controlPlane/migrationState/migrationState.js +142 -0
- package/dist/controlPlane/migrations/0003_connection_principal.d.ts +21 -0
- package/dist/controlPlane/migrations/0003_connection_principal.d.ts.map +1 -0
- package/dist/controlPlane/migrations/0003_connection_principal.js +33 -0
- package/dist/controlPlane/scope/scope.d.ts +23 -2
- package/dist/controlPlane/scope/scope.d.ts.map +1 -1
- package/dist/controlPlane/scope/scope.js +24 -3
- package/dist/createBackend.d.ts.map +1 -1
- package/dist/createBackend.js +9 -2
- package/dist/createEntrypoint.d.ts.map +1 -1
- package/dist/createEntrypoint.js +5 -3
- package/dist/crypto/envelope.d.ts +86 -0
- package/dist/crypto/envelope.d.ts.map +1 -0
- package/dist/crypto/envelope.js +129 -0
- package/dist/crypto/fieldKeys.d.ts +41 -0
- package/dist/crypto/fieldKeys.d.ts.map +1 -0
- package/dist/crypto/fieldKeys.js +56 -0
- package/dist/data/codecs.d.ts +23 -10
- package/dist/data/codecs.d.ts.map +1 -1
- package/dist/data/codecs.js +50 -36
- package/dist/entitlement/allowance.d.ts +25 -0
- package/dist/entitlement/allowance.d.ts.map +1 -0
- package/dist/entitlement/allowance.js +80 -0
- package/dist/entitlement/entitlement.d.ts +41 -0
- package/dist/entitlement/entitlement.d.ts.map +1 -1
- package/dist/entitlement/meter.d.ts +57 -0
- package/dist/entitlement/meter.d.ts.map +1 -0
- package/dist/entitlement/meter.js +13 -0
- package/dist/error/extend.d.ts +6 -5
- package/dist/error/extend.d.ts.map +1 -1
- package/dist/error/http.d.ts +168 -0
- package/dist/error/http.d.ts.map +1 -1
- package/dist/error/payload.d.ts +336 -0
- package/dist/error/payload.d.ts.map +1 -1
- package/dist/error/payload.js +116 -4
- package/dist/error/pithyError.d.ts +18 -7
- package/dist/error/pithyError.d.ts.map +1 -1
- package/dist/error/pithyError.js +6 -0
- package/dist/http/authContext.d.ts +7 -0
- package/dist/http/authContext.d.ts.map +1 -1
- package/dist/http/authContext.js +8 -1
- package/dist/http/scopeGates.d.ts +20 -0
- package/dist/http/scopeGates.d.ts.map +1 -0
- package/dist/http/scopeGates.js +36 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/logger/logger.d.ts +12 -5
- package/dist/logger/logger.d.ts.map +1 -1
- package/dist/logger/record.d.ts +98 -0
- package/dist/logger/record.d.ts.map +1 -1
- package/dist/migrations/capabilitySets.d.ts +23 -0
- package/dist/migrations/capabilitySets.d.ts.map +1 -0
- package/dist/migrations/capabilitySets.js +74 -0
- package/dist/migrations/registry.d.ts +26 -0
- package/dist/migrations/registry.d.ts.map +1 -1
- package/dist/migrations/registry.js +38 -7
- package/dist/migrations/state.d.ts +60 -0
- package/dist/migrations/state.d.ts.map +1 -0
- package/dist/migrations/state.js +46 -0
- package/dist/naming/feature.d.ts +27 -5
- package/dist/naming/feature.d.ts.map +1 -1
- package/dist/naming/feature.js +36 -6
- package/dist/naming/limits.d.ts +47 -3
- package/dist/naming/limits.d.ts.map +1 -1
- package/dist/naming/limits.js +47 -3
- package/dist/naming/provisionScope.d.ts +6 -0
- package/dist/naming/provisionScope.d.ts.map +1 -1
- package/dist/naming/provisionScope.js +8 -3
- package/dist/naming/resourceNames.d.ts +21 -2
- package/dist/naming/resourceNames.d.ts.map +1 -1
- package/dist/naming/resourceNames.js +21 -3
- package/dist/privacy/blindIndex.d.ts +27 -0
- package/dist/privacy/blindIndex.d.ts.map +1 -0
- package/dist/privacy/blindIndex.js +57 -0
- package/dist/privacy/classes.d.ts +142 -0
- package/dist/privacy/classes.d.ts.map +1 -0
- package/dist/privacy/classes.js +209 -0
- package/dist/privacy/classify.d.ts +336 -0
- package/dist/privacy/classify.d.ts.map +1 -0
- package/dist/privacy/classify.js +1024 -0
- package/dist/privacy/encrypted.d.ts +210 -0
- package/dist/privacy/encrypted.d.ts.map +1 -0
- package/dist/privacy/encrypted.js +350 -0
- package/dist/privacy/encryptedColumns.d.ts +64 -0
- package/dist/privacy/encryptedColumns.d.ts.map +1 -0
- package/dist/privacy/encryptedColumns.js +134 -0
- package/dist/privacy/fieldRotation.d.ts +34 -0
- package/dist/privacy/fieldRotation.d.ts.map +1 -0
- package/dist/privacy/fieldRotation.js +186 -0
- package/dist/privacy/inventory.d.ts +12 -0
- package/dist/privacy/inventory.d.ts.map +1 -0
- package/dist/privacy/inventory.js +154 -0
- package/dist/privacy/posture.d.ts +114 -0
- package/dist/privacy/posture.d.ts.map +1 -0
- package/dist/privacy/posture.js +136 -0
- package/dist/privacy/redact.d.ts +7 -0
- package/dist/privacy/redact.d.ts.map +1 -0
- package/dist/privacy/redact.js +404 -0
- package/dist/privacy/report.d.ts +58 -0
- package/dist/privacy/report.d.ts.map +1 -0
- package/dist/privacy/report.js +15 -0
- package/dist/privacy/retirement.d.ts +68 -0
- package/dist/privacy/retirement.d.ts.map +1 -0
- package/dist/privacy/retirement.js +72 -0
- package/dist/privacy/sealed.d.ts +87 -0
- package/dist/privacy/sealed.d.ts.map +1 -0
- package/dist/privacy/sealed.js +221 -0
- package/dist/privacy/types.d.ts +559 -0
- package/dist/privacy/types.d.ts.map +1 -0
- package/dist/privacy/types.js +2 -0
- package/dist/seed/writeD1.d.ts +7 -1
- package/dist/seed/writeD1.d.ts.map +1 -1
- package/dist/seed/writeD1.js +9 -3
- package/dist/seed/writeKv.d.ts +2 -0
- package/dist/seed/writeKv.d.ts.map +1 -1
- package/dist/seed/writeKv.js +6 -2
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/dist/worker/serviceLoopback.d.ts +36 -0
- package/dist/worker/serviceLoopback.d.ts.map +1 -0
- package/dist/worker/serviceLoopback.js +74 -0
- package/dist/workflow/faults.d.ts +12 -1
- package/dist/workflow/faults.d.ts.map +1 -1
- package/dist/workflow/faults.js +22 -15
- package/package.json +2 -2
- package/src/access/access.ts +298 -0
- package/src/access/acting.ts +59 -0
- package/src/access/policyCheck.ts +77 -0
- package/src/access/usage.ts +141 -0
- package/src/access/visibility.ts +109 -0
- package/src/audit/auditEvent.ts +4 -0
- package/src/audit/recorder.ts +10 -2
- package/src/capability/bindings.ts +159 -13
- package/src/capability/capability.ts +114 -29
- package/src/capability/purge.ts +124 -0
- package/src/capability/settings.ts +146 -2
- package/src/cli/commandCatalog.generated.ts +271 -22
- package/src/cli/commandFacts.ts +2 -0
- package/src/controlPlane/capability.ts +36 -1
- package/src/controlPlane/context.ts +4 -0
- package/src/controlPlane/data/connection.ts +90 -0
- package/src/controlPlane/discovery/adminRoute.ts +11 -0
- package/src/controlPlane/error/errors.ts +4 -3
- package/src/controlPlane/http/cors.ts +4 -4
- package/src/controlPlane/http/guard.ts +21 -0
- package/src/controlPlane/http/handlers.ts +98 -4
- package/src/controlPlane/http/readiness.ts +50 -0
- package/src/controlPlane/http/responses.ts +240 -2
- package/src/controlPlane/http/routes.ts +55 -1
- package/src/controlPlane/http/verify.ts +2 -0
- package/src/controlPlane/migrationState/migrationState.ts +200 -0
- package/src/controlPlane/migrations/0003_connection_principal.ts +37 -0
- package/src/controlPlane/scope/scope.ts +25 -2
- package/src/controlPlane/token/base64url.ts +1 -1
- package/src/createBackend.ts +28 -6
- package/src/createEntrypoint.ts +9 -3
- package/src/crypto/envelope.ts +196 -0
- package/src/crypto/fieldKeys.ts +76 -0
- package/src/data/codecs.ts +69 -37
- package/src/entitlement/allowance.ts +85 -0
- package/src/entitlement/entitlement.ts +38 -0
- package/src/entitlement/meter.ts +69 -0
- package/src/error/extend.ts +6 -5
- package/src/error/payload.ts +253 -4
- package/src/error/pithyError.ts +21 -7
- package/src/http/authContext.ts +13 -1
- package/src/http/scopeGates.ts +34 -0
- package/src/index.ts +2 -0
- package/src/logger/logger.ts +12 -5
- package/src/migrations/capabilitySets.ts +87 -0
- package/src/migrations/registry.ts +46 -2
- package/src/migrations/state.ts +94 -0
- package/src/naming/feature.ts +46 -7
- package/src/naming/limits.ts +35 -3
- package/src/naming/provisionScope.ts +19 -1
- package/src/naming/resourceNames.ts +52 -5
- package/src/privacy/blindIndex.ts +81 -0
- package/src/privacy/classes.ts +277 -0
- package/src/privacy/classify.ts +1447 -0
- package/src/privacy/encrypted.ts +513 -0
- package/src/privacy/encryptedColumns.ts +185 -0
- package/src/privacy/fieldRotation.ts +234 -0
- package/src/privacy/inventory.ts +147 -0
- package/src/privacy/posture.ts +205 -0
- package/src/privacy/redact.ts +423 -0
- package/src/privacy/report.ts +64 -0
- package/src/privacy/retirement.ts +102 -0
- package/src/privacy/sealed.ts +323 -0
- package/src/privacy/types.ts +804 -0
- package/src/seed/writeD1.ts +13 -4
- package/src/seed/writeKv.ts +6 -2
- package/src/version.generated.ts +1 -1
- package/src/worker/serviceLoopback.ts +105 -0
- package/src/workflow/faults.ts +51 -24
|
@@ -0,0 +1,1024 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { SQLiteBoolean, SQLiteDate, isSqliteJson } from "../data/codecs.js";
|
|
4
|
+
import { ownPrivacyClasses, privacyClasses, rankPrivacyClasses, strongestPrivacyClass } from "./classes.js";
|
|
5
|
+
//#region src/privacy/classify.ts
|
|
6
|
+
/**
|
|
7
|
+
* **The one recursion limit**, shared by `redact`, `classifyPrivacy` and `Redacted<T>` (`./types` reads this constant's
|
|
8
|
+
* type). Depth is counted in levels of nesting: every object, array, tuple, record, map and set a position sits inside.
|
|
9
|
+
* It applies to all nesting, not only recursion: a position deeper than this is masked whole as {@link TRUNCATED_MASK}
|
|
10
|
+
* and typed `unknown`, whatever nests it: a `z.lazy`, a getter that returns one schema, one that builds a new schema
|
|
11
|
+
* each read, or a schema written out level by level. The list assumes every class there, unless the chain repeats a
|
|
12
|
+
* position (the same set of schemas) already read on its path. A search for a mark beneath an opaque node or a marked
|
|
13
|
+
* container ends a path at the same depth, counting each lazy too, and reads every other path; so does the one
|
|
14
|
+
* `Redacted<T>` makes. So does a read of the shapes a choice can have, and of the schemas that reach one position, each
|
|
15
|
+
* counting the lazies nested above a schema: a lazy that builds a new choice each read never meets a schema twice. A
|
|
16
|
+
* union of many lazies, none inside another, counts one.
|
|
17
|
+
*/
|
|
18
|
+
const MAX_RECURSION_DEPTH = 256;
|
|
19
|
+
/**
|
|
20
|
+
* **The one budget**: how many schemas one call of `redact`, `classifyPrivacy`, `hasPrivacy` or `walkPrivacy` reads.
|
|
21
|
+
* Every position and every search in that call draws on it. Each schema a read meets counts one — a step of the read of
|
|
22
|
+
* a position, of the shapes a choice can have, or of a search beneath a marked container or an opaque node — and so
|
|
23
|
+
* does each schema a lazy or an object's shape yields: a lazy's target, and each field of a shape, where a getter runs
|
|
24
|
+
* and may build a new schema. A schema met again in the same read costs nothing. So a call reads at most this many
|
|
25
|
+
* schemas, and runs at most this many of the schema's own getters beyond the fields of the one object it was reading
|
|
26
|
+
* when the budget ran out. So its time and memory grow linearly with the size of the schema and the value, and are
|
|
27
|
+
* bounded beyond that by the budget and by what one of those getters builds. Past it the call fails closed: what it has not read, it assumes. `redact` masks each position it has
|
|
28
|
+
* not resolved whole, with the strongest class it found there or {@link TRUNCATED_MASK} where it found none; the list
|
|
29
|
+
* names every class at each position it has not read, each `truncated`. Far past any schema written out by hand, and
|
|
30
|
+
* past any recursion defined once: only a schema that builds new ones as it is read, or one wider than any written out,
|
|
31
|
+
* reaches it. Each call starts with the whole budget and keeps nothing for the next, so every answer is a function of
|
|
32
|
+
* the schema and the value alone, whichever call came first.
|
|
33
|
+
*/
|
|
34
|
+
const MAX_READ_SCHEMAS = 1e4;
|
|
35
|
+
/**
|
|
36
|
+
* How many paths {@link walkPrivacy} writes out before a position it met already is listed by reference. A schema
|
|
37
|
+
* reused in two fields at every level of thirty has thirty-one schemas and a billion paths, so a list of every path
|
|
38
|
+
* cannot be built; past this many, each new position is still read and written out once, and each one met again is
|
|
39
|
+
* listed at its own path with every class beneath it and `sameAs` the path it was written out under. So the paths it
|
|
40
|
+
* writes out are bounded too, however many reach one schema. Far past any table written out: a book of 22 groups of 22
|
|
41
|
+
* contacts of 22 addresses is written out whole.
|
|
42
|
+
*/
|
|
43
|
+
const MAX_LISTED_PATHS = 5e4;
|
|
44
|
+
/**
|
|
45
|
+
* What `redact` puts where it stops: a value nested past {@link MAX_RECURSION_DEPTH}, an opaque value over a recursion
|
|
46
|
+
* that reaches it, and a position the call ran out of {@link MAX_READ_SCHEMAS} before it read, where no class was
|
|
47
|
+
* found. A safety cutoff rather than a privacy class, so it is never a placeholder for one.
|
|
48
|
+
*/
|
|
49
|
+
const TRUNCATED_MASK = "[truncated]";
|
|
50
|
+
/**
|
|
51
|
+
* The classes {@link classifyPrivacy} lists for a position it did not read: every class registered, strongest first,
|
|
52
|
+
* because nothing there was read and any of them may be marked below it. So the list never says less than the schema
|
|
53
|
+
* marks, and `hasPrivacy` answers yes for each. A read that runs out of {@link MAX_READ_SCHEMAS} assumes them where it
|
|
54
|
+
* stopped, a cut at {@link MAX_RECURSION_DEPTH} on a chain of schemas not yet seen assumes them where it cut, and a
|
|
55
|
+
* schema the walk cannot read at all is listed with them.
|
|
56
|
+
*/
|
|
57
|
+
function unreadClasses() {
|
|
58
|
+
return rankPrivacyClasses(privacyClasses().map((each) => each.name));
|
|
59
|
+
}
|
|
60
|
+
/** A call with the whole budget, and nothing read. */
|
|
61
|
+
function newCall() {
|
|
62
|
+
return {
|
|
63
|
+
left: MAX_READ_SCHEMAS,
|
|
64
|
+
kinds: /* @__PURE__ */ new Map(),
|
|
65
|
+
stops: /* @__PURE__ */ new Map(),
|
|
66
|
+
searched: /* @__PURE__ */ new Map(),
|
|
67
|
+
positions: /* @__PURE__ */ new Map(),
|
|
68
|
+
walked: /* @__PURE__ */ new Map()
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** Spend one of the call's budget on a read: `false`, and nothing spent, once there is none left. */
|
|
72
|
+
function take(call) {
|
|
73
|
+
if (call.left <= 0) return false;
|
|
74
|
+
call.left -= 1;
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* An object's shape, read for the call: reading it runs the shape's getters, each of which may build a schema, so the
|
|
79
|
+
* call pays one for each field it holds as well as the one it took to open it. The budget may go below nothing here,
|
|
80
|
+
* by at most the width of that one object, and the next read stops.
|
|
81
|
+
*/
|
|
82
|
+
function readShape(schema, call) {
|
|
83
|
+
const shape = shapeOf(schema);
|
|
84
|
+
call.left -= Reflect.ownKeys(shape).length;
|
|
85
|
+
return shape;
|
|
86
|
+
}
|
|
87
|
+
function defOf(schema) {
|
|
88
|
+
return schema._zod.def;
|
|
89
|
+
}
|
|
90
|
+
function isSchema(value) {
|
|
91
|
+
return typeof value === "object" && value !== null && "_zod" in value;
|
|
92
|
+
}
|
|
93
|
+
/** The wrappers that are the same position as what they wrap. A brand is a type, and no node at all. */
|
|
94
|
+
const WRAPPERS = /* @__PURE__ */ new Set([
|
|
95
|
+
"optional",
|
|
96
|
+
"nullable",
|
|
97
|
+
"default",
|
|
98
|
+
"prefault",
|
|
99
|
+
"catch",
|
|
100
|
+
"readonly",
|
|
101
|
+
"nonoptional"
|
|
102
|
+
]);
|
|
103
|
+
/** The kinds a value is walked into. */
|
|
104
|
+
const STRUCTURAL = /* @__PURE__ */ new Set([
|
|
105
|
+
"object",
|
|
106
|
+
"array",
|
|
107
|
+
"tuple",
|
|
108
|
+
"record",
|
|
109
|
+
"map",
|
|
110
|
+
"set"
|
|
111
|
+
]);
|
|
112
|
+
/** The tag Zod puts on the function it wraps a constant `.catch()` value in. */
|
|
113
|
+
const CONSTANT_CATCH = "~constantCatch";
|
|
114
|
+
/**
|
|
115
|
+
* Whether a node hands back a value its own code made rather than the one it read: a `.catch()` with a fallback
|
|
116
|
+
* function, or an `.overwrite()`. A constant fallback is a value, and is walked as the wrapper it is.
|
|
117
|
+
*/
|
|
118
|
+
function makesValue(def) {
|
|
119
|
+
if (def.type === "catch") {
|
|
120
|
+
const fallback = def.catchValue;
|
|
121
|
+
if (typeof fallback !== "function" || fallback[CONSTANT_CATCH] !== true) return true;
|
|
122
|
+
}
|
|
123
|
+
return (def.checks ?? []).some((check) => check._zod?.def?.check === "overwrite");
|
|
124
|
+
}
|
|
125
|
+
/** The value a constant `.catch()` hands back, or `undefined` for any other node. */
|
|
126
|
+
function constantFallback(def) {
|
|
127
|
+
if (def.type !== "catch" || makesValue(def)) return void 0;
|
|
128
|
+
try {
|
|
129
|
+
return { value: def.catchValue() };
|
|
130
|
+
} catch {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/** The shapes a value is walked into. */
|
|
135
|
+
const STRUCTURE = [
|
|
136
|
+
"object",
|
|
137
|
+
"array",
|
|
138
|
+
"map",
|
|
139
|
+
"set"
|
|
140
|
+
];
|
|
141
|
+
/** Whether a value of one of `kinds` could be walked into. */
|
|
142
|
+
function holdsStructure(kinds) {
|
|
143
|
+
return STRUCTURE.some((kind) => kinds.has(kind));
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* A node's shapes, as a choice reads them: every shape for one that {@link makesValue} over a structure, as its own
|
|
147
|
+
* code may hand back anything. A scalar's is still its own: what it makes is the scalar a mark masks or keeps.
|
|
148
|
+
*/
|
|
149
|
+
function widened(def, kinds) {
|
|
150
|
+
return holdsStructure(kinds) && makesValue(def) ? EVERY_KIND : kinds;
|
|
151
|
+
}
|
|
152
|
+
/** Every {@link ValueKind}, in the order a mask lists them. */
|
|
153
|
+
const VALUE_KINDS = [
|
|
154
|
+
"string",
|
|
155
|
+
"number",
|
|
156
|
+
"bigint",
|
|
157
|
+
"boolean",
|
|
158
|
+
"symbol",
|
|
159
|
+
"function",
|
|
160
|
+
"date",
|
|
161
|
+
"object",
|
|
162
|
+
"array",
|
|
163
|
+
"map",
|
|
164
|
+
"set"
|
|
165
|
+
];
|
|
166
|
+
const EVERY_KIND = new Set(VALUE_KINDS);
|
|
167
|
+
const NO_KIND = /* @__PURE__ */ new Set();
|
|
168
|
+
/** The shape of a value, or `undefined` for `null` and `undefined`, which are kept wherever they are. */
|
|
169
|
+
function kindOfValue(value) {
|
|
170
|
+
const type = typeof value;
|
|
171
|
+
if (type === "undefined" || value === null) return void 0;
|
|
172
|
+
if (type !== "object") return type;
|
|
173
|
+
if (Array.isArray(value)) return "array";
|
|
174
|
+
if (value instanceof Map) return "map";
|
|
175
|
+
if (value instanceof Set) return "set";
|
|
176
|
+
return value instanceof Date ? "date" : "object";
|
|
177
|
+
}
|
|
178
|
+
/** The prototype a value of each kind has where it is what its schema parses: a plain object's, a `Date`'s … */
|
|
179
|
+
const TYPICAL = {
|
|
180
|
+
object: Object.prototype,
|
|
181
|
+
array: Array.prototype,
|
|
182
|
+
map: Map.prototype,
|
|
183
|
+
set: Set.prototype,
|
|
184
|
+
date: Date.prototype
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* Whether a value of `kind` is one its schema parses: a plain object, an `Array.prototype` array, a `Map`, a `Set` or
|
|
188
|
+
* a `Date` of its own prototype, never a subclass or a class instance. A scalar and a function always are.
|
|
189
|
+
*/
|
|
190
|
+
function isTypical(value, kind) {
|
|
191
|
+
const typical = TYPICAL[kind];
|
|
192
|
+
if (typical === void 0) return true;
|
|
193
|
+
const prototype = Object.getPrototypeOf(value);
|
|
194
|
+
return prototype === typical || kind === "object" && prototype === null;
|
|
195
|
+
}
|
|
196
|
+
/** The one shape each kind of schema produces. A record is an object, and a tuple an array. */
|
|
197
|
+
const SCHEMA_KINDS = {
|
|
198
|
+
string: "string",
|
|
199
|
+
template_literal: "string",
|
|
200
|
+
number: "number",
|
|
201
|
+
nan: "number",
|
|
202
|
+
bigint: "bigint",
|
|
203
|
+
boolean: "boolean",
|
|
204
|
+
symbol: "symbol",
|
|
205
|
+
function: "function",
|
|
206
|
+
date: "date",
|
|
207
|
+
object: "object",
|
|
208
|
+
record: "object",
|
|
209
|
+
array: "array",
|
|
210
|
+
tuple: "array",
|
|
211
|
+
map: "map",
|
|
212
|
+
set: "set"
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* The kinds a scalar, a literal, an enum or a node that holds no value names, or `undefined` for an opaque node, which
|
|
216
|
+
* takes a value of any kind and prototype: a transform, a pipe, `z.custom`, `any`, `unknown`, or one that makes its
|
|
217
|
+
* own value.
|
|
218
|
+
*/
|
|
219
|
+
function scalarKinds(node) {
|
|
220
|
+
const def = defOf(node);
|
|
221
|
+
if (makesValue(def)) return void 0;
|
|
222
|
+
const one = SCHEMA_KINDS[def.type];
|
|
223
|
+
if (one !== void 0) return /* @__PURE__ */ new Set([one]);
|
|
224
|
+
if (NO_VALUE.has(def.type)) return NO_KIND;
|
|
225
|
+
if (def.type !== "literal" && def.type !== "enum") return void 0;
|
|
226
|
+
const values = node._zod.values;
|
|
227
|
+
return values ? new Set([...values].flatMap((value) => [kindOfValue(value)].filter(isKind))) : void 0;
|
|
228
|
+
}
|
|
229
|
+
/** The kinds of schema that hold no value a mask could apply to. */
|
|
230
|
+
const NO_VALUE = /* @__PURE__ */ new Set([
|
|
231
|
+
"null",
|
|
232
|
+
"undefined",
|
|
233
|
+
"void",
|
|
234
|
+
"never"
|
|
235
|
+
]);
|
|
236
|
+
/**
|
|
237
|
+
* The shapes a schema's output can have: read through its wrappers, the options of a union, the sides of an
|
|
238
|
+
* intersection, a lazy and the kit's codecs, every option to the end. Every shape for anything opaque, whose output
|
|
239
|
+
* nothing here reads, for a read that meets a path more than {@link MAX_RECURSION_DEPTH} lazies long, and for one that
|
|
240
|
+
* runs out of the call's {@link MAX_READ_SCHEMAS}: a lazy that builds a new choice each read never meets a schema
|
|
241
|
+
* twice, and every shape is the answer that masks the most. Depth first, and the first path that runs out ends the
|
|
242
|
+
* read. A schema read to its end is remembered for the call with how deep it went, so a choice reused in many places
|
|
243
|
+
* is read once. A schema whose cycles all come round to itself or below is remembered for every read; one inside a
|
|
244
|
+
* cycle that comes round above it is remembered while that schema is still on the path, so a choice reused inside a
|
|
245
|
+
* cycle is read once too.
|
|
246
|
+
*/
|
|
247
|
+
function kindsOf(schema, call, lazies = 0) {
|
|
248
|
+
const read = {
|
|
249
|
+
call,
|
|
250
|
+
stopped: false,
|
|
251
|
+
past: false,
|
|
252
|
+
deepest: 0,
|
|
253
|
+
low: Number.POSITIVE_INFINITY,
|
|
254
|
+
stamps: 0,
|
|
255
|
+
path: /* @__PURE__ */ new Map(),
|
|
256
|
+
stack: [],
|
|
257
|
+
within: /* @__PURE__ */ new Map()
|
|
258
|
+
};
|
|
259
|
+
const found = readKinds(schema, lazies, read);
|
|
260
|
+
return read.stopped ? EVERY_KIND : found;
|
|
261
|
+
}
|
|
262
|
+
/** A schema's answer met again, `depth` lazies down: it would go as deep below as it did before. */
|
|
263
|
+
function replay(known, depth, read) {
|
|
264
|
+
if (depth + known.reach > 256) {
|
|
265
|
+
read.stopped = true;
|
|
266
|
+
read.past = true;
|
|
267
|
+
}
|
|
268
|
+
read.deepest = Math.max(read.deepest, depth + known.reach);
|
|
269
|
+
return read.stopped ? EVERY_KIND : known.kinds;
|
|
270
|
+
}
|
|
271
|
+
/** One step of {@link kindsOf}, `depth` lazies down. */
|
|
272
|
+
function readKinds(schema, depth, read) {
|
|
273
|
+
if (read.stopped) return EVERY_KIND;
|
|
274
|
+
const stop = read.call.stops.get(schema);
|
|
275
|
+
if (depth > 256 || stop !== void 0 && depth >= stop) {
|
|
276
|
+
read.stopped = true;
|
|
277
|
+
read.past = true;
|
|
278
|
+
return EVERY_KIND;
|
|
279
|
+
}
|
|
280
|
+
read.deepest = Math.max(read.deepest, depth);
|
|
281
|
+
const known = read.call.kinds.get(schema);
|
|
282
|
+
if (known) return replay(known, depth, read);
|
|
283
|
+
if (!take(read.call)) {
|
|
284
|
+
read.stopped = true;
|
|
285
|
+
return EVERY_KIND;
|
|
286
|
+
}
|
|
287
|
+
const def = defOf(schema);
|
|
288
|
+
const one = SCHEMA_KINDS[def.type];
|
|
289
|
+
if (one !== void 0) return widened(def, /* @__PURE__ */ new Set([one]));
|
|
290
|
+
if (NO_VALUE.has(def.type)) return NO_KIND;
|
|
291
|
+
if (def.type === "literal" || def.type === "enum") {
|
|
292
|
+
const values = schema._zod.values;
|
|
293
|
+
return values ? new Set([...values].flatMap((value) => [kindOfValue(value)].filter(isKind))) : EVERY_KIND;
|
|
294
|
+
}
|
|
295
|
+
const onPath = read.path.get(schema);
|
|
296
|
+
if (onPath) {
|
|
297
|
+
read.low = Math.min(read.low, onPath.index);
|
|
298
|
+
return EVERY_KIND;
|
|
299
|
+
}
|
|
300
|
+
const cycled = read.within.get(schema);
|
|
301
|
+
const anchor = cycled && read.path.get(cycled.anchor);
|
|
302
|
+
if (cycled && anchor?.stamp === cycled.stamp) {
|
|
303
|
+
read.low = Math.min(read.low, anchor.index);
|
|
304
|
+
return replay(cycled.found, depth, read);
|
|
305
|
+
}
|
|
306
|
+
const { deepest, low } = read;
|
|
307
|
+
const index = read.stack.length;
|
|
308
|
+
read.stamps += 1;
|
|
309
|
+
read.deepest = depth;
|
|
310
|
+
read.low = Number.POSITIVE_INFINITY;
|
|
311
|
+
read.path.set(schema, {
|
|
312
|
+
index,
|
|
313
|
+
stamp: read.stamps
|
|
314
|
+
});
|
|
315
|
+
read.stack.push(schema);
|
|
316
|
+
let found;
|
|
317
|
+
try {
|
|
318
|
+
found = widened(def, readStep(schema, def, depth, read));
|
|
319
|
+
} finally {
|
|
320
|
+
read.path.delete(schema);
|
|
321
|
+
read.stack.pop();
|
|
322
|
+
}
|
|
323
|
+
const reach = read.deepest - depth;
|
|
324
|
+
const below = read.low;
|
|
325
|
+
read.deepest = Math.max(deepest, read.deepest);
|
|
326
|
+
read.low = Math.min(low, below);
|
|
327
|
+
if (read.stopped && read.past && below >= index) {
|
|
328
|
+
const known = read.call.stops.get(schema);
|
|
329
|
+
read.call.stops.set(schema, known === void 0 ? depth : Math.min(known, depth));
|
|
330
|
+
}
|
|
331
|
+
if (!read.stopped) {
|
|
332
|
+
const answer = {
|
|
333
|
+
kinds: found,
|
|
334
|
+
reach
|
|
335
|
+
};
|
|
336
|
+
if (below >= index) read.call.kinds.set(schema, answer);
|
|
337
|
+
else {
|
|
338
|
+
const to = read.stack[below];
|
|
339
|
+
read.within.set(schema, {
|
|
340
|
+
found: answer,
|
|
341
|
+
anchor: to,
|
|
342
|
+
stamp: read.path.get(to).stamp
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return found;
|
|
347
|
+
}
|
|
348
|
+
/** {@link readKinds} for a node that holds other schemas. */
|
|
349
|
+
function readStep(schema, def, depth, read) {
|
|
350
|
+
if (WRAPPERS.has(def.type) && def.innerType) return readKinds(def.innerType, depth, read);
|
|
351
|
+
if (def.type === "union" && def.options) {
|
|
352
|
+
const joined = /* @__PURE__ */ new Set();
|
|
353
|
+
for (const option of def.options) {
|
|
354
|
+
for (const kind of readKinds(option, depth, read)) joined.add(kind);
|
|
355
|
+
if (joined.size === EVERY_KIND.size || read.stopped) break;
|
|
356
|
+
}
|
|
357
|
+
return joined;
|
|
358
|
+
}
|
|
359
|
+
if (def.type === "intersection" && def.left && def.right) {
|
|
360
|
+
const left = readKinds(def.left, depth, read);
|
|
361
|
+
if (left.size === 0) return NO_KIND;
|
|
362
|
+
const right = readKinds(def.right, depth, read);
|
|
363
|
+
return new Set([...left].filter((kind) => right.has(kind)));
|
|
364
|
+
}
|
|
365
|
+
if (def.type === "lazy") {
|
|
366
|
+
if (!take(read.call)) {
|
|
367
|
+
read.stopped = true;
|
|
368
|
+
return EVERY_KIND;
|
|
369
|
+
}
|
|
370
|
+
const inner = lazyInner(schema);
|
|
371
|
+
return inner ? readKinds(inner, depth + 1, read) : EVERY_KIND;
|
|
372
|
+
}
|
|
373
|
+
const codec = kitCodecOutput(schema);
|
|
374
|
+
return codec ? readKinds(codec, depth, read) : EVERY_KIND;
|
|
375
|
+
}
|
|
376
|
+
function isKind(kind) {
|
|
377
|
+
return kind !== void 0;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* The class a field was marked with, read through its wrappers — `pii(z.string()).optional()` is `pii` — or
|
|
381
|
+
* `null` when nothing along the chain carries one. A field marked twice, `pii(secret(x))`, is its strongest class.
|
|
382
|
+
* It never throws: a schema whose metadata cannot be read, cloned by `.describe()` or `.meta()` more times than Zod
|
|
383
|
+
* can read it back through, may carry any class, so it answers the strongest of {@link unreadClasses}.
|
|
384
|
+
*/
|
|
385
|
+
function privacyOf(schema) {
|
|
386
|
+
try {
|
|
387
|
+
for (let node = schema; node;) {
|
|
388
|
+
const found = strongestPrivacyClass(ownPrivacyClasses(node));
|
|
389
|
+
if (found !== void 0) return found;
|
|
390
|
+
const def = defOf(node);
|
|
391
|
+
node = WRAPPERS.has(def.type) ? def.innerType : void 0;
|
|
392
|
+
}
|
|
393
|
+
} catch {
|
|
394
|
+
return strongestPrivacyClass(unreadClasses()) ?? null;
|
|
395
|
+
}
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
/** The schema one of the kit's three codecs decodes to, or `undefined` for any other node. */
|
|
399
|
+
function kitCodecOutput(schema) {
|
|
400
|
+
const def = defOf(schema);
|
|
401
|
+
if (def.type !== "pipe") return void 0;
|
|
402
|
+
return def === defOf(SQLiteDate) || def === defOf(SQLiteBoolean) || isSqliteJson(schema) ? def.out : void 0;
|
|
403
|
+
}
|
|
404
|
+
/** What a lazy stands for: Zod's own cached target, the schema `parse` ran. Never its getter called again. */
|
|
405
|
+
function lazyInner(schema) {
|
|
406
|
+
const inner = schema._zod.innerType;
|
|
407
|
+
return isSchema(inner) ? inner : void 0;
|
|
408
|
+
}
|
|
409
|
+
/** An object's fields, read through the shape Zod freezes on first read, so every walk sees the schemas `parse` does. */
|
|
410
|
+
function shapeOf(schema) {
|
|
411
|
+
return defOf(schema).shape ?? {};
|
|
412
|
+
}
|
|
413
|
+
/** Every schema a node holds, whatever its kind: what a search for a mark reads through. */
|
|
414
|
+
function children(schema) {
|
|
415
|
+
const def = defOf(schema);
|
|
416
|
+
if (def.type === "lazy") {
|
|
417
|
+
const inner = lazyInner(schema);
|
|
418
|
+
return inner ? [inner] : [];
|
|
419
|
+
}
|
|
420
|
+
const found = def.type === "object" ? Object.values(shapeOf(schema)).filter(isSchema) : [];
|
|
421
|
+
for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(def))) {
|
|
422
|
+
if (key === "checks" || key === "getter" || !("value" in descriptor)) continue;
|
|
423
|
+
const slot = descriptor.value;
|
|
424
|
+
if (isSchema(slot)) found.push(slot);
|
|
425
|
+
else if (Array.isArray(slot)) {
|
|
426
|
+
for (const each of slot) if (isSchema(each)) found.push(each);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return found;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Every class marked anywhere beneath a schema, on every side of every node. Level by level, each level in the order
|
|
433
|
+
* the schema declares it, and each schema once. A path more than {@link MAX_RECURSION_DEPTH} levels long, counting
|
|
434
|
+
* each lazy and each structural node, ends there and marks the search `truncated` and `unread`; every other path is
|
|
435
|
+
* still read, so a class beside a recursion is found whatever the field order. Each schema is met once, so a recursion
|
|
436
|
+
* defined once comes round to a schema already read and never reaches the limit: only a chain of schemas not met
|
|
437
|
+
* before does, a factory or nesting deeper than the limit, and past it any class may be marked, so every one is
|
|
438
|
+
* assumed there. Each schema it meets costs one of the call's
|
|
439
|
+
* {@link MAX_READ_SCHEMAS}, so shallowest first, a class above the point the budget runs out on is found. There the
|
|
440
|
+
* search ends `truncated` and `unread`: what lies past it was never read, so every class is assumed there, and the
|
|
441
|
+
* search never says less than the schema marks. Remembered for the call, so a schema searched again costs nothing.
|
|
442
|
+
*/
|
|
443
|
+
function search(schema, call) {
|
|
444
|
+
const known = call.searched.get(schema);
|
|
445
|
+
if (known) return known;
|
|
446
|
+
const classes = /* @__PURE__ */ new Set();
|
|
447
|
+
let truncated = false;
|
|
448
|
+
let unread = false;
|
|
449
|
+
let spent = false;
|
|
450
|
+
const seen = /* @__PURE__ */ new Set();
|
|
451
|
+
let level = [schema];
|
|
452
|
+
for (let depth = 0; level.length > 0 && !spent; depth += 1) {
|
|
453
|
+
const next = [];
|
|
454
|
+
const stack = [...level].reverse();
|
|
455
|
+
for (let node = stack.pop(); node; node = stack.pop()) {
|
|
456
|
+
if (seen.has(node)) continue;
|
|
457
|
+
if (!take(call)) {
|
|
458
|
+
truncated = true;
|
|
459
|
+
unread = true;
|
|
460
|
+
spent = true;
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
seen.add(node);
|
|
464
|
+
for (const own of ownPrivacyClasses(node)) classes.add(own);
|
|
465
|
+
const def = defOf(node);
|
|
466
|
+
if (def.type !== "lazy" && !STRUCTURAL.has(def.type)) {
|
|
467
|
+
const inside = children(node);
|
|
468
|
+
for (let index = inside.length - 1; index >= 0; index -= 1) stack.push(inside[index]);
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
if (depth + 1 > 256) {
|
|
472
|
+
truncated = true;
|
|
473
|
+
unread = true;
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
if (!take(call)) {
|
|
477
|
+
truncated = true;
|
|
478
|
+
unread = true;
|
|
479
|
+
spent = true;
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
if (def.type === "object") readShape(node, call);
|
|
483
|
+
for (const child of children(node)) next.push(child);
|
|
484
|
+
}
|
|
485
|
+
level = next;
|
|
486
|
+
}
|
|
487
|
+
const found = {
|
|
488
|
+
classes,
|
|
489
|
+
truncated,
|
|
490
|
+
unread
|
|
491
|
+
};
|
|
492
|
+
call.searched.set(schema, found);
|
|
493
|
+
return found;
|
|
494
|
+
}
|
|
495
|
+
/** An id per schema, so a list of schemas has a key. */
|
|
496
|
+
const ids = /* @__PURE__ */ new WeakMap();
|
|
497
|
+
let lastId = 0;
|
|
498
|
+
function idOf(schema) {
|
|
499
|
+
let id = ids.get(schema);
|
|
500
|
+
if (id === void 0) {
|
|
501
|
+
lastId += 1;
|
|
502
|
+
id = lastId;
|
|
503
|
+
ids.set(schema, id);
|
|
504
|
+
}
|
|
505
|
+
return id;
|
|
506
|
+
}
|
|
507
|
+
/** The schema each leaf of one kind holds at a slot, read by `pick`. */
|
|
508
|
+
function slots(leaves, kinds, pick) {
|
|
509
|
+
return leaves.filter((leaf) => kinds.includes(defOf(leaf).type)).flatMap((leaf) => [pick(defOf(leaf))].filter(isSchema));
|
|
510
|
+
}
|
|
511
|
+
/** A catchall a value's extra key is parsed by: none for a stripped object, `never` for a strict one. */
|
|
512
|
+
function openCatchall(def) {
|
|
513
|
+
return def.catchall && defOf(def.catchall).type !== "never" ? def.catchall : void 0;
|
|
514
|
+
}
|
|
515
|
+
/** The position `schemas` reach, read once in the call for that list of schemas. */
|
|
516
|
+
function positionOf(schemas, call) {
|
|
517
|
+
const distinct = [...new Set(schemas)];
|
|
518
|
+
if (distinct.length === 0) return void 0;
|
|
519
|
+
const key = distinct.map(idOf).join(",");
|
|
520
|
+
const known = call.positions.get(key);
|
|
521
|
+
if (known) return known;
|
|
522
|
+
const { position, leaves, signature } = readPosition(distinct, call);
|
|
523
|
+
let found = position;
|
|
524
|
+
if (leaves.length > 0) {
|
|
525
|
+
const leafKey = `${leaves.map(idOf).join(",")}${position.other ? "+" : ""}#${signature}`;
|
|
526
|
+
found = call.walked.get(leafKey) ?? position;
|
|
527
|
+
call.walked.set(leafKey, found);
|
|
528
|
+
}
|
|
529
|
+
call.positions.set(key, found);
|
|
530
|
+
return found;
|
|
531
|
+
}
|
|
532
|
+
/** The kind of value a structural node holds. */
|
|
533
|
+
function leafKind(leaf) {
|
|
534
|
+
return SCHEMA_KINDS[defOf(leaf).type];
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Every class in `reaches` and beneath `leaves`, and every class assumed where a read there ran out: what a mask made
|
|
538
|
+
* of them carries. `undefined` where none is marked and no read there was cut off or ran out.
|
|
539
|
+
*/
|
|
540
|
+
function carried(reaches, leaves, call) {
|
|
541
|
+
const searches = leaves.map((leaf) => search(leaf, call));
|
|
542
|
+
const found = /* @__PURE__ */ new Set([...reaches.flatMap((reach) => reach.classes), ...searches.flatMap((each) => [...each.classes])]);
|
|
543
|
+
const truncated = reaches.some((reach) => reach.truncated) || searches.some((each) => each.truncated);
|
|
544
|
+
const unread = reaches.some((reach) => reach.unread) || searches.some((each) => each.unread);
|
|
545
|
+
if (found.size === 0 && !truncated && !unread) return void 0;
|
|
546
|
+
const assumed = unread ? unreadClasses().filter((name) => !found.has(name)) : [];
|
|
547
|
+
return {
|
|
548
|
+
className: strongestPrivacyClass([...found]) ?? "",
|
|
549
|
+
own: false,
|
|
550
|
+
classes: [...found, ...assumed],
|
|
551
|
+
assumed,
|
|
552
|
+
truncated
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
/** The mask made of `reaches` and the structural nodes masked with them, or `undefined` when none applies. */
|
|
556
|
+
function maskOf(reaches, leaves, call) {
|
|
557
|
+
if (reaches.length === 0) return void 0;
|
|
558
|
+
const mask = carried(reaches, leaves, call);
|
|
559
|
+
if (mask === void 0) return void 0;
|
|
560
|
+
const own = !mask.truncated && reaches.every((reach) => !reach.opaque) && mask.classes.length === 1;
|
|
561
|
+
return {
|
|
562
|
+
...mask,
|
|
563
|
+
own
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* The mask for a value of a kind or a prototype the schemas here do not expect: every class in `reaches` and beneath
|
|
568
|
+
* `leaves`, as beneath an opaque node, or `undefined` where none is marked and no read there was cut off or ran out.
|
|
569
|
+
*/
|
|
570
|
+
function misfitMaskOf(reaches, leaves, call) {
|
|
571
|
+
return carried(reaches, leaves, call);
|
|
572
|
+
}
|
|
573
|
+
/** A mask, written out: two positions with the same leaves and the same masks are one. */
|
|
574
|
+
function maskKey(mask) {
|
|
575
|
+
const read = mask.truncated ? "truncated" : "read";
|
|
576
|
+
return `${mask.own ? "own" : "strongest"}:${read}:${mask.classes.join("|")}:${mask.assumed.join("|")}`;
|
|
577
|
+
}
|
|
578
|
+
/** A set of shapes, written out the same way whatever order it was built in. */
|
|
579
|
+
function gateKey(gate) {
|
|
580
|
+
return VALUE_KINDS.map((kind) => gate.has(kind) ? "1" : "0").join("");
|
|
581
|
+
}
|
|
582
|
+
/** Flatten a position and decide how it is masked. Its children are read when first asked for. */
|
|
583
|
+
function readPosition(schemas, call) {
|
|
584
|
+
const reaches = [];
|
|
585
|
+
const leaves = /* @__PURE__ */ new Set();
|
|
586
|
+
const met = /* @__PURE__ */ new Set();
|
|
587
|
+
const lazyGates = /* @__PURE__ */ new Set();
|
|
588
|
+
let other = false;
|
|
589
|
+
const named = /* @__PURE__ */ new Set();
|
|
590
|
+
const taken = /* @__PURE__ */ new Set();
|
|
591
|
+
/** An opaque node: masked whole with every class beneath it, and kept where there is none. */
|
|
592
|
+
const opaque = (node, gate) => {
|
|
593
|
+
const own = scalarKinds(node);
|
|
594
|
+
for (const kind of gate) if (!own || own.has(kind)) (own ? named : taken).add(kind);
|
|
595
|
+
const inside = search(node, call);
|
|
596
|
+
if (inside.classes.size > 0 || inside.truncated) reaches.push({
|
|
597
|
+
gate,
|
|
598
|
+
classes: [...inside.classes],
|
|
599
|
+
opaque: true,
|
|
600
|
+
truncated: inside.truncated,
|
|
601
|
+
unread: inside.unread
|
|
602
|
+
});
|
|
603
|
+
other = true;
|
|
604
|
+
};
|
|
605
|
+
/**
|
|
606
|
+
* A schema the call ran out of its budget before it read, or a lazy nested past the limit: cut off, and only this
|
|
607
|
+
* path. Nothing beneath it was read, so any class may be marked there, and every one is assumed. A lazy is met once
|
|
608
|
+
* per position, so only a chain of lazies not met before, each built anew, reaches the limit.
|
|
609
|
+
*/
|
|
610
|
+
const cut = (gate) => {
|
|
611
|
+
reaches.push({
|
|
612
|
+
gate,
|
|
613
|
+
classes: [],
|
|
614
|
+
opaque: true,
|
|
615
|
+
truncated: true,
|
|
616
|
+
unread: true
|
|
617
|
+
});
|
|
618
|
+
for (const kind of gate) taken.add(kind);
|
|
619
|
+
other = true;
|
|
620
|
+
};
|
|
621
|
+
let level = schemas.map((schema) => [
|
|
622
|
+
schema,
|
|
623
|
+
EVERY_KIND,
|
|
624
|
+
false
|
|
625
|
+
]);
|
|
626
|
+
for (let lazies = 0; level.length > 0; lazies += 1) {
|
|
627
|
+
const next = [];
|
|
628
|
+
const stack = [...level].reverse();
|
|
629
|
+
for (let entry = stack.pop(); entry; entry = stack.pop()) {
|
|
630
|
+
const [node, reached, option] = entry;
|
|
631
|
+
const seen = `${idOf(node)}:${gateKey(reached)}:${option ? "option" : ""}`;
|
|
632
|
+
if (met.has(seen)) continue;
|
|
633
|
+
met.add(seen);
|
|
634
|
+
if (!take(call)) {
|
|
635
|
+
cut(reached);
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
const kinds = option ? kindsOf(node, call, lazies) : EVERY_KIND;
|
|
639
|
+
const gate = option ? new Set([...reached].filter((kind) => kinds.has(kind))) : reached;
|
|
640
|
+
if (gate.size === 0) continue;
|
|
641
|
+
if (ownPrivacyClasses(node).length > 0) {
|
|
642
|
+
const inside = search(node, call);
|
|
643
|
+
reaches.push({
|
|
644
|
+
gate,
|
|
645
|
+
classes: [.../* @__PURE__ */ new Set([...ownPrivacyClasses(node), ...inside.classes])],
|
|
646
|
+
opaque: false,
|
|
647
|
+
truncated: inside.truncated,
|
|
648
|
+
unread: inside.unread
|
|
649
|
+
});
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
const def = defOf(node);
|
|
653
|
+
const codec = kitCodecOutput(node);
|
|
654
|
+
if (makesValue(def) && holdsStructure(kindsOf(node, call, lazies))) {
|
|
655
|
+
opaque(node, gate);
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
if (def.type === "lazy") {
|
|
659
|
+
const key = `${idOf(node)}:${gateKey(gate)}`;
|
|
660
|
+
if (lazyGates.has(key)) continue;
|
|
661
|
+
lazyGates.add(key);
|
|
662
|
+
if (lazies >= 256) {
|
|
663
|
+
cut(gate);
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
if (!take(call)) {
|
|
667
|
+
cut(gate);
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
const inner = lazyInner(node);
|
|
671
|
+
if (inner) next.push([
|
|
672
|
+
inner,
|
|
673
|
+
gate,
|
|
674
|
+
false
|
|
675
|
+
]);
|
|
676
|
+
} else if (WRAPPERS.has(def.type) && def.innerType) {
|
|
677
|
+
const fallback = constantFallback(def);
|
|
678
|
+
const kind = fallback && kindOfValue(fallback.value);
|
|
679
|
+
if (fallback && kind && gate.has(kind)) (isTypical(fallback.value, kind) ? named : taken).add(kind);
|
|
680
|
+
stack.push([
|
|
681
|
+
def.innerType,
|
|
682
|
+
gate,
|
|
683
|
+
false
|
|
684
|
+
]);
|
|
685
|
+
} else if (def.type === "union" && def.options) for (const each of [...def.options].reverse()) stack.push([
|
|
686
|
+
each,
|
|
687
|
+
gate,
|
|
688
|
+
true
|
|
689
|
+
]);
|
|
690
|
+
else if (def.type === "intersection") for (const side of [def.right, def.left].filter(isSchema)) stack.push([
|
|
691
|
+
side,
|
|
692
|
+
gate,
|
|
693
|
+
false
|
|
694
|
+
]);
|
|
695
|
+
else if (codec) stack.push([
|
|
696
|
+
codec,
|
|
697
|
+
gate,
|
|
698
|
+
false
|
|
699
|
+
]);
|
|
700
|
+
else if (STRUCTURAL.has(def.type)) {
|
|
701
|
+
if (!gate.has(leafKind(node))) continue;
|
|
702
|
+
if (def.type === "object") {
|
|
703
|
+
if (!take(call)) {
|
|
704
|
+
cut(gate);
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
readShape(node, call);
|
|
708
|
+
}
|
|
709
|
+
leaves.add(node);
|
|
710
|
+
} else opaque(node, gate);
|
|
711
|
+
}
|
|
712
|
+
level = next;
|
|
713
|
+
}
|
|
714
|
+
const allLeaves = [...leaves];
|
|
715
|
+
const byKind = /* @__PURE__ */ new Map();
|
|
716
|
+
for (const kind of VALUE_KINDS) {
|
|
717
|
+
const mask = maskOf(reaches.filter((reach) => reach.gate.has(kind)), allLeaves.filter((leaf) => leafKind(leaf) === kind), call);
|
|
718
|
+
if (mask) byKind.set(kind, mask);
|
|
719
|
+
}
|
|
720
|
+
const leafList = allLeaves.filter((leaf) => !byKind.has(leafKind(leaf)));
|
|
721
|
+
const mask = maskOf(reaches, allLeaves.filter((leaf) => byKind.has(leafKind(leaf))), call);
|
|
722
|
+
const nameMask = maskOf(reaches.filter((reach) => [...reach.gate].some((kind) => kind !== "symbol")), allLeaves.filter((leaf) => byKind.has(leafKind(leaf))), call);
|
|
723
|
+
const whole = byKind.size === VALUE_KINDS.length;
|
|
724
|
+
for (const leaf of allLeaves) named.add(leafKind(leaf));
|
|
725
|
+
const signature = `${[...byKind].map(([kind, each]) => `${kind}=${maskKey(each)}`).join(";")}/${gateKey(named)}/${gateKey(taken)}`;
|
|
726
|
+
const memo = /* @__PURE__ */ new Map();
|
|
727
|
+
const once = (slot, read) => {
|
|
728
|
+
if (!memo.has(slot)) memo.set(slot, positionOf(read(), call));
|
|
729
|
+
return memo.get(slot);
|
|
730
|
+
};
|
|
731
|
+
const fields = /* @__PURE__ */ new Map();
|
|
732
|
+
const objects = leafList.filter((leaf) => defOf(leaf).type === "object");
|
|
733
|
+
const tuples = leafList.filter((leaf) => defOf(leaf).type === "tuple").map(defOf);
|
|
734
|
+
const records = slots(leafList, ["record"], (def) => def.valueType);
|
|
735
|
+
const arrays = slots(leafList, ["array"], (def) => def.element);
|
|
736
|
+
const catchalls = objects.flatMap((leaf) => [openCatchall(defOf(leaf))].filter(isSchema));
|
|
737
|
+
const open = objects.flatMap((leaf, index) => {
|
|
738
|
+
const schema = openCatchall(defOf(leaf));
|
|
739
|
+
return schema ? [{
|
|
740
|
+
index,
|
|
741
|
+
schema
|
|
742
|
+
}] : [];
|
|
743
|
+
});
|
|
744
|
+
let naming;
|
|
745
|
+
const namingOf = () => {
|
|
746
|
+
if (!naming) {
|
|
747
|
+
naming = /* @__PURE__ */ new Map();
|
|
748
|
+
objects.forEach((leaf, index) => {
|
|
749
|
+
for (const key of Reflect.ownKeys(shapeOf(leaf))) {
|
|
750
|
+
const list = naming?.get(key);
|
|
751
|
+
if (list) list.push(index);
|
|
752
|
+
else naming?.set(key, [index]);
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
return naming;
|
|
757
|
+
};
|
|
758
|
+
/** The schemas a key is read by, in the objects' order: an object's own where it names the key, else its catchall. */
|
|
759
|
+
const namedBy = (name, indexes) => {
|
|
760
|
+
const out = [];
|
|
761
|
+
let next = 0;
|
|
762
|
+
for (const index of indexes) {
|
|
763
|
+
for (; next < open.length && (open[next]?.index ?? index) < index; next += 1) out.push(open[next]?.schema);
|
|
764
|
+
if (open[next]?.index === index) next += 1;
|
|
765
|
+
out.push(shapeOf(objects[index])[name]);
|
|
766
|
+
}
|
|
767
|
+
for (; next < open.length; next += 1) out.push(open[next]?.schema);
|
|
768
|
+
return out;
|
|
769
|
+
};
|
|
770
|
+
let unnamed;
|
|
771
|
+
const misfits = /* @__PURE__ */ new Map();
|
|
772
|
+
return {
|
|
773
|
+
position: {
|
|
774
|
+
mask,
|
|
775
|
+
nameMask,
|
|
776
|
+
whole,
|
|
777
|
+
maskFor: (value) => {
|
|
778
|
+
const kind = kindOfValue(value);
|
|
779
|
+
return kind === void 0 ? void 0 : byKind.get(kind);
|
|
780
|
+
},
|
|
781
|
+
misfit: (value) => {
|
|
782
|
+
const kind = kindOfValue(value);
|
|
783
|
+
if (kind === void 0) return void 0;
|
|
784
|
+
const expected = named.has(kind);
|
|
785
|
+
if (expected ? isTypical(value, kind) : taken.has(kind)) return void 0;
|
|
786
|
+
let misfit = misfits.get(kind);
|
|
787
|
+
if (!misfit) {
|
|
788
|
+
misfit = { mask: expected ? misfitMaskOf([], leafList.filter((leaf) => leafKind(leaf) === kind), call) : misfitMaskOf(reaches, allLeaves, call) };
|
|
789
|
+
misfits.set(kind, misfit);
|
|
790
|
+
}
|
|
791
|
+
return misfit;
|
|
792
|
+
},
|
|
793
|
+
kinds: new Set(leafList.map((leaf) => defOf(leaf).type)),
|
|
794
|
+
other,
|
|
795
|
+
get keys() {
|
|
796
|
+
return [...namingOf().keys()];
|
|
797
|
+
},
|
|
798
|
+
field: (name) => {
|
|
799
|
+
if (!fields.has(name)) {
|
|
800
|
+
const indexes = namingOf().get(name);
|
|
801
|
+
if (indexes) fields.set(name, positionOf([...namedBy(name, indexes), ...records], call));
|
|
802
|
+
else {
|
|
803
|
+
unnamed ??= { at: positionOf([...open.map((each) => each.schema), ...records], call) };
|
|
804
|
+
fields.set(name, unnamed.at);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
return fields.get(name);
|
|
808
|
+
},
|
|
809
|
+
get catchall() {
|
|
810
|
+
return once("catchall", () => [...catchalls, ...records]);
|
|
811
|
+
},
|
|
812
|
+
get key() {
|
|
813
|
+
return once("key", () => slots(leafList, ["record"], (def) => def.keyType));
|
|
814
|
+
},
|
|
815
|
+
items: tuples.reduce((most, def) => Math.max(most, def.items?.length ?? 0), 0),
|
|
816
|
+
item: (index) => once(`item:${index}`, () => [...arrays, ...tuples.flatMap((def) => [def.items?.[index] ?? def.rest].filter(isSchema))]),
|
|
817
|
+
get rest() {
|
|
818
|
+
return once("rest", () => [...arrays, ...tuples.flatMap((def) => [def.rest].filter(isSchema))]);
|
|
819
|
+
},
|
|
820
|
+
get mapKey() {
|
|
821
|
+
return once("mapKey", () => slots(leafList, ["map"], (def) => def.keyType));
|
|
822
|
+
},
|
|
823
|
+
get mapValue() {
|
|
824
|
+
return once("mapValue", () => slots(leafList, ["map"], (def) => def.valueType));
|
|
825
|
+
},
|
|
826
|
+
get element() {
|
|
827
|
+
return once("element", () => slots(leafList, ["set"], (def) => def.valueType));
|
|
828
|
+
}
|
|
829
|
+
},
|
|
830
|
+
leaves: leafList,
|
|
831
|
+
signature
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
/** The position at the root of a schema, read in `call`: a new one, with the whole budget, unless one is given. */
|
|
835
|
+
function rootPosition(schema, call = newCall()) {
|
|
836
|
+
return positionOf([schema], call);
|
|
837
|
+
}
|
|
838
|
+
/** `parent.key`, or `key` at the root; an index or a bracket joins without a dot. */
|
|
839
|
+
function join(path, segment) {
|
|
840
|
+
return path === "" || segment.startsWith("[") ? `${path}${segment}` : `${path}.${segment}`;
|
|
841
|
+
}
|
|
842
|
+
/** Whether a frame's position is one it sits below: a recursion defined once, come round. */
|
|
843
|
+
function onLine(frame) {
|
|
844
|
+
for (let above = frame.parent; above; above = above.parent) if (above.at === frame.at) return true;
|
|
845
|
+
return false;
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Whether a frame is cut off at the limit with every class assumed: nested deeper than {@link MAX_RECURSION_DEPTH}, at
|
|
849
|
+
* a position not met on its path. A position is a set of schemas, and it is the position that is compared: a recursion
|
|
850
|
+
* defined once comes round to a position (the same set of schemas) already read, whose classes are listed where it was
|
|
851
|
+
* read, so it needs no cut. A chain that keeps producing positions not yet seen, a factory, nesting deeper than the
|
|
852
|
+
* limit, or once-defined cycles of different lengths side by side in a union, was never read past it: any class may be
|
|
853
|
+
* marked there, though each schema on the chain may be one already read.
|
|
854
|
+
*/
|
|
855
|
+
function cutOff(frame) {
|
|
856
|
+
return frame.depth > 256 && !onLine(frame);
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* A frame for the position `read` returns. A schema nested deeper than the stack can follow, or one whose getter
|
|
860
|
+
* throws, cannot be read: its frame is `unread`, listed with every class, and nothing throws.
|
|
861
|
+
*/
|
|
862
|
+
function frameAt(read, place) {
|
|
863
|
+
try {
|
|
864
|
+
return {
|
|
865
|
+
...place,
|
|
866
|
+
at: read()
|
|
867
|
+
};
|
|
868
|
+
} catch {
|
|
869
|
+
return {
|
|
870
|
+
...place,
|
|
871
|
+
at: void 0,
|
|
872
|
+
unread: true
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
/** The frames below a position that is read, in the order the schema names them. */
|
|
877
|
+
function framesBelow(frame, at) {
|
|
878
|
+
const next = frame.depth + 1;
|
|
879
|
+
const below = [];
|
|
880
|
+
const add = (child, path, depth, key) => {
|
|
881
|
+
const found = frameAt(child, key === void 0 ? {
|
|
882
|
+
path,
|
|
883
|
+
depth,
|
|
884
|
+
parent: frame
|
|
885
|
+
} : {
|
|
886
|
+
path,
|
|
887
|
+
depth,
|
|
888
|
+
key,
|
|
889
|
+
parent: frame
|
|
890
|
+
});
|
|
891
|
+
if (found.at !== void 0 || found.unread) below.push(found);
|
|
892
|
+
};
|
|
893
|
+
for (const key of at.keys) if (typeof key === "string") add(() => at.field(key), join(frame.path, key), next, key);
|
|
894
|
+
add(() => at.catchall, join(frame.path, "*"), next);
|
|
895
|
+
add(() => at.key, join(frame.path, "<key>"), frame.depth);
|
|
896
|
+
for (let index = 0; index < at.items; index += 1) add(() => at.item(index), `${frame.path}[${index}]`, next);
|
|
897
|
+
add(() => at.rest, `${frame.path}[]`, next);
|
|
898
|
+
add(() => at.mapKey, join(frame.path, "<key>"), next);
|
|
899
|
+
add(() => at.mapValue, join(frame.path, "*"), next);
|
|
900
|
+
add(() => at.element, `${frame.path}[]`, next);
|
|
901
|
+
return below;
|
|
902
|
+
}
|
|
903
|
+
/** Every class a frame lists at its own path, once each, with whether it is only assumed. */
|
|
904
|
+
function ownClasses(frame) {
|
|
905
|
+
const found = /* @__PURE__ */ new Map();
|
|
906
|
+
const mask = frame.at?.mask;
|
|
907
|
+
if (mask) for (const name of mask.classes) found.set(name, mask.assumed.includes(name));
|
|
908
|
+
if (frame.unread || cutOff(frame)) {
|
|
909
|
+
for (const name of unreadClasses()) if (!found.has(name)) found.set(name, true);
|
|
910
|
+
}
|
|
911
|
+
return [...found];
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* Every class listed beneath each position the walk read, by the first frame that read it, and whether each is only
|
|
915
|
+
* assumed. A class found anywhere beneath is found. A position beneath itself, a recursion defined once, is read until
|
|
916
|
+
* nothing changes, last read first, so a chain of reused schemas settles in one pass.
|
|
917
|
+
*/
|
|
918
|
+
function classesBeneath(walked) {
|
|
919
|
+
const beneath = /* @__PURE__ */ new Map();
|
|
920
|
+
for (const at of walked.keys()) beneath.set(at, /* @__PURE__ */ new Map());
|
|
921
|
+
const firsts = [...walked].reverse();
|
|
922
|
+
for (let changed = true; changed;) {
|
|
923
|
+
changed = false;
|
|
924
|
+
for (const [at, first] of firsts) {
|
|
925
|
+
const into = beneath.get(at);
|
|
926
|
+
const merge = (name, assumed) => {
|
|
927
|
+
const known = into.get(name);
|
|
928
|
+
if (known === void 0 || known && !assumed) {
|
|
929
|
+
into.set(name, assumed);
|
|
930
|
+
changed = true;
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
for (const child of first.below ?? []) {
|
|
934
|
+
if (child.depth > 256 && !cutOff(child)) continue;
|
|
935
|
+
for (const [name, assumed] of ownClasses(child)) merge(name, assumed);
|
|
936
|
+
if (child.depth > 256) continue;
|
|
937
|
+
const below = child.at && beneath.get(child.at);
|
|
938
|
+
if (below) for (const [name, assumed] of below) merge(name, assumed);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
return beneath;
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* Walk a schema tree the way {@link classifyPrivacy} does, from `path`, and report to `visitor`. Exported so a second
|
|
946
|
+
* reader of the tree — the CLI's scan — cannot walk it differently. A position met again below itself is a recursion
|
|
947
|
+
* defined once, and is not walked again: its fields are listed once. A position nested more than
|
|
948
|
+
* {@link MAX_RECURSION_DEPTH} levels deep is cut off by `redact` as {@link TRUNCATED_MASK}; where it is a position not
|
|
949
|
+
* met on its path, nothing past it was read, so it is listed with every class, each truncated.
|
|
950
|
+
*
|
|
951
|
+
* Positions are read level by level, shallowest first, and reported in the schema's own order, all in one call with one
|
|
952
|
+
* {@link MAX_READ_SCHEMAS}. Each distinct position is read once, wherever it sits; every level above the one the budget
|
|
953
|
+
* runs out on is read whole, and each position it did not read is listed with {@link unreadClasses}, each truncated.
|
|
954
|
+
* Once {@link MAX_LISTED_PATHS} paths are written out, a position met again is a reference: its path is listed with
|
|
955
|
+
* every class beneath it and the path it was first written out under, and nothing below it is written out again. So the
|
|
956
|
+
* walk is bounded by the budget, however many paths reach one schema. A schema that cannot be read at all, nested
|
|
957
|
+
* deeper than the stack or behind a getter that throws, is listed with every class, each truncated. It never throws.
|
|
958
|
+
*/
|
|
959
|
+
function walkPrivacy(schema, visitor, path = "") {
|
|
960
|
+
const call = newCall();
|
|
961
|
+
const root = frameAt(() => rootPosition(schema, call), {
|
|
962
|
+
path,
|
|
963
|
+
depth: 0
|
|
964
|
+
});
|
|
965
|
+
const queue = [root];
|
|
966
|
+
const walked = /* @__PURE__ */ new Map();
|
|
967
|
+
let references = false;
|
|
968
|
+
for (let index = 0; index < queue.length; index += 1) {
|
|
969
|
+
const frame = queue[index];
|
|
970
|
+
const { at } = frame;
|
|
971
|
+
if (at === void 0 || frame.depth > 256 || at.whole) continue;
|
|
972
|
+
if (at.kinds.size === 0 || onLine(frame)) continue;
|
|
973
|
+
const first = walked.get(at);
|
|
974
|
+
if (first === void 0) walked.set(at, frame);
|
|
975
|
+
else if (queue.length >= 5e4) {
|
|
976
|
+
frame.sameAs = first;
|
|
977
|
+
references = true;
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
980
|
+
frame.below = framesBelow(frame, at);
|
|
981
|
+
for (const child of frame.below) queue.push(child);
|
|
982
|
+
}
|
|
983
|
+
const beneath = references ? classesBeneath(walked) : /* @__PURE__ */ new Map();
|
|
984
|
+
const stack = [root];
|
|
985
|
+
for (let frame = stack.pop(); frame; frame = stack.pop()) {
|
|
986
|
+
const { at } = frame;
|
|
987
|
+
if (frame.depth <= 256 && at !== void 0 && at.mask === void 0 && frame.key !== void 0) visitor.unmarkedKey?.(frame.path, frame.key);
|
|
988
|
+
if (frame.depth <= 256 || cutOff(frame)) for (const [className, assumed] of ownClasses(frame)) visitor.marked(frame.path, className, assumed ? { truncated: true } : void 0);
|
|
989
|
+
if (frame.depth > 256) continue;
|
|
990
|
+
const reference = frame.sameAs;
|
|
991
|
+
if (reference?.at) for (const [className, assumed] of beneath.get(reference.at) ?? []) visitor.marked(frame.path, className, assumed ? {
|
|
992
|
+
truncated: true,
|
|
993
|
+
sameAs: reference.path
|
|
994
|
+
} : { sameAs: reference.path });
|
|
995
|
+
if (frame.below) for (let index = frame.below.length - 1; index >= 0; index -= 1) stack.push(frame.below[index]);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Every position `redact` masks in a schema, and its class — the Art. 30 record for one object, derived rather than
|
|
1000
|
+
* maintained. Empty for a schema with no markers. A path reached twice is listed once per class. It never throws.
|
|
1001
|
+
*/
|
|
1002
|
+
function classifyPrivacy(schema) {
|
|
1003
|
+
const fields = [];
|
|
1004
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
1005
|
+
walkPrivacy(schema, { marked: (path, className, note) => {
|
|
1006
|
+
const key = `${path}\u0000${className}`;
|
|
1007
|
+
if (byKey.has(key)) return;
|
|
1008
|
+
const field = {
|
|
1009
|
+
path,
|
|
1010
|
+
class: className
|
|
1011
|
+
};
|
|
1012
|
+
if (note?.truncated) field.truncated = true;
|
|
1013
|
+
if (note?.sameAs !== void 0) field.sameAs = note.sameAs;
|
|
1014
|
+
byKey.set(key, field);
|
|
1015
|
+
fields.push(field);
|
|
1016
|
+
} });
|
|
1017
|
+
return fields;
|
|
1018
|
+
}
|
|
1019
|
+
/** Whether a schema holds a field of `className` at any depth: exactly what {@link classifyPrivacy} lists. */
|
|
1020
|
+
function hasPrivacy(schema, className) {
|
|
1021
|
+
return classifyPrivacy(schema).some((field) => field.class === className);
|
|
1022
|
+
}
|
|
1023
|
+
//#endregion
|
|
1024
|
+
export { MAX_LISTED_PATHS, MAX_READ_SCHEMAS, MAX_RECURSION_DEPTH, TRUNCATED_MASK, classifyPrivacy, hasPrivacy, kindOfValue, newCall, positionOf, privacyOf, rootPosition, unreadClasses, walkPrivacy };
|