@honua/app-platform 0.1.12 → 0.1.13

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 (233) hide show
  1. package/columnar/apache-arrow.d.ts +17 -0
  2. package/columnar/apache-arrow.d.ts.map +1 -0
  3. package/columnar/apache-arrow.js +1036 -0
  4. package/columnar/batch-cache-conformance.d.ts +66 -0
  5. package/columnar/batch-cache-conformance.d.ts.map +1 -0
  6. package/columnar/batch-cache-conformance.js +317 -0
  7. package/columnar/batch-cache-indexeddb.d.ts +31 -0
  8. package/columnar/batch-cache-indexeddb.d.ts.map +1 -0
  9. package/columnar/batch-cache-indexeddb.js +148 -0
  10. package/columnar/batch-cache.d.ts +298 -0
  11. package/columnar/batch-cache.d.ts.map +1 -0
  12. package/columnar/batch-cache.js +757 -0
  13. package/columnar/geoarrow-aggregate.d.ts +178 -0
  14. package/columnar/geoarrow-aggregate.d.ts.map +1 -0
  15. package/columnar/geoarrow-aggregate.js +976 -0
  16. package/columnar/geoarrow-patch.d.ts +382 -0
  17. package/columnar/geoarrow-patch.d.ts.map +1 -0
  18. package/columnar/geoarrow-patch.js +1656 -0
  19. package/columnar/geoarrow-reproject.d.ts +22 -0
  20. package/columnar/geoarrow-reproject.d.ts.map +1 -0
  21. package/columnar/geoarrow-reproject.js +119 -0
  22. package/columnar/geoarrow-serialization.d.ts +68 -0
  23. package/columnar/geoarrow-serialization.d.ts.map +1 -0
  24. package/columnar/geoarrow-serialization.js +331 -0
  25. package/columnar/geoarrow-types.d.ts +271 -0
  26. package/columnar/geoarrow-types.d.ts.map +1 -0
  27. package/columnar/geoarrow-types.js +15 -0
  28. package/columnar/geoarrow.d.ts +52 -0
  29. package/columnar/geoarrow.d.ts.map +1 -0
  30. package/columnar/geoarrow.js +1469 -0
  31. package/columnar/index.d.ts +43 -0
  32. package/columnar/index.d.ts.map +1 -0
  33. package/columnar/index.js +29 -0
  34. package/columnar/operators.d.ts +70 -0
  35. package/columnar/operators.d.ts.map +1 -0
  36. package/columnar/operators.js +307 -0
  37. package/columnar/result.d.ts +369 -0
  38. package/columnar/result.d.ts.map +1 -0
  39. package/columnar/result.js +815 -0
  40. package/columnar/telemetry.d.ts +152 -0
  41. package/columnar/telemetry.d.ts.map +1 -0
  42. package/columnar/telemetry.js +265 -0
  43. package/columnar/transfer.d.ts +28 -0
  44. package/columnar/transfer.d.ts.map +1 -0
  45. package/columnar/transfer.js +669 -0
  46. package/columnar/types.d.ts +175 -0
  47. package/columnar/types.d.ts.map +1 -0
  48. package/columnar/types.js +22 -0
  49. package/columnar/worker.d.ts +157 -0
  50. package/columnar/worker.d.ts.map +1 -0
  51. package/columnar/worker.js +1059 -0
  52. package/contract/pmtiles.d.ts +6 -5
  53. package/contract/pmtiles.d.ts.map +1 -1
  54. package/contract/pmtiles.js +30 -5
  55. package/controls/catalog.d.ts.map +1 -1
  56. package/controls/catalog.js +110 -0
  57. package/controls/index.d.ts +1 -1
  58. package/controls/index.js +1 -1
  59. package/controls/types.d.ts +1 -1
  60. package/controls/types.js +1 -1
  61. package/core/client.d.ts.map +1 -1
  62. package/core/client.js +87 -19
  63. package/core/deprecation-notice.d.ts +28 -0
  64. package/core/deprecation-notice.d.ts.map +1 -0
  65. package/core/deprecation-notice.js +22 -0
  66. package/core/esri-geojson.d.ts.map +1 -1
  67. package/core/esri-geojson.js +10 -6
  68. package/core/optional-grpc-peer.d.ts +15 -0
  69. package/core/optional-grpc-peer.d.ts.map +1 -0
  70. package/core/optional-grpc-peer.js +60 -0
  71. package/esri-compat/basemap-gallery.d.ts +2 -0
  72. package/esri-compat/basemap-gallery.d.ts.map +1 -1
  73. package/esri-compat/basemap-gallery.js +12 -0
  74. package/esri-compat/basemap.d.ts +4 -0
  75. package/esri-compat/basemap.d.ts.map +1 -1
  76. package/esri-compat/basemap.js +70 -0
  77. package/esri-compat/bookmarks.d.ts +2 -0
  78. package/esri-compat/bookmarks.d.ts.map +1 -1
  79. package/esri-compat/bookmarks.js +12 -0
  80. package/esri-compat/controls.d.ts +18 -0
  81. package/esri-compat/controls.d.ts.map +1 -1
  82. package/esri-compat/controls.js +120 -57
  83. package/esri-compat/directions.d.ts +4 -0
  84. package/esri-compat/directions.d.ts.map +1 -1
  85. package/esri-compat/directions.js +30 -20
  86. package/esri-compat/editor.d.ts +4 -0
  87. package/esri-compat/editor.d.ts.map +1 -1
  88. package/esri-compat/editor.js +16 -0
  89. package/esri-compat/esri-config.d.ts +2 -1
  90. package/esri-compat/esri-config.d.ts.map +1 -1
  91. package/esri-compat/feature-effect.d.ts +23 -0
  92. package/esri-compat/feature-effect.d.ts.map +1 -0
  93. package/esri-compat/feature-effect.js +12 -0
  94. package/esri-compat/feature-form.d.ts +6 -0
  95. package/esri-compat/feature-form.d.ts.map +1 -1
  96. package/esri-compat/feature-form.js +63 -0
  97. package/esri-compat/feature-layer.d.ts +21 -2
  98. package/esri-compat/feature-layer.d.ts.map +1 -1
  99. package/esri-compat/feature-layer.js +172 -1
  100. package/esri-compat/feature-table.d.ts +5 -0
  101. package/esri-compat/feature-table.d.ts.map +1 -1
  102. package/esri-compat/feature-table.js +23 -0
  103. package/esri-compat/identity-manager.d.ts +1 -1
  104. package/esri-compat/identity-manager.js +1 -1
  105. package/esri-compat/legend.d.ts +2 -0
  106. package/esri-compat/legend.d.ts.map +1 -1
  107. package/esri-compat/legend.js +98 -0
  108. package/esri-compat/locator.d.ts +1 -1
  109. package/esri-compat/locator.js +1 -1
  110. package/esri-compat/map-view-mount.d.ts +1 -0
  111. package/esri-compat/map-view-mount.d.ts.map +1 -1
  112. package/esri-compat/map-view-mount.js +46 -25
  113. package/esri-compat/map-view.d.ts +13 -0
  114. package/esri-compat/map-view.d.ts.map +1 -1
  115. package/esri-compat/map-view.js +94 -8
  116. package/esri-compat/map.d.ts +5 -1
  117. package/esri-compat/map.d.ts.map +1 -1
  118. package/esri-compat/map.js +10 -0
  119. package/esri-compat/measurement.d.ts +2 -0
  120. package/esri-compat/measurement.d.ts.map +1 -1
  121. package/esri-compat/measurement.js +14 -0
  122. package/esri-compat/point.d.ts +7 -0
  123. package/esri-compat/point.d.ts.map +1 -1
  124. package/esri-compat/point.js +33 -3
  125. package/esri-compat/popup.d.ts +2 -0
  126. package/esri-compat/popup.d.ts.map +1 -1
  127. package/esri-compat/popup.js +12 -0
  128. package/esri-compat/portal.d.ts +1 -1
  129. package/esri-compat/portal.js +1 -1
  130. package/esri-compat/print.d.ts +3 -0
  131. package/esri-compat/print.d.ts.map +1 -1
  132. package/esri-compat/print.js +15 -0
  133. package/esri-compat/search.d.ts +4 -0
  134. package/esri-compat/search.d.ts.map +1 -1
  135. package/esri-compat/search.js +25 -28
  136. package/esri-compat/sketch.d.ts +2 -0
  137. package/esri-compat/sketch.d.ts.map +1 -1
  138. package/esri-compat/sketch.js +12 -0
  139. package/esri-compat/widget-host.d.ts +46 -17
  140. package/esri-compat/widget-host.d.ts.map +1 -1
  141. package/esri-compat/widget-host.js +84 -20
  142. package/esri-compat/widget-shell-hosts.d.ts +61 -0
  143. package/esri-compat/widget-shell-hosts.d.ts.map +1 -0
  144. package/esri-compat/widget-shell-hosts.js +76 -0
  145. package/kernel/renderer.d.ts +82 -0
  146. package/kernel/renderer.js +1 -0
  147. package/offline/conflict-resolution.d.ts +183 -0
  148. package/offline/conflict-resolution.d.ts.map +1 -0
  149. package/offline/conflict-resolution.js +286 -0
  150. package/offline/credential-screen.d.ts +34 -0
  151. package/offline/credential-screen.d.ts.map +1 -0
  152. package/offline/credential-screen.js +78 -0
  153. package/offline/digest.d.ts +15 -0
  154. package/offline/digest.d.ts.map +1 -0
  155. package/offline/digest.js +58 -0
  156. package/offline/edit-queue.d.ts +403 -0
  157. package/offline/edit-queue.d.ts.map +1 -0
  158. package/offline/edit-queue.js +1807 -0
  159. package/offline/edit-replay.d.ts +116 -0
  160. package/offline/edit-replay.d.ts.map +1 -0
  161. package/offline/edit-replay.js +392 -0
  162. package/offline/fetch-handler.d.ts +12 -0
  163. package/offline/fetch-handler.d.ts.map +1 -0
  164. package/offline/fetch-handler.js +65 -0
  165. package/offline/index.d.ts +52 -0
  166. package/offline/index.d.ts.map +1 -0
  167. package/offline/index.js +34 -0
  168. package/offline/indexeddb.d.ts +135 -0
  169. package/offline/indexeddb.d.ts.map +1 -0
  170. package/offline/indexeddb.js +888 -0
  171. package/offline/memory-store.d.ts +17 -0
  172. package/offline/memory-store.d.ts.map +1 -0
  173. package/offline/memory-store.js +183 -0
  174. package/offline/quota.d.ts +161 -0
  175. package/offline/quota.d.ts.map +1 -0
  176. package/offline/quota.js +370 -0
  177. package/offline/read-gate.d.ts +175 -0
  178. package/offline/read-gate.d.ts.map +1 -0
  179. package/offline/read-gate.js +252 -0
  180. package/offline/read-resource.d.ts +99 -0
  181. package/offline/read-resource.d.ts.map +1 -0
  182. package/offline/read-resource.js +156 -0
  183. package/offline/read.d.ts +50 -0
  184. package/offline/read.d.ts.map +1 -0
  185. package/offline/read.js +65 -0
  186. package/offline/region.d.ts +29 -0
  187. package/offline/region.d.ts.map +1 -0
  188. package/offline/region.js +1007 -0
  189. package/offline/replay-conflict.d.ts +183 -0
  190. package/offline/replay-conflict.d.ts.map +1 -0
  191. package/offline/replay-conflict.js +482 -0
  192. package/offline/selection.d.ts +140 -0
  193. package/offline/selection.d.ts.map +1 -0
  194. package/offline/selection.js +391 -0
  195. package/offline/snapshot.d.ts +132 -0
  196. package/offline/snapshot.d.ts.map +1 -0
  197. package/offline/snapshot.js +312 -0
  198. package/offline/status.d.ts +136 -0
  199. package/offline/status.d.ts.map +1 -0
  200. package/offline/status.js +479 -0
  201. package/offline/store-conformance.d.ts +48 -0
  202. package/offline/store-conformance.d.ts.map +1 -0
  203. package/offline/store-conformance.js +233 -0
  204. package/offline/tile-protocol.d.ts +81 -0
  205. package/offline/tile-protocol.d.ts.map +1 -0
  206. package/offline/tile-protocol.js +119 -0
  207. package/offline/types.d.ts +349 -0
  208. package/offline/types.d.ts.map +1 -0
  209. package/offline/types.js +75 -0
  210. package/package.json +14 -2
  211. package/scene-workspace/index.d.ts +2 -2
  212. package/scene-workspace/index.d.ts.map +1 -1
  213. package/scene-workspace/index.js +1 -1
  214. package/scene-workspace/scene-discovery.d.ts +111 -91
  215. package/scene-workspace/scene-discovery.d.ts.map +1 -1
  216. package/scene-workspace/scene-discovery.js +340 -92
  217. package/scene-workspace/scene-view.d.ts +6 -6
  218. package/scene-workspace/scene-view.d.ts.map +1 -1
  219. package/scene-workspace/scene-view.js +32 -6
  220. package/web-components/elements.d.ts +34 -0
  221. package/web-components/elements.d.ts.map +1 -1
  222. package/web-components/elements.js +82 -8
  223. package/web-components/index.d.ts +2 -0
  224. package/web-components/index.d.ts.map +1 -1
  225. package/web-components/index.js +3 -1
  226. package/web-components/map-chrome.d.ts +103 -0
  227. package/web-components/map-chrome.d.ts.map +1 -0
  228. package/web-components/map-chrome.js +325 -0
  229. package/web-components/widget-shell.d.ts +68 -0
  230. package/web-components/widget-shell.d.ts.map +1 -0
  231. package/web-components/widget-shell.js +224 -0
  232. package/widget-capabilities.d.ts +67 -0
  233. package/widget-capabilities.js +152 -0
@@ -0,0 +1,116 @@
1
+ import type { ReplicaConflictPolicy } from "../replica-sync/types.js";
2
+ import { type OfflineEditQueue, type OfflineEditQueuePartition, type OfflineFeatureEdit } from "./edit-queue.js";
3
+ import { type OfflineReplaySyncConflictBinding, type OfflineReplaySyncConflictProjectionV1 } from "./replay-conflict.js";
4
+ export declare const HONUA_OFFLINE_EDIT_REPLAY_VERSION: "1.0";
5
+ export interface OfflineEditReplayIdentity {
6
+ readonly editId: `sha256:${string}`;
7
+ readonly requestFingerprint: `sha256:${string}`;
8
+ readonly idempotencyKey: string;
9
+ }
10
+ export interface OfflineEditReplayRequest extends OfflineEditReplayIdentity {
11
+ readonly version: typeof HONUA_OFFLINE_EDIT_REPLAY_VERSION;
12
+ readonly sourceId: string;
13
+ readonly edit: OfflineFeatureEdit;
14
+ }
15
+ export interface OfflineEditAppliedAcknowledgement extends OfflineEditReplayIdentity {
16
+ readonly kind: "applied";
17
+ readonly serverOperationId: string;
18
+ readonly serverGeneration?: string;
19
+ }
20
+ export interface OfflineEditRetryableAcknowledgement extends OfflineEditReplayIdentity {
21
+ readonly kind: "retryable";
22
+ readonly retryAt: string;
23
+ readonly reasonCode: string;
24
+ }
25
+ export interface OfflineEditConflictedAcknowledgement extends OfflineEditReplayIdentity {
26
+ readonly kind: "conflicted";
27
+ readonly conflictId: string;
28
+ readonly serverGeneration?: string;
29
+ }
30
+ export type OfflineEditReplayAcknowledgement = OfflineEditAppliedAcknowledgement | OfflineEditRetryableAcknowledgement | OfflineEditConflictedAcknowledgement;
31
+ export interface OfflineEditReplayTransportContext {
32
+ readonly signal?: AbortSignal;
33
+ }
34
+ /**
35
+ * Application-owned binding to a server mutation transport. Returned values
36
+ * are treated as untrusted and validated before queue state changes.
37
+ */
38
+ export type OfflineEditReplayTransport = (request: OfflineEditReplayRequest, context: OfflineEditReplayTransportContext) => unknown | Promise<unknown>;
39
+ export interface ReplayOfflineEditPassOptions extends OfflineEditQueuePartition {
40
+ readonly workerId: string;
41
+ /** Required, between 1 and 100 inclusive. */
42
+ readonly limit: number;
43
+ /** Required, positive, and no more than 24 hours. */
44
+ readonly leaseDurationMs: number;
45
+ readonly signal?: AbortSignal;
46
+ /**
47
+ * Replica identity for conflict projection. Supply it to have every
48
+ * conflicted acknowledgement projected onto the shipped sync-conflict
49
+ * contracts as {@link OfflineEditReplayItemReceipt.syncConflict}; omit it and
50
+ * the pass behaves exactly as before. The SDK cannot derive a replica from a
51
+ * queue partition, so an absent binding means no projection rather than a
52
+ * guessed one.
53
+ */
54
+ readonly replica?: OfflineReplaySyncConflictBinding;
55
+ /**
56
+ * The replica's reconciliation policy, declared explicitly.
57
+ *
58
+ * Declaring it is how an application finds out that the SDK cannot carry the
59
+ * policy out: `client-wins` and `last-writer-wins` need a server to decide the
60
+ * outcome, so the pass refuses before it claims anything, naming the policy
61
+ * (`HonuaOfflineConflictAdjudicationError`). `manual` retains conflicts for
62
+ * review — which is also what an omitted policy does — and `server-wins`
63
+ * closes each conflict against the local edit and abandons it. See
64
+ * `OFFLINE_REPLAY_CONFLICT_POLICIES` and `docs/offline-regions.md`.
65
+ */
66
+ readonly conflictPolicy?: ReplicaConflictPolicy;
67
+ }
68
+ /** What a declared policy did to one conflicted edit. */
69
+ export type OfflineEditReplayConflictPolicyOutcome =
70
+ /** The edit is still `conflicted` and awaiting review. */
71
+ "retained-for-review"
72
+ /** The conflict was closed against the local edit, which is now `cancelled`. */
73
+ | "discarded-local-edit"
74
+ /**
75
+ * The conflict is durably recorded but the policy transition did not land —
76
+ * a concurrent writer moved the record first. The conflict stays open, and
77
+ * the pass says so instead of implying the policy was applied.
78
+ */
79
+ | "not-applied";
80
+ export interface OfflineEditReplayConflictPolicyReceiptV1 {
81
+ readonly policy: ReplicaConflictPolicy;
82
+ readonly outcome: OfflineEditReplayConflictPolicyOutcome;
83
+ }
84
+ export type OfflineEditReplayUnacknowledgedReason = "cancelled" | "identity-mismatch" | "invalid-acknowledgement" | "missing-lease" | "repeated-claim" | "transition-rejected" | "transport-threw";
85
+ export interface OfflineEditReplayItemReceipt {
86
+ readonly editId: `sha256:${string}`;
87
+ readonly outcome: "applied" | "retryable" | "conflicted" | "unacknowledged";
88
+ readonly reasonCode?: OfflineEditReplayUnacknowledgedReason;
89
+ /**
90
+ * Present only for a `conflicted` outcome when the pass was given a replica
91
+ * binding. It carries either the projected sync conflict or a typed refusal —
92
+ * never a partially guessed contract object. When a policy closed the
93
+ * conflict, the projection is of the resolved record, so its `status` and
94
+ * `localResolution` describe what the policy actually did.
95
+ */
96
+ readonly syncConflict?: OfflineReplaySyncConflictProjectionV1;
97
+ /**
98
+ * Present only for a `conflicted` outcome when the pass declared a conflict
99
+ * policy. A refused policy never reaches a receipt: the pass throws first.
100
+ */
101
+ readonly conflictPolicy?: OfflineEditReplayConflictPolicyReceiptV1;
102
+ }
103
+ export interface OfflineEditReplayPassReceipt {
104
+ readonly version: typeof HONUA_OFFLINE_EDIT_REPLAY_VERSION;
105
+ readonly claimedCount: number;
106
+ readonly invokedCount: number;
107
+ readonly appliedCount: number;
108
+ readonly retryableCount: number;
109
+ readonly conflictedCount: number;
110
+ readonly unacknowledgedCount: number;
111
+ readonly stoppedReason?: "cancelled";
112
+ readonly outcomes: readonly OfflineEditReplayItemReceipt[];
113
+ }
114
+ /** Claim and replay one bounded, deterministic queue pass. */
115
+ export declare function replayOfflineEditPass(queue: OfflineEditQueue, transport: OfflineEditReplayTransport, options: ReplayOfflineEditPassOptions): Promise<OfflineEditReplayPassReceipt>;
116
+ //# sourceMappingURL=edit-replay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edit-replay.d.ts","sourceRoot":"","sources":["../../../src/offline/edit-replay.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,392 @@
1
+ import { requireHonourableOfflineReplayConflictPolicy, } from "./conflict-resolution.js";
2
+ import { HonuaOfflineEditQueueError, MAX_OFFLINE_EDIT_LEASE_DURATION_MS, } from "./edit-queue.js";
3
+ import { normalizeOfflineReplaySyncConflictBinding, projectOfflineReplaySyncConflict, } from "./replay-conflict.js";
4
+ export const HONUA_OFFLINE_EDIT_REPLAY_VERSION = "1.0";
5
+ const DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;
6
+ const MAX_STRING_BYTES = 1024;
7
+ const encoder = new TextEncoder();
8
+ const OPTION_KEYS = new Set([
9
+ "authorizationScopeDigest",
10
+ "sourceId",
11
+ "workerId",
12
+ "limit",
13
+ "leaseDurationMs",
14
+ "signal",
15
+ "replica",
16
+ "conflictPolicy",
17
+ ]);
18
+ const COMMON_ACKNOWLEDGEMENT_KEYS = ["kind", "editId", "requestFingerprint", "idempotencyKey"];
19
+ const APPLIED_ACKNOWLEDGEMENT_KEYS = new Set([...COMMON_ACKNOWLEDGEMENT_KEYS, "serverOperationId", "serverGeneration"]);
20
+ const RETRYABLE_ACKNOWLEDGEMENT_KEYS = new Set([...COMMON_ACKNOWLEDGEMENT_KEYS, "retryAt", "reasonCode"]);
21
+ const CONFLICTED_ACKNOWLEDGEMENT_KEYS = new Set([...COMMON_ACKNOWLEDGEMENT_KEYS, "conflictId", "serverGeneration"]);
22
+ class AcknowledgementError extends Error {
23
+ code;
24
+ constructor(code) {
25
+ super(code);
26
+ this.code = code;
27
+ this.name = "AcknowledgementError";
28
+ }
29
+ }
30
+ /** Claim and replay one bounded, deterministic queue pass. */
31
+ export async function replayOfflineEditPass(queue, transport, options) {
32
+ if (!queue || typeof queue.claimReady !== "function") {
33
+ invalid("queue must implement OfflineEditQueue.", "queue");
34
+ }
35
+ if (typeof transport !== "function")
36
+ invalid("transport must be a function.", "transport");
37
+ const normalized = captureOptions(options);
38
+ if (normalized.signal?.aborted)
39
+ return createReceipt([], 0, "cancelled");
40
+ const outcomes = [];
41
+ const invokedEditIds = new Set();
42
+ let invokedCount = 0;
43
+ let stoppedReason;
44
+ while (outcomes.length < normalized.limit) {
45
+ if (normalized.signal?.aborted) {
46
+ stoppedReason = "cancelled";
47
+ break;
48
+ }
49
+ const [queued] = (await queue.claimReady({
50
+ authorizationScopeDigest: normalized.authorizationScopeDigest,
51
+ sourceId: normalized.sourceId,
52
+ workerId: normalized.workerId,
53
+ limit: 1,
54
+ leaseDurationMs: normalized.leaseDurationMs,
55
+ })).slice(0, 1);
56
+ if (!queued)
57
+ break;
58
+ if (normalized.signal?.aborted) {
59
+ outcomes.push(unacknowledged(queued.id, "cancelled"));
60
+ stoppedReason = "cancelled";
61
+ break;
62
+ }
63
+ if (invokedEditIds.has(queued.id)) {
64
+ outcomes.push(unacknowledged(queued.id, "repeated-claim"));
65
+ break;
66
+ }
67
+ const leaseToken = queued.lease?.token;
68
+ if (!leaseToken) {
69
+ outcomes.push(unacknowledged(queued.id, "missing-lease"));
70
+ break;
71
+ }
72
+ let rawAcknowledgement;
73
+ invokedCount += 1;
74
+ invokedEditIds.add(queued.id);
75
+ try {
76
+ rawAcknowledgement = await transport(replayRequest(queued), transportContext(normalized.signal));
77
+ }
78
+ catch {
79
+ const cancelled = normalized.signal?.aborted === true;
80
+ outcomes.push(unacknowledged(queued.id, cancelled ? "cancelled" : "transport-threw"));
81
+ if (cancelled)
82
+ stoppedReason = "cancelled";
83
+ break;
84
+ }
85
+ let acknowledgement;
86
+ try {
87
+ acknowledgement = captureAcknowledgement(rawAcknowledgement, queued);
88
+ }
89
+ catch (error) {
90
+ outcomes.push(unacknowledged(queued.id, error instanceof AcknowledgementError ? error.code : "invalid-acknowledgement"));
91
+ break;
92
+ }
93
+ let conflicted;
94
+ try {
95
+ if (acknowledgement.kind === "applied") {
96
+ await queue.markApplied(queued.id, leaseToken, {
97
+ serverOperationId: acknowledgement.serverOperationId,
98
+ ...(acknowledgement.serverGeneration === undefined
99
+ ? {}
100
+ : { serverGeneration: acknowledgement.serverGeneration }),
101
+ });
102
+ }
103
+ else if (acknowledgement.kind === "retryable") {
104
+ await queue.markRetry(queued.id, leaseToken, {
105
+ retryAt: acknowledgement.retryAt,
106
+ reasonCode: acknowledgement.reasonCode,
107
+ });
108
+ }
109
+ else {
110
+ conflicted = await queue.markConflicted(queued.id, leaseToken, {
111
+ conflictId: acknowledgement.conflictId,
112
+ ...(acknowledgement.serverGeneration === undefined
113
+ ? {}
114
+ : { serverGeneration: acknowledgement.serverGeneration }),
115
+ });
116
+ }
117
+ }
118
+ catch {
119
+ outcomes.push(unacknowledged(queued.id, "transition-rejected"));
120
+ break;
121
+ }
122
+ // Both run outside the transition guard on purpose: the durable conflict
123
+ // write has already succeeded, so neither a policy that cannot land nor a
124
+ // projection refusal may be reported as a rejected transition.
125
+ const policy = conflicted === undefined ? undefined : await applyConflictPolicy(queue, normalized, conflicted);
126
+ const resolved = policy?.record ?? conflicted;
127
+ const syncConflict = resolved === undefined || normalized.replica === undefined
128
+ ? undefined
129
+ : projectOfflineReplaySyncConflict({ edit: resolved, replica: normalized.replica });
130
+ outcomes.push(deepFreeze({
131
+ editId: queued.id,
132
+ outcome: acknowledgement.kind,
133
+ ...(syncConflict === undefined ? {} : { syncConflict }),
134
+ ...(policy === undefined ? {} : { conflictPolicy: policy.receipt }),
135
+ }));
136
+ }
137
+ return createReceipt(outcomes, invokedCount, stoppedReason);
138
+ }
139
+ /**
140
+ * Carry out the declared policy on one freshly conflicted edit.
141
+ *
142
+ * `manual` — and an omitted policy, which behaves the same way — decides
143
+ * nothing, so there is nothing to do beyond saying so. `server-wins` closes the
144
+ * conflict against the local edit through the queue's own reviewed-resolution
145
+ * transition, so a policy-driven closure and a reviewer-driven one leave
146
+ * identical durable state and identical audit history.
147
+ */
148
+ async function applyConflictPolicy(queue, options, conflicted) {
149
+ const policy = options.policy;
150
+ if (policy === undefined)
151
+ return undefined;
152
+ if (policy.action !== "discard-local-edit" || policy.choice === undefined || !conflicted.conflict) {
153
+ return { receipt: Object.freeze({ policy: policy.policy, outcome: "retained-for-review" }) };
154
+ }
155
+ try {
156
+ const record = await queue.resolveConflict(conflicted.id, { authorizationScopeDigest: options.authorizationScopeDigest, sourceId: options.sourceId }, { conflictId: conflicted.conflict.conflictId, choice: policy.choice });
157
+ return { receipt: Object.freeze({ policy: policy.policy, outcome: "discarded-local-edit" }), record };
158
+ }
159
+ catch {
160
+ // The conflict is durably recorded; the policy simply did not land, and the
161
+ // receipt says that rather than implying the local edit was abandoned.
162
+ return { receipt: Object.freeze({ policy: policy.policy, outcome: "not-applied" }) };
163
+ }
164
+ }
165
+ function captureOptions(value) {
166
+ const record = optionRecord(value);
167
+ for (const key of Object.keys(record)) {
168
+ if (!OPTION_KEYS.has(key))
169
+ invalid(`options.${key} is not allowed.`, `options.${key}`);
170
+ }
171
+ const authorizationScopeDigest = optionDigest(record.authorizationScopeDigest, "options.authorizationScopeDigest");
172
+ const sourceId = optionString(record.sourceId, "options.sourceId");
173
+ const workerId = optionString(record.workerId, "options.workerId");
174
+ const limit = boundedInteger(record.limit, "options.limit", 100);
175
+ const leaseDurationMs = boundedInteger(record.leaseDurationMs, "options.leaseDurationMs", MAX_OFFLINE_EDIT_LEASE_DURATION_MS);
176
+ const signal = record.signal;
177
+ if (signal !== undefined && !isAbortSignal(signal)) {
178
+ invalid("options.signal must be an AbortSignal.", "options.signal");
179
+ }
180
+ const replica = record.replica === undefined
181
+ ? undefined
182
+ : normalizeOfflineReplaySyncConflictBinding(record.replica, "options.replica");
183
+ // Refused before the first claim: a pass either honours the policy it was
184
+ // given for every conflict it meets, or it does nothing at all.
185
+ const policy = record.conflictPolicy === undefined
186
+ ? undefined
187
+ : requireHonourableOfflineReplayConflictPolicy(record.conflictPolicy, "options.conflictPolicy");
188
+ return {
189
+ authorizationScopeDigest,
190
+ sourceId,
191
+ workerId,
192
+ limit,
193
+ leaseDurationMs,
194
+ ...(signal === undefined ? {} : { signal }),
195
+ ...(replica === undefined ? {} : { replica }),
196
+ ...(policy === undefined ? {} : { policy }),
197
+ };
198
+ }
199
+ function replayRequest(queued) {
200
+ return deepFreeze({
201
+ version: HONUA_OFFLINE_EDIT_REPLAY_VERSION,
202
+ editId: queued.id,
203
+ requestFingerprint: queued.requestFingerprint,
204
+ idempotencyKey: queued.idempotencyKey,
205
+ sourceId: queued.sourceId,
206
+ edit: queued.edit,
207
+ });
208
+ }
209
+ function transportContext(signal) {
210
+ return Object.freeze(signal === undefined ? {} : { signal });
211
+ }
212
+ function captureAcknowledgement(value, queued) {
213
+ const record = dataRecord(value, "acknowledgement");
214
+ const kind = record.kind;
215
+ if (kind === "applied") {
216
+ allowedKeys(record, APPLIED_ACKNOWLEDGEMENT_KEYS, "acknowledgement");
217
+ const identity = acknowledgementIdentity(record, queued);
218
+ return deepFreeze({
219
+ kind,
220
+ ...identity,
221
+ serverOperationId: normalizedString(record.serverOperationId, "acknowledgement.serverOperationId"),
222
+ ...(record.serverGeneration === undefined
223
+ ? {}
224
+ : { serverGeneration: normalizedString(record.serverGeneration, "acknowledgement.serverGeneration") }),
225
+ });
226
+ }
227
+ if (kind === "retryable") {
228
+ allowedKeys(record, RETRYABLE_ACKNOWLEDGEMENT_KEYS, "acknowledgement");
229
+ const identity = acknowledgementIdentity(record, queued);
230
+ return deepFreeze({
231
+ kind,
232
+ ...identity,
233
+ retryAt: normalizedTimestamp(record.retryAt, "acknowledgement.retryAt"),
234
+ reasonCode: normalizedString(record.reasonCode, "acknowledgement.reasonCode"),
235
+ });
236
+ }
237
+ if (kind === "conflicted") {
238
+ allowedKeys(record, CONFLICTED_ACKNOWLEDGEMENT_KEYS, "acknowledgement");
239
+ const identity = acknowledgementIdentity(record, queued);
240
+ return deepFreeze({
241
+ kind,
242
+ ...identity,
243
+ conflictId: normalizedString(record.conflictId, "acknowledgement.conflictId"),
244
+ ...(record.serverGeneration === undefined
245
+ ? {}
246
+ : { serverGeneration: normalizedString(record.serverGeneration, "acknowledgement.serverGeneration") }),
247
+ });
248
+ }
249
+ throw new AcknowledgementError("invalid-acknowledgement");
250
+ }
251
+ function acknowledgementIdentity(record, queued) {
252
+ const identity = {
253
+ editId: digest(record.editId, "acknowledgement.editId"),
254
+ requestFingerprint: digest(record.requestFingerprint, "acknowledgement.requestFingerprint"),
255
+ idempotencyKey: normalizedString(record.idempotencyKey, "acknowledgement.idempotencyKey"),
256
+ };
257
+ if (identity.editId !== queued.id ||
258
+ identity.requestFingerprint !== queued.requestFingerprint ||
259
+ identity.idempotencyKey !== queued.idempotencyKey) {
260
+ throw new AcknowledgementError("identity-mismatch");
261
+ }
262
+ return identity;
263
+ }
264
+ function createReceipt(outcomes, invokedCount, stoppedReason) {
265
+ return deepFreeze({
266
+ version: HONUA_OFFLINE_EDIT_REPLAY_VERSION,
267
+ claimedCount: outcomes.length,
268
+ invokedCount,
269
+ appliedCount: outcomes.filter((outcome) => outcome.outcome === "applied").length,
270
+ retryableCount: outcomes.filter((outcome) => outcome.outcome === "retryable").length,
271
+ conflictedCount: outcomes.filter((outcome) => outcome.outcome === "conflicted").length,
272
+ unacknowledgedCount: outcomes.filter((outcome) => outcome.outcome === "unacknowledged").length,
273
+ ...(stoppedReason === undefined ? {} : { stoppedReason }),
274
+ outcomes: [...outcomes],
275
+ });
276
+ }
277
+ function unacknowledged(editId, reasonCode) {
278
+ return deepFreeze({ editId, outcome: "unacknowledged", reasonCode });
279
+ }
280
+ function dataRecord(value, path) {
281
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
282
+ acknowledgementInvalid(`${path} must be a plain data object.`);
283
+ }
284
+ const prototype = Object.getPrototypeOf(value);
285
+ if (prototype !== Object.prototype && prototype !== null) {
286
+ acknowledgementInvalid(`${path} must use a safe prototype.`);
287
+ }
288
+ if (Object.getOwnPropertySymbols(value).length > 0) {
289
+ acknowledgementInvalid(`${path} cannot contain symbol keys.`);
290
+ }
291
+ const descriptors = Object.getOwnPropertyDescriptors(value);
292
+ const record = Object.create(null);
293
+ for (const [key, descriptor] of Object.entries(descriptors)) {
294
+ if (!("value" in descriptor))
295
+ acknowledgementInvalid(`${path}.${key} must be a data property.`);
296
+ record[key] = descriptor.value;
297
+ }
298
+ return record;
299
+ }
300
+ function optionRecord(value) {
301
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
302
+ invalid("options must be a plain data object.", "options");
303
+ }
304
+ const prototype = Object.getPrototypeOf(value);
305
+ if (prototype !== Object.prototype && prototype !== null) {
306
+ invalid("options must use a safe prototype.", "options");
307
+ }
308
+ if (Object.getOwnPropertySymbols(value).length > 0) {
309
+ invalid("options cannot contain symbol keys.", "options");
310
+ }
311
+ const descriptors = Object.getOwnPropertyDescriptors(value);
312
+ const record = Object.create(null);
313
+ for (const [key, descriptor] of Object.entries(descriptors)) {
314
+ if (!("value" in descriptor))
315
+ invalid(`options.${key} must be a data property.`, `options.${key}`);
316
+ record[key] = descriptor.value;
317
+ }
318
+ return record;
319
+ }
320
+ function allowedKeys(record, allowed, path) {
321
+ for (const key of Object.keys(record)) {
322
+ if (!allowed.has(key))
323
+ acknowledgementInvalid(`${path}.${key} is not allowed.`);
324
+ }
325
+ }
326
+ function normalizedString(value, path) {
327
+ if (typeof value !== "string" ||
328
+ value.length === 0 ||
329
+ value.length > MAX_STRING_BYTES ||
330
+ value.trim() !== value ||
331
+ encoder.encode(value).byteLength > MAX_STRING_BYTES) {
332
+ acknowledgementInvalid(`${path} must be a normalized non-empty string of at most 1024 UTF-8 bytes.`);
333
+ }
334
+ return value;
335
+ }
336
+ function optionString(value, path) {
337
+ if (typeof value !== "string" ||
338
+ value.length === 0 ||
339
+ value.length > MAX_STRING_BYTES ||
340
+ value.trim() !== value ||
341
+ encoder.encode(value).byteLength > MAX_STRING_BYTES) {
342
+ invalid(`${path} must be a normalized non-empty string of at most 1024 UTF-8 bytes.`, path);
343
+ }
344
+ return value;
345
+ }
346
+ function optionDigest(value, path) {
347
+ const result = optionString(value, path);
348
+ if (!DIGEST_PATTERN.test(result))
349
+ invalid(`${path} must be a lowercase SHA-256 digest.`, path);
350
+ return result;
351
+ }
352
+ function digest(value, path) {
353
+ const result = normalizedString(value, path);
354
+ if (!DIGEST_PATTERN.test(result))
355
+ acknowledgementInvalid(`${path} must be a lowercase SHA-256 digest.`);
356
+ return result;
357
+ }
358
+ function normalizedTimestamp(value, path) {
359
+ const result = normalizedString(value, path);
360
+ const time = Date.parse(result);
361
+ if (!Number.isFinite(time) || new Date(time).toISOString() !== result) {
362
+ acknowledgementInvalid(`${path} must be a normalized ISO timestamp.`);
363
+ }
364
+ return result;
365
+ }
366
+ function boundedInteger(value, path, maximum) {
367
+ if (!Number.isSafeInteger(value) || value <= 0 || value > maximum) {
368
+ invalid(`${path} must be a positive safe integer no greater than ${maximum}.`, path);
369
+ }
370
+ return value;
371
+ }
372
+ function isAbortSignal(value) {
373
+ return (typeof value === "object" &&
374
+ value !== null &&
375
+ typeof value.aborted === "boolean" &&
376
+ typeof value.addEventListener === "function");
377
+ }
378
+ function acknowledgementInvalid(message) {
379
+ void message;
380
+ throw new AcknowledgementError("invalid-acknowledgement");
381
+ }
382
+ function invalid(message, path) {
383
+ throw new HonuaOfflineEditQueueError("invalid-edit", message, { path });
384
+ }
385
+ function deepFreeze(value) {
386
+ if (typeof value !== "object" || value === null || Object.isFrozen(value))
387
+ return value;
388
+ Object.freeze(value);
389
+ for (const child of Object.values(value))
390
+ deepFreeze(child);
391
+ return value;
392
+ }
@@ -0,0 +1,12 @@
1
+ import type { OfflineRegionFetchHandler, OfflineRegionFetchHandlerOptions } from "./types.js";
2
+ /**
3
+ * Creates a storage-backed fetch handler for service workers and fetch-event
4
+ * integrations. The host owns URL matching; the SDK only turns a matched,
5
+ * credential-free resource identity into a response.
6
+ *
7
+ * A miss, unsupported method, or expired region returns `undefined`, allowing
8
+ * the host to continue its normal network fallback. Storage failures are
9
+ * propagated so a host can distinguish them from an ordinary cache miss.
10
+ */
11
+ export declare function createOfflineRegionFetchHandler(options: OfflineRegionFetchHandlerOptions): OfflineRegionFetchHandler;
12
+ //# sourceMappingURL=fetch-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-handler.d.ts","sourceRoot":"","sources":["../../../src/offline/fetch-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EACzB,gCAAgC,EAEjC,MAAM,YAAY,CAAC;AAEpB;;;;;;;;GAQG;AACH,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,gCAAgC,GAAG,yBAAyB,CAmBpH"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Creates a storage-backed fetch handler for service workers and fetch-event
3
+ * integrations. The host owns URL matching; the SDK only turns a matched,
4
+ * credential-free resource identity into a response.
5
+ *
6
+ * A miss, unsupported method, or expired region returns `undefined`, allowing
7
+ * the host to continue its normal network fallback. Storage failures are
8
+ * propagated so a host can distinguish them from an ordinary cache miss.
9
+ */
10
+ export function createOfflineRegionFetchHandler(options) {
11
+ if (!options.store)
12
+ throw new TypeError("An offline region store is required.");
13
+ if (typeof options.regionId !== "string" || options.regionId.length === 0)
14
+ throw new TypeError("regionId must be non-empty.");
15
+ if (typeof options.match !== "function")
16
+ throw new TypeError("A request matcher is required.");
17
+ const now = options.now ?? (() => new Date());
18
+ return async (request) => {
19
+ if (!(request instanceof Request))
20
+ throw new TypeError("request must be a Request.");
21
+ if (request.method !== "GET" && request.method !== "HEAD")
22
+ return undefined;
23
+ const resourceId = await options.match(request);
24
+ if (resourceId === undefined)
25
+ return undefined;
26
+ if (typeof resourceId !== "string" || resourceId.length === 0)
27
+ throw new TypeError("The request matcher returned an invalid resource ID.");
28
+ const read = await options.store.readResource(options.regionId, resourceId);
29
+ if (!read || isExpired(read, now()))
30
+ return undefined;
31
+ return toResponse(read, request.method === "HEAD");
32
+ };
33
+ }
34
+ function toResponse(read, head) {
35
+ const headers = new Headers({
36
+ "content-length": String(read.bytes.byteLength),
37
+ "x-honua-offline-region": read.regionId,
38
+ "x-honua-offline-state": read.manifest.source.observation.state,
39
+ "x-honua-offline-observed-at": read.manifest.source.observation.observedAt,
40
+ });
41
+ setHeaderIfValid(headers, "x-honua-offline-source-version", read.resource.sourceVersion);
42
+ setHeaderIfValid(headers, "x-honua-offline-schema-version", read.resource.schemaVersion);
43
+ setHeaderIfValid(headers, "x-honua-offline-plan-version", read.resource.planVersion);
44
+ if (read.resource.contentType)
45
+ headers.set("content-type", read.resource.contentType);
46
+ if (read.manifest.expiresAt)
47
+ headers.set("x-honua-offline-expires-at", read.manifest.expiresAt);
48
+ return new Response(head ? null : Uint8Array.from(read.bytes), { status: 200, headers });
49
+ }
50
+ function setHeaderIfValid(headers, name, value) {
51
+ if ([...value].every((character) => {
52
+ const codePoint = character.codePointAt(0) ?? 0;
53
+ return codePoint >= 0x20 && codePoint <= 0xff && codePoint !== 0x7f;
54
+ })) {
55
+ headers.set(name, value);
56
+ }
57
+ }
58
+ function isExpired(read, now) {
59
+ if (!(now instanceof Date) || !Number.isFinite(now.getTime()))
60
+ throw new TypeError("now must be a valid Date.");
61
+ if (!read.manifest.expiresAt)
62
+ return false;
63
+ const expiresAt = Date.parse(read.manifest.expiresAt);
64
+ return !Number.isFinite(expiresAt) || now.getTime() >= expiresAt;
65
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Storage-neutral contracts for bounded, downloadable offline regions.
3
+ *
4
+ * This experimental entrypoint provides an IndexedDB adapter for browser
5
+ * persistence and a storage-backed fetch handler for service-worker or fetch
6
+ * integrations. Applications inject request matching, resource loading, and
7
+ * network policy; the SDK owns deterministic manifest identity, credential
8
+ * projection, cache diagnostics, quota admission against the origin's real
9
+ * storage estimate, integrity checks, cancellation, progress semantics, durable
10
+ * edit-queue state, and the composed local-first status that names one honest
11
+ * state across cached reads and undelivered writes. Applications still own
12
+ * connectivity policy, persistence requests, and edit replay transport binding.
13
+ *
14
+ * @experimental
15
+ */
16
+ export { createOfflineRegionDiagnostic, createOfflineRegionManifest, downloadOfflineRegion, planOfflineRegionAdmission, verifyOfflineRegionManifest, } from "./region.js";
17
+ export { canonicalizeOfflineRegionQuery, compareOfflineRegionBounds, isWgs84LonLatCrs, normalizeOfflineRegionTileKey, OFFLINE_REGION_DEFAULT_TILE_MATRIX_SET, OFFLINE_REGION_MAX_TILE_ZOOM, OFFLINE_REGION_PROTOCOL, offlineRegionAssetSelector, offlineRegionAuthorizationScopeDigest, offlineRegionMetadataSelector, offlineRegionQueryFingerprint, offlineRegionResourceId, offlineRegionTileEnvelope, offlineRegionTileSelector, } from "./selection.js";
18
+ export type { OfflineRegionBoundsRelation, OfflineRegionCanonicalQueryV1, OfflineRegionQueryScopeV1, OfflineRegionResourceSelector, OfflineRegionSelectionIdentityV1, OfflineRegionTileSelectorOptions, } from "./selection.js";
19
+ export { createOfflineRegionFeatureBatch, createOfflineRegionSnapshotLoader, decodeOfflineRegionFeatureBatch, DEFAULT_OFFLINE_REGION_SNAPSHOT_MAX_CONTENTS, encodeOfflineRegionFeatureBatch, HONUA_OFFLINE_FEATURE_BATCH_KIND, HONUA_OFFLINE_FEATURE_BATCH_VERSION, HONUA_OFFLINE_REGION_SNAPSHOT_KIND, HONUA_OFFLINE_REGION_SNAPSHOT_VERSION, planOfflineRegionSnapshot, } from "./snapshot.js";
20
+ export type { OfflineRegionFeatureBatchV1, OfflineRegionSnapshotContentV1, OfflineRegionSnapshotEntryV1, OfflineRegionSnapshotSelectionV1, OfflineRegionSnapshotV1, } from "./snapshot.js";
21
+ export { createOfflineRegionQueryPlanCache, DEFAULT_OFFLINE_REGION_READ_STALE_AFTER_MS, HONUA_OFFLINE_REGION_READ_KIND, HONUA_OFFLINE_REGION_READ_VERSION, } from "./read-gate.js";
22
+ export type { OfflineRegionReadCacheDecisionV1, OfflineRegionReadFreshness, OfflineRegionReadGateOptions, OfflineRegionReadProvenanceV1, OfflineRegionStoreReadOptions, } from "./read-gate.js";
23
+ export { readOfflineRegionQuery } from "./read.js";
24
+ export type { OfflineRegionQueryReadV1, ReadOfflineRegionQueryOptions } from "./read.js";
25
+ export { readOfflineRegionAsset, readOfflineRegionMetadata, readOfflineRegionResource, readOfflineRegionTile, resolveOfflineRegionResourceId, } from "./read-resource.js";
26
+ export type { OfflineRegionMetadataReadV1, OfflineRegionResourceReadV1, OfflineRegionTileReadV1, ReadOfflineRegionAssetOptions, ReadOfflineRegionMetadataOptions, ReadOfflineRegionResourceOptions, ReadOfflineRegionTileOptions, } from "./read-resource.js";
27
+ export { buildOfflineRegionTileUrlTemplate, createOfflineRegionTileProtocol, OFFLINE_REGION_TILE_SCHEME, parseOfflineRegionTileUrl, registerOfflineRegionTileProtocol, } from "./tile-protocol.js";
28
+ export type { CreateOfflineRegionTileProtocolOptions, OfflineRegionProtocolHandler, OfflineRegionProtocolRegistrar, OfflineRegionProtocolRequest, OfflineRegionProtocolResponse, OfflineRegionTileAddressV1, } from "./tile-protocol.js";
29
+ export { createMemoryOfflineRegionStore, MemoryOfflineRegionStore } from "./memory-store.js";
30
+ export type { MemoryOfflineRegionStoreOptions } from "./memory-store.js";
31
+ export { HONUA_OFFLINE_REGION_STORE_CONFORMANCE_KIND, HONUA_OFFLINE_REGION_STORE_CONFORMANCE_VERSION, OFFLINE_REGION_STORE_CONFORMANCE_CASES, runOfflineRegionStoreConformance, } from "./store-conformance.js";
32
+ export type { OfflineRegionStoreConformanceCaseResultV1, OfflineRegionStoreConformanceOptions, OfflineRegionStoreConformanceReportV1, } from "./store-conformance.js";
33
+ export { applyOfflineRegionSchemaMigration, createIndexedDbOfflineRegionStore, HONUA_OFFLINE_REGION_SCHEMA_VERSION, HONUA_OFFLINE_REGION_STORE_RECOVERY_KIND, HONUA_OFFLINE_REGION_STORE_RECOVERY_VERSION, IndexedDbOfflineRegionStore, OFFLINE_REGION_SCHEMA_MIGRATIONS, planOfflineRegionSchemaMigration, readableOfflineRegionSchemaVersions, } from "./indexeddb.js";
34
+ export type { OfflineRegionSchemaMigrationPlanV1, OfflineRegionSchemaMigrationRefusal, OfflineRegionSchemaMigrationV1, OfflineRegionStoreRecoveryV1, } from "./indexeddb.js";
35
+ export { DEFAULT_OFFLINE_STORAGE_HEADROOM_RATIO, DEFAULT_OFFLINE_STORAGE_MIN_RESERVE_BYTES, HONUA_OFFLINE_STORAGE_BUDGET_KIND, HONUA_OFFLINE_STORAGE_BUDGET_VERSION, HONUA_OFFLINE_STORAGE_PERSISTENCE_KIND, HONUA_OFFLINE_STORAGE_PERSISTENCE_VERSION, isStorageQuotaPressureError, probeOfflineStorageBudget, requestOfflinePersistentStorage, } from "./quota.js";
36
+ export type { OfflineStorageBudgetAvailableV1, OfflineStorageBudgetUnavailableReason, OfflineStorageBudgetUnavailableV1, OfflineStorageBudgetV1, OfflineStorageManagerLike, OfflineStoragePersistence, OfflineStoragePersistenceRequestStatus, OfflineStoragePersistenceRequestV1, OfflineStoragePersistenceUnavailableReason, ProbeOfflineStorageBudgetOptions, RequestOfflinePersistentStorageOptions, } from "./quota.js";
37
+ export { createOfflineRegionFetchHandler } from "./fetch-handler.js";
38
+ export { createIndexedDbOfflineEditQueue, createMemoryOfflineEditQueue, IndexedDbOfflineEditQueue, MemoryOfflineEditQueue, } from "./edit-queue.js";
39
+ export { HONUA_OFFLINE_EDIT_REPLAY_VERSION, replayOfflineEditPass } from "./edit-replay.js";
40
+ export { classifyOfflineReplayConflictPolicy, HONUA_OFFLINE_CONFLICT_RESOLUTION_KIND, HONUA_OFFLINE_CONFLICT_RESOLUTION_VERSION, HonuaOfflineConflictAdjudicationError, isHonuaOfflineConflictAdjudicationError, OFFLINE_REPLAY_CONFLICT_POLICIES, recordOfflineConflictResolution, } from "./conflict-resolution.js";
41
+ export type { OfflineConflictAdjudicationCode, OfflineConflictResolutionReceiptV1, OfflineReplayConflictPolicyAction, OfflineReplayConflictPolicyDisposition, OfflineReplayConflictPolicyReason, OfflineReplayConflictPolicyRuleV1, RecordOfflineConflictResolutionOptions, } from "./conflict-resolution.js";
42
+ export { HONUA_OFFLINE_REPLAY_SYNC_CONFLICT_KIND, HONUA_OFFLINE_REPLAY_SYNC_CONFLICT_VERSION, OFFLINE_REPLAY_SYNC_CONFLICT_FIELD_MAP, projectOfflineReplaySyncConflict, } from "./replay-conflict.js";
43
+ export type { OfflineReplayLocalConflictResolutionV1, OfflineReplayProjectedSyncConflict, OfflineReplaySyncConflictBinding, OfflineReplaySyncConflictFieldDisposition, OfflineReplaySyncConflictFieldMappingV1, OfflineReplaySyncConflictProjectedV1, OfflineReplaySyncConflictProjectionV1, OfflineReplaySyncConflictRefusalReason, OfflineReplaySyncConflictRefusedV1, OfflineReplaySyncConflictUnavailableMember, OfflineReplaySyncConflictUnavailableReason, OfflineReplaySyncConflictUnavailableV1, ProjectOfflineReplaySyncConflictInput, } from "./replay-conflict.js";
44
+ export { createLocalFirstStatus, DEFAULT_LOCAL_FIRST_MAX_EDITS, DEFAULT_LOCAL_FIRST_MAX_LISTED_EDIT_IDS, DEFAULT_LOCAL_FIRST_MAX_REGIONS, HONUA_LOCAL_FIRST_STATUS_KIND, HONUA_LOCAL_FIRST_STATUS_VERSION, } from "./status.js";
45
+ export type { CreateLocalFirstStatusOptions, LocalFirstCompleteness, LocalFirstConnectivity, LocalFirstFreshness, LocalFirstReadAvailability, LocalFirstReads, LocalFirstRegionSummary, LocalFirstState, LocalFirstStateReason, LocalFirstStatusLimits, LocalFirstStatusV1, LocalFirstWriteCoverage, LocalFirstWrites, LocalFirstWriteState, } from "./status.js";
46
+ export { DEFAULT_OFFLINE_EDIT_QUEUE_MAX_AUDIT_EVENTS, DEFAULT_OFFLINE_EDIT_QUEUE_MAX_DEPENDENCIES, DEFAULT_OFFLINE_EDIT_QUEUE_MAX_EDITS, DEFAULT_OFFLINE_EDIT_QUEUE_MAX_PAYLOAD_BYTES, DEFAULT_OFFLINE_EDIT_QUEUE_MAX_RECOVERY_BYTES, DEFAULT_OFFLINE_EDIT_QUEUE_MAX_RECOVERY_RECORDS, HONUA_OFFLINE_EDIT_QUEUE_RECOVERY_KIND, HONUA_OFFLINE_EDIT_QUEUE_RECOVERY_VERSION, HONUA_OFFLINE_EDIT_QUEUE_VERSION, HonuaOfflineEditQueueError, inspectStoredOfflineEdit, inspectStoredOfflineEditMetadata, inspectStoredOfflineEditTombstone, MAX_OFFLINE_EDIT_LEASE_DURATION_MS, planOfflineEditQueueRecovery, } from "./edit-queue.js";
47
+ export type { CancelOfflineEditInput, ClaimOfflineEditsOptions, EnqueueOfflineEditInput, IndexedDbOfflineEditQueueOptions, ListOfflineEditsOptions, MarkOfflineEditAppliedInput, MarkOfflineEditConflictedInput, MarkOfflineEditRetryInput, OfflineEditAppliedOutcome, OfflineEditAuditEvent, OfflineEditAuditEventKind, OfflineEditCancellationOutcome, OfflineEditConflictOutcome, OfflineEditConflictResolutionAcknowledgement, OfflineEditConflictResolutionChoice, OfflineEditConflictResolutionDisposition, OfflineEditConflictResolutionOutcome, OfflineEditEnqueueResult, OfflineEditJsonValue, OfflineEditLease, OfflineEditOperation, OfflineEditQueue, OfflineEditQueueDiscardReason, OfflineEditQueueErrorCode, OfflineEditQueueInspection, OfflineEditQueueMetadata, OfflineEditQueueOptions, OfflineEditQueuePartition, OfflineEditQueueRecoveryLimits, OfflineEditQueueRecoveryPlanV1, OfflineEditQueueRecoveryRowsV1, OfflineEditQueueRecoveryV1, OfflineEditQueueRepairReason, OfflineEditQueueStateCounts, OfflineEditQueueStoredRecord, OfflineEditQueueTombstone, OfflineEditRetry, OfflineFeatureEdit, OfflineQueuedEdit, OfflineQueuedEditState, PruneTerminalOfflineEditsOptions, ResolveOfflineEditConflictInput, } from "./edit-queue.js";
48
+ export type { OfflineEditAppliedAcknowledgement, OfflineEditConflictedAcknowledgement, OfflineEditReplayAcknowledgement, OfflineEditReplayConflictPolicyOutcome, OfflineEditReplayConflictPolicyReceiptV1, OfflineEditReplayIdentity, OfflineEditReplayItemReceipt, OfflineEditReplayPassReceipt, OfflineEditReplayRequest, OfflineEditReplayTransport, OfflineEditReplayTransportContext, OfflineEditReplayUnacknowledgedReason, OfflineEditRetryableAcknowledgement, ReplayOfflineEditPassOptions, } from "./edit-replay.js";
49
+ export type { IndexedDbOfflineRegionStoreOptions } from "./indexeddb.js";
50
+ export { DEFAULT_OFFLINE_REGION_MAX_ATTRIBUTIONS, DEFAULT_OFFLINE_REGION_MAX_LOGICAL_BYTES, DEFAULT_OFFLINE_REGION_MAX_METADATA_ENTRIES, DEFAULT_OFFLINE_REGION_MAX_RESOURCES, DEFAULT_OFFLINE_REGION_MAX_STRING_BYTES, HONUA_OFFLINE_REGION_DIAGNOSTIC_KIND, HONUA_OFFLINE_REGION_DIAGNOSTIC_VERSION, HONUA_OFFLINE_REGION_KIND, HONUA_OFFLINE_REGION_VERSION, HonuaOfflineRegionError, } from "./types.js";
51
+ export type { CreateOfflineRegionDiagnosticOptions, CreateOfflineRegionManifestInput, OfflineRegionAdmissionPlan, OfflineRegionBounds, OfflineRegionCacheAdmin, OfflineRegionCacheInventory, OfflineRegionCommitGuard, OfflineRegionDownloadOptions, OfflineRegionDownloadProgress, OfflineRegionDownloadReceipt, OfflineRegionDiagnosticAdmission, OfflineRegionDiagnosticV1, OfflineRegionErrorCode, OfflineRegionFetchHandler, OfflineRegionFetchHandlerOptions, OfflineRegionLimits, OfflineRegionManifestV1, OfflineRegionObservation, OfflineRegionResourceInput, OfflineRegionResourceMatcher, OfflineRegionResourceRead, OfflineRegionResourceKind, OfflineRegionResourceLoader, OfflineRegionResourceV1, OfflineRegionStore, OfflineRegionStoredRegion, OfflineRegionValidator, OfflineRegionWriteTransaction, } from "./types.js";
52
+ //# sourceMappingURL=index.d.ts.map