@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.
Files changed (270) hide show
  1. package/dist/access/access.d.ts +172 -0
  2. package/dist/access/access.d.ts.map +1 -0
  3. package/dist/access/access.js +147 -0
  4. package/dist/access/acting.d.ts +49 -0
  5. package/dist/access/acting.d.ts.map +1 -0
  6. package/dist/access/acting.js +21 -0
  7. package/dist/access/policyCheck.d.ts +33 -0
  8. package/dist/access/policyCheck.d.ts.map +1 -0
  9. package/dist/access/policyCheck.js +48 -0
  10. package/dist/access/usage.d.ts +83 -0
  11. package/dist/access/usage.d.ts.map +1 -0
  12. package/dist/access/usage.js +104 -0
  13. package/dist/access/visibility.d.ts +66 -0
  14. package/dist/access/visibility.d.ts.map +1 -0
  15. package/dist/access/visibility.js +86 -0
  16. package/dist/audit/auditEvent.d.ts +5 -0
  17. package/dist/audit/auditEvent.d.ts.map +1 -1
  18. package/dist/audit/auditEvent.js +2 -0
  19. package/dist/audit/recorder.d.ts +10 -2
  20. package/dist/audit/recorder.d.ts.map +1 -1
  21. package/dist/capability/bindings.d.ts +47 -2
  22. package/dist/capability/bindings.d.ts.map +1 -1
  23. package/dist/capability/bindings.js +117 -7
  24. package/dist/capability/capability.d.ts +110 -28
  25. package/dist/capability/capability.d.ts.map +1 -1
  26. package/dist/capability/capability.js +2 -0
  27. package/dist/capability/manifest.d.ts +2 -1
  28. package/dist/capability/manifest.d.ts.map +1 -1
  29. package/dist/capability/manifest.js +1 -1
  30. package/dist/capability/purge.d.ts +80 -0
  31. package/dist/capability/purge.d.ts.map +1 -0
  32. package/dist/capability/purge.js +76 -0
  33. package/dist/capability/settings.d.ts +86 -2
  34. package/dist/capability/settings.d.ts.map +1 -1
  35. package/dist/capability/settings.js +80 -1
  36. package/dist/cli/commandCatalog.generated.d.ts +238 -24
  37. package/dist/cli/commandCatalog.generated.d.ts.map +1 -1
  38. package/dist/cli/commandCatalog.generated.js +251 -22
  39. package/dist/cli/commandFacts.d.ts +2 -1
  40. package/dist/cli/commandFacts.d.ts.map +1 -1
  41. package/dist/cli/commandFacts.js +2 -0
  42. package/dist/controlPlane/capability.d.ts.map +1 -1
  43. package/dist/controlPlane/capability.js +19 -2
  44. package/dist/controlPlane/context.d.ts +5 -0
  45. package/dist/controlPlane/context.d.ts.map +1 -1
  46. package/dist/controlPlane/context.js +3 -1
  47. package/dist/controlPlane/data/connection.d.ts +49 -12
  48. package/dist/controlPlane/data/connection.d.ts.map +1 -1
  49. package/dist/controlPlane/data/connection.js +56 -2
  50. package/dist/controlPlane/discovery/adminRoute.d.ts +29 -0
  51. package/dist/controlPlane/discovery/adminRoute.d.ts.map +1 -1
  52. package/dist/controlPlane/discovery/adminRoute.js +3 -0
  53. package/dist/controlPlane/error/errors.d.ts +4 -3
  54. package/dist/controlPlane/error/errors.d.ts.map +1 -1
  55. package/dist/controlPlane/http/cors.d.ts +4 -4
  56. package/dist/controlPlane/http/cors.js +4 -4
  57. package/dist/controlPlane/http/guard.d.ts +11 -0
  58. package/dist/controlPlane/http/guard.d.ts.map +1 -1
  59. package/dist/controlPlane/http/guard.js +17 -1
  60. package/dist/controlPlane/http/handlers.d.ts +139 -0
  61. package/dist/controlPlane/http/handlers.d.ts.map +1 -1
  62. package/dist/controlPlane/http/handlers.js +67 -4
  63. package/dist/controlPlane/http/readiness.d.ts +33 -0
  64. package/dist/controlPlane/http/readiness.d.ts.map +1 -0
  65. package/dist/controlPlane/http/readiness.js +39 -0
  66. package/dist/controlPlane/http/responses.d.ts +245 -0
  67. package/dist/controlPlane/http/responses.d.ts.map +1 -1
  68. package/dist/controlPlane/http/responses.js +101 -4
  69. package/dist/controlPlane/http/routes.d.ts +2 -0
  70. package/dist/controlPlane/http/routes.d.ts.map +1 -1
  71. package/dist/controlPlane/http/routes.js +31 -3
  72. package/dist/controlPlane/http/verify.d.ts.map +1 -1
  73. package/dist/controlPlane/http/verify.js +2 -1
  74. package/dist/controlPlane/migrationState/migrationState.d.ts +102 -0
  75. package/dist/controlPlane/migrationState/migrationState.d.ts.map +1 -0
  76. package/dist/controlPlane/migrationState/migrationState.js +142 -0
  77. package/dist/controlPlane/migrations/0003_connection_principal.d.ts +21 -0
  78. package/dist/controlPlane/migrations/0003_connection_principal.d.ts.map +1 -0
  79. package/dist/controlPlane/migrations/0003_connection_principal.js +33 -0
  80. package/dist/controlPlane/scope/scope.d.ts +23 -2
  81. package/dist/controlPlane/scope/scope.d.ts.map +1 -1
  82. package/dist/controlPlane/scope/scope.js +24 -3
  83. package/dist/createBackend.d.ts.map +1 -1
  84. package/dist/createBackend.js +9 -2
  85. package/dist/createEntrypoint.d.ts.map +1 -1
  86. package/dist/createEntrypoint.js +5 -3
  87. package/dist/crypto/envelope.d.ts +86 -0
  88. package/dist/crypto/envelope.d.ts.map +1 -0
  89. package/dist/crypto/envelope.js +129 -0
  90. package/dist/crypto/fieldKeys.d.ts +41 -0
  91. package/dist/crypto/fieldKeys.d.ts.map +1 -0
  92. package/dist/crypto/fieldKeys.js +56 -0
  93. package/dist/data/codecs.d.ts +23 -10
  94. package/dist/data/codecs.d.ts.map +1 -1
  95. package/dist/data/codecs.js +50 -36
  96. package/dist/entitlement/allowance.d.ts +25 -0
  97. package/dist/entitlement/allowance.d.ts.map +1 -0
  98. package/dist/entitlement/allowance.js +80 -0
  99. package/dist/entitlement/entitlement.d.ts +41 -0
  100. package/dist/entitlement/entitlement.d.ts.map +1 -1
  101. package/dist/entitlement/meter.d.ts +57 -0
  102. package/dist/entitlement/meter.d.ts.map +1 -0
  103. package/dist/entitlement/meter.js +13 -0
  104. package/dist/error/extend.d.ts +6 -5
  105. package/dist/error/extend.d.ts.map +1 -1
  106. package/dist/error/http.d.ts +168 -0
  107. package/dist/error/http.d.ts.map +1 -1
  108. package/dist/error/payload.d.ts +336 -0
  109. package/dist/error/payload.d.ts.map +1 -1
  110. package/dist/error/payload.js +116 -4
  111. package/dist/error/pithyError.d.ts +18 -7
  112. package/dist/error/pithyError.d.ts.map +1 -1
  113. package/dist/error/pithyError.js +6 -0
  114. package/dist/http/authContext.d.ts +7 -0
  115. package/dist/http/authContext.d.ts.map +1 -1
  116. package/dist/http/authContext.js +8 -1
  117. package/dist/http/scopeGates.d.ts +20 -0
  118. package/dist/http/scopeGates.d.ts.map +1 -0
  119. package/dist/http/scopeGates.js +36 -0
  120. package/dist/index.d.ts +1 -1
  121. package/dist/index.d.ts.map +1 -1
  122. package/dist/index.js +2 -2
  123. package/dist/logger/logger.d.ts +12 -5
  124. package/dist/logger/logger.d.ts.map +1 -1
  125. package/dist/logger/record.d.ts +98 -0
  126. package/dist/logger/record.d.ts.map +1 -1
  127. package/dist/migrations/capabilitySets.d.ts +23 -0
  128. package/dist/migrations/capabilitySets.d.ts.map +1 -0
  129. package/dist/migrations/capabilitySets.js +74 -0
  130. package/dist/migrations/registry.d.ts +26 -0
  131. package/dist/migrations/registry.d.ts.map +1 -1
  132. package/dist/migrations/registry.js +38 -7
  133. package/dist/migrations/state.d.ts +60 -0
  134. package/dist/migrations/state.d.ts.map +1 -0
  135. package/dist/migrations/state.js +46 -0
  136. package/dist/naming/feature.d.ts +27 -5
  137. package/dist/naming/feature.d.ts.map +1 -1
  138. package/dist/naming/feature.js +36 -6
  139. package/dist/naming/limits.d.ts +47 -3
  140. package/dist/naming/limits.d.ts.map +1 -1
  141. package/dist/naming/limits.js +47 -3
  142. package/dist/naming/provisionScope.d.ts +6 -0
  143. package/dist/naming/provisionScope.d.ts.map +1 -1
  144. package/dist/naming/provisionScope.js +8 -3
  145. package/dist/naming/resourceNames.d.ts +21 -2
  146. package/dist/naming/resourceNames.d.ts.map +1 -1
  147. package/dist/naming/resourceNames.js +21 -3
  148. package/dist/privacy/blindIndex.d.ts +27 -0
  149. package/dist/privacy/blindIndex.d.ts.map +1 -0
  150. package/dist/privacy/blindIndex.js +57 -0
  151. package/dist/privacy/classes.d.ts +142 -0
  152. package/dist/privacy/classes.d.ts.map +1 -0
  153. package/dist/privacy/classes.js +209 -0
  154. package/dist/privacy/classify.d.ts +336 -0
  155. package/dist/privacy/classify.d.ts.map +1 -0
  156. package/dist/privacy/classify.js +1024 -0
  157. package/dist/privacy/encrypted.d.ts +210 -0
  158. package/dist/privacy/encrypted.d.ts.map +1 -0
  159. package/dist/privacy/encrypted.js +350 -0
  160. package/dist/privacy/encryptedColumns.d.ts +64 -0
  161. package/dist/privacy/encryptedColumns.d.ts.map +1 -0
  162. package/dist/privacy/encryptedColumns.js +134 -0
  163. package/dist/privacy/fieldRotation.d.ts +34 -0
  164. package/dist/privacy/fieldRotation.d.ts.map +1 -0
  165. package/dist/privacy/fieldRotation.js +186 -0
  166. package/dist/privacy/inventory.d.ts +12 -0
  167. package/dist/privacy/inventory.d.ts.map +1 -0
  168. package/dist/privacy/inventory.js +154 -0
  169. package/dist/privacy/posture.d.ts +114 -0
  170. package/dist/privacy/posture.d.ts.map +1 -0
  171. package/dist/privacy/posture.js +136 -0
  172. package/dist/privacy/redact.d.ts +7 -0
  173. package/dist/privacy/redact.d.ts.map +1 -0
  174. package/dist/privacy/redact.js +404 -0
  175. package/dist/privacy/report.d.ts +58 -0
  176. package/dist/privacy/report.d.ts.map +1 -0
  177. package/dist/privacy/report.js +15 -0
  178. package/dist/privacy/retirement.d.ts +68 -0
  179. package/dist/privacy/retirement.d.ts.map +1 -0
  180. package/dist/privacy/retirement.js +72 -0
  181. package/dist/privacy/sealed.d.ts +87 -0
  182. package/dist/privacy/sealed.d.ts.map +1 -0
  183. package/dist/privacy/sealed.js +221 -0
  184. package/dist/privacy/types.d.ts +559 -0
  185. package/dist/privacy/types.d.ts.map +1 -0
  186. package/dist/privacy/types.js +2 -0
  187. package/dist/seed/writeD1.d.ts +7 -1
  188. package/dist/seed/writeD1.d.ts.map +1 -1
  189. package/dist/seed/writeD1.js +9 -3
  190. package/dist/seed/writeKv.d.ts +2 -0
  191. package/dist/seed/writeKv.d.ts.map +1 -1
  192. package/dist/seed/writeKv.js +6 -2
  193. package/dist/version.generated.d.ts +1 -1
  194. package/dist/version.generated.js +1 -1
  195. package/dist/worker/serviceLoopback.d.ts +36 -0
  196. package/dist/worker/serviceLoopback.d.ts.map +1 -0
  197. package/dist/worker/serviceLoopback.js +74 -0
  198. package/dist/workflow/faults.d.ts +12 -1
  199. package/dist/workflow/faults.d.ts.map +1 -1
  200. package/dist/workflow/faults.js +22 -15
  201. package/package.json +2 -2
  202. package/src/access/access.ts +298 -0
  203. package/src/access/acting.ts +59 -0
  204. package/src/access/policyCheck.ts +77 -0
  205. package/src/access/usage.ts +141 -0
  206. package/src/access/visibility.ts +109 -0
  207. package/src/audit/auditEvent.ts +4 -0
  208. package/src/audit/recorder.ts +10 -2
  209. package/src/capability/bindings.ts +159 -13
  210. package/src/capability/capability.ts +114 -29
  211. package/src/capability/purge.ts +124 -0
  212. package/src/capability/settings.ts +146 -2
  213. package/src/cli/commandCatalog.generated.ts +271 -22
  214. package/src/cli/commandFacts.ts +2 -0
  215. package/src/controlPlane/capability.ts +36 -1
  216. package/src/controlPlane/context.ts +4 -0
  217. package/src/controlPlane/data/connection.ts +90 -0
  218. package/src/controlPlane/discovery/adminRoute.ts +11 -0
  219. package/src/controlPlane/error/errors.ts +4 -3
  220. package/src/controlPlane/http/cors.ts +4 -4
  221. package/src/controlPlane/http/guard.ts +21 -0
  222. package/src/controlPlane/http/handlers.ts +98 -4
  223. package/src/controlPlane/http/readiness.ts +50 -0
  224. package/src/controlPlane/http/responses.ts +240 -2
  225. package/src/controlPlane/http/routes.ts +55 -1
  226. package/src/controlPlane/http/verify.ts +2 -0
  227. package/src/controlPlane/migrationState/migrationState.ts +200 -0
  228. package/src/controlPlane/migrations/0003_connection_principal.ts +37 -0
  229. package/src/controlPlane/scope/scope.ts +25 -2
  230. package/src/controlPlane/token/base64url.ts +1 -1
  231. package/src/createBackend.ts +28 -6
  232. package/src/createEntrypoint.ts +9 -3
  233. package/src/crypto/envelope.ts +196 -0
  234. package/src/crypto/fieldKeys.ts +76 -0
  235. package/src/data/codecs.ts +69 -37
  236. package/src/entitlement/allowance.ts +85 -0
  237. package/src/entitlement/entitlement.ts +38 -0
  238. package/src/entitlement/meter.ts +69 -0
  239. package/src/error/extend.ts +6 -5
  240. package/src/error/payload.ts +253 -4
  241. package/src/error/pithyError.ts +21 -7
  242. package/src/http/authContext.ts +13 -1
  243. package/src/http/scopeGates.ts +34 -0
  244. package/src/index.ts +2 -0
  245. package/src/logger/logger.ts +12 -5
  246. package/src/migrations/capabilitySets.ts +87 -0
  247. package/src/migrations/registry.ts +46 -2
  248. package/src/migrations/state.ts +94 -0
  249. package/src/naming/feature.ts +46 -7
  250. package/src/naming/limits.ts +35 -3
  251. package/src/naming/provisionScope.ts +19 -1
  252. package/src/naming/resourceNames.ts +52 -5
  253. package/src/privacy/blindIndex.ts +81 -0
  254. package/src/privacy/classes.ts +277 -0
  255. package/src/privacy/classify.ts +1447 -0
  256. package/src/privacy/encrypted.ts +513 -0
  257. package/src/privacy/encryptedColumns.ts +185 -0
  258. package/src/privacy/fieldRotation.ts +234 -0
  259. package/src/privacy/inventory.ts +147 -0
  260. package/src/privacy/posture.ts +205 -0
  261. package/src/privacy/redact.ts +423 -0
  262. package/src/privacy/report.ts +64 -0
  263. package/src/privacy/retirement.ts +102 -0
  264. package/src/privacy/sealed.ts +323 -0
  265. package/src/privacy/types.ts +804 -0
  266. package/src/seed/writeD1.ts +13 -4
  267. package/src/seed/writeKv.ts +6 -2
  268. package/src/version.generated.ts +1 -1
  269. package/src/worker/serviceLoopback.ts +105 -0
  270. 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 };