@honua/react 0.1.2-beta.0 → 0.1.4-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.
- package/contract/discovery.d.ts +34 -0
- package/contract/discovery.d.ts.map +1 -1
- package/contract/index.d.ts +7 -5
- package/contract/index.d.ts.map +1 -1
- package/contract/index.js +3 -2
- package/contract/pmtiles.d.ts +10 -1
- package/contract/pmtiles.d.ts.map +1 -1
- package/contract/pmtiles.js +10 -2
- package/contract/protocol-module.d.ts +84 -9
- package/contract/protocol-module.d.ts.map +1 -1
- package/contract/query-filter-fes.d.ts +18 -0
- package/contract/query-filter-fes.d.ts.map +1 -0
- package/contract/query-filter-fes.js +122 -0
- package/contract/query-filter.d.ts +300 -0
- package/contract/query-filter.d.ts.map +1 -0
- package/contract/query-filter.js +883 -0
- package/contract/schema.d.ts +12 -0
- package/contract/schema.d.ts.map +1 -1
- package/contract/schema.js +34 -0
- package/contract/source-capability-support.d.ts.map +1 -1
- package/contract/source-capability-support.js +15 -3
- package/contract/source.d.ts +45 -25
- package/contract/source.d.ts.map +1 -1
- package/contract/source.js +719 -479
- package/contract/types.d.ts +87 -18
- package/contract/types.d.ts.map +1 -1
- package/contract/widget-source.d.ts.map +1 -1
- package/core/client.d.ts +45 -21
- package/core/client.d.ts.map +1 -1
- package/core/client.js +224 -46
- package/core/error-classifications.d.ts +6 -0
- package/core/error-classifications.d.ts.map +1 -1
- package/core/error-classifications.js +6 -0
- package/core/error-code-registry.d.ts +6 -0
- package/core/error-code-registry.d.ts.map +1 -1
- package/core/error-code-registry.js +6 -0
- package/core/error-envelope.js +2 -0
- package/core/errors.d.ts +1 -1
- package/core/errors.js +1 -1
- package/core/geoparquet-sql.d.ts +68 -4
- package/core/geoparquet-sql.d.ts.map +1 -1
- package/core/geoparquet-sql.js +325 -8
- package/core/odata-write-codec.d.ts.map +1 -1
- package/core/odata-write-codec.js +0 -1
- package/core/odata.d.ts +6 -0
- package/core/odata.d.ts.map +1 -1
- package/core/odata.js +6 -1
- package/core/ogc-processes.d.ts +182 -25
- package/core/ogc-processes.d.ts.map +1 -1
- package/core/ogc-processes.js +602 -30
- package/core/process-runner.d.ts +8 -1
- package/core/process-runner.d.ts.map +1 -1
- package/core/process-runner.js +1 -0
- package/core/protocol-transport.d.ts +2 -0
- package/core/protocol-transport.d.ts.map +1 -1
- package/core/stac.d.ts +31 -0
- package/core/stac.d.ts.map +1 -1
- package/core/stac.js +136 -19
- package/core/surfaces.d.ts.map +1 -1
- package/core/surfaces.js +273 -68
- package/core/types.d.ts +123 -9
- package/core/types.d.ts.map +1 -1
- package/core/wfs-axis-order.d.ts +18 -0
- package/core/wfs-axis-order.d.ts.map +1 -0
- package/core/wfs-axis-order.js +108 -0
- package/core/wfs-capabilities.d.ts +65 -24
- package/core/wfs-capabilities.d.ts.map +1 -1
- package/core/wfs-capabilities.js +237 -27
- package/core/wfs-filter.d.ts +26 -3
- package/core/wfs-filter.d.ts.map +1 -1
- package/core/wfs-filter.js +48 -53
- package/core/wfs-protocol-error.d.ts +12 -0
- package/core/wfs-protocol-error.d.ts.map +1 -0
- package/core/wfs-protocol-error.js +13 -0
- package/core/wfs.d.ts +53 -1
- package/core/wfs.d.ts.map +1 -1
- package/core/wfs.js +251 -29
- package/core/wms-feature-info.d.ts +92 -0
- package/core/wms-feature-info.d.ts.map +1 -0
- package/core/wms-feature-info.js +285 -0
- package/core/wms.d.ts +9 -0
- package/core/wms.d.ts.map +1 -1
- package/core/wms.js +17 -5
- package/esri-compat/event-bus.d.ts +20 -0
- package/esri-compat/event-bus.d.ts.map +1 -1
- package/esri-compat/feature-filter.d.ts +46 -1
- package/esri-compat/feature-filter.d.ts.map +1 -1
- package/esri-compat/feature-filter.js +49 -0
- package/esri-compat/geojson-layer.d.ts +4 -2
- package/esri-compat/geojson-layer.d.ts.map +1 -1
- package/esri-compat/geojson-layer.js +4 -2
- package/esri-compat/identity-manager.js +2 -2
- package/esri-compat/imagery-layer.d.ts +2 -1
- package/esri-compat/imagery-layer.d.ts.map +1 -1
- package/esri-compat/imagery-layer.js +2 -1
- package/esri-compat/layer-list.d.ts +8 -1
- package/esri-compat/layer-list.d.ts.map +1 -1
- package/esri-compat/layer-list.js +11 -4
- package/esri-compat/legend.d.ts.map +1 -1
- package/esri-compat/legend.js +1 -1
- package/esri-compat/locator.d.ts +297 -0
- package/esri-compat/locator.d.ts.map +1 -0
- package/esri-compat/locator.js +579 -0
- package/esri-compat/map-image-layer.d.ts.map +1 -1
- package/esri-compat/map-view.d.ts.map +1 -1
- package/esri-compat/map-view.js +1 -1
- package/esri-compat/scene-view.d.ts.map +1 -1
- package/esri-compat/time-slider.d.ts +29 -0
- package/esri-compat/time-slider.d.ts.map +1 -1
- package/esri-compat/time-slider.js +117 -0
- package/esri-compat/wfs-layer.d.ts +2 -1
- package/esri-compat/wfs-layer.d.ts.map +1 -1
- package/esri-compat/wfs-layer.js +2 -1
- package/esri-compat/widget-host.d.ts +42 -6
- package/esri-compat/widget-host.d.ts.map +1 -1
- package/esri-compat/widget-host.js +99 -10
- package/generated-app/projection.d.ts.map +1 -1
- package/generated-app/runtime.d.ts.map +1 -1
- package/map/data-to-map-bridge.d.ts +1 -1
- package/map/data-to-map-bridge.d.ts.map +1 -1
- package/map/data-to-map-bridge.js +1 -2
- package/map/honua-map.d.ts.map +1 -1
- package/map/honua-map.js +1 -1
- package/map/index.d.ts +2 -0
- package/map/index.d.ts.map +1 -1
- package/map/index.js +2 -0
- package/map/realtime-reconciliation-adapter.d.ts +98 -0
- package/map/realtime-reconciliation-adapter.d.ts.map +1 -0
- package/map/realtime-reconciliation-adapter.js +84 -0
- package/map/temporal-playback.d.ts +25 -0
- package/map/temporal-playback.d.ts.map +1 -1
- package/map/temporal-playback.js +29 -1
- package/package.json +4 -3
- package/query-planner/canonical-filter.d.ts +30 -0
- package/query-planner/canonical-filter.d.ts.map +1 -0
- package/query-planner/canonical-filter.js +30 -0
- package/query-planner/columnar-plan.d.ts +63 -0
- package/query-planner/columnar-plan.d.ts.map +1 -0
- package/query-planner/columnar-plan.js +94 -0
- package/query-planner/diagnostics.d.ts +3 -3
- package/query-planner/diagnostics.d.ts.map +1 -1
- package/query-planner/diagnostics.js +17 -4
- package/query-planner/duckdb.d.ts.map +1 -1
- package/query-planner/duckdb.js +16 -2
- package/query-planner/execution-context.d.ts.map +1 -1
- package/query-planner/execution-context.js +3 -1
- package/query-planner/executor.d.ts.map +1 -1
- package/query-planner/executor.js +7 -1
- package/query-planner/geoservices.d.ts.map +1 -1
- package/query-planner/geoservices.js +24 -5
- package/query-planner/grpc.d.ts.map +1 -1
- package/query-planner/grpc.js +29 -5
- package/query-planner/index.d.ts +20 -5
- package/query-planner/index.d.ts.map +1 -1
- package/query-planner/index.js +16 -3
- package/query-planner/ir.d.ts +6 -1
- package/query-planner/ir.d.ts.map +1 -1
- package/query-planner/ir.js +63 -1
- package/query-planner/odata-protocol-module.d.ts +54 -0
- package/query-planner/odata-protocol-module.d.ts.map +1 -0
- package/query-planner/odata-protocol-module.js +199 -0
- package/query-planner/odata-v1.d.ts +23 -0
- package/query-planner/odata-v1.d.ts.map +1 -0
- package/query-planner/odata-v1.js +202 -0
- package/query-planner/odata.d.ts +0 -3
- package/query-planner/odata.d.ts.map +1 -1
- package/query-planner/odata.js +0 -143
- package/query-planner/ogc-features.d.ts.map +1 -1
- package/query-planner/ogc-features.js +23 -1
- package/query-planner/planner.d.ts +2 -2
- package/query-planner/planner.d.ts.map +1 -1
- package/query-planner/planner.js +146 -27
- package/query-planner/representation.d.ts +59 -0
- package/query-planner/representation.d.ts.map +1 -0
- package/query-planner/representation.js +140 -0
- package/query-planner/types.d.ts +153 -3
- package/query-planner/types.d.ts.map +1 -1
- package/query-planner/wfs-protocol-module.d.ts +57 -0
- package/query-planner/wfs-protocol-module.d.ts.map +1 -0
- package/query-planner/wfs-protocol-module.js +731 -0
- package/query-planner/wfs-v1.d.ts +41 -0
- package/query-planner/wfs-v1.d.ts.map +1 -0
- package/query-planner/wfs-v1.js +366 -0
- package/query-planner/wfs.d.ts +1 -3
- package/query-planner/wfs.d.ts.map +1 -1
- package/query-planner/wfs.js +1 -70
- package/react/honua-map.d.ts +3 -1
- package/react/honua-map.d.ts.map +1 -1
- package/react/honua-map.js +11 -1
- package/react/hooks.d.ts +1 -1
- package/react/hooks.js +1 -1
- package/react/selection.d.ts.map +1 -1
- package/react/selection.js +0 -0
- package/react/use-mounted-source.d.ts +1 -1
- package/react/use-mounted-source.js +1 -1
- package/realtime/checkpoint-store.d.ts +206 -0
- package/realtime/checkpoint-store.d.ts.map +1 -0
- package/realtime/checkpoint-store.js +504 -0
- package/realtime/contract.d.ts +142 -0
- package/realtime/contract.d.ts.map +1 -0
- package/realtime/contract.js +154 -0
- package/realtime/exploration.d.ts +16 -0
- package/realtime/exploration.d.ts.map +1 -0
- package/realtime/exploration.js +32 -0
- package/realtime/honua-server.d.ts +131 -0
- package/realtime/honua-server.d.ts.map +1 -0
- package/realtime/honua-server.js +302 -0
- package/realtime/index.d.ts +39 -0
- package/realtime/index.d.ts.map +1 -0
- package/realtime/index.js +26 -0
- package/realtime/odata-delta.d.ts +114 -0
- package/realtime/odata-delta.d.ts.map +1 -0
- package/realtime/odata-delta.js +483 -0
- package/realtime/projections.d.ts +30 -0
- package/realtime/projections.d.ts.map +1 -0
- package/realtime/projections.js +51 -0
- package/realtime/reconciliation.d.ts +245 -0
- package/realtime/reconciliation.d.ts.map +1 -0
- package/realtime/reconciliation.js +377 -0
- package/realtime/reducer.d.ts +14 -0
- package/realtime/reducer.d.ts.map +1 -0
- package/realtime/reducer.js +351 -0
- package/realtime/resumable-transport.d.ts +111 -0
- package/realtime/resumable-transport.d.ts.map +1 -0
- package/realtime/resumable-transport.js +406 -0
- package/realtime/resumable.d.ts +104 -0
- package/realtime/resumable.d.ts.map +1 -0
- package/realtime/resumable.js +632 -0
- package/realtime/sse.d.ts +23 -0
- package/realtime/sse.d.ts.map +1 -0
- package/realtime/sse.js +147 -0
- package/realtime/store.d.ts +18 -0
- package/realtime/store.d.ts.map +1 -0
- package/realtime/store.js +63 -0
- package/realtime/types.d.ts +190 -0
- package/realtime/types.d.ts.map +1 -0
- package/realtime/types.js +10 -0
- package/realtime/websocket.d.ts +60 -0
- package/realtime/websocket.d.ts.map +1 -0
- package/realtime/websocket.js +157 -0
- package/realtime/wire.d.ts +28 -0
- package/realtime/wire.d.ts.map +1 -0
- package/realtime/wire.js +73 -0
- package/runtime/index.d.ts +6 -2
- package/runtime/index.d.ts.map +1 -1
- package/runtime/index.js +3 -1
- package/runtime/load-package.d.ts +24 -0
- package/runtime/load-package.d.ts.map +1 -1
- package/runtime/load-package.js +45 -1
- package/runtime/maplibre-renderer.d.ts +11 -0
- package/runtime/maplibre-renderer.d.ts.map +1 -1
- package/runtime/maplibre-renderer.js +32 -7
- package/runtime/offline-region-protocol.d.ts +73 -0
- package/runtime/offline-region-protocol.d.ts.map +1 -0
- package/runtime/offline-region-protocol.js +63 -0
- package/runtime/offline-region-style.d.ts +78 -0
- package/runtime/offline-region-style.d.ts.map +1 -0
- package/runtime/offline-region-style.js +147 -0
- package/runtime/pmtiles-protocol.d.ts +7 -8
- package/runtime/pmtiles-protocol.d.ts.map +1 -1
- package/runtime/pmtiles-protocol.js +19 -66
- package/runtime/protocol-registry.d.ts +55 -0
- package/runtime/protocol-registry.d.ts.map +1 -0
- package/runtime/protocol-registry.js +103 -0
- package/runtime/runtime.d.ts +2 -0
- package/runtime/runtime.d.ts.map +1 -1
- package/runtime/runtime.js +6 -1
- package/studio/index.d.ts +24 -7
- package/studio/index.d.ts.map +1 -1
- package/studio/index.js +21 -7
- package/studio/lifecycle-client.d.ts +164 -0
- package/studio/lifecycle-client.d.ts.map +1 -0
- package/studio/lifecycle-client.js +299 -0
- package/studio/lifecycle-errors.d.ts +91 -0
- package/studio/lifecycle-errors.d.ts.map +1 -0
- package/studio/lifecycle-errors.js +77 -0
- package/studio/lifecycle-types.d.ts +321 -0
- package/studio/lifecycle-types.d.ts.map +1 -0
- package/studio/lifecycle-types.js +22 -0
- package/webmap/convert-label.js +0 -1
- package/webmap/convert-symbol.d.ts.map +1 -1
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded, resumable SSE and WebSocket transports for `@honua/sdk-js/realtime`
|
|
3
|
+
* (issue #557), built on the #556 snapshot/delta/cursor/resume/plan-identity
|
|
4
|
+
* contract.
|
|
5
|
+
*
|
|
6
|
+
* `resumable.ts` deliberately never reconnects a transport — that ownership
|
|
7
|
+
* is explicitly deferred to this module (see
|
|
8
|
+
* `docs/decisions/realtime-snapshot-delta-cursor-resume-plan-identity-contract.md`,
|
|
9
|
+
* "the gate never reconnects a transport itself"). This module closes that
|
|
10
|
+
* gap by wrapping any raw {@link RealtimeFeatureTransport} (the SSE adapter
|
|
11
|
+
* in `sse.ts`, the WebSocket adapter in `websocket.ts`, or a custom one) with:
|
|
12
|
+
*
|
|
13
|
+
* - the bounded delivery gate from `resumable.ts` (`maxPendingEvents`,
|
|
14
|
+
* duplicate/gap detection, cross-scope checkpoint rejection);
|
|
15
|
+
* - reconnect ownership with exponential-backoff-and-jitter retry, bounded by
|
|
16
|
+
* `reconnect.maxAttempts` — exhausting attempts fails closed rather than
|
|
17
|
+
* retrying forever;
|
|
18
|
+
* - a heartbeat/liveness timeout that treats prolonged silence as a
|
|
19
|
+
* transport-detected gap;
|
|
20
|
+
* - resume-vs-resnapshot selection driven entirely by the gate's own
|
|
21
|
+
* `phase`: a reconnect resumes from the last accepted checkpoint's cursor
|
|
22
|
+
* only while the gate considers it valid, and requests a fresh snapshot
|
|
23
|
+
* the moment the gate (or this module, via `requireResnapshot`) decides
|
|
24
|
+
* otherwise — REQ-003's "resume only with a valid scoped cursor";
|
|
25
|
+
* - idempotent disposal that stops timers, the active connection, and the
|
|
26
|
+
* delivery gate exactly once, however disposal was triggered
|
|
27
|
+
* (caller `close()`, external `AbortSignal`, or an unrecoverable failure);
|
|
28
|
+
* - redacted telemetry (`onTelemetry`) built on `contract.ts`'s
|
|
29
|
+
* `deriveRealtimeContractAuthority` / `redactRealtimeCheckpoint`, so a raw
|
|
30
|
+
* cursor, watermark, or delta-token never reaches a log or metrics sink.
|
|
31
|
+
*
|
|
32
|
+
* The result still satisfies `RealtimeFeatureTransport`, so it composes with
|
|
33
|
+
* `createRealtimeFeatureStore` exactly like any other transport.
|
|
34
|
+
*
|
|
35
|
+
* @module
|
|
36
|
+
*/
|
|
37
|
+
import { deriveRealtimeContractAuthority, redactRealtimeCheckpoint } from "./contract.js";
|
|
38
|
+
import { HonuaRealtimeResumeError, createResumableRealtimeSubscription, } from "./resumable.js";
|
|
39
|
+
import { createRealtimeServerSentEventsTransport } from "./sse.js";
|
|
40
|
+
import { createRealtimeWebSocketTransport } from "./websocket.js";
|
|
41
|
+
const DEFAULT_MAX_ATTEMPTS = 8;
|
|
42
|
+
const DEFAULT_BASE_DELAY_MS = 250;
|
|
43
|
+
const DEFAULT_MAX_DELAY_MS = 30_000;
|
|
44
|
+
const DEFAULT_RESET_AFTER_MS = 15_000;
|
|
45
|
+
const EXTERNAL_RESNAPSHOT_REASONS = new Set([
|
|
46
|
+
"cursor-expired",
|
|
47
|
+
"resume-unsupported",
|
|
48
|
+
"transport-gap",
|
|
49
|
+
]);
|
|
50
|
+
function normalizeReconnectPolicy(policy) {
|
|
51
|
+
return {
|
|
52
|
+
maxAttempts: nonNegativeIntOr(policy?.maxAttempts, DEFAULT_MAX_ATTEMPTS),
|
|
53
|
+
baseDelayMs: nonNegativeIntOr(policy?.baseDelayMs, DEFAULT_BASE_DELAY_MS),
|
|
54
|
+
maxDelayMs: nonNegativeIntOr(policy?.maxDelayMs, DEFAULT_MAX_DELAY_MS),
|
|
55
|
+
resetAfterMs: nonNegativeIntOr(policy?.resetAfterMs, DEFAULT_RESET_AFTER_MS),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function nonNegativeIntOr(value, fallback) {
|
|
59
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
|
|
60
|
+
return fallback;
|
|
61
|
+
return Math.trunc(value);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Exponential backoff with jitter in the [0.5x, 1x] band for the given
|
|
65
|
+
* zero-based reconnect attempt — the same shape as
|
|
66
|
+
* `core/request-pipeline.ts`'s `resolveRetryDelayMs`, reimplemented locally
|
|
67
|
+
* so `@honua/sdk-js/realtime` does not pull in the HTTP request pipeline's
|
|
68
|
+
* error/type graph for one small helper. Exported so the schedule can be
|
|
69
|
+
* asserted deterministically without driving a fake transport end-to-end.
|
|
70
|
+
*/
|
|
71
|
+
export function computeReconnectDelayMs(policy, attempt) {
|
|
72
|
+
const normalized = normalizeReconnectPolicy(policy);
|
|
73
|
+
const exponential = normalized.baseDelayMs * 2 ** Math.max(0, attempt);
|
|
74
|
+
const capped = Math.min(normalized.maxDelayMs, exponential);
|
|
75
|
+
return capped * (0.5 + Math.random() * 0.5);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Wrap a raw {@link RealtimeFeatureTransport} with bounded, resumable
|
|
79
|
+
* delivery: the #556 delivery gate, reconnect/backoff, heartbeat timeout,
|
|
80
|
+
* and redacted telemetry. See the module doc for the full behavior.
|
|
81
|
+
*/
|
|
82
|
+
export function createResumableRealtimeTransport(transport, options) {
|
|
83
|
+
const policy = normalizeReconnectPolicy(options.reconnect);
|
|
84
|
+
return {
|
|
85
|
+
capabilities: transport.capabilities,
|
|
86
|
+
subscribe(request, observer) {
|
|
87
|
+
if (request.signal?.aborted) {
|
|
88
|
+
observer.complete();
|
|
89
|
+
return { close: () => { } };
|
|
90
|
+
}
|
|
91
|
+
// Every field that could seed a resume position is dropped: this
|
|
92
|
+
// wrapper always derives `resumeFrom` from the gate's own validated
|
|
93
|
+
// checkpoint (see `connect()`), never from caller input, so a stale or
|
|
94
|
+
// unscoped position can never reach the wire.
|
|
95
|
+
const { cursor: _cursor, watermark: _watermark, timestamp: _timestamp, deltaToken: _deltaToken, resumeFrom: _resumeFrom, signal: _signal, ...baseIdentity } = request;
|
|
96
|
+
const lifecycle = new AbortController();
|
|
97
|
+
// `lifecycle.signal` is handed to `gate` and to every `transport.subscribe()`
|
|
98
|
+
// call so a well-behaved transport (sse.ts, websocket.ts) tears itself down
|
|
99
|
+
// too, but disposal here never depends on that: `disposeGracefully`/
|
|
100
|
+
// `failClosed` below also close the active handle directly, so an external
|
|
101
|
+
// abort is honored even against a transport double that ignores `signal`.
|
|
102
|
+
const externalAbort = () => disposeGracefully("Subscription signal was aborted.");
|
|
103
|
+
if (request.signal)
|
|
104
|
+
request.signal.addEventListener("abort", externalAbort, { once: true });
|
|
105
|
+
let gate;
|
|
106
|
+
let activeHandle;
|
|
107
|
+
let heartbeatTimer;
|
|
108
|
+
let reconnectTimer;
|
|
109
|
+
let reconnectAttempt = 0;
|
|
110
|
+
let connectedAt;
|
|
111
|
+
let closed = false;
|
|
112
|
+
let expectingTeardown = false;
|
|
113
|
+
const currentNow = () => options.now?.() ?? Date.now();
|
|
114
|
+
function emitTelemetry() {
|
|
115
|
+
if (!options.onTelemetry || !gate)
|
|
116
|
+
return;
|
|
117
|
+
const state = gate.state;
|
|
118
|
+
try {
|
|
119
|
+
options.onTelemetry({
|
|
120
|
+
authority: deriveRealtimeContractAuthority(state, {
|
|
121
|
+
staleAfterMs: options.staleAfterMs,
|
|
122
|
+
now: currentNow(),
|
|
123
|
+
}),
|
|
124
|
+
reconnectAttempt,
|
|
125
|
+
acceptedEventCount: state.acceptedEventCount,
|
|
126
|
+
duplicateEventCount: state.duplicateEventCount,
|
|
127
|
+
gapCount: state.gapCount,
|
|
128
|
+
overflowCount: state.overflowCount,
|
|
129
|
+
...(state.checkpoint ? { checkpoint: redactRealtimeCheckpoint(state.checkpoint) } : {}),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// Telemetry is diagnostic and must never affect delivery; a
|
|
134
|
+
// throwing onTelemetry callback is the caller's bug, not a
|
|
135
|
+
// reason to fail the subscription.
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function hasBeenStableSince() {
|
|
139
|
+
return connectedAt !== undefined && currentNow() - connectedAt >= policy.resetAfterMs;
|
|
140
|
+
}
|
|
141
|
+
function clearHeartbeatTimer() {
|
|
142
|
+
if (heartbeatTimer !== undefined) {
|
|
143
|
+
clearTimeout(heartbeatTimer);
|
|
144
|
+
heartbeatTimer = undefined;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function clearReconnectTimer() {
|
|
148
|
+
if (reconnectTimer !== undefined) {
|
|
149
|
+
clearTimeout(reconnectTimer);
|
|
150
|
+
reconnectTimer = undefined;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function armHeartbeat() {
|
|
154
|
+
if (options.heartbeatTimeoutMs === undefined)
|
|
155
|
+
return;
|
|
156
|
+
clearHeartbeatTimer();
|
|
157
|
+
heartbeatTimer = setTimeout(onHeartbeatTimeout, options.heartbeatTimeoutMs);
|
|
158
|
+
}
|
|
159
|
+
function onHeartbeatTimeout() {
|
|
160
|
+
heartbeatTimer = undefined;
|
|
161
|
+
if (closed || !gate)
|
|
162
|
+
return;
|
|
163
|
+
gate.requireResnapshot("transport-gap", `No realtime event observed within ${String(options.heartbeatTimeoutMs)}ms.`);
|
|
164
|
+
scheduleReconnect("heartbeat-timeout");
|
|
165
|
+
}
|
|
166
|
+
function teardownActiveHandle() {
|
|
167
|
+
clearHeartbeatTimer();
|
|
168
|
+
if (!activeHandle)
|
|
169
|
+
return;
|
|
170
|
+
const handle = activeHandle;
|
|
171
|
+
activeHandle = undefined;
|
|
172
|
+
expectingTeardown = true;
|
|
173
|
+
try {
|
|
174
|
+
handle.close();
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
expectingTeardown = false;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function isExternalResnapshotReason(code) {
|
|
181
|
+
return EXTERNAL_RESNAPSHOT_REASONS.has(code);
|
|
182
|
+
}
|
|
183
|
+
function handleTransportError(error) {
|
|
184
|
+
if (closed)
|
|
185
|
+
return;
|
|
186
|
+
const resumeError = error instanceof HonuaRealtimeResumeError ? error : undefined;
|
|
187
|
+
if (!resumeError?.retryable) {
|
|
188
|
+
failClosed(error);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (isExternalResnapshotReason(resumeError.code)) {
|
|
192
|
+
gate?.requireResnapshot(resumeError.code, resumeError.message);
|
|
193
|
+
}
|
|
194
|
+
scheduleReconnect(resumeError.message);
|
|
195
|
+
}
|
|
196
|
+
function scheduleReconnect(detail) {
|
|
197
|
+
if (closed || lifecycle.signal.aborted || !gate)
|
|
198
|
+
return;
|
|
199
|
+
// A burst of in-flight deliveries (e.g. several queued events that
|
|
200
|
+
// each resolve `resnapshot-required`) can each call this before the
|
|
201
|
+
// pending reconnect delay fires. Only the first is honored — a
|
|
202
|
+
// reconnect is already scheduled, so later calls in the same burst
|
|
203
|
+
// must be deduplicated into it rather than each arming their own
|
|
204
|
+
// `setTimeout` (which would leak timers, double-count
|
|
205
|
+
// `reconnectAttempt`, and open several concurrent connections when
|
|
206
|
+
// they all fire).
|
|
207
|
+
if (reconnectTimer !== undefined)
|
|
208
|
+
return;
|
|
209
|
+
teardownActiveHandle();
|
|
210
|
+
if (reconnectAttempt >= policy.maxAttempts) {
|
|
211
|
+
failClosed(new HonuaRealtimeResumeError("delivery-failed", `Realtime transport reconnect attempts exhausted after ${String(reconnectAttempt)} tries (${detail}).`));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const delay = computeReconnectDelayMs(options.reconnect, reconnectAttempt);
|
|
215
|
+
reconnectAttempt += 1;
|
|
216
|
+
emitTelemetry();
|
|
217
|
+
reconnectTimer = setTimeout(() => {
|
|
218
|
+
reconnectTimer = undefined;
|
|
219
|
+
void connect();
|
|
220
|
+
}, delay);
|
|
221
|
+
}
|
|
222
|
+
async function connect() {
|
|
223
|
+
if (closed || lifecycle.signal.aborted || !gate)
|
|
224
|
+
return;
|
|
225
|
+
connectedAt = undefined;
|
|
226
|
+
// Resume only while the gate still considers the last checkpoint
|
|
227
|
+
// valid (REQ-003). Once the gate — or `requireResnapshot` above —
|
|
228
|
+
// has moved to `resnapshot-required`, the checkpoint is left in
|
|
229
|
+
// place for callers that read it, but this wrapper never sends it
|
|
230
|
+
// back over the wire until a fresh snapshot is accepted.
|
|
231
|
+
const requiresResnapshot = gate.state.phase === "resnapshot-required";
|
|
232
|
+
const resumeFrom = requiresResnapshot ? undefined : gate.state.checkpoint?.resume;
|
|
233
|
+
// The gate's sole recovery path out of `resnapshot-required` is a
|
|
234
|
+
// `snapshot` event (`resumable.ts`'s `enqueue`: every other event is
|
|
235
|
+
// rejected while that phase holds). A subscription originally opened
|
|
236
|
+
// in pure `delta` mode never asks the transport for an initial
|
|
237
|
+
// snapshot, so reconnecting still in `delta` mode after a
|
|
238
|
+
// cursor-expired / transport-gap resnapshot would drop `resumeFrom`
|
|
239
|
+
// but never receive the snapshot needed to leave `resnapshot-required`
|
|
240
|
+
// — every subsequent delta is silently rejected and the caller loses
|
|
241
|
+
// the gap's data (and everything after it) for good. Upgrade to
|
|
242
|
+
// `snapshot-then-delta` so the reconnect requests a fresh snapshot
|
|
243
|
+
// before deltas resume.
|
|
244
|
+
const modeOverride = requiresResnapshot && baseIdentity.mode === "delta" ? { mode: "snapshot-then-delta" } : {};
|
|
245
|
+
const effectiveRequest = {
|
|
246
|
+
...baseIdentity,
|
|
247
|
+
...modeOverride,
|
|
248
|
+
...(resumeFrom ? { resumeFrom } : {}),
|
|
249
|
+
signal: lifecycle.signal,
|
|
250
|
+
};
|
|
251
|
+
try {
|
|
252
|
+
activeHandle = transport.subscribe(effectiveRequest, innerObserver);
|
|
253
|
+
}
|
|
254
|
+
catch (cause) {
|
|
255
|
+
handleTransportError(cause);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (closed) {
|
|
259
|
+
// The transport completed or errored synchronously inside
|
|
260
|
+
// subscribe(), before returning its handle: disposal already ran
|
|
261
|
+
// without it. Close it now so nothing leaks.
|
|
262
|
+
activeHandle?.close();
|
|
263
|
+
activeHandle = undefined;
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
emitTelemetry();
|
|
267
|
+
}
|
|
268
|
+
/** Stop timers, the active connection, and the gate exactly once, then report a clean end. Idempotent. */
|
|
269
|
+
function disposeGracefully(reason) {
|
|
270
|
+
if (closed)
|
|
271
|
+
return;
|
|
272
|
+
closed = true;
|
|
273
|
+
clearHeartbeatTimer();
|
|
274
|
+
clearReconnectTimer();
|
|
275
|
+
teardownActiveHandle();
|
|
276
|
+
gate?.close(reason);
|
|
277
|
+
if (request.signal)
|
|
278
|
+
request.signal.removeEventListener("abort", externalAbort);
|
|
279
|
+
if (!lifecycle.signal.aborted)
|
|
280
|
+
lifecycle.abort(reason);
|
|
281
|
+
emitTelemetry();
|
|
282
|
+
observer.complete();
|
|
283
|
+
}
|
|
284
|
+
/** Stop timers, the active connection, and the gate exactly once, then report a terminal failure. Idempotent. */
|
|
285
|
+
function failClosed(error) {
|
|
286
|
+
if (closed)
|
|
287
|
+
return;
|
|
288
|
+
closed = true;
|
|
289
|
+
clearHeartbeatTimer();
|
|
290
|
+
clearReconnectTimer();
|
|
291
|
+
teardownActiveHandle();
|
|
292
|
+
gate?.close(errorMessage(error));
|
|
293
|
+
if (request.signal)
|
|
294
|
+
request.signal.removeEventListener("abort", externalAbort);
|
|
295
|
+
if (!lifecycle.signal.aborted)
|
|
296
|
+
lifecycle.abort("realtime resumable transport failed closed");
|
|
297
|
+
emitTelemetry();
|
|
298
|
+
observer.error(error);
|
|
299
|
+
}
|
|
300
|
+
const innerObserver = {
|
|
301
|
+
next(event) {
|
|
302
|
+
switch (event.type) {
|
|
303
|
+
case "snapshot":
|
|
304
|
+
case "upsert":
|
|
305
|
+
case "delete":
|
|
306
|
+
case "delta": {
|
|
307
|
+
armHeartbeat();
|
|
308
|
+
if (!gate)
|
|
309
|
+
return;
|
|
310
|
+
void gate.enqueue(event).then((result) => {
|
|
311
|
+
if (closed)
|
|
312
|
+
return;
|
|
313
|
+
if (result.status === "applied" || result.status === "duplicate") {
|
|
314
|
+
reconnectAttempt = 0;
|
|
315
|
+
}
|
|
316
|
+
else if (result.status === "resnapshot-required") {
|
|
317
|
+
scheduleReconnect(result.reason ?? "resnapshot-required");
|
|
318
|
+
}
|
|
319
|
+
else if (result.status === "error") {
|
|
320
|
+
failClosed(new HonuaRealtimeResumeError(result.reason ?? "delivery-failed", gate?.state.detail ?? "Realtime delivery failed."));
|
|
321
|
+
}
|
|
322
|
+
emitTelemetry();
|
|
323
|
+
});
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
case "heartbeat": {
|
|
327
|
+
armHeartbeat();
|
|
328
|
+
if (hasBeenStableSince())
|
|
329
|
+
reconnectAttempt = 0;
|
|
330
|
+
observer.next(event);
|
|
331
|
+
emitTelemetry();
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
case "status": {
|
|
335
|
+
if (event.status === "live")
|
|
336
|
+
connectedAt = currentNow();
|
|
337
|
+
armHeartbeat();
|
|
338
|
+
observer.next(event);
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
case "error": {
|
|
342
|
+
observer.next(event);
|
|
343
|
+
if (event.terminal)
|
|
344
|
+
failClosed(event.error);
|
|
345
|
+
else
|
|
346
|
+
handleTransportError(event.error);
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
error(error) {
|
|
352
|
+
handleTransportError(error);
|
|
353
|
+
},
|
|
354
|
+
complete() {
|
|
355
|
+
if (expectingTeardown)
|
|
356
|
+
return;
|
|
357
|
+
disposeGracefully("Realtime transport completed.");
|
|
358
|
+
},
|
|
359
|
+
};
|
|
360
|
+
void (async () => {
|
|
361
|
+
try {
|
|
362
|
+
gate = await createResumableRealtimeSubscription({
|
|
363
|
+
context: options.context,
|
|
364
|
+
apply: (applied) => observer.next(applied),
|
|
365
|
+
checkpointStore: options.checkpointStore,
|
|
366
|
+
initialCheckpoint: options.initialCheckpoint,
|
|
367
|
+
maxPendingEvents: options.maxPendingEvents,
|
|
368
|
+
maxRecentEventIds: options.maxRecentEventIds,
|
|
369
|
+
now: options.now,
|
|
370
|
+
signal: lifecycle.signal,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
catch (cause) {
|
|
374
|
+
if (!closed)
|
|
375
|
+
failClosed(cause);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (closed || lifecycle.signal.aborted)
|
|
379
|
+
return;
|
|
380
|
+
await connect();
|
|
381
|
+
})();
|
|
382
|
+
return {
|
|
383
|
+
close() {
|
|
384
|
+
disposeGracefully("Subscription closed by caller.");
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Convenience factory combining {@link createRealtimeServerSentEventsTransport}
|
|
392
|
+
* with {@link createResumableRealtimeTransport}.
|
|
393
|
+
*/
|
|
394
|
+
export function createResumableServerSentEventsTransport(sseOptions, resumableOptions) {
|
|
395
|
+
return createResumableRealtimeTransport(createRealtimeServerSentEventsTransport(sseOptions), resumableOptions);
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Convenience factory combining {@link createRealtimeWebSocketTransport}
|
|
399
|
+
* with {@link createResumableRealtimeTransport}.
|
|
400
|
+
*/
|
|
401
|
+
export function createResumableWebSocketTransport(webSocketOptions, resumableOptions) {
|
|
402
|
+
return createResumableRealtimeTransport(createRealtimeWebSocketTransport(webSocketOptions), resumableOptions);
|
|
403
|
+
}
|
|
404
|
+
function errorMessage(value) {
|
|
405
|
+
return value instanceof Error ? value.message : String(value);
|
|
406
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport-neutral, resumable delivery gate for realtime feature events.
|
|
3
|
+
*
|
|
4
|
+
* This module deliberately does not reconnect a transport. It protects the
|
|
5
|
+
* boundary between a transport and a consumer: compatible checkpoints resume,
|
|
6
|
+
* ordered events advance durably, duplicates are bounded, and gaps or queue
|
|
7
|
+
* overflow require an explicit replacement snapshot.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
import type { SourceId } from "../contract/types.js";
|
|
12
|
+
import { HonuaSdkError } from "../core/error-envelope.js";
|
|
13
|
+
import type { RealtimeDeleteEvent, RealtimeDeltaEvent, RealtimeResumeCheckpoint, RealtimeSnapshotEvent, RealtimeUpsertEvent } from "./types.js";
|
|
14
|
+
export declare const REALTIME_DURABLE_CHECKPOINT_VERSION: 1;
|
|
15
|
+
export type RealtimeSequencedEvent<TFeature = unknown> = RealtimeSnapshotEvent<TFeature> | RealtimeUpsertEvent<TFeature> | RealtimeDeleteEvent | RealtimeDeltaEvent<TFeature>;
|
|
16
|
+
export interface RealtimeResumeContextV1 {
|
|
17
|
+
readonly kind: "honua.realtime-resume-context";
|
|
18
|
+
readonly version: 1;
|
|
19
|
+
readonly sourceId: SourceId;
|
|
20
|
+
/** Stable, credential-free identity of the accepted query or plan. */
|
|
21
|
+
readonly queryFingerprint: string;
|
|
22
|
+
readonly sourceVersion: string;
|
|
23
|
+
readonly schemaVersion: string;
|
|
24
|
+
/** Stable opaque ACL/scope identity. Never pass a bearer token. */
|
|
25
|
+
readonly authorizationScopeFingerprint: string;
|
|
26
|
+
}
|
|
27
|
+
export interface RealtimeDurableCheckpointV1 {
|
|
28
|
+
readonly kind: "honua.realtime-checkpoint";
|
|
29
|
+
readonly version: typeof REALTIME_DURABLE_CHECKPOINT_VERSION;
|
|
30
|
+
readonly context: RealtimeResumeContextV1;
|
|
31
|
+
readonly resume: Readonly<RealtimeResumeCheckpoint> & {
|
|
32
|
+
readonly sequence: number;
|
|
33
|
+
};
|
|
34
|
+
readonly recentEventIds: readonly string[];
|
|
35
|
+
readonly savedAt: string;
|
|
36
|
+
}
|
|
37
|
+
export type RealtimeCheckpointCompatibilityCode = "compatible" | "invalid-checkpoint" | "source-changed" | "query-changed" | "source-version-changed" | "schema-version-changed" | "authorization-scope-changed";
|
|
38
|
+
export interface RealtimeCheckpointCompatibility {
|
|
39
|
+
readonly compatible: boolean;
|
|
40
|
+
readonly code: RealtimeCheckpointCompatibilityCode;
|
|
41
|
+
readonly reason: string;
|
|
42
|
+
}
|
|
43
|
+
export type ResumableRealtimePhase = "awaiting-snapshot" | "resuming" | "live" | "resnapshot-required" | "error" | "closed";
|
|
44
|
+
export type ResumableRealtimeReasonCode = RealtimeCheckpointCompatibilityCode | "snapshot-required" | "replacement-snapshot-required" | "invalid-event" | "sequence-missing" | "checkpoint-conflict" | "sequence-gap" | "event-id-reused" | "buffer-overflow" | "consumer-failed" | "delivery-failed" | "checkpoint-load-failed" | "checkpoint-save-failed" | "cursor-expired" | "resume-unsupported" | "transport-gap" | "cancelled" | "closed";
|
|
45
|
+
export type RealtimeExternalResnapshotReason = "cursor-expired" | "resume-unsupported" | "transport-gap";
|
|
46
|
+
export interface ResumableRealtimeState {
|
|
47
|
+
readonly phase: ResumableRealtimePhase;
|
|
48
|
+
readonly reason?: ResumableRealtimeReasonCode;
|
|
49
|
+
readonly detail?: string;
|
|
50
|
+
readonly checkpoint?: RealtimeDurableCheckpointV1;
|
|
51
|
+
readonly checkpointPersisted: boolean;
|
|
52
|
+
readonly pendingEvents: number;
|
|
53
|
+
readonly acceptedEventCount: number;
|
|
54
|
+
readonly duplicateEventCount: number;
|
|
55
|
+
readonly gapCount: number;
|
|
56
|
+
readonly overflowCount: number;
|
|
57
|
+
}
|
|
58
|
+
export type ResumableRealtimeDeliveryStatus = "applied" | "duplicate" | "resnapshot-required" | "error" | "cancelled";
|
|
59
|
+
export interface ResumableRealtimeDelivery {
|
|
60
|
+
readonly status: ResumableRealtimeDeliveryStatus;
|
|
61
|
+
readonly reason?: ResumableRealtimeReasonCode;
|
|
62
|
+
readonly checkpoint?: RealtimeDurableCheckpointV1;
|
|
63
|
+
}
|
|
64
|
+
export interface RealtimeCheckpointStore {
|
|
65
|
+
load(context: RealtimeResumeContextV1, signal: AbortSignal): Promise<RealtimeDurableCheckpointV1 | undefined>;
|
|
66
|
+
save(checkpoint: RealtimeDurableCheckpointV1, signal: AbortSignal): Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
export interface CreateResumableRealtimeSubscriptionOptions<TFeature = unknown> {
|
|
69
|
+
readonly context: RealtimeResumeContextV1;
|
|
70
|
+
readonly apply: (event: RealtimeSequencedEvent<TFeature>, signal: AbortSignal) => void | Promise<void>;
|
|
71
|
+
readonly checkpointStore?: RealtimeCheckpointStore;
|
|
72
|
+
/** Takes precedence over `checkpointStore.load` when supplied. */
|
|
73
|
+
readonly initialCheckpoint?: RealtimeDurableCheckpointV1;
|
|
74
|
+
/** Includes the currently applying event. @default 64 */
|
|
75
|
+
readonly maxPendingEvents?: number;
|
|
76
|
+
/** Bounded event-id history persisted with each checkpoint. @default 256 */
|
|
77
|
+
readonly maxRecentEventIds?: number;
|
|
78
|
+
readonly now?: () => number;
|
|
79
|
+
readonly signal?: AbortSignal;
|
|
80
|
+
}
|
|
81
|
+
export interface ResumableRealtimeSubscription<TFeature = unknown> {
|
|
82
|
+
readonly state: ResumableRealtimeState;
|
|
83
|
+
enqueue(event: RealtimeSequencedEvent<TFeature>): Promise<ResumableRealtimeDelivery>;
|
|
84
|
+
/** Project an adapter/server resume failure into an explicit snapshot transition. */
|
|
85
|
+
requireResnapshot(reason: RealtimeExternalResnapshotReason, detail?: string): void;
|
|
86
|
+
close(reason?: string): void;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Public realtime failure with the original detailed reason on `.code` and a
|
|
90
|
+
* stable common-envelope recovery class on `.sdkCode`. Classification metadata
|
|
91
|
+
* does not reconnect a transport or request a replacement snapshot.
|
|
92
|
+
*/
|
|
93
|
+
export declare class HonuaRealtimeResumeError extends HonuaSdkError {
|
|
94
|
+
readonly code: ResumableRealtimeReasonCode;
|
|
95
|
+
constructor(code: ResumableRealtimeReasonCode, message: string, options?: ErrorOptions);
|
|
96
|
+
}
|
|
97
|
+
/** Validate a checkpoint against the exact source/query/schema/ACL context. */
|
|
98
|
+
export declare function evaluateRealtimeCheckpoint(context: RealtimeResumeContextV1, checkpoint: RealtimeDurableCheckpointV1): RealtimeCheckpointCompatibility;
|
|
99
|
+
/**
|
|
100
|
+
* Create a serial, bounded delivery gate. Loading a durable checkpoint is the
|
|
101
|
+
* only asynchronous construction step; no transport is opened here.
|
|
102
|
+
*/
|
|
103
|
+
export declare function createResumableRealtimeSubscription<TFeature = unknown>(options: CreateResumableRealtimeSubscriptionOptions<TFeature>): Promise<ResumableRealtimeSubscription<TFeature>>;
|
|
104
|
+
//# sourceMappingURL=resumable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resumable.d.ts","sourceRoot":"","sources":["../../../src/realtime/resumable.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAuB,aAAa,EAA0B,MAAM,2BAA2B,CAAC;AACvG,OAAO,KAAK,EACV,mBAAmB,EACnB,kBAAkB,EAClB,wBAAwB,EACxB,qBAAqB,EACrB,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,mCAAmC,EAAG,CAAU,CAAC;AAE9D,MAAM,MAAM,sBAAsB,CAAC,QAAQ,GAAG,OAAO,IACjD,qBAAqB,CAAC,QAAQ,CAAC,GAC/B,mBAAmB,CAAC,QAAQ,CAAC,GAC7B,mBAAmB,GACnB,kBAAkB,CAAC,QAAQ,CAAC,CAAC;AAEjC,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,EAAE,+BAA+B,CAAC;IAC/C,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,sEAAsE;IACtE,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,mEAAmE;IACnE,QAAQ,CAAC,6BAA6B,EAAE,MAAM,CAAC;CAChD;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,IAAI,EAAE,2BAA2B,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,OAAO,mCAAmC,CAAC;IAC7D,QAAQ,CAAC,OAAO,EAAE,uBAAuB,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,wBAAwB,CAAC,GAAG;QAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACpF,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,MAAM,mCAAmC,GAC3C,YAAY,GACZ,oBAAoB,GACpB,gBAAgB,GAChB,eAAe,GACf,wBAAwB,GACxB,wBAAwB,GACxB,6BAA6B,CAAC;AAElC,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,mCAAmC,CAAC;IACnD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,sBAAsB,GAC9B,mBAAmB,GACnB,UAAU,GACV,MAAM,GACN,qBAAqB,GACrB,OAAO,GACP,QAAQ,CAAC;AAEb,MAAM,MAAM,2BAA2B,GACnC,mCAAmC,GACnC,mBAAmB,GACnB,+BAA+B,GAC/B,eAAe,GACf,kBAAkB,GAClB,qBAAqB,GACrB,cAAc,GACd,iBAAiB,GACjB,iBAAiB,GACjB,iBAAiB,GACjB,iBAAiB,GACjB,wBAAwB,GACxB,wBAAwB,GACxB,gBAAgB,GAChB,oBAAoB,GACpB,eAAe,GACf,WAAW,GACX,QAAQ,CAAC;AAEb,MAAM,MAAM,gCAAgC,GAAG,gBAAgB,GAAG,oBAAoB,GAAG,eAAe,CAAC;AAEzG,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,KAAK,EAAE,sBAAsB,CAAC;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,2BAA2B,CAAC;IAC9C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,2BAA2B,CAAC;IAClD,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;IACtC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,MAAM,+BAA+B,GAAG,SAAS,GAAG,WAAW,GAAG,qBAAqB,GAAG,OAAO,GAAG,WAAW,CAAC;AAEtH,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,MAAM,EAAE,+BAA+B,CAAC;IACjD,QAAQ,CAAC,MAAM,CAAC,EAAE,2BAA2B,CAAC;IAC9C,QAAQ,CAAC,UAAU,CAAC,EAAE,2BAA2B,CAAC;CACnD;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,CAAC,OAAO,EAAE,uBAAuB,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,2BAA2B,GAAG,SAAS,CAAC,CAAC;IAC9G,IAAI,CAAC,UAAU,EAAE,2BAA2B,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnF;AAED,MAAM,WAAW,0CAA0C,CAAC,QAAQ,GAAG,OAAO;IAC5E,QAAQ,CAAC,OAAO,EAAE,uBAAuB,CAAC;IAC1C,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,sBAAsB,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvG,QAAQ,CAAC,eAAe,CAAC,EAAE,uBAAuB,CAAC;IACnD,kEAAkE;IAClE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,2BAA2B,CAAC;IACzD,yDAAyD;IACzD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,4EAA4E;IAC5E,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,MAAM,WAAW,6BAA6B,CAAC,QAAQ,GAAG,OAAO;IAC/D,QAAQ,CAAC,KAAK,EAAE,sBAAsB,CAAC;IACvC,OAAO,CAAC,KAAK,EAAE,sBAAsB,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;IACrF,qFAAqF;IACrF,iBAAiB,CAAC,MAAM,EAAE,gCAAgC,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnF,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;;;GAIG;AACH,qBAAa,wBAAyB,SAAQ,aAAa;aAEvC,IAAI,EAAE,2BAA2B;gBAAjC,IAAI,EAAE,2BAA2B,EACjD,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,YAAY;CAQzB;AAqED,+EAA+E;AAC/E,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,uBAAuB,EAChC,UAAU,EAAE,2BAA2B,GACtC,+BAA+B,CAwBjC;AAED;;;GAGG;AACH,wBAAsB,mCAAmC,CAAC,QAAQ,GAAG,OAAO,EAC1E,OAAO,EAAE,0CAA0C,CAAC,QAAQ,CAAC,GAC5D,OAAO,CAAC,6BAA6B,CAAC,QAAQ,CAAC,CAAC,CAyTlD"}
|