@thomasfosterau/legation-browser 0.0.0-stage → 0.1.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/CHANGELOG.md +842 -0
- package/LICENSE +21 -0
- package/README.md +323 -2
- package/dist/index.d.ts +656 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1109 -0
- package/dist/index.js.map +1 -0
- package/dist/rolldown-runtime-D8jQpDG7.js +12 -0
- package/package.json +57 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,656 @@
|
|
|
1
|
+
import { IndexedDb } from "@effect/platform-browser";
|
|
2
|
+
import { Client, ClientStore, Collection, Identity, Mutation, Node, Query, StoreError, Transport, TransportError } from "@thomasfosterau/legation";
|
|
3
|
+
import * as Layer from "effect/Layer";
|
|
4
|
+
import { ClientEngine, Coverage, EngineHost, HttpCache, Mirror, MirrorChannel, MirrorChannel as MirrorChannel$1, MirrorProtocol } from "@thomasfosterau/legation/internal";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
import * as Scope from "effect/Scope";
|
|
7
|
+
import * as Stream from "effect/Stream";
|
|
8
|
+
import * as Duration from "effect/Duration";
|
|
9
|
+
import * as Schema from "effect/Schema";
|
|
10
|
+
declare namespace IndexedDbClientStore_d_exports {
|
|
11
|
+
export { IndexedDbClientStoreOptions, layer, layerNamespaced, layerWindow, namespacedPrefix };
|
|
12
|
+
}
|
|
13
|
+
/** Options for the IndexedDB-backed `ClientStore` layers. */
|
|
14
|
+
interface IndexedDbClientStoreOptions {
|
|
15
|
+
/** IndexedDB database name. Defaults to platform-browser's default. */
|
|
16
|
+
readonly database?: string;
|
|
17
|
+
/** Key namespace inside the store. Defaults to core's `"legation/"`. */
|
|
18
|
+
readonly prefix?: string;
|
|
19
|
+
/**
|
|
20
|
+
* The identity this replica belongs to — an opaque, app-supplied
|
|
21
|
+
* fingerprint, **never PII and never a credential** (see
|
|
22
|
+
* {@link Identity.Fingerprint}). Supplying it turns the identity boundary
|
|
23
|
+
* on: the store compares it with its stamp on open and resets the replica
|
|
24
|
+
* on a mismatch, quarantining unpushed writes under the superseded
|
|
25
|
+
* fingerprint. Unset leaves the boundary off.
|
|
26
|
+
*/
|
|
27
|
+
readonly identity?: Identity.Fingerprint;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* ClientStore over IndexedDB. Requires the `IndexedDb` primitives service;
|
|
31
|
+
* use {@link layerWindow} where browser globals are available, or provide
|
|
32
|
+
* `IndexedDb` yourself (tests use fake-indexeddb).
|
|
33
|
+
*
|
|
34
|
+
* A pull and a push acknowledgement commit as one IndexedDB transaction (#393),
|
|
35
|
+
* so a durable pull writes its rows once and writes no release record.
|
|
36
|
+
*/
|
|
37
|
+
declare const layer: (options?: IndexedDbClientStoreOptions) => Layer.Layer<ClientStore.ClientStore, never, IndexedDb.IndexedDb>;
|
|
38
|
+
/** Self-contained layer using the browser's global `indexedDB`. */
|
|
39
|
+
declare const layerWindow: (options?: IndexedDbClientStoreOptions) => Layer.Layer<ClientStore.ClientStore>;
|
|
40
|
+
/**
|
|
41
|
+
* The key prefix {@link layerNamespaced} derives for a fingerprint:
|
|
42
|
+
* core's `"legation/"` namespace with a percent-encoded identity segment
|
|
43
|
+
* under it, so two principals' replicas cannot collide and neither can
|
|
44
|
+
* collide with an un-namespaced one.
|
|
45
|
+
*/
|
|
46
|
+
declare const namespacedPrefix: (identity: Identity.Fingerprint) => string;
|
|
47
|
+
/**
|
|
48
|
+
* The namespacing alternative to resetting (#212): one replica per identity
|
|
49
|
+
* in a single IndexedDB database, keyed by fingerprint.
|
|
50
|
+
*
|
|
51
|
+
* A principal's rows are unreachable from another principal's store because
|
|
52
|
+
* they are under a different key prefix — no comparison, no reset, and a
|
|
53
|
+
* user who switches back finds their replica still warm. The fingerprint is
|
|
54
|
+
* still stamped, so a store reached through this layer keeps the same
|
|
55
|
+
* guarantee if it is ever opened un-namespaced.
|
|
56
|
+
*
|
|
57
|
+
* The cost is disk: every identity that has ever signed in on this device
|
|
58
|
+
* keeps a replica until something clears it. Prefer {@link layerWindow} with
|
|
59
|
+
* an `identity` where sign-ins are unbounded.
|
|
60
|
+
*
|
|
61
|
+
* Pass `database` to isolate at the database level instead of the key level
|
|
62
|
+
* — a heavier boundary (a whole IndexedDB database per identity, deletable
|
|
63
|
+
* in one call) for apps that want it.
|
|
64
|
+
*/
|
|
65
|
+
declare const layerNamespaced: (identity: Identity.Fingerprint, options?: Omit<IndexedDbClientStoreOptions, "identity" | "prefix">) => Layer.Layer<ClientStore.ClientStore>;
|
|
66
|
+
declare namespace BroadcastMirrorChannel_d_exports {
|
|
67
|
+
export { make$1 as make };
|
|
68
|
+
}
|
|
69
|
+
/** A {@link MirrorChannel.MirrorChannel} over a `BroadcastChannel` name. */
|
|
70
|
+
declare const make$1: (name: string) => Effect.Effect<MirrorChannel$1.MirrorChannel, never, Scope.Scope>;
|
|
71
|
+
declare namespace BackgroundSync_d_exports {
|
|
72
|
+
export { FlushOptions, FlushOutcome, HostNotReady, IdentitySource, ListenOptions$1 as ListenOptions, Listener$1 as Listener, RegisterOnJournalOptions, RegisterOptions, RegisterOutcome, SyncEvent, SyncManager, SyncRegistration, SyncScope, defaultReadyTimeout, defaultTag, flush, identityUnchecked, listen$1 as listen, register, registerOnJournal };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The tag the library registers and answers. Short on purpose: Chromium
|
|
76
|
+
* refuses over-long tags.
|
|
77
|
+
*/
|
|
78
|
+
declare const defaultTag = "legation-flush";
|
|
79
|
+
/** How long a `sync` event waits for a host to attach before giving up. */
|
|
80
|
+
declare const defaultReadyTimeout: Duration.Input;
|
|
81
|
+
/** `SyncManager`: absent where the browser has no Background Sync. */
|
|
82
|
+
interface SyncManager {
|
|
83
|
+
readonly register: (tag: string) => Promise<void>;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* A `ServiceWorkerRegistration`, or any stand-in for one. Typed as `object`
|
|
87
|
+
* rather than a slice with an optional `sync`, because TypeScript's weak-type
|
|
88
|
+
* check would then refuse a real registration (whose declared type has no
|
|
89
|
+
* `sync`); the member is probed at runtime instead (see `syncManagerOf`).
|
|
90
|
+
*/
|
|
91
|
+
type SyncRegistration = object;
|
|
92
|
+
/** The slice of the worker's `SyncEvent` the listener reads. */
|
|
93
|
+
interface SyncEvent {
|
|
94
|
+
readonly tag: string;
|
|
95
|
+
/** Chromium: `true` when the browser will not retry this registration again. */
|
|
96
|
+
readonly lastChance?: boolean;
|
|
97
|
+
readonly waitUntil: (promise: Promise<unknown>) => void;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The slice of `ServiceWorkerGlobalScope` the listener needs — `self` in a
|
|
101
|
+
* worker. `registration` is where the host registers the tag from.
|
|
102
|
+
*/
|
|
103
|
+
interface SyncScope {
|
|
104
|
+
readonly addEventListener: (type: "sync", listener: (event: SyncEvent) => void) => void;
|
|
105
|
+
readonly registration?: SyncRegistration | undefined;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* What asking for a sync did. None of these is a failure: registration is
|
|
109
|
+
* advisory, and the journal flushes on the connectivity path whatever
|
|
110
|
+
* happens here.
|
|
111
|
+
*
|
|
112
|
+
* - `Registered` — the browser will fire the tag's `sync` event.
|
|
113
|
+
* - `Unsupported` — no service-worker registration, or no `SyncManager` on
|
|
114
|
+
* it (Safari, Firefox): the connectivity path flushes.
|
|
115
|
+
* - `Refused` — the browser declined (permission withheld, no active worker,
|
|
116
|
+
* or — from inside a worker — no window of the origin open).
|
|
117
|
+
*/
|
|
118
|
+
type RegisterOutcome = "Registered" | "Unsupported" | "Refused";
|
|
119
|
+
/** Options for {@link register}. */
|
|
120
|
+
interface RegisterOptions {
|
|
121
|
+
/** Defaults to {@link defaultTag}; must match the listener's. */
|
|
122
|
+
readonly tag?: string;
|
|
123
|
+
/**
|
|
124
|
+
* Where to register. Defaults to the page's own service-worker
|
|
125
|
+
* registration (`navigator.serviceWorker.getRegistration()` — never
|
|
126
|
+
* `ready`, which would wait forever on a page no worker controls).
|
|
127
|
+
*/
|
|
128
|
+
readonly registration?: Effect.Effect<SyncRegistration | undefined>;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Ask the browser to wake the worker with the library's `sync` event once
|
|
132
|
+
* connectivity is back. Call it from the page after a journaled mutation, or
|
|
133
|
+
* let `ServiceWorkerHost` do it from the worker (see {@link ListenOptions}).
|
|
134
|
+
*
|
|
135
|
+
* Never fails and never logs above debug level: absence is the common case
|
|
136
|
+
* (Background Sync is Chromium-only), and the journal still flushes on the
|
|
137
|
+
* next connectivity signal. Registering a tag that is already pending is a
|
|
138
|
+
* no-op for the browser, so calling it once per mutation is fine.
|
|
139
|
+
*/
|
|
140
|
+
declare const register: (options?: RegisterOptions) => Effect.Effect<RegisterOutcome>;
|
|
141
|
+
/** Options for {@link registerOnJournal}. */
|
|
142
|
+
interface RegisterOnJournalOptions extends RegisterOptions {
|
|
143
|
+
/**
|
|
144
|
+
* Whether the journal cannot drain right now. Defaults to
|
|
145
|
+
* `navigator.onLine === false` in the worker (`WorkerNavigator.onLine`).
|
|
146
|
+
*/
|
|
147
|
+
readonly offline?: Effect.Effect<boolean>;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Register the tag for the scope's lifetime whenever `engine`'s journal
|
|
151
|
+
* cannot drain now — the host's side of {@link register}, since in the
|
|
152
|
+
* service-worker topology a page's mutation is journaled in the worker:
|
|
153
|
+
*
|
|
154
|
+
* - a mutation is journaled while {@link RegisterOnJournalOptions.offline}
|
|
155
|
+
* holds; and
|
|
156
|
+
* - a push fails terminally (`ClientEngine.syncFailures`, after the retry
|
|
157
|
+
* policy is exhausted) — reachable network, unreachable backend.
|
|
158
|
+
*
|
|
159
|
+
* Never on an online mutation: the sync loop drains those, debounced and
|
|
160
|
+
* compacted, and an online registration would fire `sync` at once and push
|
|
161
|
+
* each write on its own (#246). Chromium only accepts a registration from a
|
|
162
|
+
* worker while a window of the origin is open; a refusal is a debug line.
|
|
163
|
+
*/
|
|
164
|
+
declare const registerOnJournal: (engine: ClientEngine.ClientEngine, options?: RegisterOnJournalOptions) => Effect.Effect<void, never, Scope.Scope>;
|
|
165
|
+
/**
|
|
166
|
+
* What a flush did. Each is a settled outcome — the `sync` event resolves and
|
|
167
|
+
* the browser does not retry:
|
|
168
|
+
*
|
|
169
|
+
* - `Flushed` — the push completed (possibly with nothing pending).
|
|
170
|
+
* Acknowledged entries are trimmed and their optimistic effects dropped; a
|
|
171
|
+
* permanently rejected one settles as `rejected` in `mutationStates`, with
|
|
172
|
+
* its reason.
|
|
173
|
+
* - `IdentityChanged` — the store is not stamped for whoever is signed in now
|
|
174
|
+
* (see {@link ListenOptions.identity}); nothing was pushed, and the journal
|
|
175
|
+
* waits for the next page boot to apply the boundary's transition.
|
|
176
|
+
* - `UpdateNeeded` — the server rejected this build's schema version; the
|
|
177
|
+
* journal is retained and `onUpdateNeeded` has fired. Retrying cannot help
|
|
178
|
+
* until the app updates.
|
|
179
|
+
*/
|
|
180
|
+
type FlushOutcome = "Flushed" | "IdentityChanged" | "UpdateNeeded";
|
|
181
|
+
/**
|
|
182
|
+
* The explicit opt-out from the flush's identity check, for an app with **no
|
|
183
|
+
* signed-in identity at all** (every session is the same public principal).
|
|
184
|
+
* The flush then relies on the store's boundary alone: quarantined entries
|
|
185
|
+
* are still unreachable from push, but a store still stamped for a user who
|
|
186
|
+
* signed out in a tab that closed before `Client.reset` ran **would be
|
|
187
|
+
* pushed**. Never pass this in an app that has sign-in.
|
|
188
|
+
*/
|
|
189
|
+
declare const identityUnchecked: unique symbol;
|
|
190
|
+
/**
|
|
191
|
+
* Who is signed in now, as the app resolves it inside the worker — or
|
|
192
|
+
* {@link identityUnchecked}. See {@link ListenOptions.identity}.
|
|
193
|
+
*/
|
|
194
|
+
type IdentitySource = Effect.Effect<Identity.Fingerprint | undefined> | typeof identityUnchecked;
|
|
195
|
+
/** Options for {@link flush}. */
|
|
196
|
+
interface FlushOptions {
|
|
197
|
+
/** See {@link ListenOptions.identity}. */
|
|
198
|
+
readonly identity: IdentitySource;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Flush `engine`'s journal through the ordinary push path, behind the
|
|
202
|
+
* identity check. A journal larger than half the engine's unpulled bound goes
|
|
203
|
+
* in chunks with a pull before each chunk after the first, so a flush may
|
|
204
|
+
* pull (#429); a smaller journal is pushed without pulling. Fails — so the
|
|
205
|
+
* `sync` event rejects and the browser reschedules — on a transport or store
|
|
206
|
+
* failure, with the journal intact. A pull between chunks that fails is a
|
|
207
|
+
* transport failure of the same kind, so the flush never reports `Flushed`
|
|
208
|
+
* over a journal it stopped short of.
|
|
209
|
+
*/
|
|
210
|
+
declare const flush: (engine: ClientEngine.ClientEngine, options: FlushOptions) => Effect.Effect<FlushOutcome, TransportError | StoreError>;
|
|
211
|
+
declare const HostNotReady_base: Schema.Class<HostNotReady, Schema.TaggedStruct<"HostNotReady", {
|
|
212
|
+
readonly tag: Schema.String;
|
|
213
|
+
readonly message: Schema.String;
|
|
214
|
+
}>, import("effect/Cause").YieldableError & {
|
|
215
|
+
readonly brand: unique symbol;
|
|
216
|
+
}>;
|
|
217
|
+
/** No host attached the listener within its `readyTimeout`. */
|
|
218
|
+
declare class HostNotReady extends HostNotReady_base {}
|
|
219
|
+
/** Options for {@link listen}. */
|
|
220
|
+
interface ListenOptions$1 {
|
|
221
|
+
/** The tag to answer, and to register from the host. Defaults to {@link defaultTag}. */
|
|
222
|
+
readonly tag?: string;
|
|
223
|
+
/** The worker's global scope. Defaults to `globalThis` (`self` in a worker). */
|
|
224
|
+
readonly scope?: SyncScope;
|
|
225
|
+
/**
|
|
226
|
+
* How long a `sync` event waits for a host to attach before rejecting, so
|
|
227
|
+
* the browser reschedules it. Defaults to {@link defaultReadyTimeout}. A
|
|
228
|
+
* worker that only boots its host on a page's handshake never attaches on
|
|
229
|
+
* a tab-less wake; boot the host at worker start for background flushing.
|
|
230
|
+
*/
|
|
231
|
+
readonly readyTimeout?: Duration.Input;
|
|
232
|
+
/**
|
|
233
|
+
* **Required.** The fingerprint of whoever is signed in **now**, as the app
|
|
234
|
+
* resolves it from inside the worker (e.g. a session-subject hash it keeps
|
|
235
|
+
* beside its session). A flush pushes only if the store's stamp equals it
|
|
236
|
+
* exactly; `undefined` means "cannot tell" and skips.
|
|
237
|
+
*
|
|
238
|
+
* Required rather than optional because the store's own boundary covers
|
|
239
|
+
* only a reset that ran: a tab closed before `Client.reset` leaves the old
|
|
240
|
+
* identity's journal in the pushable keyspace, and a background flush would
|
|
241
|
+
* send it under whoever's cookies the worker now carries. An app with no
|
|
242
|
+
* sign-in passes {@link identityUnchecked}, which accepts that risk
|
|
243
|
+
* explicitly.
|
|
244
|
+
*/
|
|
245
|
+
readonly identity: IdentitySource;
|
|
246
|
+
/**
|
|
247
|
+
* Register the tag from the worker when the attached engine's journal
|
|
248
|
+
* cannot drain now (see {@link registerOnJournal}). Defaults to `true`;
|
|
249
|
+
* turn it off when the page registers with {@link register} itself.
|
|
250
|
+
*/
|
|
251
|
+
readonly registerOnJournal?: boolean;
|
|
252
|
+
/**
|
|
253
|
+
* Whether the journal cannot drain now, for {@link registerOnJournal}.
|
|
254
|
+
* Defaults to `navigator.onLine === false`.
|
|
255
|
+
*/
|
|
256
|
+
readonly offline?: Effect.Effect<boolean>;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* The worker's `sync` listener, created by {@link listen}. It answers its
|
|
260
|
+
* tag from the moment it exists; a host attaches an engine with
|
|
261
|
+
* {@link Listener.serve}, which `ServiceWorkerHost.start` does when given the
|
|
262
|
+
* listener as `backgroundSync`.
|
|
263
|
+
*/
|
|
264
|
+
interface Listener$1 {
|
|
265
|
+
/** The tag this listener answers. */
|
|
266
|
+
readonly tag: string;
|
|
267
|
+
/**
|
|
268
|
+
* Attach `engine` for the scope's lifetime: `sync` events for the tag —
|
|
269
|
+
* including any that arrived before this call — flush its journal, run
|
|
270
|
+
* under the caller's services (logger, tracer). Closing the scope detaches
|
|
271
|
+
* it; a later event then waits for the next host.
|
|
272
|
+
*/
|
|
273
|
+
readonly serve: (engine: ClientEngine.ClientEngine) => Effect.Effect<void, never, Scope.Scope>;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Install the `sync` listener on the worker's scope — **synchronously, at the
|
|
277
|
+
* top level of the worker script** (see the module docs for why). Deliberately
|
|
278
|
+
* a plain function rather than an Effect for that reason.
|
|
279
|
+
*
|
|
280
|
+
* ```ts
|
|
281
|
+
* // service-worker.ts, top level
|
|
282
|
+
* const backgroundSync = BackgroundSync.listen({ identity: currentFingerprint });
|
|
283
|
+
* // ... later, once the engine's layers are built:
|
|
284
|
+
* yield* ServiceWorkerHost.start({ backgroundSync, registry, nodes, urls });
|
|
285
|
+
* ```
|
|
286
|
+
*/
|
|
287
|
+
declare const listen$1: (options: ListenOptions$1) => Listener$1;
|
|
288
|
+
declare namespace PushPoke_d_exports {
|
|
289
|
+
export { ListenOptions, Listener, PokeOutcome, PushEvent, PushScope, listen };
|
|
290
|
+
}
|
|
291
|
+
/** The slice of a worker's `PushEvent` the listener reads. */
|
|
292
|
+
interface PushEvent {
|
|
293
|
+
/** The push's data (`PushMessageData`, or `null` for a push without any). */
|
|
294
|
+
readonly data: unknown;
|
|
295
|
+
readonly waitUntil: (promise: Promise<unknown>) => void;
|
|
296
|
+
}
|
|
297
|
+
/** The slice of `ServiceWorkerGlobalScope` the listener needs — `self` in a worker. */
|
|
298
|
+
interface PushScope {
|
|
299
|
+
readonly addEventListener: (type: "push", listener: (event: PushEvent) => void) => void;
|
|
300
|
+
readonly registration?: SyncRegistration;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* What a recognised push did.
|
|
304
|
+
*
|
|
305
|
+
* - `Registered` / `Unsupported` / `Refused` — the Background Sync
|
|
306
|
+
* registration's outcome (`action: "backgroundSync"`, the default).
|
|
307
|
+
* - `Pulled` / `PullFailed` — the engine pulled, or the pull failed and the
|
|
308
|
+
* next connectivity signal pulls instead (`action: "pull"`).
|
|
309
|
+
* - `IdentityChanged` — the replica is not stamped for whoever is signed in
|
|
310
|
+
* now, so nothing was done.
|
|
311
|
+
*/
|
|
312
|
+
type PokeOutcome = RegisterOutcome | "Pulled" | "PullFailed" | "IdentityChanged";
|
|
313
|
+
/** Options for {@link listen}. */
|
|
314
|
+
interface ListenOptions {
|
|
315
|
+
/**
|
|
316
|
+
* **Required.** Who is signed in now, as `BackgroundSync.listen` takes it:
|
|
317
|
+
* a recognised push acts only if the replica's stamp equals it exactly.
|
|
318
|
+
* An app with no sign-in passes `BackgroundSync.identityUnchecked`.
|
|
319
|
+
*/
|
|
320
|
+
readonly identity: IdentitySource;
|
|
321
|
+
/**
|
|
322
|
+
* What a recognised push does: `"backgroundSync"` (the default) registers the
|
|
323
|
+
* library's sync tag; `"pull"` pulls at once.
|
|
324
|
+
*/
|
|
325
|
+
readonly action?: "backgroundSync" | "pull";
|
|
326
|
+
/** The sync tag to register; defaults to `BackgroundSync.defaultTag`. */
|
|
327
|
+
readonly tag?: string;
|
|
328
|
+
/** The worker's global scope. Defaults to `globalThis` (`self` in a worker). */
|
|
329
|
+
readonly scope?: PushScope;
|
|
330
|
+
/**
|
|
331
|
+
* How long a recognised push waits for a host to attach an engine. Defaults
|
|
332
|
+
* to `BackgroundSync.defaultReadyTimeout`.
|
|
333
|
+
*/
|
|
334
|
+
readonly readyTimeout?: Duration.Input;
|
|
335
|
+
}
|
|
336
|
+
/** The `push` listener {@link listen} installs; a host attaches its engine with {@link Listener.serve}. */
|
|
337
|
+
interface Listener {
|
|
338
|
+
/**
|
|
339
|
+
* Attach `engine` for the scope's lifetime, which `ServiceWorkerHost.start`
|
|
340
|
+
* does when given the listener as `pushPoke`. A push that arrived before
|
|
341
|
+
* then waits for it, up to the ready timeout.
|
|
342
|
+
*/
|
|
343
|
+
readonly serve: (engine: ClientEngine.ClientEngine) => Effect.Effect<void, never, Scope.Scope>;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Install the `push` listener on the worker's scope, synchronously at the top
|
|
347
|
+
* level of the worker script.
|
|
348
|
+
*
|
|
349
|
+
* ```ts
|
|
350
|
+
* // service-worker.ts, top level
|
|
351
|
+
* const backgroundSync = BackgroundSync.listen({ identity: currentFingerprint });
|
|
352
|
+
* const pushPoke = PushPoke.listen({ identity: currentFingerprint });
|
|
353
|
+
* // ... once the engine's layers are built:
|
|
354
|
+
* yield* ServiceWorkerHost.start({ backgroundSync, pushPoke, registry, nodes });
|
|
355
|
+
* ```
|
|
356
|
+
*/
|
|
357
|
+
declare const listen: (options: ListenOptions) => Listener;
|
|
358
|
+
declare namespace ServiceWorkerHost_d_exports {
|
|
359
|
+
export { Host, ServiceWorkerHostOptions, defaultChannelName, run$1 as run, start };
|
|
360
|
+
}
|
|
361
|
+
/** Options for {@link start} and {@link run}. */
|
|
362
|
+
interface ServiceWorkerHostOptions {
|
|
363
|
+
/** BroadcastChannel name shared with the mirrors it serves. */
|
|
364
|
+
readonly channelName?: string;
|
|
365
|
+
/**
|
|
366
|
+
* The collection registry mirrors' `related` registrations resolve against
|
|
367
|
+
* (forwarded to {@link EngineHost.run}). Pass it whenever mirrors use
|
|
368
|
+
* relational reads.
|
|
369
|
+
*/
|
|
370
|
+
readonly registry?: Collection.CollectionRegistry;
|
|
371
|
+
/**
|
|
372
|
+
* The node graph and URL-keyed reads for `SubscribeUrl` registrations
|
|
373
|
+
* (#159): a page with no `Client` posts `{ _tag: "SubscribeUrl",
|
|
374
|
+
* mirrorId, url }` on the channel and the host pins the URL's windows
|
|
375
|
+
* under that id — forwarded to {@link EngineHost.run}. Build `urls` with
|
|
376
|
+
* `JsonApiQuery.urlReads(nodes)` from `@thomasfosterau/legation-jsonapi`.
|
|
377
|
+
*/
|
|
378
|
+
readonly nodes?: Node.NodeRegistry;
|
|
379
|
+
readonly urls?: Client.UrlReads;
|
|
380
|
+
/**
|
|
381
|
+
* The worker's Background Sync listener (#213), from
|
|
382
|
+
* `BackgroundSync.listen({ identity })` at the top level of the worker script. The host
|
|
383
|
+
* attaches its engine for as long as it serves: the listener's `sync`
|
|
384
|
+
* events — including one that woke the worker before the host had booted —
|
|
385
|
+
* flush the journal through `push`, and the tag is registered when the
|
|
386
|
+
* journal cannot drain now (offline, or a terminally failed push). Unset, no listener is served and `sync` events are not flushed.
|
|
387
|
+
*/
|
|
388
|
+
readonly backgroundSync?: Listener$1;
|
|
389
|
+
/**
|
|
390
|
+
* Web Push as a poke carrier (#214): install `PushPoke.listen({ identity })`
|
|
391
|
+
* at the top level of the worker script and pass it here. The host attaches
|
|
392
|
+
* its engine, so a recognised push registers the Background Sync (or pulls,
|
|
393
|
+
* with `action: "pull"`) once the replica's identity checks out. Unset, the
|
|
394
|
+
* library's push is not answered.
|
|
395
|
+
*/
|
|
396
|
+
readonly pushPoke?: Listener;
|
|
397
|
+
}
|
|
398
|
+
/** The default channel name; Mirror setups should use the same one. */
|
|
399
|
+
declare const defaultChannelName = "legation";
|
|
400
|
+
/** The serving host: the engine it serves and what it holds for the mirrors. */
|
|
401
|
+
interface Host {
|
|
402
|
+
/** The engine the host serves — the replica a façade reads (`ClientView.fromEngine(engine)`). */
|
|
403
|
+
readonly engine: ClientEngine.ClientEngine;
|
|
404
|
+
/**
|
|
405
|
+
* The registrations the host holds on its mirrors' behalf at this moment
|
|
406
|
+
* (`EngineHost.Host.registrations`): the ledger a façade's coverage check
|
|
407
|
+
* scans, `Coverage.coverageOf(nodes, view.query, host.registrations)`.
|
|
408
|
+
*/
|
|
409
|
+
readonly registrations: Effect.Effect<ReadonlyArray<Coverage.Entry>>;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Start the host: serve mirrors over the broadcast channel and start the
|
|
413
|
+
* engine's sync loop (pull on poke, push+pull on reconnect), then return
|
|
414
|
+
* the {@link Host}. Both the host and `runSync` fork their fibers into the
|
|
415
|
+
* surrounding scope, so the caller must keep that scope open for as long
|
|
416
|
+
* as it serves (`Effect.never`, as {@link run} does); closing it stops
|
|
417
|
+
* serving.
|
|
418
|
+
*/
|
|
419
|
+
declare const start: (options?: ServiceWorkerHostOptions) => Effect.Effect<Host, never, ClientEngine.ClientEngine | Scope.Scope>;
|
|
420
|
+
/**
|
|
421
|
+
* Run the host: {@link start}, then serve forever. NEVER returns — both the
|
|
422
|
+
* host and `runSync` fork their fibers into the surrounding scope, so
|
|
423
|
+
* returning would let a caller's `Effect.scoped` tear the whole host down
|
|
424
|
+
* immediately. Interrupt it / close the scope to stop serving.
|
|
425
|
+
*/
|
|
426
|
+
declare const run$1: (options?: ServiceWorkerHostOptions) => Effect.Effect<never, never, ClientEngine.ClientEngine | Scope.Scope>;
|
|
427
|
+
declare namespace ElectedHost_d_exports {
|
|
428
|
+
export { ElectedHost, ElectedHostOptions, Role, run };
|
|
429
|
+
}
|
|
430
|
+
/** Whether this tab is currently serving the engine or mirroring another tab's. */
|
|
431
|
+
type Role = "host" | "mirror";
|
|
432
|
+
/** Options for {@link run}. */
|
|
433
|
+
interface ElectedHostOptions {
|
|
434
|
+
/** The Web Lock name AND the sync-protocol client group — one election per group. */
|
|
435
|
+
readonly clientGroupId: string;
|
|
436
|
+
readonly nodes: Node.NodeRegistry;
|
|
437
|
+
readonly mutations: Mutation.MutatorRegistry;
|
|
438
|
+
/**
|
|
439
|
+
* `ClientStore` (durable — pair {@link IndexedDbClientStore}) and
|
|
440
|
+
* `ClientTransport`, composed as a Layer so winning the election is what
|
|
441
|
+
* triggers connecting: nothing here opens a socket or an IndexedDB
|
|
442
|
+
* transaction until this tab is actually elected, keeping a losing (or
|
|
443
|
+
* still-waiting) tab's footprint to the mirror alone.
|
|
444
|
+
*/
|
|
445
|
+
readonly dependencies: Layer.Layer<ClientStore.ClientStore | Transport.ClientTransport>;
|
|
446
|
+
/**
|
|
447
|
+
* The engine's client identity while this tab hosts. Defaults to
|
|
448
|
+
* `host-${clientGroupId}` — deterministic and shared by construction, so
|
|
449
|
+
* whichever tab wins resumes the SAME journal/rows/cookie regardless of
|
|
450
|
+
* which physical tab or random id it happens to be (a fresh random id
|
|
451
|
+
* per boot would fork the mutation-id sequence and orphan whatever the
|
|
452
|
+
* old host had already journaled).
|
|
453
|
+
*/
|
|
454
|
+
readonly hostClientId?: string;
|
|
455
|
+
/** BroadcastChannel name shared with every tab in this group. Defaults to {@link ServiceWorkerHost.defaultChannelName}. */
|
|
456
|
+
readonly channelName?: string;
|
|
457
|
+
/** Forwarded to {@link EngineHost.run} for mirrors' `related` registrations. */
|
|
458
|
+
readonly registry?: Collection.CollectionRegistry;
|
|
459
|
+
/**
|
|
460
|
+
* URL-keyed reads (#159): lets the returned Client `subscribe(url)`, and
|
|
461
|
+
* — with `nodes` — lets the hosting tab answer `SubscribeUrl` from pages
|
|
462
|
+
* without a Client (forwarded to {@link EngineHost.run}).
|
|
463
|
+
*/
|
|
464
|
+
readonly urls?: Client.UrlReads;
|
|
465
|
+
readonly queries?: Query.QueryRegistry;
|
|
466
|
+
readonly syncLoop?: ClientEngine.SyncLoopOptions;
|
|
467
|
+
/** Override for tests/non-browser injection. Defaults to `navigator.locks`. */
|
|
468
|
+
readonly locks?: LockManager;
|
|
469
|
+
}
|
|
470
|
+
/** What {@link run} returns: one stable Client for the tab, and a stream of its host/mirror role. */
|
|
471
|
+
interface ElectedHost {
|
|
472
|
+
/**
|
|
473
|
+
* One stable Client for the tab's whole lifetime: reads and writes go
|
|
474
|
+
* through the mirror until this tab is elected, and through the owned
|
|
475
|
+
* engine from that point on — the switch is transparent to callers (a
|
|
476
|
+
* live `observe`/`related` re-hydrates from the new tier; explicit
|
|
477
|
+
* `subscribe` pins are replayed onto it).
|
|
478
|
+
*/
|
|
479
|
+
readonly client: Client.Client;
|
|
480
|
+
/** Purely observational — devtools/debug UI. Never drives application logic. */
|
|
481
|
+
readonly role: Stream.Stream<Role>;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Elect this tab into hosting `clientGroupId`'s engine — full main-thread
|
|
485
|
+
* host (ADR-0010) while the lock is held, a mirror over the same channel
|
|
486
|
+
* otherwise — and hand back one `Client` that works unchanged across the
|
|
487
|
+
* transition, plus a `role` stream for observability.
|
|
488
|
+
*/
|
|
489
|
+
declare const run: (options: ElectedHostOptions) => Effect.Effect<ElectedHost, never, Scope.Scope>;
|
|
490
|
+
declare namespace CacheApiHttpCache_d_exports {
|
|
491
|
+
export { CacheLike, CacheStorageLike, FetchHandlerOptions, FetchHandlerResult, fetchHandler, make, metaHeader, open };
|
|
492
|
+
}
|
|
493
|
+
/** The subset of the Cache API this store uses. */
|
|
494
|
+
interface CacheLike {
|
|
495
|
+
readonly match: (request: Request) => Promise<Response | undefined>;
|
|
496
|
+
readonly put: (request: Request, response: Response) => Promise<void>;
|
|
497
|
+
readonly delete: (request: Request) => Promise<boolean>;
|
|
498
|
+
readonly keys: () => Promise<ReadonlyArray<Request>>;
|
|
499
|
+
}
|
|
500
|
+
/** The slice of the Cache API's `CacheStorage` that {@link open} uses (`caches` in a browser). */
|
|
501
|
+
interface CacheStorageLike {
|
|
502
|
+
readonly open: (name: string) => Promise<CacheLike>;
|
|
503
|
+
}
|
|
504
|
+
/** Header carrying the serialized CachedResponseMeta on stored copies. */
|
|
505
|
+
declare const metaHeader = "x-legation-cache-meta";
|
|
506
|
+
/** ResponseCacheStore over one named Cache API cache. */
|
|
507
|
+
declare const make: (cache: CacheLike) => HttpCache.ResponseCacheStore;
|
|
508
|
+
/** Open a named cache (defaults to the global `caches`). */
|
|
509
|
+
declare const open: (name: string, cacheStorage?: CacheStorageLike) => Effect.Effect<HttpCache.ResponseCacheStore, StoreError>;
|
|
510
|
+
/** Options for {@link fetchHandler}. */
|
|
511
|
+
interface FetchHandlerOptions {
|
|
512
|
+
readonly store: HttpCache.ResponseCacheStore;
|
|
513
|
+
/** Per-request policy; `undefined` bypasses the cache entirely. */
|
|
514
|
+
readonly policy: (request: Request) => HttpCache.HttpCachePolicy | undefined;
|
|
515
|
+
/** The network. Defaults to the global `fetch`. */
|
|
516
|
+
readonly fetch?: (request: Request) => Promise<Response>;
|
|
517
|
+
}
|
|
518
|
+
/** What {@link fetchHandler} answers a request with. */
|
|
519
|
+
interface FetchHandlerResult {
|
|
520
|
+
readonly response: Response;
|
|
521
|
+
/**
|
|
522
|
+
* Present when a stale entry was served: refreshes the cache in the
|
|
523
|
+
* background. Service workers pass it to `event.waitUntil`.
|
|
524
|
+
*/
|
|
525
|
+
readonly revalidate?: Effect.Effect<void, StoreError | TransportError> | undefined;
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Build the stale-while-revalidate handler: fresh entries are served from
|
|
529
|
+
* cache, stale-but-unexpired entries are served immediately with a
|
|
530
|
+
* background revalidation, and expired or missing entries reload from the
|
|
531
|
+
* network (caching ok GET responses).
|
|
532
|
+
*/
|
|
533
|
+
declare const fetchHandler: (options: FetchHandlerOptions) => (request: Request) => Effect.Effect<FetchHandlerResult, StoreError | TransportError>;
|
|
534
|
+
declare namespace HttpCacheInvalidation_d_exports {
|
|
535
|
+
export { FINE_TAG_ROW_BUDGET, InvalidateOnChangesOptions, TagsForOptions, TagsForResult, Touched, collectionChanges, defaultFineTagFor, defaultTagFor, invalidateOnChanges, tagsFor, tagsForObserved };
|
|
536
|
+
}
|
|
537
|
+
/** The default coarse tag convention: `collection:{name}` (`HttpCache.tagFor`, shared with the edge, #206). */
|
|
538
|
+
declare const defaultTagFor: (collection: string) => string;
|
|
539
|
+
/** The default fine, per-row tag convention: `collection:{name}:{id}` (`HttpCache.fineTagFor`). */
|
|
540
|
+
declare const defaultFineTagFor: (collection: string, id: string) => string;
|
|
541
|
+
/**
|
|
542
|
+
* The fine-tag row budget: a response enumerating more rows than this is
|
|
543
|
+
* tagged coarse-only (see {@link tagsFor}) rather than carrying one fine tag
|
|
544
|
+
* per row — a document listing thousands of rows cannot carry thousands of
|
|
545
|
+
* tags on a bounded header.
|
|
546
|
+
*
|
|
547
|
+
* Derived against the real ceiling on the header this convention rides on —
|
|
548
|
+
* `x-legation-cache-meta` (`CacheApiHttpCache.metaHeader`), which carries
|
|
549
|
+
* `encodeURIComponent(JSON.stringify(meta))` and so must fit under Node's
|
|
550
|
+
* default `--max-http-header-size` of 16 KiB (16384 bytes) wherever responses are
|
|
551
|
+
* served by a Node HTTP server:
|
|
552
|
+
*
|
|
553
|
+
* - `encodeURIComponent` expands every byte outside its unreserved set to
|
|
554
|
+
* `%XX` (3 bytes), so 3x the raw byte length is a safe upper bound on
|
|
555
|
+
* encoded size regardless of which characters a collection name or row id
|
|
556
|
+
* use.
|
|
557
|
+
* - Reserve 4096 bytes (16 KiB / 4) for the rest of `CachedResponseMeta` —
|
|
558
|
+
* `url`, three numeric timestamps, the coarse tag, and JSON's own
|
|
559
|
+
* braces/keys/quotes — leaving `16384 - 4096 = 12288` bytes for fine tags.
|
|
560
|
+
* - One fine tag, worst case: a JSON array entry `"collection:{name}:{id}",`
|
|
561
|
+
* with a 40-char collection name and a 40-char row id (a UUIDv4 is 36
|
|
562
|
+
* chars) is `2 (quotes) + 11 ("collection:") + 40 + 1 (":") + 40 + 1 (",")
|
|
563
|
+
* = 95` raw bytes, i.e. at most `95 * 3 = 285` encoded bytes.
|
|
564
|
+
* - `N = floor(12288 / 285) = 43`.
|
|
565
|
+
*/
|
|
566
|
+
declare const FINE_TAG_ROW_BUDGET = 43;
|
|
567
|
+
/** The collections and individual rows one engine event touched (`HttpCache.Touched`). */
|
|
568
|
+
type Touched = HttpCache.Touched;
|
|
569
|
+
/**
|
|
570
|
+
* Derive the change feed from an engine's event stream: the distinct
|
|
571
|
+
* collections and rows each `PullCompleted` (always present) and scoped
|
|
572
|
+
* `ViewRebuilt` (when present) touched, dropping events that touched
|
|
573
|
+
* nothing. Feed the result to {@link invalidateOnChanges}.
|
|
574
|
+
*/
|
|
575
|
+
declare const collectionChanges: (events: Stream.Stream<ClientEngine.EngineEvent>) => Stream.Stream<Touched>;
|
|
576
|
+
/** Options for {@link invalidateOnChanges}. */
|
|
577
|
+
interface InvalidateOnChangesOptions {
|
|
578
|
+
readonly store: HttpCache.ResponseCacheStore;
|
|
579
|
+
/** Touched collections/rows per change (e.g. from {@link collectionChanges}). */
|
|
580
|
+
readonly changes: Stream.Stream<Touched>;
|
|
581
|
+
/** Map a collection name to its coarse cache tag; defaults to {@link defaultTagFor}. */
|
|
582
|
+
readonly tagFor?: (collection: string) => string;
|
|
583
|
+
/** Map a row to its fine cache tag; defaults to {@link defaultFineTagFor}. */
|
|
584
|
+
readonly fineTagFor?: (collection: string, id: string) => string;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Fork a scoped fibre that, per emitted change, evicts every cached response
|
|
588
|
+
* tagged for the touched collections (coarse) **and** the touched rows
|
|
589
|
+
* (fine) — `store.invalidateTags`. Invalidation cost stays proportional to
|
|
590
|
+
* the store's tag enumeration — this scans no more than `invalidateTags`
|
|
591
|
+
* already does. Runs until the scope closes; a store error is logged and
|
|
592
|
+
* swallowed, never fatal to the fibre, so a transient cache failure doesn't
|
|
593
|
+
* stop future invalidations.
|
|
594
|
+
*/
|
|
595
|
+
declare const invalidateOnChanges: (options: InvalidateOnChangesOptions) => Effect.Effect<void, never, Scope.Scope>;
|
|
596
|
+
/** Options for {@link tagsFor}. */
|
|
597
|
+
interface TagsForOptions {
|
|
598
|
+
/** Map a collection name to its coarse cache tag; defaults to {@link defaultTagFor}. */
|
|
599
|
+
readonly tagFor?: (collection: string) => string;
|
|
600
|
+
/** Map a row to its fine cache tag; defaults to {@link defaultFineTagFor}. */
|
|
601
|
+
readonly fineTagFor?: (collection: string, id: string) => string;
|
|
602
|
+
}
|
|
603
|
+
/** What {@link tagsFor} returns. */
|
|
604
|
+
interface TagsForResult {
|
|
605
|
+
/** The tags to attach to the response (`HttpCachePolicy.tags`). */
|
|
606
|
+
readonly tags: ReadonlyArray<string>;
|
|
607
|
+
/**
|
|
608
|
+
* `true` when `rowIds.length` exceeded {@link FINE_TAG_ROW_BUDGET}, so
|
|
609
|
+
* `tags` carries the coarse tag only rather than one fine tag per row.
|
|
610
|
+
*/
|
|
611
|
+
readonly budgetExceeded: boolean;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Build the cache tags for a response that enumerates `rowIds` of
|
|
615
|
+
* `collection`: the coarse tag, plus one fine tag per row when the row count
|
|
616
|
+
* is within {@link FINE_TAG_ROW_BUDGET} — beyond it, the coarse tag alone
|
|
617
|
+
* (`budgetExceeded: true`), since a fine tag per row would blow the header
|
|
618
|
+
* budget the tag rides on. Pure and synchronous, for use building
|
|
619
|
+
* `HttpCachePolicy.tags`; pair with {@link tagsForObserved} to also record
|
|
620
|
+
* the fallback when it happens.
|
|
621
|
+
*/
|
|
622
|
+
declare const tagsFor: (collection: string, rowIds: ReadonlyArray<string>, options?: TagsForOptions) => TagsForResult;
|
|
623
|
+
/**
|
|
624
|
+
* {@link tagsFor}, plus an observable fallback: a response that exceeded the
|
|
625
|
+
* budget is never silently coarse — it logs a warning and increments
|
|
626
|
+
* `legation_cache_tag_budget_exceeded_total` (`Telemetry.cacheTagBudgetExceededTotal`),
|
|
627
|
+
* following the same "safely degraded, but counted" idiom as the engine's
|
|
628
|
+
* other bounded-hub fallbacks (`legation_hints_dropped_total`,
|
|
629
|
+
* `legation_change_collapses_total`).
|
|
630
|
+
*/
|
|
631
|
+
declare const tagsForObserved: (collection: string, rowIds: ReadonlyArray<string>, options?: TagsForOptions) => Effect.Effect<TagsForResult>;
|
|
632
|
+
declare namespace BrowserConnectivity_d_exports {
|
|
633
|
+
export { BrowserConnectivityOptions, withBrowserConnectivity };
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Options for {@link withBrowserConnectivity}.
|
|
637
|
+
*/
|
|
638
|
+
interface BrowserConnectivityOptions {
|
|
639
|
+
/**
|
|
640
|
+
* How often a runtime with no `window` (a service worker) reads
|
|
641
|
+
* `navigator.onLine` to detect a flip, since it has no `online`/`offline`
|
|
642
|
+
* events. Defaults to `"5 seconds"`. The interval bounds how long a
|
|
643
|
+
* reconnect takes to reach the sync loop. Ignored where `window` exists.
|
|
644
|
+
*/
|
|
645
|
+
readonly pollInterval?: Duration.Input | undefined;
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Wrap a `ClientTransport` so its connection status combines
|
|
649
|
+
* `navigator.onLine` with the transport's own signal, and a return-to-visible
|
|
650
|
+
* surfaces a poke. Where there is no `window` (a service worker), the
|
|
651
|
+
* navigator signal is probed every `pollInterval` instead of listened for.
|
|
652
|
+
* Scoped: forked fibres and DOM listeners are torn down when the scope closes.
|
|
653
|
+
*/
|
|
654
|
+
declare const withBrowserConnectivity: (transport: Transport.ClientTransport, options?: BrowserConnectivityOptions) => Effect.Effect<Transport.ClientTransport, never, Scope.Scope>;
|
|
655
|
+
export { BackgroundSync_d_exports as BackgroundSync, BroadcastMirrorChannel_d_exports as BroadcastMirrorChannel, BrowserConnectivity_d_exports as BrowserConnectivity, CacheApiHttpCache_d_exports as CacheApiHttpCache, ElectedHost_d_exports as ElectedHost, EngineHost, HttpCacheInvalidation_d_exports as HttpCacheInvalidation, IndexedDbClientStore_d_exports as IndexedDbClientStore, Mirror, MirrorChannel, MirrorProtocol, PushPoke_d_exports as PushPoke, ServiceWorkerHost_d_exports as ServiceWorkerHost };
|
|
656
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":["make","MirrorChannel","ListenOptions","Listener","listen","BackgroundSync.SyncRegistration","BackgroundSync.RegisterOutcome","BackgroundSync.IdentitySource","BackgroundSync.Listener","PushPoke.Listener","run"],"sources":["../src/IndexedDbClientStore.ts","../src/BroadcastMirrorChannel.ts","../src/BackgroundSync.ts","../src/PushPoke.ts","../src/ServiceWorkerHost.ts","../src/ElectedHost.ts","../src/CacheApiHttpCache.ts","../src/HttpCacheInvalidation.ts","../src/BrowserConnectivity.ts"],"mappings":"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"}
|