@honua/react 0.1.0-beta.0 → 0.1.2-beta.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 (315) hide show
  1. package/contract/discovery.d.ts +231 -0
  2. package/contract/discovery.d.ts.map +1 -0
  3. package/contract/discovery.js +554 -0
  4. package/contract/edit-sketch.d.ts +8 -0
  5. package/contract/edit-sketch.d.ts.map +1 -1
  6. package/contract/edit-sketch.js +15 -0
  7. package/contract/edit-snapping.d.ts +160 -0
  8. package/contract/edit-snapping.d.ts.map +1 -0
  9. package/contract/edit-snapping.js +675 -0
  10. package/contract/generated/projjson-v0.7-crs-validator.d.ts +35 -0
  11. package/contract/generated/projjson-v0.7-crs-validator.d.ts.map +1 -0
  12. package/contract/generated/projjson-v0.7-crs-validator.js +3941 -0
  13. package/contract/index.d.ts +11 -2
  14. package/contract/index.d.ts.map +1 -1
  15. package/contract/index.js +4 -1
  16. package/contract/pmtiles.d.ts +31 -0
  17. package/contract/pmtiles.d.ts.map +1 -1
  18. package/contract/pmtiles.js +60 -20
  19. package/contract/protocol-module.d.ts +70 -0
  20. package/contract/protocol-module.d.ts.map +1 -0
  21. package/contract/protocol-module.js +1 -0
  22. package/contract/schema-envelope.d.ts +26 -0
  23. package/contract/schema-envelope.d.ts.map +1 -0
  24. package/contract/schema-envelope.js +25 -0
  25. package/contract/schema.d.ts +582 -0
  26. package/contract/schema.d.ts.map +1 -0
  27. package/contract/schema.js +2483 -0
  28. package/contract/source-capability-support.d.ts +6 -0
  29. package/contract/source-capability-support.d.ts.map +1 -0
  30. package/contract/source-capability-support.js +154 -0
  31. package/contract/source.d.ts +35 -17
  32. package/contract/source.d.ts.map +1 -1
  33. package/contract/source.js +267 -71
  34. package/contract/types.d.ts +140 -4
  35. package/contract/types.d.ts.map +1 -1
  36. package/contract/types.js +6 -14
  37. package/core/bounded-json.d.ts +4 -0
  38. package/core/bounded-json.d.ts.map +1 -0
  39. package/core/bounded-json.js +38 -0
  40. package/core/cache-state.d.ts +9 -0
  41. package/core/cache-state.d.ts.map +1 -1
  42. package/core/cache-state.js +10 -0
  43. package/core/capabilities-xml.d.ts +33 -0
  44. package/core/capabilities-xml.d.ts.map +1 -0
  45. package/core/capabilities-xml.js +331 -0
  46. package/core/client.d.ts +4 -1
  47. package/core/client.d.ts.map +1 -1
  48. package/core/client.js +115 -29
  49. package/core/error-classifications.d.ts +153 -0
  50. package/core/error-classifications.d.ts.map +1 -0
  51. package/core/error-classifications.js +140 -0
  52. package/core/error-code-registry.d.ts +158 -0
  53. package/core/error-code-registry.d.ts.map +1 -0
  54. package/core/error-code-registry.js +143 -0
  55. package/core/error-envelope.d.ts +68 -0
  56. package/core/error-envelope.d.ts.map +1 -0
  57. package/core/error-envelope.js +357 -0
  58. package/core/errors.d.ts +45 -29
  59. package/core/errors.d.ts.map +1 -1
  60. package/core/errors.js +97 -50
  61. package/core/geoparquet-sql.d.ts +30 -2
  62. package/core/geoparquet-sql.d.ts.map +1 -1
  63. package/core/geoparquet-sql.js +38 -3
  64. package/core/odata-key-path.d.ts +12 -0
  65. package/core/odata-key-path.d.ts.map +1 -0
  66. package/core/odata-key-path.js +28 -0
  67. package/core/odata-write-codec.d.ts +66 -0
  68. package/core/odata-write-codec.d.ts.map +1 -0
  69. package/core/odata-write-codec.js +1083 -0
  70. package/core/odata.d.ts +59 -7
  71. package/core/odata.d.ts.map +1 -1
  72. package/core/odata.js +431 -99
  73. package/core/ogc-features.d.ts.map +1 -1
  74. package/core/ogc-features.js +3 -0
  75. package/core/ogc-maps.d.ts +7 -0
  76. package/core/ogc-maps.d.ts.map +1 -1
  77. package/core/ogc-maps.js +46 -7
  78. package/core/ogc-processes.d.ts +2 -1
  79. package/core/ogc-processes.d.ts.map +1 -1
  80. package/core/ogc-processes.js +33 -5
  81. package/core/ogc-records.d.ts +9 -0
  82. package/core/ogc-records.d.ts.map +1 -1
  83. package/core/ogc-records.js +78 -29
  84. package/core/ogc-tiles.d.ts +10 -1
  85. package/core/ogc-tiles.d.ts.map +1 -1
  86. package/core/ogc-tiles.js +56 -17
  87. package/core/path-utils.d.ts +9 -0
  88. package/core/path-utils.d.ts.map +1 -1
  89. package/core/path-utils.js +19 -0
  90. package/core/protocol-transport.d.ts +1 -0
  91. package/core/protocol-transport.d.ts.map +1 -1
  92. package/core/request-pipeline.d.ts.map +1 -1
  93. package/core/request-pipeline.js +1 -1
  94. package/core/spatial-filter.d.ts +8 -4
  95. package/core/spatial-filter.d.ts.map +1 -1
  96. package/core/spatial-filter.js +206 -16
  97. package/core/stac-static.d.ts +6 -2
  98. package/core/stac-static.d.ts.map +1 -1
  99. package/core/stac-static.js +260 -69
  100. package/core/surfaces.d.ts.map +1 -1
  101. package/core/surfaces.js +69 -5
  102. package/core/types.d.ts +125 -7
  103. package/core/types.d.ts.map +1 -1
  104. package/core/wfs-capabilities.d.ts +2 -0
  105. package/core/wfs-capabilities.d.ts.map +1 -1
  106. package/core/wfs-capabilities.js +8 -1
  107. package/core/wfs.d.ts.map +1 -1
  108. package/core/wfs.js +10 -0
  109. package/core/wms-capabilities.d.ts +56 -84
  110. package/core/wms-capabilities.d.ts.map +1 -1
  111. package/core/wms-capabilities.js +212 -509
  112. package/core/wmts-capabilities.d.ts +62 -62
  113. package/core/wmts-capabilities.d.ts.map +1 -1
  114. package/core/wmts-capabilities.js +224 -371
  115. package/esri-compat/editor.d.ts +8 -0
  116. package/esri-compat/editor.d.ts.map +1 -1
  117. package/esri-compat/editor.js +13 -0
  118. package/esri-compat/feature-layer.d.ts +2 -0
  119. package/esri-compat/feature-layer.d.ts.map +1 -1
  120. package/esri-compat/feature-layer.js +1 -0
  121. package/esri-compat/layer-list.d.ts +11 -0
  122. package/esri-compat/layer-list.d.ts.map +1 -1
  123. package/esri-compat/layer-list.js +49 -0
  124. package/esri-compat/legend.d.ts +10 -0
  125. package/esri-compat/legend.d.ts.map +1 -1
  126. package/esri-compat/legend.js +27 -0
  127. package/esri-compat/portal.d.ts +197 -0
  128. package/esri-compat/portal.d.ts.map +1 -0
  129. package/esri-compat/portal.js +251 -0
  130. package/esri-compat/renderer-objects.d.ts +40 -0
  131. package/esri-compat/renderer-objects.d.ts.map +1 -0
  132. package/esri-compat/renderer-objects.js +62 -0
  133. package/esri-compat/sketch.d.ts +43 -0
  134. package/esri-compat/sketch.d.ts.map +1 -1
  135. package/esri-compat/sketch.js +46 -0
  136. package/esri-compat/snapping.d.ts +43 -0
  137. package/esri-compat/snapping.d.ts.map +1 -0
  138. package/esri-compat/snapping.js +45 -0
  139. package/esri-compat/url.d.ts.map +1 -1
  140. package/esri-compat/url.js +1 -1
  141. package/esri-compat/widget-host.d.ts +84 -0
  142. package/esri-compat/widget-host.d.ts.map +1 -0
  143. package/esri-compat/widget-host.js +159 -0
  144. package/filter-registry/index.d.ts +145 -0
  145. package/filter-registry/index.d.ts.map +1 -0
  146. package/filter-registry/index.js +531 -0
  147. package/generated-app/errors.d.ts +18 -2
  148. package/generated-app/errors.d.ts.map +1 -1
  149. package/generated-app/errors.js +44 -11
  150. package/map/automatic-mount-integration.d.ts +134 -0
  151. package/map/automatic-mount-integration.d.ts.map +1 -0
  152. package/map/automatic-mount-integration.js +305 -0
  153. package/map/automatic-source-strategy.d.ts +125 -0
  154. package/map/automatic-source-strategy.d.ts.map +1 -0
  155. package/map/automatic-source-strategy.js +494 -0
  156. package/map/data-to-map-bridge.d.ts +293 -0
  157. package/map/data-to-map-bridge.d.ts.map +1 -0
  158. package/map/data-to-map-bridge.js +817 -0
  159. package/map/geojson-projection.d.ts +33 -0
  160. package/map/geojson-projection.d.ts.map +1 -0
  161. package/map/geojson-projection.js +118 -0
  162. package/map/index.d.ts +14 -0
  163. package/map/index.d.ts.map +1 -1
  164. package/map/index.js +8 -0
  165. package/map/raster-source-spec.d.ts +30 -0
  166. package/map/raster-source-spec.d.ts.map +1 -0
  167. package/map/raster-source-spec.js +190 -0
  168. package/map/raster-source-strategy.d.ts +65 -0
  169. package/map/raster-source-strategy.d.ts.map +1 -0
  170. package/map/raster-source-strategy.js +232 -0
  171. package/map/source-to-maplibre.d.ts +91 -0
  172. package/map/source-to-maplibre.d.ts.map +1 -0
  173. package/map/source-to-maplibre.js +454 -0
  174. package/map/temporal-playback.d.ts +139 -0
  175. package/map/temporal-playback.d.ts.map +1 -0
  176. package/map/temporal-playback.js +247 -0
  177. package/package.json +14 -1
  178. package/query-planner/canonical.d.ts +7 -0
  179. package/query-planner/canonical.d.ts.map +1 -0
  180. package/query-planner/canonical.js +145 -0
  181. package/query-planner/cql2-json.d.ts +17 -0
  182. package/query-planner/cql2-json.d.ts.map +1 -0
  183. package/query-planner/cql2-json.js +646 -0
  184. package/query-planner/diagnostics.d.ts +35 -0
  185. package/query-planner/diagnostics.d.ts.map +1 -0
  186. package/query-planner/diagnostics.js +373 -0
  187. package/query-planner/duckdb.d.ts +79 -0
  188. package/query-planner/duckdb.d.ts.map +1 -0
  189. package/query-planner/duckdb.js +758 -0
  190. package/query-planner/execution-context.d.ts +5 -0
  191. package/query-planner/execution-context.d.ts.map +1 -0
  192. package/query-planner/execution-context.js +50 -0
  193. package/query-planner/executor.d.ts +5 -0
  194. package/query-planner/executor.d.ts.map +1 -0
  195. package/query-planner/executor.js +223 -0
  196. package/query-planner/geoservices.d.ts +91 -0
  197. package/query-planner/geoservices.d.ts.map +1 -0
  198. package/query-planner/geoservices.js +667 -0
  199. package/query-planner/grpc.d.ts +100 -0
  200. package/query-planner/grpc.d.ts.map +1 -0
  201. package/query-planner/grpc.js +736 -0
  202. package/query-planner/index.d.ts +44 -0
  203. package/query-planner/index.d.ts.map +1 -0
  204. package/query-planner/index.js +30 -0
  205. package/query-planner/ir.d.ts +20 -0
  206. package/query-planner/ir.d.ts.map +1 -0
  207. package/query-planner/ir.js +417 -0
  208. package/query-planner/local-aggregate.d.ts +5 -0
  209. package/query-planner/local-aggregate.d.ts.map +1 -0
  210. package/query-planner/local-aggregate.js +126 -0
  211. package/query-planner/odata.d.ts +48 -0
  212. package/query-planner/odata.d.ts.map +1 -0
  213. package/query-planner/odata.js +662 -0
  214. package/query-planner/ogc-compiler.d.ts +55 -0
  215. package/query-planner/ogc-compiler.d.ts.map +1 -0
  216. package/query-planner/ogc-compiler.js +480 -0
  217. package/query-planner/ogc-features.d.ts +53 -0
  218. package/query-planner/ogc-features.d.ts.map +1 -0
  219. package/query-planner/ogc-features.js +617 -0
  220. package/query-planner/planner.d.ts +17 -0
  221. package/query-planner/planner.d.ts.map +1 -0
  222. package/query-planner/planner.js +818 -0
  223. package/query-planner/resource.d.ts +94 -0
  224. package/query-planner/resource.d.ts.map +1 -0
  225. package/query-planner/resource.js +526 -0
  226. package/query-planner/semantic-canonical.d.ts +15 -0
  227. package/query-planner/semantic-canonical.d.ts.map +1 -0
  228. package/query-planner/semantic-canonical.js +62 -0
  229. package/query-planner/semantic-compat.d.ts +10 -0
  230. package/query-planner/semantic-compat.d.ts.map +1 -0
  231. package/query-planner/semantic-compat.js +35 -0
  232. package/query-planner/semantic-compiler.d.ts +64 -0
  233. package/query-planner/semantic-compiler.d.ts.map +1 -0
  234. package/query-planner/semantic-compiler.js +81 -0
  235. package/query-planner/semantic-literals.d.ts +30 -0
  236. package/query-planner/semantic-literals.d.ts.map +1 -0
  237. package/query-planner/semantic-literals.js +133 -0
  238. package/query-planner/semantic-types.d.ts +265 -0
  239. package/query-planner/semantic-types.d.ts.map +1 -0
  240. package/query-planner/semantic-types.js +1 -0
  241. package/query-planner/semantic.d.ts +54 -0
  242. package/query-planner/semantic.d.ts.map +1 -0
  243. package/query-planner/semantic.js +1421 -0
  244. package/query-planner/types.d.ts +560 -0
  245. package/query-planner/types.d.ts.map +1 -0
  246. package/query-planner/types.js +49 -0
  247. package/query-planner/wfs-semantic.d.ts +58 -0
  248. package/query-planner/wfs-semantic.d.ts.map +1 -0
  249. package/query-planner/wfs-semantic.js +547 -0
  250. package/query-planner/wfs.d.ts +6 -0
  251. package/query-planner/wfs.d.ts.map +1 -0
  252. package/query-planner/wfs.js +71 -0
  253. package/react/external-map.d.ts +67 -0
  254. package/react/external-map.d.ts.map +1 -0
  255. package/react/external-map.js +63 -0
  256. package/react/honua-map.d.ts.map +1 -1
  257. package/react/honua-map.js +3 -2
  258. package/react/honua-source-layer.d.ts +110 -0
  259. package/react/honua-source-layer.d.ts.map +1 -0
  260. package/react/honua-source-layer.js +101 -0
  261. package/react/hooks.d.ts +28 -0
  262. package/react/hooks.d.ts.map +1 -1
  263. package/react/hooks.js +26 -4
  264. package/react/index.d.ts +8 -0
  265. package/react/index.d.ts.map +1 -1
  266. package/react/index.js +5 -0
  267. package/react/query-cache.d.ts +26 -4
  268. package/react/query-cache.d.ts.map +1 -1
  269. package/react/query-cache.js +51 -8
  270. package/react/selection.d.ts +177 -0
  271. package/react/selection.d.ts.map +1 -0
  272. package/react/selection.js +0 -0
  273. package/react/use-mounted-source.d.ts +69 -0
  274. package/react/use-mounted-source.d.ts.map +1 -0
  275. package/react/use-mounted-source.js +346 -0
  276. package/runtime/edit-snapping.d.ts +107 -0
  277. package/runtime/edit-snapping.d.ts.map +1 -0
  278. package/runtime/edit-snapping.js +190 -0
  279. package/runtime/errors.d.ts +3 -2
  280. package/runtime/errors.d.ts.map +1 -1
  281. package/runtime/errors.js +19 -2
  282. package/runtime/index.d.ts +6 -0
  283. package/runtime/index.d.ts.map +1 -1
  284. package/runtime/index.js +3 -0
  285. package/runtime/maplibre-renderer.d.ts +41 -0
  286. package/runtime/maplibre-renderer.d.ts.map +1 -0
  287. package/runtime/maplibre-renderer.js +322 -0
  288. package/runtime/query-tiles.d.ts +2 -1
  289. package/runtime/query-tiles.d.ts.map +1 -1
  290. package/runtime/query-tiles.js +6 -2
  291. package/runtime/source-bridge.d.ts +4 -4
  292. package/runtime/source-bridge.d.ts.map +1 -1
  293. package/runtime/source-bridge.js +9 -56
  294. package/runtime/style-interactions.d.ts +3 -2
  295. package/runtime/style-interactions.d.ts.map +1 -1
  296. package/runtime/style-interactions.js +11 -3
  297. package/runtime/terra-draw-sketch.d.ts +275 -0
  298. package/runtime/terra-draw-sketch.d.ts.map +1 -0
  299. package/runtime/terra-draw-sketch.js +404 -0
  300. package/source-capability-registry.d.ts +1 -0
  301. package/source-capability-registry.js +1 -0
  302. package/source-capability-types.d.ts +186 -0
  303. package/source-capability-types.js +7 -0
  304. package/style/index.d.ts +2 -0
  305. package/style/index.d.ts.map +1 -1
  306. package/style/index.js +2 -0
  307. package/style/renderers.d.ts +326 -0
  308. package/style/renderers.d.ts.map +1 -0
  309. package/style/renderers.js +517 -0
  310. package/webmap/convert-renderer.d.ts +24 -3
  311. package/webmap/convert-renderer.d.ts.map +1 -1
  312. package/webmap/convert-renderer.js +93 -112
  313. package/webmap/index.d.ts +1 -1
  314. package/webmap/index.d.ts.map +1 -1
  315. package/webmap/index.js +1 -1
@@ -0,0 +1,2483 @@
1
+ /**
2
+ * Experimental vendor-neutral source schema v2.
3
+ *
4
+ * This module is the production projection of the contract accepted in
5
+ * `docs/decisions/vendor-neutral-source-contract-v2.md`. It deliberately lives
6
+ * beside, rather than replacing, the legacy Esri-shaped `SourceSchema`: the
7
+ * discovery rollout dual-reads both contracts until the 1.0 cutover.
8
+ *
9
+ * @experimental
10
+ * @module
11
+ */
12
+ import { canonicalStringify, sha256, toJsonValue } from "../query-planner/canonical.js";
13
+ import validateProjJsonV07Crs from "./generated/projjson-v0.7-crs-validator.js";
14
+ import { SOURCE_SCHEMA_V2_KIND, SOURCE_SCHEMA_V2_VERSION } from "./schema-envelope.js";
15
+ import { PROTOCOLS } from "./types.js";
16
+ export { SOURCE_SCHEMA_V2_KIND, SOURCE_SCHEMA_V2_VERSION } from "./schema-envelope.js";
17
+ export const SOURCE_SCHEMA_V2_FINGERPRINT_DOMAIN = "honua:schema:2.0";
18
+ const SHA256_PATTERN = /^sha256:[0-9a-f]{64}$/;
19
+ const EXTENSION_ID_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9_-]*\.)+[A-Za-z0-9][A-Za-z0-9_-]*$/;
20
+ const MAX_NATIVE_DEFINITION_BYTES = 64 * 1024;
21
+ const MAX_PROJJSON_BYTES = 64 * 1024;
22
+ const MAX_FIELD_DOMAIN_BYTES = 1024 * 1024;
23
+ const MAX_CODED_DOMAIN_VALUES = 10_000;
24
+ const MAX_SCHEMA_BYTES = 4 * 1024 * 1024;
25
+ const MAX_TYPE_DEPTH = 32;
26
+ // A struct level adds a type object, fields array, and field object to the raw
27
+ // graph. Keep this defensive prewalk high enough for the independently
28
+ // enforced 32-level logical-type limit while still bounding adversarial input.
29
+ const MAX_SCHEMA_GRAPH_DEPTH = MAX_TYPE_DEPTH * 4 + 32;
30
+ const MAX_JSON_VALUE_DEPTH = 32;
31
+ const MAX_JSON_NODE_COUNT = 100_000;
32
+ const PROJJSON_V07_SCHEMA = "https://proj.org/schemas/v0.7/projjson.schema.json";
33
+ const PROJJSON_V07_TYPES = new Set([
34
+ "GeographicCRS",
35
+ "GeodeticCRS",
36
+ "ProjectedCRS",
37
+ "VerticalCRS",
38
+ "CompoundCRS",
39
+ "BoundCRS",
40
+ "EngineeringCRS",
41
+ "ParametricCRS",
42
+ "TemporalCRS",
43
+ "DerivedProjectedCRS",
44
+ "DerivedGeographicCRS",
45
+ "DerivedGeodeticCRS",
46
+ "DerivedVerticalCRS",
47
+ "DerivedEngineeringCRS",
48
+ "DerivedTemporalCRS",
49
+ "DerivedParametricCRS",
50
+ ]);
51
+ /** Create, validate, canonicalize, fingerprint, and deeply freeze a schema. */
52
+ export function createSourceSchemaV2(input) {
53
+ const plainInput = object(toPlainJson(input), "$");
54
+ const candidate = {
55
+ ...plainInput,
56
+ kind: SOURCE_SCHEMA_V2_KIND,
57
+ version: SOURCE_SCHEMA_V2_VERSION,
58
+ };
59
+ const normalized = normalizeSchema(candidate, false);
60
+ const fingerprint = schemaFingerprint(normalized);
61
+ const schema = { ...normalized, fingerprint };
62
+ assertSchemaByteBound(schema);
63
+ return deepFreeze(schema);
64
+ }
65
+ /** Parse a serialized/object schema, rejecting future versions and fingerprint drift. */
66
+ export function parseSourceSchemaV2(value) {
67
+ let parsed;
68
+ if (typeof value === "string") {
69
+ if (value.length > MAX_SCHEMA_BYTES || utf8ByteLength(value) > MAX_SCHEMA_BYTES) {
70
+ throw new TypeError(`Serialized SourceSchemaV2 exceeds the ${MAX_SCHEMA_BYTES}-byte bound`);
71
+ }
72
+ try {
73
+ parsed = JSON.parse(value);
74
+ }
75
+ catch (cause) {
76
+ throw new TypeError("SourceSchemaV2 must be valid JSON", { cause });
77
+ }
78
+ assertUniqueJsonObjectNames(value);
79
+ }
80
+ else {
81
+ parsed = value;
82
+ }
83
+ const candidate = toPlainJson(parsed);
84
+ const record = object(candidate, "$", [
85
+ "kind",
86
+ "version",
87
+ "fingerprint",
88
+ "fields",
89
+ "key",
90
+ "geometry",
91
+ "temporal",
92
+ "openContent",
93
+ "provenance",
94
+ "extensions",
95
+ ]);
96
+ if (record.kind !== SOURCE_SCHEMA_V2_KIND || record.version !== SOURCE_SCHEMA_V2_VERSION) {
97
+ throw new TypeError(`Unsupported source schema discriminator; expected ${SOURCE_SCHEMA_V2_KIND}@${SOURCE_SCHEMA_V2_VERSION}`);
98
+ }
99
+ if (typeof record.fingerprint !== "string" || !SHA256_PATTERN.test(record.fingerprint)) {
100
+ throw new TypeError("SourceSchemaV2.fingerprint must be a lowercase SHA-256 digest");
101
+ }
102
+ const normalized = normalizeSchema(record, true);
103
+ const computed = schemaFingerprint(normalized);
104
+ if (record.fingerprint !== computed)
105
+ throw new TypeError("SourceSchemaV2 fingerprint does not match canonical content");
106
+ const schema = { ...normalized, fingerprint: computed };
107
+ assertSchemaByteBound(schema);
108
+ return deepFreeze(schema);
109
+ }
110
+ /** Canonical JSON form suitable for cache/storage interchange. */
111
+ export function serializeSourceSchemaV2(schema) {
112
+ const verified = parseSourceSchemaV2(schema);
113
+ return canonicalStringify(toJsonValue(verified));
114
+ }
115
+ /** JSON-safe clone whose fingerprint and nested immutability are revalidated. */
116
+ export function cloneSourceSchemaV2(schema) {
117
+ return parseSourceSchemaV2(serializeSourceSchemaV2(schema));
118
+ }
119
+ export function sourceSchemaIdentity(schema) {
120
+ if (schema.state === "known") {
121
+ const verified = parseSourceSchemaV2(schema.value);
122
+ return deepFreeze({ state: "known", fingerprint: verified.fingerprint });
123
+ }
124
+ if (!includes(["not-requested", "request-failed", "not-advertised", "invalid"], schema.reason)) {
125
+ throw new TypeError("SchemaState.reason is invalid");
126
+ }
127
+ return deepFreeze({ state: "unavailable", reason: schema.reason, provenance: cloneProvenance(schema.provenance) });
128
+ }
129
+ /** @internal Adapter boundary for degrading malformed CRS metadata without losing its enclosing schema. */
130
+ export function validateSourceCrsDefinition(value) {
131
+ return deepFreeze(normalizeCrsDefinition(toPlainJson(value), "$.crs"));
132
+ }
133
+ /** @internal Query/spatial boundary for a fully validated executable CRS binding. */
134
+ export function validateExecutableCrsBinding(value, path = "$.crs") {
135
+ const binding = normalizeCrsBinding(toPlainJson(value), path);
136
+ if (!isResolvedCrsDefinition(binding.definition)) {
137
+ throw new TypeError(`${path}.definition must be resolved before spatial execution`);
138
+ }
139
+ if (binding.coordinateOrder.state !== "known") {
140
+ throw new TypeError(`${path}.coordinateOrder must be known before spatial execution`);
141
+ }
142
+ return deepFreeze({
143
+ definition: binding.definition,
144
+ coordinateOrder: binding.coordinateOrder,
145
+ ...(binding.coordinateEpoch === undefined ? {} : { coordinateEpoch: binding.coordinateEpoch }),
146
+ provenance: binding.provenance,
147
+ });
148
+ }
149
+ function normalizeSchema(value, hasFingerprint) {
150
+ const allowed = [
151
+ "kind",
152
+ "version",
153
+ ...(hasFingerprint ? ["fingerprint"] : []),
154
+ "fields",
155
+ "key",
156
+ "geometry",
157
+ "temporal",
158
+ "openContent",
159
+ "provenance",
160
+ "extensions",
161
+ ];
162
+ const record = object(value, "$", allowed);
163
+ if (record.kind !== SOURCE_SCHEMA_V2_KIND || record.version !== SOURCE_SCHEMA_V2_VERSION) {
164
+ throw new TypeError(`SourceSchemaV2 must use ${SOURCE_SCHEMA_V2_KIND}@${SOURCE_SCHEMA_V2_VERSION}`);
165
+ }
166
+ const fields = array(record.fields, "$.fields").map((field, index) => normalizeField(field, `$.fields[${index}]`, 0));
167
+ fields.sort(compareFields);
168
+ unique(fields.map((field) => field.name), "SourceSchemaV2 field names");
169
+ unique(fields.map((field) => canonicalStringify(toJsonValue(field.path))), "SourceSchemaV2 field paths");
170
+ const names = new Set(fields.map((field) => field.name));
171
+ const key = normalizeKey(record.key, names);
172
+ const geometry = normalizeGeometrySchema(record.geometry, names, fields);
173
+ const temporal = normalizeTemporalSchema(record.temporal, names, fields);
174
+ validateFieldSemantics(fields, key, geometry, temporal);
175
+ if (record.openContent !== "closed" && record.openContent !== "open" && record.openContent !== "unknown") {
176
+ throw new TypeError("SourceSchemaV2.openContent is invalid");
177
+ }
178
+ const provenance = normalizeProvenanceArray(record.provenance, "$.provenance");
179
+ const extensions = record.extensions === undefined ? undefined : normalizeExtensions(record.extensions, "$.extensions");
180
+ return {
181
+ kind: SOURCE_SCHEMA_V2_KIND,
182
+ version: SOURCE_SCHEMA_V2_VERSION,
183
+ fields,
184
+ key,
185
+ geometry,
186
+ temporal,
187
+ openContent: record.openContent,
188
+ provenance,
189
+ ...(extensions ? { extensions } : {}),
190
+ };
191
+ }
192
+ function normalizeField(value, path, depth) {
193
+ const record = object(value, path, [
194
+ "name",
195
+ "path",
196
+ "title",
197
+ "description",
198
+ "type",
199
+ "nullability",
200
+ "mutability",
201
+ "roles",
202
+ "defaultValue",
203
+ "domain",
204
+ "constraints",
205
+ "native",
206
+ "extensions",
207
+ ]);
208
+ const name = text(record.name, `${path}.name`);
209
+ const fieldPath = nonEmptyTextArray(record.path, `${path}.path`);
210
+ const type = normalizeLogicalType(record.type, `${path}.type`, depth + 1);
211
+ if (!includes(["nullable", "non-nullable", "unknown"], record.nullability)) {
212
+ throw new TypeError(`${path}.nullability is invalid`);
213
+ }
214
+ if (!includes(["read-only", "read-write", "write-once", "unknown"], record.mutability)) {
215
+ throw new TypeError(`${path}.mutability is invalid`);
216
+ }
217
+ const roles = [
218
+ ...new Set(array(record.roles, `${path}.roles`).map((role, index) => fieldRole(role, `${path}.roles[${index}]`))),
219
+ ].sort(compareCanonical);
220
+ const domain = normalizeDomain(record.domain, `${path}.domain`, type);
221
+ const constraints = normalizeConstraints(record.constraints, `${path}.constraints`, type);
222
+ const native = array(record.native, `${path}.native`).map((item, index) => normalizeNative(item, `${path}.native[${index}]`));
223
+ const extensions = record.extensions === undefined ? undefined : normalizeExtensions(record.extensions, `${path}.extensions`);
224
+ const defaultValue = Object.hasOwn(record, "defaultValue")
225
+ ? jsonValue(record.defaultValue, `${path}.defaultValue`)
226
+ : undefined;
227
+ if (defaultValue === null && record.nullability === "non-nullable") {
228
+ throw new TypeError(`${path}.defaultValue cannot be null for a non-nullable field`);
229
+ }
230
+ if (defaultValue !== undefined && !logicalValueCompatible(defaultValue, type)) {
231
+ throw new TypeError(`${path}.defaultValue is incompatible with logical type ${type.kind}`);
232
+ }
233
+ const normalized = {
234
+ name,
235
+ path: fieldPath,
236
+ ...(record.title === undefined ? {} : { title: text(record.title, `${path}.title`) }),
237
+ ...(record.description === undefined ? {} : { description: text(record.description, `${path}.description`) }),
238
+ type,
239
+ nullability: record.nullability,
240
+ mutability: record.mutability,
241
+ roles,
242
+ ...(Object.hasOwn(record, "defaultValue") ? { defaultValue: defaultValue } : {}),
243
+ domain,
244
+ constraints,
245
+ native,
246
+ ...(extensions ? { extensions } : {}),
247
+ };
248
+ const domainBytes = new TextEncoder().encode(canonicalStringify(toJsonValue({ domain: normalized.domain, constraints: normalized.constraints }))).byteLength;
249
+ if (domainBytes > MAX_FIELD_DOMAIN_BYTES) {
250
+ throw new TypeError(`${path} domain and constraints exceed the ${MAX_FIELD_DOMAIN_BYTES}-byte bound`);
251
+ }
252
+ return normalized;
253
+ }
254
+ function normalizeLogicalType(value, path, depth) {
255
+ if (depth > MAX_TYPE_DEPTH)
256
+ throw new TypeError(`${path} exceeds maximum logical-type depth ${MAX_TYPE_DEPTH}`);
257
+ const record = object(value, path);
258
+ const kind = record.kind;
259
+ switch (kind) {
260
+ case "boolean":
261
+ case "uuid":
262
+ case "date":
263
+ case "json":
264
+ case "geometry":
265
+ exactKeys(record, path, ["kind"]);
266
+ return { kind };
267
+ case "integer": {
268
+ exactKeys(record, path, ["kind", "bits", "signed", "jsonEncoding"]);
269
+ if (!includes([8, 16, 32, 64], record.bits))
270
+ throw new TypeError(`${path}.bits is invalid`);
271
+ if (typeof record.signed !== "boolean")
272
+ throw new TypeError(`${path}.signed must be boolean`);
273
+ if (record.jsonEncoding !== "number" && record.jsonEncoding !== "string") {
274
+ throw new TypeError(`${path}.jsonEncoding is invalid`);
275
+ }
276
+ return { kind, bits: record.bits, signed: record.signed, jsonEncoding: record.jsonEncoding };
277
+ }
278
+ case "float":
279
+ exactKeys(record, path, ["kind", "bits"]);
280
+ if (record.bits !== 32 && record.bits !== 64)
281
+ throw new TypeError(`${path}.bits is invalid`);
282
+ return { kind, bits: record.bits };
283
+ case "decimal": {
284
+ exactKeys(record, path, ["kind", "precision", "scale", "jsonEncoding"]);
285
+ if (record.jsonEncoding !== "number" && record.jsonEncoding !== "string") {
286
+ throw new TypeError(`${path}.jsonEncoding is invalid`);
287
+ }
288
+ const precision = optionalPositiveInteger(record.precision, `${path}.precision`);
289
+ const scale = optionalNonNegativeInteger(record.scale, `${path}.scale`);
290
+ if (precision !== undefined && scale !== undefined && scale > precision) {
291
+ throw new TypeError(`${path}.scale must not exceed precision`);
292
+ }
293
+ return {
294
+ kind,
295
+ ...(precision === undefined ? {} : { precision }),
296
+ ...(scale === undefined ? {} : { scale }),
297
+ jsonEncoding: record.jsonEncoding,
298
+ };
299
+ }
300
+ case "string": {
301
+ exactKeys(record, path, ["kind", "maxLength", "encoding"]);
302
+ const maxLength = optionalNonNegativeInteger(record.maxLength, `${path}.maxLength`);
303
+ return {
304
+ kind,
305
+ ...(maxLength === undefined ? {} : { maxLength }),
306
+ ...(record.encoding === undefined ? {} : { encoding: text(record.encoding, `${path}.encoding`) }),
307
+ };
308
+ }
309
+ case "binary":
310
+ exactKeys(record, path, ["kind", "encoding"]);
311
+ if (!includes(["base64", "url", "opaque"], record.encoding)) {
312
+ throw new TypeError(`${path}.encoding is invalid`);
313
+ }
314
+ return { kind, encoding: record.encoding };
315
+ case "time":
316
+ case "duration":
317
+ exactKeys(record, path, ["kind", "unit"]);
318
+ return { kind, unit: temporalUnit(record.unit, `${path}.unit`) };
319
+ case "timestamp":
320
+ exactKeys(record, path, ["kind", "unit", "timezone"]);
321
+ if (!includes(["utc", "offset", "local", "unknown"], record.timezone)) {
322
+ throw new TypeError(`${path}.timezone is invalid`);
323
+ }
324
+ return { kind, unit: temporalUnit(record.unit, `${path}.unit`), timezone: record.timezone };
325
+ case "list":
326
+ exactKeys(record, path, ["kind", "element"]);
327
+ return { kind, element: normalizeLogicalType(record.element, `${path}.element`, depth + 1) };
328
+ case "struct": {
329
+ exactKeys(record, path, ["kind", "fields"]);
330
+ const fields = array(record.fields, `${path}.fields`).map((field, index) =>
331
+ // normalizeField advances once when it enters the child's logical
332
+ // type. Passing the current type depth avoids counting a struct field
333
+ // wrapper as a second logical-type level.
334
+ normalizeField(field, `${path}.fields[${index}]`, depth));
335
+ fields.sort(compareFields);
336
+ unique(fields.map((field) => field.name), `${path} field names`);
337
+ unique(fields.map((field) => canonicalStringify(toJsonValue(field.path))), `${path} field paths`);
338
+ return { kind, fields };
339
+ }
340
+ case "union": {
341
+ exactKeys(record, path, ["kind", "members"]);
342
+ const members = array(record.members, `${path}.members`).map((member, index) => normalizeLogicalType(member, `${path}.members[${index}]`, depth + 1));
343
+ if (members.length < 2)
344
+ throw new TypeError(`${path}.members must contain at least two types`);
345
+ members.sort(compareCanonical);
346
+ unique(members.map((member) => canonicalStringify(toJsonValue(typeFingerprintProjection(member)))), `${path}.members`);
347
+ return { kind, members: members };
348
+ }
349
+ case "unknown":
350
+ exactKeys(record, path, ["kind", "reason", "native"]);
351
+ if (!includes(["missing", "unrecognized", "conflicting", "unsupported"], record.reason)) {
352
+ throw new TypeError(`${path}.reason is invalid`);
353
+ }
354
+ return {
355
+ kind,
356
+ reason: record.reason,
357
+ ...(record.native === undefined ? {} : { native: normalizeNative(record.native, `${path}.native`) }),
358
+ };
359
+ default:
360
+ throw new TypeError(`${path}.kind is not a recognized logical type`);
361
+ }
362
+ }
363
+ function normalizeDomain(value, path, type) {
364
+ const record = object(value, path);
365
+ switch (record.state) {
366
+ case "none":
367
+ exactKeys(record, path, ["state", "reason"]);
368
+ if (record.reason !== "unconstrained" && record.reason !== "not-applicable") {
369
+ throw new TypeError(`${path}.reason is invalid`);
370
+ }
371
+ return { state: "none", reason: record.reason };
372
+ case "coded": {
373
+ exactKeys(record, path, ["state", "values", "openness"]);
374
+ if (!includes(["closed", "open", "unknown"], record.openness)) {
375
+ throw new TypeError(`${path}.openness is invalid`);
376
+ }
377
+ const rawValues = array(record.values, `${path}.values`);
378
+ if (rawValues.length === 0 || rawValues.length > MAX_CODED_DOMAIN_VALUES) {
379
+ throw new TypeError(`${path}.values must contain 1-${MAX_CODED_DOMAIN_VALUES} entries`);
380
+ }
381
+ const values = rawValues.map((entry, index) => {
382
+ const coded = object(entry, `${path}.values[${index}]`, ["value", "label", "description", "extensions"]);
383
+ const domainValue = coded.value;
384
+ if (domainValue === null ||
385
+ (typeof domainValue !== "string" && typeof domainValue !== "number" && typeof domainValue !== "boolean") ||
386
+ (typeof domainValue === "number" && !Number.isFinite(domainValue))) {
387
+ throw new TypeError(`${path}.values[${index}].value must be a finite JSON scalar`);
388
+ }
389
+ if (!domainValueCompatible(domainValue, type)) {
390
+ throw new TypeError(`${path}.values[${index}].value is incompatible with logical type ${type.kind}`);
391
+ }
392
+ const extensions = coded.extensions === undefined
393
+ ? undefined
394
+ : normalizeExtensions(coded.extensions, `${path}.values[${index}].extensions`);
395
+ return {
396
+ value: domainValue,
397
+ ...(coded.label === undefined ? {} : { label: text(coded.label, `${path}.values[${index}].label`) }),
398
+ ...(coded.description === undefined
399
+ ? {}
400
+ : { description: text(coded.description, `${path}.values[${index}].description`) }),
401
+ ...(extensions ? { extensions } : {}),
402
+ };
403
+ });
404
+ values.sort((left, right) => compareCanonical(left.value, right.value));
405
+ unique(values.map((entry) => canonicalStringify(toJsonValue(entry.value))), `${path}.values`);
406
+ return { state: "coded", values: values, openness: record.openness };
407
+ }
408
+ case "range": {
409
+ exactKeys(record, path, ["state", "minimum", "maximum", "unit"]);
410
+ if (record.minimum === undefined && record.maximum === undefined) {
411
+ throw new TypeError(`${path} must include a minimum or maximum`);
412
+ }
413
+ const minimum = record.minimum === undefined ? undefined : normalizeRangeEndpoint(record.minimum, `${path}.minimum`);
414
+ const maximum = record.maximum === undefined ? undefined : normalizeRangeEndpoint(record.maximum, `${path}.maximum`);
415
+ if (!rangeType(type))
416
+ throw new TypeError(`${path} range is incompatible with logical type ${type.kind}`);
417
+ if (minimum && !domainValueCompatible(minimum.value, type)) {
418
+ throw new TypeError(`${path}.minimum is incompatible with logical type ${type.kind}`);
419
+ }
420
+ if (maximum && !domainValueCompatible(maximum.value, type)) {
421
+ throw new TypeError(`${path}.maximum is incompatible with logical type ${type.kind}`);
422
+ }
423
+ if (minimum && maximum) {
424
+ if (typeof minimum.value !== typeof maximum.value)
425
+ throw new TypeError(`${path} endpoints use conflicting encodings`);
426
+ const comparison = compareDomainValues(minimum.value, maximum.value, type);
427
+ if (comparison === undefined) {
428
+ throw new TypeError(`${path} endpoints cannot be ordered deterministically`);
429
+ }
430
+ if (comparison > 0) {
431
+ throw new TypeError(`${path}.minimum must not exceed maximum`);
432
+ }
433
+ if (comparison === 0 && (!minimum.inclusive || !maximum.inclusive)) {
434
+ throw new TypeError(`${path} equal endpoints must both be inclusive`);
435
+ }
436
+ }
437
+ return {
438
+ state: "range",
439
+ ...(minimum ? { minimum } : {}),
440
+ ...(maximum ? { maximum } : {}),
441
+ ...(record.unit === undefined ? {} : { unit: text(record.unit, `${path}.unit`) }),
442
+ };
443
+ }
444
+ case "unknown":
445
+ exactKeys(record, path, ["state", "reason", "native"]);
446
+ if (!includes(["not-reported", "unrecognized", "conflicting", "limit-exceeded"], record.reason)) {
447
+ throw new TypeError(`${path}.reason is invalid`);
448
+ }
449
+ return {
450
+ state: "unknown",
451
+ reason: record.reason,
452
+ ...(record.native === undefined ? {} : { native: normalizeNative(record.native, `${path}.native`) }),
453
+ };
454
+ default:
455
+ throw new TypeError(`${path}.state is invalid`);
456
+ }
457
+ }
458
+ function normalizeRangeEndpoint(value, path) {
459
+ const record = object(value, path, ["value", "inclusive"]);
460
+ if ((typeof record.value !== "number" && typeof record.value !== "string") || record.value === "") {
461
+ throw new TypeError(`${path}.value must be a number or non-empty string`);
462
+ }
463
+ if (typeof record.value === "number" && !Number.isFinite(record.value))
464
+ throw new TypeError(`${path}.value must be finite`);
465
+ if (typeof record.inclusive !== "boolean")
466
+ throw new TypeError(`${path}.inclusive must be boolean`);
467
+ return { value: record.value, inclusive: record.inclusive };
468
+ }
469
+ function normalizeConstraints(value, path, type) {
470
+ const record = object(value, path);
471
+ if (record.state === "none") {
472
+ exactKeys(record, path, ["state"]);
473
+ return { state: "none" };
474
+ }
475
+ if (record.state === "unknown") {
476
+ exactKeys(record, path, ["state", "reason", "native"]);
477
+ if (!includes(["not-reported", "unrecognized", "conflicting", "limit-exceeded"], record.reason)) {
478
+ throw new TypeError(`${path}.reason is invalid`);
479
+ }
480
+ return {
481
+ state: "unknown",
482
+ reason: record.reason,
483
+ ...(record.native === undefined ? {} : { native: normalizeNative(record.native, `${path}.native`) }),
484
+ };
485
+ }
486
+ if (record.state !== "known" && record.state !== "partial")
487
+ throw new TypeError(`${path}.state is invalid`);
488
+ exactKeys(record, path, record.state === "known" ? ["state", "values"] : ["state", "values", "reason", "native"]);
489
+ const values = array(record.values, `${path}.values`).map((entry, index) => normalizeConstraint(entry, `${path}.values[${index}]`, type));
490
+ if (values.length === 0)
491
+ throw new TypeError(`${path}.values must be non-empty`);
492
+ values.sort((left, right) => compareCanonical(left.kind, right.kind));
493
+ unique(values.map((entry) => entry.kind), `${path}.values constraint kinds`);
494
+ if (record.state === "known")
495
+ return { state: "known", values: values };
496
+ if (!includes(["unrecognized", "conflicting", "limit-exceeded"], record.reason)) {
497
+ throw new TypeError(`${path}.reason is invalid`);
498
+ }
499
+ const native = array(record.native, `${path}.native`).map((entry, index) => normalizeNative(entry, `${path}.native[${index}]`));
500
+ if (native.length === 0)
501
+ throw new TypeError(`${path}.native must be non-empty`);
502
+ native.sort((left, right) => compareCanonical(nativeIdentity(left), nativeIdentity(right)));
503
+ unique(native.map((entry) => canonicalStringify(nativeIdentity(entry))), `${path}.native identities`);
504
+ return {
505
+ state: "partial",
506
+ values: values,
507
+ reason: record.reason,
508
+ native: native,
509
+ };
510
+ }
511
+ function normalizeConstraint(value, path, type) {
512
+ const record = object(value, path);
513
+ const kind = text(record.kind, `${path}.kind`);
514
+ if (kind === "length") {
515
+ exactKeys(record, path, ["kind", "minimum", "maximum"]);
516
+ if (type.kind !== "string" && type.kind !== "binary" && type.kind !== "list") {
517
+ throw new TypeError(`${path} length constraint is incompatible with ${type.kind}`);
518
+ }
519
+ const minimum = optionalNonNegativeInteger(record.minimum, `${path}.minimum`);
520
+ const maximum = optionalNonNegativeInteger(record.maximum, `${path}.maximum`);
521
+ if (minimum === undefined && maximum === undefined)
522
+ throw new TypeError(`${path} requires a bound`);
523
+ if (minimum !== undefined && maximum !== undefined && minimum > maximum)
524
+ throw new TypeError(`${path} bounds conflict`);
525
+ if (type.kind === "string" && type.maxLength !== undefined && maximum !== undefined && type.maxLength !== maximum) {
526
+ throw new TypeError(`${path}.maximum conflicts with logical string maxLength`);
527
+ }
528
+ return { kind, ...(minimum === undefined ? {} : { minimum }), ...(maximum === undefined ? {} : { maximum }) };
529
+ }
530
+ if (kind === "pattern") {
531
+ exactKeys(record, path, ["kind", "syntax", "expression", "flags"]);
532
+ if (type.kind !== "string")
533
+ throw new TypeError(`${path} pattern constraint requires a string type`);
534
+ if (record.syntax !== "ecma-262")
535
+ throw new TypeError(`${path}.syntax must be ecma-262`);
536
+ const expression = text(record.expression, `${path}.expression`, true);
537
+ const flags = record.flags === undefined ? undefined : text(record.flags, `${path}.flags`, true);
538
+ try {
539
+ void new RegExp(expression, flags);
540
+ }
541
+ catch (cause) {
542
+ throw new TypeError(`${path} contains an invalid ECMA-262 pattern`, { cause });
543
+ }
544
+ return { kind, syntax: "ecma-262", expression, ...(flags === undefined ? {} : { flags }) };
545
+ }
546
+ if (kind === "multiple-of") {
547
+ exactKeys(record, path, ["kind", "value"]);
548
+ if (type.kind !== "integer" && type.kind !== "float" && type.kind !== "decimal") {
549
+ throw new TypeError(`${path} multiple-of constraint requires a numeric type`);
550
+ }
551
+ if (typeof record.value !== "number" || !Number.isFinite(record.value) || record.value <= 0) {
552
+ throw new TypeError(`${path}.value must be finite and positive`);
553
+ }
554
+ return { kind, value: record.value };
555
+ }
556
+ if (kind === "unique") {
557
+ exactKeys(record, path, ["kind"]);
558
+ return { kind };
559
+ }
560
+ extensionIdentifier(kind, `${path}.kind`);
561
+ exactKeys(record, path, ["kind", "value"]);
562
+ return { kind: kind, value: jsonValue(record.value, `${path}.value`) };
563
+ }
564
+ function normalizeKey(value, names) {
565
+ const record = object(value, "$.key");
566
+ if (record.state === "none") {
567
+ exactKeys(record, "$.key", ["state"]);
568
+ return { state: "none" };
569
+ }
570
+ if (record.state === "known") {
571
+ exactKeys(record, "$.key", ["state", "fields"]);
572
+ const fields = nonEmptyTextArray(record.fields, "$.key.fields");
573
+ unique(fields, "$.key.fields");
574
+ for (const field of fields)
575
+ if (!names.has(field))
576
+ throw new TypeError(`$.key.fields references unknown field ${field}`);
577
+ return { state: "known", fields };
578
+ }
579
+ if (record.state === "unknown") {
580
+ exactKeys(record, "$.key", ["state", "reason"]);
581
+ if (!includes(["metadata-unavailable", "not-declared", "conflicting"], record.reason)) {
582
+ throw new TypeError("$.key.reason is invalid");
583
+ }
584
+ return { state: "unknown", reason: record.reason };
585
+ }
586
+ throw new TypeError("$.key.state is invalid");
587
+ }
588
+ function normalizeGeometrySchema(value, names, fields) {
589
+ const record = object(value, "$.geometry");
590
+ const hasDescendantGeometry = fields.some((field) => logicalTypeHasDescendantGeometry(field.type));
591
+ if (hasDescendantGeometry && (record.state !== "unknown" || record.reason !== "unrecognized")) {
592
+ throw new TypeError("$.geometry must be unknown/unrecognized while geometry exists below a top-level field path");
593
+ }
594
+ if (record.state === "none") {
595
+ exactKeys(record, "$.geometry", ["state", "reason"]);
596
+ if (record.reason !== "declared-non-spatial" && record.reason !== "no-geometry-fields") {
597
+ throw new TypeError("$.geometry.reason is invalid");
598
+ }
599
+ if (fields.some((field) => field.type.kind === "geometry")) {
600
+ throw new TypeError("$.geometry cannot be none while a logical geometry field exists");
601
+ }
602
+ return { state: "none", reason: record.reason };
603
+ }
604
+ if (record.state === "unknown") {
605
+ exactKeys(record, "$.geometry", ["state", "reason", "native"]);
606
+ if (!includes(["metadata-unavailable", "missing", "unrecognized", "conflicting"], record.reason)) {
607
+ throw new TypeError("$.geometry.reason is invalid");
608
+ }
609
+ return {
610
+ state: "unknown",
611
+ reason: record.reason,
612
+ ...(record.native === undefined ? {} : { native: normalizeNative(record.native, "$.geometry.native") }),
613
+ };
614
+ }
615
+ if (record.state !== "known")
616
+ throw new TypeError("$.geometry.state is invalid");
617
+ exactKeys(record, "$.geometry", ["state", "fields", "primaryField"]);
618
+ const geometryFields = array(record.fields, "$.geometry.fields").map((entry, index) => normalizeGeometryField(entry, `$.geometry.fields[${index}]`));
619
+ if (geometryFields.length === 0)
620
+ throw new TypeError("$.geometry.fields must be non-empty");
621
+ geometryFields.sort((left, right) => compareUtf8(left.field, right.field));
622
+ unique(geometryFields.map((field) => field.field), "$.geometry.fields");
623
+ for (const field of geometryFields) {
624
+ if (!names.has(field.field))
625
+ throw new TypeError(`$.geometry.fields references unknown field ${field.field}`);
626
+ if (fields.find((candidate) => candidate.name === field.field)?.type.kind !== "geometry") {
627
+ throw new TypeError(`$.geometry.fields ${field.field} is not logically typed as geometry`);
628
+ }
629
+ }
630
+ const primary = normalizePrimaryGeometry(record.primaryField, new Set(geometryFields.map((field) => field.field)));
631
+ return {
632
+ state: "known",
633
+ fields: geometryFields,
634
+ primaryField: primary,
635
+ };
636
+ }
637
+ function normalizeGeometryField(value, path) {
638
+ const record = object(value, path, ["field", "geometryTypes", "crs", "layout", "allowsEmpty"]);
639
+ const geometryTypes = normalizeGeometryTypes(record.geometryTypes, `${path}.geometryTypes`);
640
+ if (!includes(["xy", "xyz", "xym", "xyzm", "unknown"], record.layout)) {
641
+ throw new TypeError(`${path}.layout is invalid`);
642
+ }
643
+ if (typeof record.allowsEmpty !== "boolean" && record.allowsEmpty !== "unknown") {
644
+ throw new TypeError(`${path}.allowsEmpty is invalid`);
645
+ }
646
+ return {
647
+ field: text(record.field, `${path}.field`),
648
+ geometryTypes,
649
+ crs: normalizeCrsBinding(record.crs, `${path}.crs`),
650
+ layout: record.layout,
651
+ allowsEmpty: record.allowsEmpty,
652
+ };
653
+ }
654
+ function normalizeGeometryTypes(value, path) {
655
+ const record = object(value, path);
656
+ if (record.state === "known") {
657
+ exactKeys(record, path, ["state", "type"]);
658
+ return { state: "known", type: geometryKind(record.type, `${path}.type`) };
659
+ }
660
+ if (record.state === "mixed") {
661
+ exactKeys(record, path, ["state", "types"]);
662
+ const types = array(record.types, `${path}.types`).map((entry, index) => geometryKind(entry, `${path}.types[${index}]`));
663
+ if (types.length < 2)
664
+ throw new TypeError(`${path}.types must contain at least two kinds`);
665
+ types.sort(compareCanonical);
666
+ unique(types, `${path}.types`);
667
+ return { state: "mixed", types: types };
668
+ }
669
+ if (record.state === "unknown") {
670
+ exactKeys(record, path, ["state", "reason", "native"]);
671
+ if (!includes(["missing", "unrecognized", "conflicting", "unsupported"], record.reason)) {
672
+ throw new TypeError(`${path}.reason is invalid`);
673
+ }
674
+ return {
675
+ state: "unknown",
676
+ reason: record.reason,
677
+ ...(record.native === undefined ? {} : { native: normalizeNative(record.native, `${path}.native`) }),
678
+ };
679
+ }
680
+ throw new TypeError(`${path}.state is invalid`);
681
+ }
682
+ function normalizePrimaryGeometry(value, geometryFields) {
683
+ const record = object(value, "$.geometry.primaryField");
684
+ if (record.state === "known") {
685
+ exactKeys(record, "$.geometry.primaryField", ["state", "field"]);
686
+ const field = text(record.field, "$.geometry.primaryField.field");
687
+ if (!geometryFields.has(field))
688
+ throw new TypeError("$.geometry.primaryField references an unknown geometry field");
689
+ return { state: "known", field };
690
+ }
691
+ if (record.state === "none") {
692
+ exactKeys(record, "$.geometry.primaryField", ["state", "reason"]);
693
+ if (record.reason !== "not-declared" && record.reason !== "no-default") {
694
+ throw new TypeError("$.geometry.primaryField.reason is invalid");
695
+ }
696
+ return { state: "none", reason: record.reason };
697
+ }
698
+ if (record.state === "unknown") {
699
+ exactKeys(record, "$.geometry.primaryField", ["state", "reason"]);
700
+ if (record.reason !== "metadata-unavailable" && record.reason !== "conflicting") {
701
+ throw new TypeError("$.geometry.primaryField.reason is invalid");
702
+ }
703
+ return { state: "unknown", reason: record.reason };
704
+ }
705
+ throw new TypeError("$.geometry.primaryField.state is invalid");
706
+ }
707
+ function normalizeTemporalSchema(value, names, fields) {
708
+ const record = object(value, "$.temporal");
709
+ const requireTemporal = (field) => {
710
+ if (!names.has(field))
711
+ throw new TypeError(`$.temporal references unknown field ${field}`);
712
+ const kind = fields.find((candidate) => candidate.name === field)?.type.kind;
713
+ if (kind !== "date" && kind !== "time" && kind !== "timestamp") {
714
+ throw new TypeError(`$.temporal field ${field} is not an instant or endpoint type`);
715
+ }
716
+ };
717
+ if (record.state === "none") {
718
+ exactKeys(record, "$.temporal", ["state"]);
719
+ return { state: "none" };
720
+ }
721
+ if (record.state === "instant") {
722
+ exactKeys(record, "$.temporal", ["state", "field"]);
723
+ const field = text(record.field, "$.temporal.field");
724
+ requireTemporal(field);
725
+ return { state: "instant", field };
726
+ }
727
+ if (record.state === "interval") {
728
+ exactKeys(record, "$.temporal", ["state", "startField", "endField"]);
729
+ const startField = text(record.startField, "$.temporal.startField");
730
+ const endField = text(record.endField, "$.temporal.endField");
731
+ requireTemporal(startField);
732
+ requireTemporal(endField);
733
+ if (startField === endField)
734
+ throw new TypeError("$.temporal interval fields must differ");
735
+ return { state: "interval", startField, endField };
736
+ }
737
+ if (record.state === "mixed") {
738
+ exactKeys(record, "$.temporal", ["state", "fields"]);
739
+ const temporalFields = [...nonEmptyTextArray(record.fields, "$.temporal.fields")].sort(compareCanonical);
740
+ unique(temporalFields, "$.temporal.fields");
741
+ for (const field of temporalFields)
742
+ requireTemporal(field);
743
+ return { state: "mixed", fields: temporalFields };
744
+ }
745
+ if (record.state === "unknown") {
746
+ exactKeys(record, "$.temporal", ["state", "reason"]);
747
+ if (!includes(["metadata-unavailable", "not-declared", "conflicting"], record.reason)) {
748
+ throw new TypeError("$.temporal.reason is invalid");
749
+ }
750
+ return { state: "unknown", reason: record.reason };
751
+ }
752
+ throw new TypeError("$.temporal.state is invalid");
753
+ }
754
+ function validateFieldSemantics(fields, key, geometry, temporal) {
755
+ const keyFields = new Set(key.state === "known" ? key.fields : []);
756
+ const geometryFields = new Set(geometry.state === "known" ? geometry.fields.map((field) => field.field) : []);
757
+ const instantFields = new Set(temporal.state === "instant" ? [temporal.field] : []);
758
+ const mixedFields = new Set(temporal.state === "mixed" ? temporal.fields : []);
759
+ const simultaneouslyAddressablePaths = new Set(fields.map((field) => canonicalStringify(toJsonValue(field.path))));
760
+ for (const field of fields) {
761
+ const has = (role) => field.roles.includes(role);
762
+ if (has("geometry") !== (field.type.kind === "geometry")) {
763
+ throw new TypeError(`Field ${field.name} geometry type, role, and schema membership must agree`);
764
+ }
765
+ if (geometry.state === "known" && geometryFields.has(field.name) !== (field.type.kind === "geometry")) {
766
+ throw new TypeError(`Field ${field.name} geometry type, role, and schema membership must agree`);
767
+ }
768
+ if (has("primary-key") !== keyFields.has(field.name)) {
769
+ throw new TypeError(`Field ${field.name} primary-key role must agree with schema.key`);
770
+ }
771
+ if (keyFields.has(field.name)) {
772
+ if (field.nullability !== "non-nullable") {
773
+ throw new TypeError(`Known key field ${field.name} must be non-nullable`);
774
+ }
775
+ if (!featureIdentityLogicalType(field.type)) {
776
+ throw new TypeError(`Known key field ${field.name} cannot produce a FeatureIdentityValue`);
777
+ }
778
+ }
779
+ const temporalType = field.type.kind === "date" || field.type.kind === "time" || field.type.kind === "timestamp";
780
+ if ((has("time-instant") || has("time-start") || has("time-end") || has("created-at") || has("updated-at")) &&
781
+ !temporalType) {
782
+ throw new TypeError(`Field ${field.name} has a temporal role but is not temporally typed`);
783
+ }
784
+ if (temporal.state === "interval") {
785
+ if (has("time-start") !== (field.name === temporal.startField)) {
786
+ throw new TypeError(`Field ${field.name} time-start role must agree with schema.temporal`);
787
+ }
788
+ if (has("time-end") !== (field.name === temporal.endField)) {
789
+ throw new TypeError(`Field ${field.name} time-end role must agree with schema.temporal`);
790
+ }
791
+ }
792
+ else if (temporal.state !== "mixed" && (has("time-start") || has("time-end"))) {
793
+ throw new TypeError(`Field ${field.name} interval role requires interval temporal schema`);
794
+ }
795
+ if (temporal.state !== "mixed" && has("time-instant") !== instantFields.has(field.name)) {
796
+ throw new TypeError(`Field ${field.name} time-instant role must agree with schema.temporal`);
797
+ }
798
+ if (temporal.state === "mixed") {
799
+ const hasTemporalRole = has("time-instant") || has("time-start") || has("time-end") || has("created-at") || has("updated-at");
800
+ if (mixedFields.has(field.name) !== hasTemporalRole) {
801
+ throw new TypeError(`Field ${field.name} temporal role must agree with mixed schema.temporal`);
802
+ }
803
+ }
804
+ if (field.type.kind === "geometry" && Object.hasOwn(field, "defaultValue") && field.defaultValue !== null) {
805
+ const geometryField = geometry.state === "known" ? geometry.fields.find((item) => item.field === field.name) : undefined;
806
+ const summary = canonicalGeometrySummary(field.defaultValue);
807
+ const expectedArity = geometryField?.layout === "xy"
808
+ ? 2
809
+ : geometryField?.layout === "xyz" || geometryField?.layout === "xym"
810
+ ? 3
811
+ : geometryField?.layout === "xyzm"
812
+ ? 4
813
+ : undefined;
814
+ if (expectedArity !== undefined && summary?.arity !== expectedArity) {
815
+ throw new TypeError(`Field ${field.name} defaultValue ordinate arity does not match declared ${geometryField.layout} layout`);
816
+ }
817
+ if (summary &&
818
+ geometryField?.geometryTypes.state === "known" &&
819
+ summary.type !== geometryField.geometryTypes.type) {
820
+ throw new TypeError(`Field ${field.name} defaultValue type ${summary.type} does not match declared ${geometryField.geometryTypes.type}`);
821
+ }
822
+ if (summary &&
823
+ geometryField?.geometryTypes.state === "mixed" &&
824
+ !geometryField.geometryTypes.types.includes(summary.type)) {
825
+ throw new TypeError(`Field ${field.name} defaultValue type ${summary.type} is not in the declared mixed types`);
826
+ }
827
+ }
828
+ validateDescendantFieldSemantics(field.type, field.path, simultaneouslyAddressablePaths);
829
+ }
830
+ }
831
+ function featureIdentityLogicalType(type) {
832
+ if (type.kind === "union")
833
+ return type.members.every(featureIdentityLogicalType);
834
+ return includes([
835
+ "boolean",
836
+ "integer",
837
+ "float",
838
+ "decimal",
839
+ "string",
840
+ "binary",
841
+ "uuid",
842
+ "date",
843
+ "time",
844
+ "timestamp",
845
+ "duration",
846
+ ], type.kind);
847
+ }
848
+ function logicalTypeContainsGeometry(type) {
849
+ if (type.kind === "geometry")
850
+ return true;
851
+ if (type.kind === "list")
852
+ return logicalTypeContainsGeometry(type.element);
853
+ if (type.kind === "union")
854
+ return type.members.some(logicalTypeContainsGeometry);
855
+ if (type.kind === "struct")
856
+ return type.fields.some((field) => logicalTypeContainsGeometry(field.type));
857
+ return false;
858
+ }
859
+ function logicalTypeHasDescendantGeometry(type) {
860
+ return type.kind !== "geometry" && logicalTypeContainsGeometry(type);
861
+ }
862
+ function validateDescendantFieldSemantics(type, parentPath, simultaneouslyAddressablePaths) {
863
+ if (type.kind === "list") {
864
+ validateDescendantFieldSemantics(type.element, parentPath, simultaneouslyAddressablePaths);
865
+ return;
866
+ }
867
+ if (type.kind === "union") {
868
+ const branchPaths = new Set();
869
+ for (const member of type.members) {
870
+ const branchAddressablePaths = new Set(simultaneouslyAddressablePaths);
871
+ validateDescendantFieldSemantics(member, parentPath, branchAddressablePaths);
872
+ for (const path of branchAddressablePaths) {
873
+ if (!simultaneouslyAddressablePaths.has(path))
874
+ branchPaths.add(path);
875
+ }
876
+ }
877
+ for (const path of branchPaths)
878
+ simultaneouslyAddressablePaths.add(path);
879
+ return;
880
+ }
881
+ if (type.kind !== "struct")
882
+ return;
883
+ for (const field of type.fields) {
884
+ const label = field.path.join(".");
885
+ if (!strictPathDescendant(field.path, parentPath)) {
886
+ throw new TypeError(`Nested field ${label} path must strictly extend parent path ${parentPath.join(".")}`);
887
+ }
888
+ const pathIdentity = canonicalStringify(toJsonValue(field.path));
889
+ if (simultaneouslyAddressablePaths.has(pathIdentity)) {
890
+ throw new TypeError(`Logical field path ${label} collides with another simultaneously addressable field`);
891
+ }
892
+ simultaneouslyAddressablePaths.add(pathIdentity);
893
+ if (field.roles.includes("geometry") !== (field.type.kind === "geometry")) {
894
+ throw new TypeError(`Nested field ${label} geometry type and role must agree`);
895
+ }
896
+ const unreachableRole = field.roles.find((role) => role !== "geometry" &&
897
+ includes(["primary-key", "feature-id", "time-instant", "time-start", "time-end", "created-at", "updated-at"], role));
898
+ if (unreachableRole) {
899
+ throw new TypeError(`Nested field ${label} cannot carry ${unreachableRole}; source key/time identity is top-level string-addressed`);
900
+ }
901
+ validateDescendantFieldSemantics(field.type, field.path, simultaneouslyAddressablePaths);
902
+ }
903
+ }
904
+ function strictPathDescendant(path, parentPath) {
905
+ return path.length > parentPath.length && parentPath.every((segment, index) => path[index] === segment);
906
+ }
907
+ function normalizeCrsBinding(value, path) {
908
+ const record = object(value, path, ["definition", "coordinateOrder", "coordinateEpoch", "provenance"]);
909
+ const coordinateEpoch = record.coordinateEpoch === undefined ? undefined : finiteNumber(record.coordinateEpoch, `${path}.coordinateEpoch`);
910
+ const definition = normalizeCrsDefinition(record.definition, `${path}.definition`);
911
+ const provenance = normalizeCrsProvenance(record.provenance, `${path}.provenance`);
912
+ if (provenance.method === "reprojected" &&
913
+ canonicalStringify(crsDefinitionFingerprintProjection(definition)) !==
914
+ canonicalStringify(crsDefinitionFingerprintProjection(provenance.reprojection.target))) {
915
+ throw new TypeError(`${path}.definition must semantically match provenance.reprojection.target`);
916
+ }
917
+ return {
918
+ definition,
919
+ coordinateOrder: normalizeAxisOrder(record.coordinateOrder, `${path}.coordinateOrder`),
920
+ ...(coordinateEpoch === undefined ? {} : { coordinateEpoch }),
921
+ provenance,
922
+ };
923
+ }
924
+ function normalizeCrsDefinition(value, path) {
925
+ const record = object(value, path);
926
+ switch (record.kind) {
927
+ case "authority":
928
+ exactKeys(record, path, ["kind", "authority", "code", "version", "uri", "wkt", "definitionAxisOrder"]);
929
+ return {
930
+ kind: "authority",
931
+ authority: text(record.authority, `${path}.authority`),
932
+ code: text(record.code, `${path}.code`),
933
+ ...(record.version === undefined ? {} : { version: text(record.version, `${path}.version`) }),
934
+ ...(record.uri === undefined ? {} : { uri: absoluteUri(record.uri, `${path}.uri`) }),
935
+ ...(record.wkt === undefined ? {} : { wkt: text(record.wkt, `${path}.wkt`) }),
936
+ definitionAxisOrder: normalizeAxisOrder(record.definitionAxisOrder, `${path}.definitionAxisOrder`),
937
+ };
938
+ case "wkt":
939
+ exactKeys(record, path, ["kind", "wkt", "dialect", "validation", "name", "definitionAxisOrder"]);
940
+ if (!includes(["wkt1", "wkt2", "unknown"], record.dialect))
941
+ throw new TypeError(`${path}.dialect is invalid`);
942
+ if (!includes(["unverified", "engine"], record.validation)) {
943
+ throw new TypeError(`${path}.validation is invalid`);
944
+ }
945
+ return {
946
+ kind: "wkt",
947
+ wkt: text(record.wkt, `${path}.wkt`),
948
+ dialect: record.dialect,
949
+ validation: record.validation,
950
+ ...(record.name === undefined ? {} : { name: text(record.name, `${path}.name`) }),
951
+ definitionAxisOrder: normalizeAxisOrder(record.definitionAxisOrder, `${path}.definitionAxisOrder`),
952
+ };
953
+ case "uri":
954
+ exactKeys(record, path, ["kind", "uri", "name", "definitionAxisOrder"]);
955
+ return {
956
+ kind: "uri",
957
+ uri: absoluteUri(record.uri, `${path}.uri`),
958
+ ...(record.name === undefined ? {} : { name: text(record.name, `${path}.name`) }),
959
+ definitionAxisOrder: normalizeAxisOrder(record.definitionAxisOrder, `${path}.definitionAxisOrder`),
960
+ };
961
+ case "projjson": {
962
+ exactKeys(record, path, ["kind", "projjson", "name", "definitionAxisOrder"]);
963
+ const projjson = jsonObject(record.projjson, `${path}.projjson`);
964
+ if (new TextEncoder().encode(canonicalStringify(toJsonValue(projjson))).byteLength > MAX_PROJJSON_BYTES) {
965
+ throw new TypeError(`${path}.projjson exceeds ${MAX_PROJJSON_BYTES} bytes`);
966
+ }
967
+ validateProjJson(projjson, `${path}.projjson`);
968
+ return {
969
+ kind: "projjson",
970
+ projjson,
971
+ ...(record.name === undefined ? {} : { name: text(record.name, `${path}.name`) }),
972
+ definitionAxisOrder: normalizeAxisOrder(record.definitionAxisOrder, `${path}.definitionAxisOrder`),
973
+ };
974
+ }
975
+ case "unknown":
976
+ exactKeys(record, path, ["kind", "reason", "native"]);
977
+ if (!includes(["missing", "unrecognized", "conflicting"], record.reason)) {
978
+ throw new TypeError(`${path}.reason is invalid`);
979
+ }
980
+ return {
981
+ kind: "unknown",
982
+ reason: record.reason,
983
+ ...(record.native === undefined ? {} : { native: normalizeNative(record.native, `${path}.native`) }),
984
+ };
985
+ default:
986
+ throw new TypeError(`${path}.kind is invalid`);
987
+ }
988
+ }
989
+ function normalizeAxisOrder(value, path) {
990
+ const record = object(value, path);
991
+ if (record.state === "known") {
992
+ exactKeys(record, path, ["state", "source", "axes"]);
993
+ if (!includes(["crs-definition", "protocol", "encoding", "declared"], record.source)) {
994
+ throw new TypeError(`${path}.source is invalid`);
995
+ }
996
+ const axes = array(record.axes, `${path}.axes`).map((axis, index) => normalizeAxis(axis, `${path}.axes[${index}]`));
997
+ if (axes.length < 2)
998
+ throw new TypeError(`${path}.axes must contain at least two axes`);
999
+ return {
1000
+ state: "known",
1001
+ source: record.source,
1002
+ axes: axes,
1003
+ };
1004
+ }
1005
+ if (record.state === "unknown") {
1006
+ exactKeys(record, path, ["state", "reason", "native"]);
1007
+ if (!includes(["missing", "unrecognized", "conflicting"], record.reason)) {
1008
+ throw new TypeError(`${path}.reason is invalid`);
1009
+ }
1010
+ return {
1011
+ state: "unknown",
1012
+ reason: record.reason,
1013
+ ...(record.native === undefined ? {} : { native: normalizeNative(record.native, `${path}.native`) }),
1014
+ };
1015
+ }
1016
+ throw new TypeError(`${path}.state is invalid`);
1017
+ }
1018
+ function normalizeAxis(value, path) {
1019
+ const record = object(value, path, ["name", "abbreviation", "direction", "unit"]);
1020
+ if (!includes(["east", "west", "north", "south", "up", "down", "future", "past", "other"], record.direction)) {
1021
+ throw new TypeError(`${path}.direction is invalid`);
1022
+ }
1023
+ return {
1024
+ name: text(record.name, `${path}.name`),
1025
+ ...(record.abbreviation === undefined ? {} : { abbreviation: text(record.abbreviation, `${path}.abbreviation`) }),
1026
+ direction: record.direction,
1027
+ unit: text(record.unit, `${path}.unit`),
1028
+ };
1029
+ }
1030
+ function normalizeCrsProvenance(value, path) {
1031
+ const record = object(value, path);
1032
+ if (record.method === "reprojected") {
1033
+ exactKeys(record, path, ["method", "native", "reprojection"]);
1034
+ const reprojection = object(record.reprojection, `${path}.reprojection`, [
1035
+ "source",
1036
+ "target",
1037
+ "operation",
1038
+ "engine",
1039
+ "accuracyMeters",
1040
+ "transformedAt",
1041
+ ]);
1042
+ const source = normalizeCrsDefinition(reprojection.source, `${path}.reprojection.source`);
1043
+ const target = normalizeCrsDefinition(reprojection.target, `${path}.reprojection.target`);
1044
+ if (!isResolvedCrsDefinition(source) || !isResolvedCrsDefinition(target))
1045
+ throw new TypeError(`${path}.reprojection CRS must be resolved`);
1046
+ return {
1047
+ method: "reprojected",
1048
+ ...(record.native === undefined ? {} : { native: normalizeNative(record.native, `${path}.native`) }),
1049
+ reprojection: {
1050
+ source,
1051
+ target,
1052
+ ...(reprojection.operation === undefined
1053
+ ? {}
1054
+ : { operation: text(reprojection.operation, `${path}.reprojection.operation`) }),
1055
+ engine: text(reprojection.engine, `${path}.reprojection.engine`),
1056
+ ...(reprojection.accuracyMeters === undefined
1057
+ ? {}
1058
+ : {
1059
+ accuracyMeters: nonNegativeFiniteNumber(reprojection.accuracyMeters, `${path}.reprojection.accuracyMeters`),
1060
+ }),
1061
+ ...(reprojection.transformedAt === undefined
1062
+ ? {}
1063
+ : { transformedAt: rfc3339Timestamp(reprojection.transformedAt, `${path}.reprojection.transformedAt`) }),
1064
+ },
1065
+ };
1066
+ }
1067
+ if (!includes(["metadata", "payload", "standard-default", "declared"], record.method)) {
1068
+ throw new TypeError(`${path}.method is invalid`);
1069
+ }
1070
+ exactKeys(record, path, ["method", "native"]);
1071
+ return {
1072
+ method: record.method,
1073
+ ...(record.native === undefined ? {} : { native: normalizeNative(record.native, `${path}.native`) }),
1074
+ };
1075
+ }
1076
+ function isResolvedCrsDefinition(definition) {
1077
+ return definition.kind !== "unknown" && (definition.kind !== "wkt" || definition.validation === "engine");
1078
+ }
1079
+ function normalizeNative(value, path) {
1080
+ const record = object(value, path, ["protocol", "name", "namespace", "path", "definition"]);
1081
+ const protocol = sourceProtocol(record.protocol, `${path}.protocol`);
1082
+ const rawDefinition = record.definition === undefined ? undefined : jsonValue(record.definition, `${path}.definition`);
1083
+ if (rawDefinition !== undefined &&
1084
+ new TextEncoder().encode(canonicalStringify(toJsonValue(rawDefinition))).byteLength > MAX_NATIVE_DEFINITION_BYTES) {
1085
+ throw new TypeError(`${path}.definition exceeds ${MAX_NATIVE_DEFINITION_BYTES} bytes`);
1086
+ }
1087
+ const definition = rawDefinition === undefined ? undefined : sanitizeNativeDefinition(rawDefinition);
1088
+ return {
1089
+ protocol,
1090
+ name: text(record.name, `${path}.name`),
1091
+ ...(record.namespace === undefined ? {} : { namespace: text(record.namespace, `${path}.namespace`) }),
1092
+ ...(record.path === undefined ? {} : { path: textArray(record.path, `${path}.path`) }),
1093
+ ...(definition === undefined ? {} : { definition }),
1094
+ };
1095
+ }
1096
+ const REDACTED_NATIVE_VALUE = "[REDACTED]";
1097
+ const MAX_NATIVE_SANITIZER_DEPTH = 8;
1098
+ const SENSITIVE_NATIVE_KEY = /^(?:authorization|proxy[-_]?authorization|auth[-_]?token|bearer|bearer[-_]?token|jwt|token|access[-_]?token|refresh[-_]?token|id[-_]?token|api[-_]?key|apikey|subscription[-_]?key|ocp[-_]?apim[-_]?subscription[-_]?key|secret|client[-_]?secret|password|passwd|pwd|credential|private[-_]?key|session(?:[-_]?id|[-_]?token)?|cookie|set[-_]?cookie|aws[-_]?access[-_]?key[-_]?id|aws[-_]?secret[-_]?access[-_]?key)$/i;
1099
+ const SENSITIVE_QUERY_NAMES = new Set([
1100
+ "authorization",
1101
+ "auth",
1102
+ "authtoken",
1103
+ "bearer",
1104
+ "jwt",
1105
+ "token",
1106
+ "accesstoken",
1107
+ "refreshtoken",
1108
+ "idtoken",
1109
+ "apikey",
1110
+ "key",
1111
+ "secret",
1112
+ "clientsecret",
1113
+ "password",
1114
+ "pwd",
1115
+ "credential",
1116
+ "session",
1117
+ "sessionid",
1118
+ "subscriptionkey",
1119
+ "ocpapimsubscriptionkey",
1120
+ "signature",
1121
+ "sig",
1122
+ "policy",
1123
+ "expires",
1124
+ "sas",
1125
+ "se",
1126
+ "sp",
1127
+ "spr",
1128
+ "sr",
1129
+ "sv",
1130
+ "code",
1131
+ ]);
1132
+ function sanitizeNativeDefinition(value) {
1133
+ if (typeof value === "string")
1134
+ return sanitizeNativeString(value);
1135
+ if (value === null || typeof value === "number" || typeof value === "boolean")
1136
+ return value;
1137
+ if (Array.isArray(value))
1138
+ return value.map(sanitizeNativeDefinition);
1139
+ const sanitized = Object.create(null);
1140
+ for (const [key, child] of Object.entries(value)) {
1141
+ sanitized[key] =
1142
+ SENSITIVE_NATIVE_KEY.test(key) && !credentialPlaceholder(child)
1143
+ ? REDACTED_NATIVE_VALUE
1144
+ : sanitizeNativeDefinition(child);
1145
+ }
1146
+ return sanitized;
1147
+ }
1148
+ function sanitizeNativeString(value, depth = 0) {
1149
+ if (/-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/.test(value))
1150
+ return REDACTED_NATIVE_VALUE;
1151
+ let sanitized = replaceNativeUrls(value, depth);
1152
+ sanitized = replaceUnqualifiedUserInfo(sanitized);
1153
+ sanitized = sanitized.replace(/\b(Bearer|Basic)\s+[^\s,;]+/gi, `$1 ${REDACTED_NATIVE_VALUE}`);
1154
+ sanitized = sanitized.replace(/\b(?:eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}|(?:AKIA|ASIA|AIDA|AROA|AIPA|ANPA|ANVA|ASCA)[A-Z0-9]{16}|gh[pousr]_[A-Za-z0-9]{20,})\b/g, REDACTED_NATIVE_VALUE);
1155
+ sanitized = sanitized.replace(/\b(auth[-_]?token|bearer|jwt|token|access[-_]?token|refresh[-_]?token|id[-_]?token|api[-_]?key|apikey|subscription[-_]?key|secret|client[-_]?secret|password|passwd|pwd|credential|session(?:[-_]?id|[-_]?token)?|signature|sig|aws[-_]?access[-_]?key[-_]?id|aws[-_]?secret[-_]?access[-_]?key)\s*([=:])\s*([^\s&,;"']+)/gi, (_match, key, separator, credential) => credentialPlaceholder(credential)
1156
+ ? `${key}${separator}${credential}`
1157
+ : `${key}${separator}${REDACTED_NATIVE_VALUE}`);
1158
+ return sanitized;
1159
+ }
1160
+ function replaceNativeUrls(value, depth) {
1161
+ let output = "";
1162
+ let copiedThrough = 0;
1163
+ let cursor = 0;
1164
+ while (cursor < value.length) {
1165
+ if (!asciiLetter(value.charCodeAt(cursor))) {
1166
+ cursor += 1;
1167
+ continue;
1168
+ }
1169
+ const start = cursor;
1170
+ cursor += 1;
1171
+ while (cursor < value.length && asciiSchemeCharacter(value.charCodeAt(cursor)))
1172
+ cursor += 1;
1173
+ if (!schemeAuthorityAt(value, start, cursor))
1174
+ continue;
1175
+ let end = cursor + 3;
1176
+ while (end < value.length && !nativeUrlTerminator(value.charCodeAt(end)))
1177
+ end += 1;
1178
+ output += value.slice(copiedThrough, start);
1179
+ output += sanitizeNativeUrl(value.slice(start, end), depth);
1180
+ copiedThrough = end;
1181
+ cursor = end;
1182
+ }
1183
+ return copiedThrough === 0 ? value : output + value.slice(copiedThrough);
1184
+ }
1185
+ function sanitizeNativeUrl(value, depth) {
1186
+ const colon = value.indexOf(":");
1187
+ if (colon < 1 || !schemeAuthorityAt(value, 0, colon))
1188
+ return value;
1189
+ if (value.includes("\\"))
1190
+ return REDACTED_NATIVE_VALUE;
1191
+ try {
1192
+ const url = new URL(value);
1193
+ url.username = "";
1194
+ url.password = "";
1195
+ const sanitizedQuery = new URLSearchParams();
1196
+ for (const [key, entry] of url.searchParams) {
1197
+ sanitizedQuery.append(key, sensitiveQueryParameter(key) && !credentialPlaceholder(entry) ? REDACTED_NATIVE_VALUE : entry);
1198
+ }
1199
+ url.search = sanitizedQuery.toString();
1200
+ if (url.hash) {
1201
+ url.hash =
1202
+ depth >= MAX_NATIVE_SANITIZER_DEPTH ? REDACTED_NATIVE_VALUE : sanitizeNativeString(url.hash, depth + 1);
1203
+ }
1204
+ return url.toString();
1205
+ }
1206
+ catch {
1207
+ // A malformed authority/port cannot be safely decomposed. Native evidence
1208
+ // is diagnostic-only, so fail closed rather than preserve possible userinfo.
1209
+ return REDACTED_NATIVE_VALUE;
1210
+ }
1211
+ }
1212
+ function replaceUnqualifiedUserInfo(value) {
1213
+ let output = "";
1214
+ let cursor = 0;
1215
+ while (cursor < value.length) {
1216
+ if (nativeCredentialBoundary(value.charCodeAt(cursor))) {
1217
+ output += value[cursor];
1218
+ cursor += 1;
1219
+ continue;
1220
+ }
1221
+ const start = cursor;
1222
+ let end = cursor + 1;
1223
+ while (end < value.length && !nativeCredentialBoundary(value.charCodeAt(end)))
1224
+ end += 1;
1225
+ output += nativeSegmentContainsUserInfo(value, start, end) ? REDACTED_NATIVE_VALUE : value.slice(start, end);
1226
+ cursor = end;
1227
+ }
1228
+ return output;
1229
+ }
1230
+ function nativeSegmentContainsUserInfo(value, start, end) {
1231
+ let colon = -1;
1232
+ for (let cursor = start; cursor < end; cursor += 1) {
1233
+ const code = value.charCodeAt(cursor);
1234
+ if (code === 58)
1235
+ colon = cursor;
1236
+ else if (code === 64 && colon > start && colon < cursor && cursor + 1 < end)
1237
+ return true;
1238
+ }
1239
+ return false;
1240
+ }
1241
+ function nativeCredentialBoundary(code) {
1242
+ return (code <= 32 ||
1243
+ code === 34 ||
1244
+ code === 39 ||
1245
+ code === 40 ||
1246
+ code === 41 ||
1247
+ code === 44 ||
1248
+ code === 59 ||
1249
+ code === 60 ||
1250
+ code === 61 ||
1251
+ code === 62);
1252
+ }
1253
+ function schemeAuthorityAt(value, start, colon) {
1254
+ if (colon <= start || value.charCodeAt(colon) !== 58 || !asciiLetter(value.charCodeAt(start)))
1255
+ return false;
1256
+ for (let index = start + 1; index < colon; index += 1) {
1257
+ if (!asciiSchemeCharacter(value.charCodeAt(index)))
1258
+ return false;
1259
+ }
1260
+ return slashOrBackslash(value.charCodeAt(colon + 1)) && slashOrBackslash(value.charCodeAt(colon + 2));
1261
+ }
1262
+ function asciiLetter(code) {
1263
+ return (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
1264
+ }
1265
+ function asciiSchemeCharacter(code) {
1266
+ return asciiLetter(code) || (code >= 48 && code <= 57) || code === 43 || code === 45 || code === 46;
1267
+ }
1268
+ function slashOrBackslash(code) {
1269
+ return code === 47 || code === 92;
1270
+ }
1271
+ function nativeUrlTerminator(code) {
1272
+ return code <= 32 || code === 34 || code === 39 || code === 44 || code === 60 || code === 62;
1273
+ }
1274
+ function sensitiveQueryParameter(key) {
1275
+ const normalized = key.replace(/[^A-Za-z0-9]/g, "").toLowerCase();
1276
+ return SENSITIVE_QUERY_NAMES.has(normalized) || normalized.startsWith("xamz") || normalized.startsWith("xgoog");
1277
+ }
1278
+ function credentialPlaceholder(value) {
1279
+ if (value === null || value === "")
1280
+ return true;
1281
+ if (typeof value !== "string")
1282
+ return false;
1283
+ const normalized = value.trim();
1284
+ return (/^\$\{[A-Za-z_][A-Za-z0-9_]*\}$/.test(normalized) ||
1285
+ /^\{\{[A-Za-z_][A-Za-z0-9_]*\}\}$/.test(normalized) ||
1286
+ /^<[A-Za-z_][A-Za-z0-9_-]*>$/.test(normalized) ||
1287
+ /^\[[A-Za-z_][A-Za-z0-9_-]*\]$/.test(normalized) ||
1288
+ /^(?:YOUR|EXAMPLE|PLACEHOLDER|REDACTED|CHANGEME)(?:[_-][A-Za-z0-9_-]+)?$/i.test(normalized));
1289
+ }
1290
+ function normalizeProvenanceArray(value, path) {
1291
+ const provenance = array(value, path).map((entry, index) => normalizeProvenance(entry, `${path}[${index}]`));
1292
+ if (provenance.length === 0)
1293
+ throw new TypeError(`${path} must be non-empty`);
1294
+ return provenance;
1295
+ }
1296
+ function normalizeProvenance(value, path) {
1297
+ const record = object(value, path, ["method", "protocol", "source", "observedAt", "validator", "detail"]);
1298
+ if (!includes(["observed", "declared", "standard-default", "inferred", "unavailable"], record.method)) {
1299
+ throw new TypeError(`${path}.method is invalid`);
1300
+ }
1301
+ const validator = record.validator === undefined ? undefined : normalizeValidator(record.validator, `${path}.validator`);
1302
+ return {
1303
+ method: record.method,
1304
+ protocol: sourceProtocol(record.protocol, `${path}.protocol`),
1305
+ source: sanitizeProvenanceText(text(record.source, `${path}.source`)),
1306
+ ...(record.observedAt === undefined
1307
+ ? {}
1308
+ : { observedAt: rfc3339Timestamp(record.observedAt, `${path}.observedAt`) }),
1309
+ ...(validator ? { validator } : {}),
1310
+ ...(record.detail === undefined ? {} : { detail: sanitizeProvenanceText(text(record.detail, `${path}.detail`)) }),
1311
+ };
1312
+ }
1313
+ function sanitizeProvenanceText(value) {
1314
+ return sanitizeNativeString(value);
1315
+ }
1316
+ function normalizeValidator(value, path) {
1317
+ const record = object(value, path, ["kind", "value"]);
1318
+ if (!includes(["etag", "last-modified", "version"], record.kind))
1319
+ throw new TypeError(`${path}.kind is invalid`);
1320
+ return { kind: record.kind, value: text(record.value, `${path}.value`) };
1321
+ }
1322
+ function normalizeExtensions(value, path) {
1323
+ const record = object(value, path);
1324
+ const out = {};
1325
+ for (const key of Object.keys(record).sort()) {
1326
+ extensionIdentifier(key, `${path}.${key}`);
1327
+ out[key] = jsonValue(record[key], `${path}.${key}`);
1328
+ }
1329
+ return out;
1330
+ }
1331
+ function schemaFingerprint(schema) {
1332
+ const projection = {
1333
+ kind: schema.kind,
1334
+ version: schema.version,
1335
+ openContent: schema.openContent,
1336
+ ...(schema.extensions ? { extensions: schema.extensions } : {}),
1337
+ fields: schema.fields.map(fieldFingerprintProjection),
1338
+ key: schema.key,
1339
+ geometry: geometryFingerprintProjection(schema.geometry),
1340
+ temporal: schema.temporal,
1341
+ };
1342
+ return sha256(`${SOURCE_SCHEMA_V2_FINGERPRINT_DOMAIN}\n${canonicalStringify(toJsonValue(projection))}`);
1343
+ }
1344
+ function fieldFingerprintProjection(field) {
1345
+ return toJsonValue({
1346
+ name: field.name,
1347
+ path: field.path,
1348
+ type: typeFingerprintProjection(field.type),
1349
+ nullability: field.nullability,
1350
+ mutability: field.mutability,
1351
+ defaultValuePresent: Object.hasOwn(field, "defaultValue"),
1352
+ ...(Object.hasOwn(field, "defaultValue") ? { defaultValue: field.defaultValue } : {}),
1353
+ roles: [...field.roles].sort(compareCanonical),
1354
+ domain: domainFingerprintProjection(field.domain),
1355
+ constraints: constraintFingerprintProjection(field.constraints),
1356
+ ...(field.extensions ? { extensions: field.extensions } : {}),
1357
+ });
1358
+ }
1359
+ function typeFingerprintProjection(type) {
1360
+ if (type.kind === "unknown")
1361
+ return { kind: type.kind, reason: type.reason };
1362
+ if (type.kind === "list")
1363
+ return { kind: type.kind, element: typeFingerprintProjection(type.element) };
1364
+ if (type.kind === "struct") {
1365
+ return { kind: type.kind, fields: [...type.fields].sort(compareFields).map(fieldFingerprintProjection) };
1366
+ }
1367
+ if (type.kind === "union") {
1368
+ return { kind: type.kind, members: [...type.members].map(typeFingerprintProjection).sort(compareCanonical) };
1369
+ }
1370
+ return toJsonValue(type);
1371
+ }
1372
+ function domainFingerprintProjection(domain) {
1373
+ if (domain.state === "unknown") {
1374
+ return {
1375
+ state: domain.state,
1376
+ reason: domain.reason,
1377
+ nativePresent: domain.native !== undefined,
1378
+ ...(domain.native ? { native: nativeIdentity(domain.native) } : {}),
1379
+ };
1380
+ }
1381
+ return toJsonValue(domain);
1382
+ }
1383
+ function constraintFingerprintProjection(constraints) {
1384
+ if (constraints.state === "unknown") {
1385
+ return {
1386
+ state: constraints.state,
1387
+ reason: constraints.reason,
1388
+ nativePresent: constraints.native !== undefined,
1389
+ ...(constraints.native ? { native: nativeIdentity(constraints.native) } : {}),
1390
+ };
1391
+ }
1392
+ if (constraints.state === "partial") {
1393
+ return {
1394
+ state: constraints.state,
1395
+ reason: constraints.reason,
1396
+ values: constraints.values,
1397
+ native: constraints.native.map(nativeIdentity).sort(compareCanonical),
1398
+ };
1399
+ }
1400
+ return toJsonValue(constraints);
1401
+ }
1402
+ function nativeIdentity(native) {
1403
+ return {
1404
+ protocol: native.protocol,
1405
+ name: native.name,
1406
+ ...(native.namespace === undefined ? {} : { namespace: native.namespace }),
1407
+ ...(native.path === undefined ? {} : { path: native.path }),
1408
+ };
1409
+ }
1410
+ function geometryFingerprintProjection(geometry) {
1411
+ if (geometry.state !== "known") {
1412
+ return geometry.state === "unknown"
1413
+ ? { state: geometry.state, reason: geometry.reason }
1414
+ : { state: geometry.state, reason: geometry.reason };
1415
+ }
1416
+ return toJsonValue({
1417
+ state: geometry.state,
1418
+ fields: geometry.fields.map((field) => ({
1419
+ field: field.field,
1420
+ geometryTypes: field.geometryTypes.state === "mixed"
1421
+ ? { state: "mixed", types: [...field.geometryTypes.types].sort(compareCanonical) }
1422
+ : field.geometryTypes.state === "unknown"
1423
+ ? { state: "unknown", reason: field.geometryTypes.reason }
1424
+ : field.geometryTypes,
1425
+ crs: crsFingerprintProjection(field.crs),
1426
+ layout: field.layout,
1427
+ allowsEmpty: field.allowsEmpty,
1428
+ })),
1429
+ primaryField: geometry.primaryField,
1430
+ });
1431
+ }
1432
+ function crsFingerprintProjection(binding) {
1433
+ return {
1434
+ definition: crsDefinitionFingerprintProjection(binding.definition),
1435
+ coordinateOrder: axisFingerprintProjection(binding.coordinateOrder),
1436
+ ...(binding.coordinateEpoch === undefined ? {} : { coordinateEpoch: binding.coordinateEpoch }),
1437
+ provenance: binding.provenance.method === "reprojected"
1438
+ ? {
1439
+ method: "reprojected",
1440
+ reprojection: {
1441
+ source: crsDefinitionFingerprintProjection(binding.provenance.reprojection.source),
1442
+ target: crsDefinitionFingerprintProjection(binding.provenance.reprojection.target),
1443
+ ...(binding.provenance.reprojection.operation === undefined
1444
+ ? {}
1445
+ : { operation: binding.provenance.reprojection.operation }),
1446
+ engine: binding.provenance.reprojection.engine,
1447
+ ...(binding.provenance.reprojection.accuracyMeters === undefined
1448
+ ? {}
1449
+ : { accuracyMeters: binding.provenance.reprojection.accuracyMeters }),
1450
+ },
1451
+ }
1452
+ : { method: binding.provenance.method },
1453
+ };
1454
+ }
1455
+ function crsDefinitionFingerprintProjection(definition) {
1456
+ switch (definition.kind) {
1457
+ case "authority":
1458
+ return {
1459
+ kind: definition.kind,
1460
+ authority: definition.authority,
1461
+ code: definition.code,
1462
+ ...(definition.version === undefined ? {} : { version: definition.version }),
1463
+ ...(definition.uri === undefined ? {} : { uri: definition.uri }),
1464
+ ...(definition.wkt === undefined ? {} : { wkt: definition.wkt }),
1465
+ definitionAxisOrder: axisFingerprintProjection(definition.definitionAxisOrder),
1466
+ };
1467
+ case "wkt":
1468
+ return {
1469
+ kind: definition.kind,
1470
+ wkt: definition.wkt,
1471
+ dialect: definition.dialect,
1472
+ validation: definition.validation,
1473
+ definitionAxisOrder: axisFingerprintProjection(definition.definitionAxisOrder),
1474
+ };
1475
+ case "uri":
1476
+ return {
1477
+ kind: definition.kind,
1478
+ uri: definition.uri,
1479
+ definitionAxisOrder: axisFingerprintProjection(definition.definitionAxisOrder),
1480
+ };
1481
+ case "projjson":
1482
+ return {
1483
+ kind: definition.kind,
1484
+ projjson: definition.projjson,
1485
+ definitionAxisOrder: axisFingerprintProjection(definition.definitionAxisOrder),
1486
+ };
1487
+ case "unknown":
1488
+ return { kind: definition.kind, reason: definition.reason };
1489
+ }
1490
+ }
1491
+ function axisFingerprintProjection(order) {
1492
+ return order.state === "known"
1493
+ ? {
1494
+ state: order.state,
1495
+ source: order.source,
1496
+ axes: order.axes.map((axis) => ({
1497
+ // Axis display names/abbreviations do not define semantic identity.
1498
+ direction: axis.direction,
1499
+ unit: axis.unit,
1500
+ })),
1501
+ }
1502
+ : { state: order.state, reason: order.reason };
1503
+ }
1504
+ function cloneProvenance(value) {
1505
+ return normalizeProvenanceArray(toPlainJson(value), "$.provenance");
1506
+ }
1507
+ function toPlainJson(value) {
1508
+ assertBoundedGraphDepth(value, "$", MAX_SCHEMA_GRAPH_DEPTH);
1509
+ return toJsonValue(value);
1510
+ }
1511
+ function jsonValue(value, path) {
1512
+ assertBoundedGraphDepth(value, path, MAX_JSON_VALUE_DEPTH);
1513
+ return toJsonValue(value, path);
1514
+ }
1515
+ function jsonObject(value, path) {
1516
+ const json = jsonValue(value, path);
1517
+ if (json === null || Array.isArray(json) || typeof json !== "object")
1518
+ throw new TypeError(`${path} must be an object`);
1519
+ return json;
1520
+ }
1521
+ function object(value, path, allowed) {
1522
+ if (value === null || typeof value !== "object" || Array.isArray(value))
1523
+ throw new TypeError(`${path} must be an object`);
1524
+ const record = value;
1525
+ if (allowed)
1526
+ exactKeys(record, path, allowed);
1527
+ return record;
1528
+ }
1529
+ function exactKeys(record, path, allowed) {
1530
+ const set = new Set(allowed);
1531
+ const unexpected = Object.keys(record).find((key) => !set.has(key));
1532
+ if (unexpected)
1533
+ throw new TypeError(`${path}.${unexpected} is not part of SourceSchemaV2`);
1534
+ }
1535
+ function array(value, path) {
1536
+ if (!Array.isArray(value))
1537
+ throw new TypeError(`${path} must be an array`);
1538
+ return value;
1539
+ }
1540
+ function text(value, path, allowEmpty = false) {
1541
+ if (typeof value !== "string" || (!allowEmpty && value.trim() === ""))
1542
+ throw new TypeError(`${path} must be a non-empty string`);
1543
+ return value;
1544
+ }
1545
+ function textArray(value, path) {
1546
+ return array(value, path).map((entry, index) => text(entry, `${path}[${index}]`));
1547
+ }
1548
+ function nonEmptyTextArray(value, path) {
1549
+ const out = textArray(value, path);
1550
+ if (out.length === 0)
1551
+ throw new TypeError(`${path} must be non-empty`);
1552
+ return out;
1553
+ }
1554
+ function optionalPositiveInteger(value, path) {
1555
+ if (value === undefined)
1556
+ return undefined;
1557
+ if (!Number.isSafeInteger(value) || value <= 0)
1558
+ throw new TypeError(`${path} must be a positive safe integer`);
1559
+ return value;
1560
+ }
1561
+ function optionalNonNegativeInteger(value, path) {
1562
+ if (value === undefined)
1563
+ return undefined;
1564
+ if (!Number.isSafeInteger(value) || value < 0)
1565
+ throw new TypeError(`${path} must be a non-negative safe integer`);
1566
+ return value;
1567
+ }
1568
+ function finiteNumber(value, path) {
1569
+ if (typeof value !== "number" || !Number.isFinite(value))
1570
+ throw new TypeError(`${path} must be finite`);
1571
+ return value;
1572
+ }
1573
+ function nonNegativeFiniteNumber(value, path) {
1574
+ const number = finiteNumber(value, path);
1575
+ if (number < 0)
1576
+ throw new TypeError(`${path} must be non-negative`);
1577
+ return number;
1578
+ }
1579
+ function rfc3339Timestamp(value, path) {
1580
+ const timestamp = text(value, path);
1581
+ if (!timestampValue(timestamp, "nanosecond", "offset")) {
1582
+ throw new TypeError(`${path} must be an RFC 3339 timestamp`);
1583
+ }
1584
+ return timestamp;
1585
+ }
1586
+ function temporalUnit(value, path) {
1587
+ if (!includes(["second", "millisecond", "microsecond", "nanosecond"], value)) {
1588
+ throw new TypeError(`${path} is invalid`);
1589
+ }
1590
+ return value;
1591
+ }
1592
+ function geometryKind(value, path) {
1593
+ if (!includes([
1594
+ "Point",
1595
+ "MultiPoint",
1596
+ "LineString",
1597
+ "MultiLineString",
1598
+ "Polygon",
1599
+ "MultiPolygon",
1600
+ "GeometryCollection",
1601
+ ], value)) {
1602
+ throw new TypeError(`${path} is invalid`);
1603
+ }
1604
+ return value;
1605
+ }
1606
+ function fieldRole(value, path) {
1607
+ if (includes([
1608
+ "primary-key",
1609
+ "feature-id",
1610
+ "geometry",
1611
+ "time-instant",
1612
+ "time-start",
1613
+ "time-end",
1614
+ "created-at",
1615
+ "updated-at",
1616
+ ], value)) {
1617
+ return value;
1618
+ }
1619
+ return extensionIdentifier(value, path);
1620
+ }
1621
+ function sourceProtocol(value, path) {
1622
+ if (typeof value === "string" && PROTOCOLS.includes(value)) {
1623
+ return value;
1624
+ }
1625
+ return extensionIdentifier(value, path);
1626
+ }
1627
+ function extensionIdentifier(value, path) {
1628
+ if (typeof value !== "string" || !EXTENSION_ID_PATTERN.test(value)) {
1629
+ throw new TypeError(`${path} must be a reverse-DNS extension identifier`);
1630
+ }
1631
+ return value;
1632
+ }
1633
+ function absoluteUri(value, path) {
1634
+ const textValue = text(value, path);
1635
+ if (textValue.trim() !== textValue ||
1636
+ [...textValue].some((character) => character.charCodeAt(0) <= 0x20 || character.charCodeAt(0) === 0x7f)) {
1637
+ throw new TypeError(`${path} must not contain whitespace or control characters`);
1638
+ }
1639
+ let parsed;
1640
+ try {
1641
+ parsed = new URL(textValue);
1642
+ }
1643
+ catch {
1644
+ // URL parse errors retain the full input, including credentials, on their
1645
+ // `input` property. Keep validation errors path-only so they are safe to
1646
+ // serialize or log.
1647
+ throw new TypeError(`${path} must be an absolute URI`);
1648
+ }
1649
+ if (!parsed.protocol || !["http:", "https:", "urn:"].includes(parsed.protocol)) {
1650
+ throw new TypeError(`${path} uses an unsafe or unsupported URI scheme`);
1651
+ }
1652
+ if (parsed.username || parsed.password)
1653
+ throw new TypeError(`${path} must not include user-info`);
1654
+ if (textValue.includes("?"))
1655
+ throw new TypeError(`${path} must not include a query`);
1656
+ if (textValue.includes("#"))
1657
+ throw new TypeError(`${path} must not include a fragment`);
1658
+ if (/%(?![0-9A-Fa-f]{2})/.test(textValue))
1659
+ throw new TypeError(`${path} contains malformed percent encoding`);
1660
+ for (const match of textValue.matchAll(/%([0-9A-Fa-f]{2})/g)) {
1661
+ const encoded = match[1];
1662
+ const decoded = String.fromCharCode(Number.parseInt(encoded, 16));
1663
+ if (encoded !== encoded.toUpperCase() || /^[A-Za-z0-9._~-]$/.test(decoded)) {
1664
+ throw new TypeError(`${path} contains a non-canonical percent encoding`);
1665
+ }
1666
+ }
1667
+ const canonical = parsed.toString();
1668
+ if (canonical !== textValue)
1669
+ throw new TypeError(`${path} must use the canonical RFC 3986 serialization`);
1670
+ return canonical;
1671
+ }
1672
+ function logicalValueCompatible(value, type) {
1673
+ if (value === null)
1674
+ return true;
1675
+ if (type.kind === "list")
1676
+ return Array.isArray(value) && value.every((entry) => logicalValueCompatible(entry, type.element));
1677
+ if (type.kind === "struct") {
1678
+ if (Array.isArray(value) || typeof value !== "object")
1679
+ return false;
1680
+ const record = value;
1681
+ const fields = new Map(type.fields.map((field) => [field.name, field]));
1682
+ if (Object.keys(record).some((key) => !fields.has(key)))
1683
+ return false;
1684
+ return type.fields.every((field) => {
1685
+ if (!Object.hasOwn(record, field.name))
1686
+ return field.nullability !== "non-nullable";
1687
+ const child = record[field.name];
1688
+ return !(child === null && field.nullability === "non-nullable") && logicalValueCompatible(child, field.type);
1689
+ });
1690
+ }
1691
+ if (type.kind === "union")
1692
+ return type.members.some((member) => logicalValueCompatible(value, member));
1693
+ if (type.kind === "json" || type.kind === "unknown")
1694
+ return true;
1695
+ if (type.kind === "geometry")
1696
+ return canonicalGeometryValue(value);
1697
+ if (Array.isArray(value) || typeof value === "object")
1698
+ return false;
1699
+ return domainValueCompatible(value, type);
1700
+ }
1701
+ /**
1702
+ * Test a JSON scalar against the canonical SourceSchemaV2 logical-type value
1703
+ * semantics. Query admission imports this internal seam so binary encodings,
1704
+ * numeric precision, and temporal precision cannot drift from schema truth.
1705
+ *
1706
+ * @internal
1707
+ */
1708
+ export function isLogicalDomainValueCompatible(value, type) {
1709
+ if (value === null || Array.isArray(value) || typeof value === "object")
1710
+ return false;
1711
+ return domainValueCompatible(value, type);
1712
+ }
1713
+ /**
1714
+ * Compare two canonical scalar values with the exact ordering used by
1715
+ * SourceSchemaV2 range domains. Timestamp offsets, leap seconds, and up to
1716
+ * nanosecond precision are preserved; no comparison is routed through
1717
+ * millisecond-limited `Date` arithmetic.
1718
+ *
1719
+ * `undefined` means that the values are invalid for the logical type or do
1720
+ * not share a deterministic timeline (for example, a local timestamp and an
1721
+ * offset timestamp under `timezone: "unknown"`).
1722
+ *
1723
+ * @internal
1724
+ */
1725
+ export function compareLogicalDomainValues(left, right, type) {
1726
+ if (!isLogicalDomainValueCompatible(left, type) || !isLogicalDomainValueCompatible(right, type)) {
1727
+ return undefined;
1728
+ }
1729
+ return compareDomainValues(left, right, type);
1730
+ }
1731
+ function canonicalGeometryValue(value) {
1732
+ return canonicalGeometrySummary(value) !== undefined;
1733
+ }
1734
+ function canonicalGeometrySummary(value) {
1735
+ const state = {};
1736
+ if (!inspectCanonicalGeometry(value, state) || state.arity === undefined)
1737
+ return undefined;
1738
+ return { arity: state.arity, type: value.type };
1739
+ }
1740
+ function inspectCanonicalGeometry(value, state) {
1741
+ if (value === null || Array.isArray(value) || typeof value !== "object")
1742
+ return false;
1743
+ const record = value;
1744
+ if (typeof record.type !== "string")
1745
+ return false;
1746
+ if (record.type === "GeometryCollection") {
1747
+ return (exactJsonKeys(record, ["type", "geometries"]) &&
1748
+ Array.isArray(record.geometries) &&
1749
+ record.geometries.length > 0 &&
1750
+ record.geometries.every((geometry) => inspectCanonicalGeometry(geometry, state)));
1751
+ }
1752
+ if (!exactJsonKeys(record, ["type", "coordinates"]))
1753
+ return false;
1754
+ const position = (candidate) => {
1755
+ if (!Array.isArray(candidate) ||
1756
+ candidate.length < 2 ||
1757
+ candidate.length > 4 ||
1758
+ !candidate.every((ordinate) => typeof ordinate === "number" && Number.isFinite(ordinate))) {
1759
+ return false;
1760
+ }
1761
+ const arity = candidate.length;
1762
+ if (state.arity !== undefined && state.arity !== arity)
1763
+ return false;
1764
+ state.arity = arity;
1765
+ return true;
1766
+ };
1767
+ const positions = (candidate, minimum) => Array.isArray(candidate) && candidate.length >= minimum && candidate.every(position);
1768
+ const rings = (candidate) => {
1769
+ if (!Array.isArray(candidate) || candidate.length === 0)
1770
+ return false;
1771
+ return candidate.every((ring) => {
1772
+ if (!positions(ring, 4))
1773
+ return false;
1774
+ const first = ring[0];
1775
+ const last = ring[ring.length - 1];
1776
+ return (Array.isArray(first) &&
1777
+ Array.isArray(last) &&
1778
+ first.length === last.length &&
1779
+ first.every((ordinate, index) => ordinate === last[index]));
1780
+ });
1781
+ };
1782
+ switch (record.type) {
1783
+ case "Point":
1784
+ return position(record.coordinates);
1785
+ case "MultiPoint":
1786
+ return positions(record.coordinates, 1);
1787
+ case "LineString":
1788
+ return positions(record.coordinates, 2);
1789
+ case "MultiLineString":
1790
+ return (Array.isArray(record.coordinates) &&
1791
+ record.coordinates.length > 0 &&
1792
+ record.coordinates.every((line) => positions(line, 2)));
1793
+ case "Polygon":
1794
+ return rings(record.coordinates);
1795
+ case "MultiPolygon":
1796
+ return Array.isArray(record.coordinates) && record.coordinates.length > 0 && record.coordinates.every(rings);
1797
+ default:
1798
+ return false;
1799
+ }
1800
+ }
1801
+ function exactJsonKeys(record, expected) {
1802
+ const keys = Object.keys(record);
1803
+ return keys.length === expected.length && expected.every((key) => Object.hasOwn(record, key));
1804
+ }
1805
+ function domainValueCompatible(value, type) {
1806
+ switch (type.kind) {
1807
+ case "boolean":
1808
+ return typeof value === "boolean";
1809
+ case "integer":
1810
+ if (type.jsonEncoding === "string") {
1811
+ return typeof value === "string" && integerString(value, type.bits, type.signed);
1812
+ }
1813
+ return typeof value === "number" && integerNumber(value, type.bits, type.signed);
1814
+ case "float":
1815
+ return typeof value === "number" && Number.isFinite(value);
1816
+ case "decimal":
1817
+ return type.jsonEncoding === "string"
1818
+ ? typeof value === "string" && decimalString(value, type.precision, type.scale)
1819
+ : typeof value === "number" && decimalNumber(value, type.precision, type.scale);
1820
+ case "string":
1821
+ return typeof value === "string" && (type.maxLength === undefined || value.length <= type.maxLength);
1822
+ case "binary":
1823
+ return typeof value === "string" && binaryValue(value, type.encoding);
1824
+ case "uuid":
1825
+ return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(value);
1826
+ case "date":
1827
+ return typeof value === "string" && calendarDate(value);
1828
+ case "time":
1829
+ return typeof value === "string" && timeOfDay(value, type.unit);
1830
+ case "timestamp":
1831
+ return typeof value === "string" && timestampValue(value, type.unit, type.timezone);
1832
+ case "duration":
1833
+ return typeof value === "string" && durationValue(value);
1834
+ case "json":
1835
+ case "unknown":
1836
+ return true;
1837
+ case "union":
1838
+ return type.members.some((member) => domainValueCompatible(value, member));
1839
+ case "geometry":
1840
+ case "list":
1841
+ case "struct":
1842
+ return false;
1843
+ }
1844
+ }
1845
+ function rangeType(type) {
1846
+ return (type.kind === "integer" ||
1847
+ type.kind === "float" ||
1848
+ type.kind === "decimal" ||
1849
+ type.kind === "string" ||
1850
+ type.kind === "date" ||
1851
+ type.kind === "time" ||
1852
+ type.kind === "timestamp" ||
1853
+ type.kind === "unknown");
1854
+ }
1855
+ function compareDomainValues(left, right, type) {
1856
+ if (typeof left === "number" && typeof right === "number")
1857
+ return left === right ? 0 : left < right ? -1 : 1;
1858
+ if (typeof left !== "string" || typeof right !== "string")
1859
+ return 0;
1860
+ if (type.kind === "integer" || type.kind === "decimal")
1861
+ return compareDecimalStrings(left, right);
1862
+ if (type.kind === "timestamp") {
1863
+ const leftTime = exactTimestampKey(left);
1864
+ const rightTime = exactTimestampKey(right);
1865
+ if (!leftTime || !rightTime || leftTime.zoned !== rightTime.zoned)
1866
+ return undefined;
1867
+ if (leftTime.epochSecond !== rightTime.epochSecond) {
1868
+ return leftTime.epochSecond < rightTime.epochSecond ? -1 : 1;
1869
+ }
1870
+ if (leftTime.leapSecond !== rightTime.leapSecond)
1871
+ return leftTime.leapSecond ? -1 : 1;
1872
+ return leftTime.fraction === rightTime.fraction ? 0 : leftTime.fraction < rightTime.fraction ? -1 : 1;
1873
+ }
1874
+ if (type.kind === "time") {
1875
+ const leftTime = exactTimeKey(left);
1876
+ const rightTime = exactTimeKey(right);
1877
+ if (leftTime === undefined || rightTime === undefined)
1878
+ return undefined;
1879
+ return leftTime === rightTime ? 0 : leftTime < rightTime ? -1 : 1;
1880
+ }
1881
+ return left === right ? 0 : left < right ? -1 : 1;
1882
+ }
1883
+ function exactTimeKey(value) {
1884
+ const match = /^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)(?:\.(\d{1,9}))?$/.exec(value);
1885
+ if (!match)
1886
+ return undefined;
1887
+ const seconds = Number(match[1]) * 3_600 + Number(match[2]) * 60 + Number(match[3]);
1888
+ return BigInt(seconds) * 1000000000n + BigInt((match[4] ?? "").padEnd(9, "0") || "0");
1889
+ }
1890
+ function exactTimestampKey(value) {
1891
+ const timestamp = parseTimestampValue(value, "nanosecond");
1892
+ if (!timestamp)
1893
+ return undefined;
1894
+ const localSecond = BigInt(daysFromCivil(timestamp.year, timestamp.month, timestamp.day)) * 86400n +
1895
+ BigInt(timestamp.hour * 3_600 + timestamp.minute * 60 + timestamp.second);
1896
+ return {
1897
+ epochSecond: localSecond - BigInt(timestamp.offsetSeconds ?? 0),
1898
+ fraction: BigInt(timestamp.fraction.padEnd(9, "0") || "0"),
1899
+ leapSecond: timestamp.second === 60,
1900
+ zoned: timestamp.zone !== undefined,
1901
+ };
1902
+ }
1903
+ /** Proleptic-Gregorian civil day number, based on Howard Hinnant's days-from-civil algorithm. */
1904
+ function daysFromCivil(year, month, day) {
1905
+ const adjustedYear = year - (month <= 2 ? 1 : 0);
1906
+ const era = Math.floor(adjustedYear / 400);
1907
+ const yearOfEra = adjustedYear - era * 400;
1908
+ const monthPrime = month + (month > 2 ? -3 : 9);
1909
+ const dayOfYear = Math.floor((153 * monthPrime + 2) / 5) + day - 1;
1910
+ const dayOfEra = yearOfEra * 365 + Math.floor(yearOfEra / 4) - Math.floor(yearOfEra / 100) + dayOfYear;
1911
+ return era * 146_097 + dayOfEra - 719_468;
1912
+ }
1913
+ function integerString(value, bits, signed) {
1914
+ if (value.length > 20)
1915
+ return false;
1916
+ if (!(signed ? /^-?(?:0|[1-9]\d*)$/ : /^(?:0|[1-9]\d*)$/).test(value) || value === "-0")
1917
+ return false;
1918
+ return integerBigInt(BigInt(value), bits, signed);
1919
+ }
1920
+ function integerNumber(value, bits, signed) {
1921
+ return Number.isSafeInteger(value) && integerBigInt(BigInt(value), bits, signed);
1922
+ }
1923
+ function integerBigInt(value, bits, signed) {
1924
+ const width = BigInt(bits);
1925
+ const minimum = signed ? -(1n << (width - 1n)) : 0n;
1926
+ const maximum = signed ? (1n << (width - 1n)) - 1n : (1n << width) - 1n;
1927
+ return value >= minimum && value <= maximum;
1928
+ }
1929
+ function decimalString(value, precision, scale) {
1930
+ const match = /^(-?)(0|[1-9]\d*)(?:\.(\d+))?$/.exec(value);
1931
+ if (!match)
1932
+ return false;
1933
+ const integer = match[2];
1934
+ const fraction = match[3] ?? "";
1935
+ if (match[1] === "-" && integer === "0" && !/[1-9]/.test(fraction))
1936
+ return false;
1937
+ return decimalDigitsFit(integer, fraction, precision, scale);
1938
+ }
1939
+ function binaryValue(value, encoding) {
1940
+ if (encoding === "opaque")
1941
+ return true;
1942
+ if (encoding === "base64") {
1943
+ return /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value);
1944
+ }
1945
+ try {
1946
+ absoluteUri(value, "binary URL");
1947
+ return true;
1948
+ }
1949
+ catch {
1950
+ return false;
1951
+ }
1952
+ }
1953
+ function decimalNumber(value, precision, scale) {
1954
+ if (!Number.isFinite(value))
1955
+ return false;
1956
+ const expanded = expandExponentialNumber(Object.is(value, -0) ? 0 : value);
1957
+ const match = /^-?(0|[1-9]\d*)(?:\.(\d+))?$/.exec(expanded);
1958
+ return match !== null && decimalDigitsFit(match[1], match[2] ?? "", precision, scale);
1959
+ }
1960
+ function decimalDigitsFit(integer, fraction, precision, scale) {
1961
+ if (scale !== undefined && fraction.length > scale)
1962
+ return false;
1963
+ const integerDigits = integer === "0" ? 0 : integer.length;
1964
+ if (precision !== undefined && scale !== undefined && integerDigits > precision - scale)
1965
+ return false;
1966
+ const totalDigits = Math.max(1, integerDigits + fraction.length);
1967
+ return precision === undefined || totalDigits <= precision;
1968
+ }
1969
+ function expandExponentialNumber(value) {
1970
+ const lexical = String(value);
1971
+ const match = /^(-?)(\d+)(?:\.(\d+))?[eE]([+-]?\d+)$/.exec(lexical);
1972
+ if (!match)
1973
+ return lexical;
1974
+ const sign = match[1];
1975
+ const integer = match[2];
1976
+ const fraction = match[3] ?? "";
1977
+ const exponent = Number(match[4]);
1978
+ const digits = `${integer}${fraction}`;
1979
+ const point = integer.length + exponent;
1980
+ if (point <= 0)
1981
+ return `${sign}0.${"0".repeat(-point)}${digits}`;
1982
+ if (point >= digits.length)
1983
+ return `${sign}${digits}${"0".repeat(point - digits.length)}`;
1984
+ return `${sign}${digits.slice(0, point)}.${digits.slice(point)}`;
1985
+ }
1986
+ function calendarDate(value) {
1987
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
1988
+ if (!match)
1989
+ return false;
1990
+ const year = Number(match[1]);
1991
+ const month = Number(match[2]);
1992
+ const day = Number(match[3]);
1993
+ if (month < 1 || month > 12 || day < 1)
1994
+ return false;
1995
+ const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
1996
+ const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
1997
+ return day <= days[month - 1];
1998
+ }
1999
+ function timeOfDay(value, unit) {
2000
+ const match = /^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)(?:\.(\d{1,9}))?$/.exec(value);
2001
+ if (!match)
2002
+ return false;
2003
+ const fractionLength = match[4]?.length ?? 0;
2004
+ return fractionLength <= temporalFractionDigits(unit);
2005
+ }
2006
+ function timestampValue(value, unit, timezone) {
2007
+ const timestamp = parseTimestampValue(value, unit);
2008
+ if (!timestamp)
2009
+ return false;
2010
+ if (timezone === "utc" && timestamp.zone !== "Z")
2011
+ return false;
2012
+ if (timezone === "offset" && timestamp.zone === undefined)
2013
+ return false;
2014
+ if (timezone === "local" && timestamp.zone !== undefined)
2015
+ return false;
2016
+ return true;
2017
+ }
2018
+ function parseTimestampValue(value, unit) {
2019
+ const match = /^(\d{4})-(\d{2})-(\d{2})T([01]\d|2[0-3]):([0-5]\d):([0-5]\d|60)(?:\.(\d{1,9}))?(Z|[+-]\d{2}:\d{2})?$/.exec(value);
2020
+ if (!match)
2021
+ return undefined;
2022
+ const year = Number(match[1]);
2023
+ const month = Number(match[2]);
2024
+ const day = Number(match[3]);
2025
+ const hour = Number(match[4]);
2026
+ const minute = Number(match[5]);
2027
+ const second = Number(match[6]);
2028
+ const fraction = match[7] ?? "";
2029
+ const zone = match[8];
2030
+ if (!calendarDate(`${match[1]}-${match[2]}-${match[3]}`) || fraction.length > temporalFractionDigits(unit)) {
2031
+ return undefined;
2032
+ }
2033
+ const offsetSeconds = zone === undefined ? undefined : timestampOffsetSeconds(zone);
2034
+ if (zone !== undefined && offsetSeconds === undefined)
2035
+ return undefined;
2036
+ if (second === 60 &&
2037
+ (offsetSeconds === undefined || !isUtcLeapSecondBoundary(year, month, day, hour, minute, offsetSeconds))) {
2038
+ return undefined;
2039
+ }
2040
+ return {
2041
+ year,
2042
+ month,
2043
+ day,
2044
+ hour,
2045
+ minute,
2046
+ second,
2047
+ fraction,
2048
+ ...(zone === undefined ? {} : { zone, offsetSeconds: offsetSeconds }),
2049
+ };
2050
+ }
2051
+ function timestampOffsetSeconds(zone) {
2052
+ if (zone === "Z")
2053
+ return 0;
2054
+ if (!validOffset(zone))
2055
+ return undefined;
2056
+ const match = /^([+-])(\d{2}):(\d{2})$/.exec(zone);
2057
+ const magnitude = Number(match[2]) * 3_600 + Number(match[3]) * 60;
2058
+ return match[1] === "-" ? -magnitude : magnitude;
2059
+ }
2060
+ function isUtcLeapSecondBoundary(year, month, day, hour, minute, offsetSeconds) {
2061
+ const localSecondBeforeLeap = BigInt(daysFromCivil(year, month, day)) * 86400n + BigInt(hour * 3_600 + minute * 60 + 59);
2062
+ const utcSecondBeforeLeap = localSecondBeforeLeap - BigInt(offsetSeconds);
2063
+ for (let candidateYear = year - 1; candidateYear <= year + 1; candidateYear++) {
2064
+ for (const [candidateMonth, candidateDay] of [
2065
+ [6, 30],
2066
+ [12, 31],
2067
+ ]) {
2068
+ const boundary = BigInt(daysFromCivil(candidateYear, candidateMonth, candidateDay)) * 86400n + 86399n;
2069
+ if (utcSecondBeforeLeap === boundary)
2070
+ return true;
2071
+ }
2072
+ }
2073
+ return false;
2074
+ }
2075
+ function temporalFractionDigits(unit) {
2076
+ switch (unit) {
2077
+ case "second":
2078
+ return 0;
2079
+ case "millisecond":
2080
+ return 3;
2081
+ case "microsecond":
2082
+ return 6;
2083
+ case "nanosecond":
2084
+ return 9;
2085
+ }
2086
+ }
2087
+ function validOffset(value) {
2088
+ const match = /^[+-](\d{2}):(\d{2})$/.exec(value);
2089
+ return match !== null && Number(match[1]) <= 23 && Number(match[2]) <= 59;
2090
+ }
2091
+ function durationValue(value) {
2092
+ return /^-?P(?=\d|T\d)(?:\d+(?:\.\d+)?Y)?(?:\d+(?:\.\d+)?M)?(?:\d+(?:\.\d+)?W)?(?:\d+(?:\.\d+)?D)?(?:T(?=\d)(?:\d+(?:\.\d+)?H)?(?:\d+(?:\.\d+)?M)?(?:\d+(?:\.\d+)?S)?)?$/.test(value);
2093
+ }
2094
+ function compareDecimalStrings(left, right) {
2095
+ const normalizedLeft = decimalParts(left);
2096
+ const normalizedRight = decimalParts(right);
2097
+ if (normalizedLeft.negative !== normalizedRight.negative)
2098
+ return normalizedLeft.negative ? -1 : 1;
2099
+ const direction = normalizedLeft.negative ? -1 : 1;
2100
+ if (normalizedLeft.integer.length !== normalizedRight.integer.length) {
2101
+ return normalizedLeft.integer.length < normalizedRight.integer.length ? -direction : direction;
2102
+ }
2103
+ if (normalizedLeft.integer !== normalizedRight.integer) {
2104
+ return normalizedLeft.integer < normalizedRight.integer ? -direction : direction;
2105
+ }
2106
+ const fractionalLength = Math.max(normalizedLeft.fraction.length, normalizedRight.fraction.length);
2107
+ const leftFraction = normalizedLeft.fraction.padEnd(fractionalLength, "0");
2108
+ const rightFraction = normalizedRight.fraction.padEnd(fractionalLength, "0");
2109
+ if (leftFraction === rightFraction)
2110
+ return 0;
2111
+ return leftFraction < rightFraction ? -direction : direction;
2112
+ }
2113
+ function decimalParts(value) {
2114
+ const negative = value.startsWith("-");
2115
+ const unsigned = negative ? value.slice(1) : value;
2116
+ const [integer = "0", fraction = ""] = unsigned.split(".");
2117
+ let integerStart = 0;
2118
+ while (integerStart < integer.length - 1 && integer.charCodeAt(integerStart) === 48)
2119
+ integerStart += 1;
2120
+ let fractionEnd = fraction.length;
2121
+ while (fractionEnd > 0 && fraction.charCodeAt(fractionEnd - 1) === 48)
2122
+ fractionEnd -= 1;
2123
+ const normalizedInteger = integer.slice(integerStart);
2124
+ const normalizedFraction = fraction.slice(0, fractionEnd);
2125
+ const zero = normalizedInteger === "0" && normalizedFraction === "";
2126
+ return { negative: zero ? false : negative, integer: normalizedInteger, fraction: normalizedFraction };
2127
+ }
2128
+ function validateProjJson(projjson, path) {
2129
+ assertBoundedGraphDepth(projjson, path, 32);
2130
+ if (projjson.$schema !== undefined && projjson.$schema !== PROJJSON_V07_SCHEMA) {
2131
+ throw new TypeError(`${path}.$schema must identify the supported PROJJSON v0.7 schema`);
2132
+ }
2133
+ if (typeof projjson.type !== "string" || !PROJJSON_V07_TYPES.has(projjson.type)) {
2134
+ throw new TypeError(`${path}.type is not a supported PROJJSON v0.7 CRS root`);
2135
+ }
2136
+ const validator = validateProjJsonV07Crs;
2137
+ if (!validator(projjson)) {
2138
+ const selected = selectProjJsonError(projjson.type, validator.errors ?? []);
2139
+ const instancePath = selected?.instancePath ? jsonPointerPath(selected.instancePath) : "";
2140
+ const missing = selected?.params?.missingProperty;
2141
+ const additional = selected?.params?.additionalProperty;
2142
+ const selectedPath = missing
2143
+ ? `${instancePath}.${missing}`
2144
+ : additional
2145
+ ? `${instancePath}.${additional}`
2146
+ : instancePath;
2147
+ const detail = selected?.message ? `: ${selected.message}` : "";
2148
+ throw new TypeError(`${path}${selectedPath} does not satisfy the official PROJJSON v0.7 CRS schema${detail}`);
2149
+ }
2150
+ validateProjJsonUris(projjson, path);
2151
+ }
2152
+ function validateProjJsonUris(projjson, path) {
2153
+ const stack = [{ value: projjson, path }];
2154
+ while (stack.length > 0) {
2155
+ const current = stack.pop();
2156
+ if (current.value === null || typeof current.value !== "object")
2157
+ continue;
2158
+ if (Array.isArray(current.value)) {
2159
+ for (const [index, child] of current.value.entries()) {
2160
+ stack.push({ value: child, path: `${current.path}[${index}]` });
2161
+ }
2162
+ continue;
2163
+ }
2164
+ for (const [key, child] of Object.entries(current.value)) {
2165
+ const childPath = `${current.path}.${key}`;
2166
+ if (key === "uri")
2167
+ absoluteUri(child, childPath);
2168
+ else
2169
+ stack.push({ value: child, path: childPath });
2170
+ }
2171
+ }
2172
+ }
2173
+ function selectProjJsonError(type, errors) {
2174
+ // Ajv's standalone code preserves precise instance paths but referenced
2175
+ // branch validators use local schema paths (for example `#/required`). Pick
2176
+ // a missing property from the selected root vocabulary before considering
2177
+ // deeper errors from the other oneOf branches.
2178
+ const rootRequired = {
2179
+ GeographicCRS: ["name", "datum", "datum_ensemble"],
2180
+ GeodeticCRS: ["name", "datum", "datum_ensemble"],
2181
+ ProjectedCRS: ["name", "base_crs", "conversion", "coordinate_system"],
2182
+ VerticalCRS: ["name", "datum", "datum_ensemble"],
2183
+ CompoundCRS: ["name", "components"],
2184
+ BoundCRS: ["source_crs", "target_crs", "transformation"],
2185
+ EngineeringCRS: ["name", "datum"],
2186
+ ParametricCRS: ["name", "datum"],
2187
+ TemporalCRS: ["name", "datum"],
2188
+ DerivedProjectedCRS: ["name", "base_crs", "conversion", "coordinate_system"],
2189
+ DerivedGeographicCRS: ["name", "base_crs", "conversion", "coordinate_system"],
2190
+ DerivedGeodeticCRS: ["name", "base_crs", "conversion", "coordinate_system"],
2191
+ DerivedVerticalCRS: ["name", "base_crs", "conversion", "coordinate_system"],
2192
+ DerivedEngineeringCRS: ["name", "base_crs", "conversion", "coordinate_system"],
2193
+ DerivedTemporalCRS: ["name", "base_crs", "conversion", "coordinate_system"],
2194
+ DerivedParametricCRS: ["name", "base_crs", "conversion", "coordinate_system"],
2195
+ };
2196
+ const expected = rootRequired[type] ?? [];
2197
+ const missingRoot = errors.find((error) => error.instancePath === "" &&
2198
+ error.keyword === "required" &&
2199
+ error.params?.missingProperty !== undefined &&
2200
+ expected.includes(error.params.missingProperty));
2201
+ if (missingRoot)
2202
+ return missingRoot;
2203
+ const officialRootMembers = new Set([
2204
+ "$schema",
2205
+ "type",
2206
+ "name",
2207
+ "datum",
2208
+ "datum_ensemble",
2209
+ "coordinate_system",
2210
+ "deformation_models",
2211
+ "base_crs",
2212
+ "conversion",
2213
+ "components",
2214
+ "source_crs",
2215
+ "target_crs",
2216
+ "transformation",
2217
+ "geoid_model",
2218
+ "geoid_models",
2219
+ "scope",
2220
+ "area",
2221
+ "bbox",
2222
+ "vertical_extent",
2223
+ "temporal_extent",
2224
+ "usages",
2225
+ "remarks",
2226
+ "id",
2227
+ "ids",
2228
+ ]);
2229
+ const unknownAdditional = errors.find((error) => error.instancePath === "" &&
2230
+ error.keyword === "additionalProperties" &&
2231
+ error.params?.additionalProperty !== undefined &&
2232
+ !officialRootMembers.has(error.params.additionalProperty));
2233
+ if (unknownAdditional)
2234
+ return unknownAdditional;
2235
+ return ([...errors]
2236
+ .filter((error) => error.keyword !== "oneOf" && error.instancePath !== "/type")
2237
+ .sort((left, right) => (right.instancePath?.length ?? 0) - (left.instancePath?.length ?? 0))[0] ?? errors[0]);
2238
+ }
2239
+ function jsonPointerPath(pointer) {
2240
+ return pointer
2241
+ .split("/")
2242
+ .slice(1)
2243
+ .map((part) => part.replace(/~1/g, "/").replace(/~0/g, "~"))
2244
+ .map((part) => (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(part) ? `.${part}` : `[${JSON.stringify(part)}]`))
2245
+ .join("");
2246
+ }
2247
+ function assertBoundedGraphDepth(value, path, maximum) {
2248
+ const stack = [{ kind: "enter", value, depth: 0, path }];
2249
+ const ancestors = new WeakSet();
2250
+ let nodes = 0;
2251
+ let scalarAndKeyBytes = 0;
2252
+ const consumeBytes = (textValue, valuePath) => {
2253
+ assertUnicodeScalarString(textValue, valuePath);
2254
+ const remaining = MAX_SCHEMA_BYTES - scalarAndKeyBytes;
2255
+ if (remaining < 0 || textValue.length > remaining) {
2256
+ throw new TypeError(`${path} exceeds the ${MAX_SCHEMA_BYTES}-byte bound`);
2257
+ }
2258
+ scalarAndKeyBytes += utf8ByteLength(textValue);
2259
+ if (scalarAndKeyBytes > MAX_SCHEMA_BYTES) {
2260
+ throw new TypeError(`${path} exceeds the ${MAX_SCHEMA_BYTES}-byte bound`);
2261
+ }
2262
+ };
2263
+ while (stack.length > 0) {
2264
+ const current = stack.pop();
2265
+ if (current.kind === "leave") {
2266
+ ancestors.delete(current.value);
2267
+ continue;
2268
+ }
2269
+ if (current.value === null) {
2270
+ consumeBytes("null", current.path);
2271
+ continue;
2272
+ }
2273
+ if (typeof current.value === "string") {
2274
+ consumeBytes(current.value, current.path);
2275
+ continue;
2276
+ }
2277
+ if (typeof current.value === "boolean") {
2278
+ consumeBytes(current.value ? "true" : "false", current.path);
2279
+ continue;
2280
+ }
2281
+ if (typeof current.value === "number") {
2282
+ if (!Number.isFinite(current.value))
2283
+ throw new TypeError(`${current.path} must contain only finite numbers`);
2284
+ consumeBytes(String(current.value), current.path);
2285
+ continue;
2286
+ }
2287
+ if (typeof current.value !== "object") {
2288
+ throw new TypeError(`${current.path} contains unsupported ${typeof current.value}`);
2289
+ }
2290
+ if (current.depth > maximum)
2291
+ throw new TypeError(`${current.path} exceeds maximum JSON nesting depth ${maximum}`);
2292
+ if (++nodes > MAX_JSON_NODE_COUNT)
2293
+ throw new TypeError(`${path} exceeds the bounded JSON node count`);
2294
+ if (ancestors.has(current.value))
2295
+ throw new TypeError(`${current.path} must not contain cycles`);
2296
+ const prototype = Object.getPrototypeOf(current.value);
2297
+ if (!Array.isArray(current.value) && prototype !== Object.prototype && prototype !== null) {
2298
+ throw new TypeError(`${current.path} must contain only plain JSON objects`);
2299
+ }
2300
+ if (Object.getOwnPropertySymbols(current.value).length > 0) {
2301
+ throw new TypeError(`${current.path} must not contain symbol keys`);
2302
+ }
2303
+ ancestors.add(current.value);
2304
+ stack.push({ kind: "leave", value: current.value });
2305
+ if (Array.isArray(current.value)) {
2306
+ const arrayValue = current.value;
2307
+ if (arrayValue.length > MAX_JSON_NODE_COUNT) {
2308
+ throw new TypeError(`${current.path} exceeds the bounded JSON array length`);
2309
+ }
2310
+ const extraKey = Object.keys(arrayValue).find((key) => !/^(?:0|[1-9]\d*)$/.test(key) || Number(key) >= arrayValue.length);
2311
+ if (extraKey !== undefined)
2312
+ throw new TypeError(`${current.path}.${extraKey} is not a JSON array element`);
2313
+ for (let index = arrayValue.length - 1; index >= 0; index--) {
2314
+ const descriptor = Object.getOwnPropertyDescriptor(arrayValue, String(index));
2315
+ if (!descriptor || !("value" in descriptor)) {
2316
+ throw new TypeError(`${current.path}[${index}] must be a plain JSON value`);
2317
+ }
2318
+ stack.push({
2319
+ kind: "enter",
2320
+ value: descriptor.value,
2321
+ depth: current.depth + 1,
2322
+ path: `${current.path}[${index}]`,
2323
+ });
2324
+ }
2325
+ continue;
2326
+ }
2327
+ const descriptors = Object.getOwnPropertyDescriptors(current.value);
2328
+ const keys = Object.keys(current.value);
2329
+ for (let index = keys.length - 1; index >= 0; index--) {
2330
+ const key = keys[index];
2331
+ consumeBytes(key, `${current.path} object key`);
2332
+ const descriptor = descriptors[key];
2333
+ if (!descriptor || !("value" in descriptor)) {
2334
+ throw new TypeError(`${current.path}.${key} must be a plain JSON value`);
2335
+ }
2336
+ stack.push({
2337
+ kind: "enter",
2338
+ value: descriptor.value,
2339
+ depth: current.depth + 1,
2340
+ path: `${current.path}.${key}`,
2341
+ });
2342
+ }
2343
+ }
2344
+ }
2345
+ function assertUnicodeScalarString(value, path) {
2346
+ for (let index = 0; index < value.length; index++) {
2347
+ const codeUnit = value.charCodeAt(index);
2348
+ if (codeUnit >= 0xd800 && codeUnit <= 0xdbff) {
2349
+ const next = value.charCodeAt(index + 1);
2350
+ if (!(next >= 0xdc00 && next <= 0xdfff)) {
2351
+ throw new TypeError(`${path} contains an unpaired high Unicode surrogate`);
2352
+ }
2353
+ index++;
2354
+ }
2355
+ else if (codeUnit >= 0xdc00 && codeUnit <= 0xdfff) {
2356
+ throw new TypeError(`${path} contains an unpaired low Unicode surrogate`);
2357
+ }
2358
+ }
2359
+ }
2360
+ /** Reject non-I-JSON duplicate names before JSON.parse's last-name-wins object is trusted. */
2361
+ function assertUniqueJsonObjectNames(source) {
2362
+ let index = 0;
2363
+ const whitespace = () => {
2364
+ while (index < source.length && /\s/.test(source[index]))
2365
+ index++;
2366
+ };
2367
+ const stringToken = () => {
2368
+ const start = index++;
2369
+ while (index < source.length) {
2370
+ const character = source[index++];
2371
+ if (character === "\\")
2372
+ index++;
2373
+ else if (character === '"')
2374
+ break;
2375
+ }
2376
+ return JSON.parse(source.slice(start, index));
2377
+ };
2378
+ const value = (depth) => {
2379
+ if (depth > MAX_SCHEMA_GRAPH_DEPTH) {
2380
+ throw new TypeError(`Serialized SourceSchemaV2 exceeds maximum JSON nesting depth ${MAX_SCHEMA_GRAPH_DEPTH}`);
2381
+ }
2382
+ whitespace();
2383
+ const character = source[index];
2384
+ if (character === '"') {
2385
+ stringToken();
2386
+ return;
2387
+ }
2388
+ if (character === "{") {
2389
+ index++;
2390
+ whitespace();
2391
+ const names = new Set();
2392
+ if (source[index] === "}") {
2393
+ index++;
2394
+ return;
2395
+ }
2396
+ while (index < source.length) {
2397
+ const name = stringToken();
2398
+ if (names.has(name))
2399
+ throw new TypeError("SourceSchemaV2 JSON contains a duplicate object name");
2400
+ names.add(name);
2401
+ whitespace();
2402
+ index++; // colon; JSON syntax was already validated by the native parser.
2403
+ value(depth + 1);
2404
+ whitespace();
2405
+ if (source[index] === "}") {
2406
+ index++;
2407
+ return;
2408
+ }
2409
+ index++; // comma
2410
+ whitespace();
2411
+ }
2412
+ return;
2413
+ }
2414
+ if (character === "[") {
2415
+ index++;
2416
+ whitespace();
2417
+ if (source[index] === "]") {
2418
+ index++;
2419
+ return;
2420
+ }
2421
+ while (index < source.length) {
2422
+ value(depth + 1);
2423
+ whitespace();
2424
+ if (source[index] === "]") {
2425
+ index++;
2426
+ return;
2427
+ }
2428
+ index++; // comma
2429
+ }
2430
+ return;
2431
+ }
2432
+ while (index < source.length && !/[\s,}\]]/.test(source[index]))
2433
+ index++;
2434
+ };
2435
+ value(0);
2436
+ }
2437
+ function assertSchemaByteBound(schema) {
2438
+ const serialized = canonicalStringify(toJsonValue(schema));
2439
+ if (utf8ByteLength(serialized) > MAX_SCHEMA_BYTES) {
2440
+ throw new TypeError(`SourceSchemaV2 exceeds the ${MAX_SCHEMA_BYTES}-byte bound`);
2441
+ }
2442
+ }
2443
+ function utf8ByteLength(value) {
2444
+ return new TextEncoder().encode(value).byteLength;
2445
+ }
2446
+ function compareFields(left, right) {
2447
+ const leftPath = canonicalStringify(toJsonValue(left.path));
2448
+ const rightPath = canonicalStringify(toJsonValue(right.path));
2449
+ return leftPath === rightPath ? compareUtf8(left.name, right.name) : compareUtf8(leftPath, rightPath);
2450
+ }
2451
+ function compareCanonical(left, right) {
2452
+ return compareUtf8(canonicalStringify(toJsonValue(left)), canonicalStringify(toJsonValue(right)));
2453
+ }
2454
+ function compareUtf8(left, right) {
2455
+ const leftBytes = new TextEncoder().encode(left);
2456
+ const rightBytes = new TextEncoder().encode(right);
2457
+ const length = Math.min(leftBytes.length, rightBytes.length);
2458
+ for (let index = 0; index < length; index++) {
2459
+ const difference = leftBytes[index] - rightBytes[index];
2460
+ if (difference !== 0)
2461
+ return difference;
2462
+ }
2463
+ return leftBytes.length - rightBytes.length;
2464
+ }
2465
+ function unique(values, label) {
2466
+ const seen = new Set();
2467
+ for (const value of values) {
2468
+ if (seen.has(value))
2469
+ throw new TypeError(`${label} must not contain duplicates`);
2470
+ seen.add(value);
2471
+ }
2472
+ }
2473
+ function includes(values, value) {
2474
+ return values.includes(value);
2475
+ }
2476
+ function deepFreeze(value) {
2477
+ if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
2478
+ for (const child of Object.values(value))
2479
+ deepFreeze(child);
2480
+ Object.freeze(value);
2481
+ }
2482
+ return value;
2483
+ }