@drakkar.software/starfish-client 3.0.0-alpha.5 → 3.0.0-alpha.50
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/README.md +59 -0
- package/dist/append-log.d.ts +228 -0
- package/dist/append-log.js +267 -0
- package/dist/background-sync.js +29 -0
- package/dist/bindings/legend.d.ts +23 -0
- package/dist/bindings/legend.js +32 -0
- package/dist/bindings/legend.js.map +2 -2
- package/dist/bindings/suspense.js +49 -0
- package/dist/bindings/zustand.d.ts +167 -2
- package/dist/bindings/zustand.js +941 -82
- package/dist/bindings/zustand.js.map +4 -4
- package/dist/blob-seal.d.ts +123 -0
- package/dist/client.d.ts +270 -5
- package/dist/client.js +391 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.js +18 -0
- package/dist/debounced-sync.js +120 -0
- package/dist/dedup.js +35 -0
- package/dist/events.d.ts +150 -0
- package/dist/events.js +116 -0
- package/dist/events.js.map +7 -0
- package/dist/export.js +115 -0
- package/dist/fetch.d.ts +40 -0
- package/dist/fetch.js +51 -14
- package/dist/fetch.js.map +2 -2
- package/dist/history.js +61 -0
- package/dist/index.d.ts +16 -7
- package/dist/index.js +1029 -94
- package/dist/index.js.map +4 -4
- package/dist/kv-cache.d.ts +63 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.js +80 -0
- package/dist/migrate.js +38 -0
- package/dist/mobile-lifecycle.d.ts +28 -1
- package/dist/mobile-lifecycle.js +94 -0
- package/dist/multi-store.js +92 -0
- package/dist/mutate.d.ts +39 -0
- package/dist/polling.js +52 -0
- package/dist/resolvers.js +223 -0
- package/dist/service-worker.js +55 -0
- package/dist/storage/indexeddb.js +59 -0
- package/dist/sync.d.ts +83 -0
- package/dist/sync.js +181 -0
- package/dist/types.d.ts +106 -11
- package/dist/types.js +18 -0
- package/dist/validate.js +28 -0
- package/package.json +12 -3
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic client-side blob sealing helpers.
|
|
3
|
+
*
|
|
4
|
+
* Provides a structural {@link ByteSealer} interface and two convenience
|
|
5
|
+
* functions — {@link sealAndPushBlob} / {@link pullAndOpenBlob} — that wire a
|
|
6
|
+
* sealer (e.g. a `KeyringEncryptor` from `@drakkar.software/starfish-keyring`)
|
|
7
|
+
* into the binary blob transport (`StarfishClient.pushBlob` / `pullBlob`).
|
|
8
|
+
*
|
|
9
|
+
* The server collection must use `encryption: "none"` — all sealing and
|
|
10
|
+
* unsealing happens client-side. The server stores and returns opaque
|
|
11
|
+
* ciphertext.
|
|
12
|
+
*
|
|
13
|
+
* **AAD (Additional Authenticated Data):** callers MUST pass a stable, unique
|
|
14
|
+
* `aad` string (typically the storage path, e.g. the value returned by a
|
|
15
|
+
* `*Name(spaceId, blobId)` helper). The AAD is bound into the AES-GCM tag
|
|
16
|
+
* and prevents ciphertext relocation: a blob sealed with AAD `"path/a"` cannot
|
|
17
|
+
* be opened as `"path/b"`. When `aad` is omitted, the document key (path with
|
|
18
|
+
* the `/push/` or `/pull/` action prefix stripped) is used as a fallback —
|
|
19
|
+
* the same key is derived on both the seal and open side so round-trips work
|
|
20
|
+
* without an explicit `aad`. **Callers with existing sealed data MUST pass the
|
|
21
|
+
* same explicit AAD they used when sealing** to preserve back-compat.
|
|
22
|
+
*
|
|
23
|
+
* @module blob-seal
|
|
24
|
+
*/
|
|
25
|
+
import type { StarfishClient } from "./client.js";
|
|
26
|
+
import type { BlobPushResult } from "./client.js";
|
|
27
|
+
/**
|
|
28
|
+
* Structural interface satisfied by a `KeyringEncryptor` from
|
|
29
|
+
* `@drakkar.software/starfish-keyring` (and any compatible cipher adapter).
|
|
30
|
+
*
|
|
31
|
+
* Implementations must use AES-256-GCM and bind the `aad` into the ciphertext
|
|
32
|
+
* tag so that relocation is detected on open.
|
|
33
|
+
*/
|
|
34
|
+
export interface ByteSealer {
|
|
35
|
+
/**
|
|
36
|
+
* Encrypt `bytes` and return the sealed ciphertext.
|
|
37
|
+
* @param bytes - Plaintext bytes to seal.
|
|
38
|
+
* @param aad - Additional authenticated data bound into the ciphertext tag.
|
|
39
|
+
* Must be the same string passed to {@link openBytes}.
|
|
40
|
+
*/
|
|
41
|
+
sealBytes(bytes: Uint8Array, aad?: string): Promise<Uint8Array>;
|
|
42
|
+
/**
|
|
43
|
+
* Decrypt a sealed blob back to plaintext bytes.
|
|
44
|
+
* @param blob - Ciphertext produced by {@link sealBytes}.
|
|
45
|
+
* @param aad - Same AAD string that was used during sealing.
|
|
46
|
+
* @throws if the ciphertext has been tampered with or the AAD does not match.
|
|
47
|
+
*/
|
|
48
|
+
openBytes(blob: Uint8Array, aad?: string): Promise<Uint8Array>;
|
|
49
|
+
}
|
|
50
|
+
/** Options for {@link sealAndPushBlob}. */
|
|
51
|
+
export interface SealAndPushBlobOptions {
|
|
52
|
+
/**
|
|
53
|
+
* Additional authenticated data bound into the AES-GCM ciphertext tag.
|
|
54
|
+
*
|
|
55
|
+
* Callers with existing sealed data MUST pass the **same explicit AAD** they
|
|
56
|
+
* used when originally sealing so that already-stored blobs can still be
|
|
57
|
+
* opened. When omitted, the document key (push path with the `/push/`
|
|
58
|
+
* prefix stripped) is used as a fallback.
|
|
59
|
+
*/
|
|
60
|
+
aad?: string;
|
|
61
|
+
/**
|
|
62
|
+
* When set, throws a `RangeError` before sealing if `bytes.length` exceeds
|
|
63
|
+
* this limit. Checked against the **plaintext** size (before sealing adds
|
|
64
|
+
* overhead).
|
|
65
|
+
*
|
|
66
|
+
* Note: AES-256-GCM sealing adds ~28 bytes of overhead (12-byte nonce +
|
|
67
|
+
* 16-byte tag). If you are mirroring the server's `maxBodyBytes` limit,
|
|
68
|
+
* subtract at least 28 to ensure the sealed ciphertext also fits.
|
|
69
|
+
*/
|
|
70
|
+
maxBytes?: number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Seal `bytes` with `sealer` (AAD bound to the storage path) and push the
|
|
74
|
+
* resulting ciphertext to the server via `client.pushBlob`.
|
|
75
|
+
*
|
|
76
|
+
* Sealed bytes are always pushed with `Content-Type: application/octet-stream`,
|
|
77
|
+
* matching the `allowedMimeTypes` of a {@link createSealedParquetCollection} preset.
|
|
78
|
+
*
|
|
79
|
+
* @param client - A connected `StarfishClient`.
|
|
80
|
+
* @param sealer - A `ByteSealer` (e.g. `KeyringEncryptor` from `starfish-keyring`).
|
|
81
|
+
* @param path - Push path (starts with `/push/…`).
|
|
82
|
+
* @param bytes - Plaintext bytes to seal and upload.
|
|
83
|
+
* @param opts - See {@link SealAndPushBlobOptions}.
|
|
84
|
+
* @returns The server's push result (hash).
|
|
85
|
+
*
|
|
86
|
+
* @throws {RangeError} if `opts.maxBytes` is set and `bytes.length` exceeds it.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* const result = await sealAndPushBlob(client, enc, objectBlobPush(spaceId, blobId), bytes, {
|
|
91
|
+
* aad: objectBlobName(spaceId, blobId),
|
|
92
|
+
* })
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export declare function sealAndPushBlob(client: StarfishClient, sealer: ByteSealer, path: string, bytes: Uint8Array, opts?: SealAndPushBlobOptions): Promise<BlobPushResult>;
|
|
96
|
+
/** Options for {@link pullAndOpenBlob}. */
|
|
97
|
+
export interface PullAndOpenBlobOptions {
|
|
98
|
+
/**
|
|
99
|
+
* Additional authenticated data — must match the AAD used when sealing.
|
|
100
|
+
* Defaults to the document key (pull path with the `/pull/` prefix stripped),
|
|
101
|
+
* which matches the default AAD used by {@link sealAndPushBlob}.
|
|
102
|
+
*/
|
|
103
|
+
aad?: string;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Pull a sealed blob from the server and unseal it with `sealer`.
|
|
107
|
+
*
|
|
108
|
+
* @param client - A connected `StarfishClient`.
|
|
109
|
+
* @param sealer - A `ByteSealer` that can open the sealed bytes.
|
|
110
|
+
* @param path - Pull path (starts with `/pull/…` or is a bare document key).
|
|
111
|
+
* @param opts - See {@link PullAndOpenBlobOptions}.
|
|
112
|
+
* @returns The original plaintext bytes.
|
|
113
|
+
*
|
|
114
|
+
* @throws if the ciphertext is invalid, tampered with, or the AAD does not match.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```ts
|
|
118
|
+
* const bytes = await pullAndOpenBlob(client, enc, objectBlobPull(spaceId, blobId), {
|
|
119
|
+
* aad: objectBlobName(spaceId, blobId),
|
|
120
|
+
* })
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
export declare function pullAndOpenBlob(client: StarfishClient, sealer: ByteSealer, path: string, opts?: PullAndOpenBlobOptions): Promise<Uint8Array>;
|
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import type { PullResult, PushSuccess } from "@drakkar.software/starfish-protocol";
|
|
2
|
+
import { type AppendAuthor } from "@drakkar.software/starfish-protocol";
|
|
2
3
|
import type { StarfishClientOptions } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Whether a {@link PullResult} was served from the offline read-through cache
|
|
6
|
+
* (the transport was unreachable) rather than a live server response. Used by
|
|
7
|
+
* {@link SyncManager} to surface a `stale` flag to the UI without treating a
|
|
8
|
+
* cache hit as proof the server is reachable.
|
|
9
|
+
*/
|
|
10
|
+
export declare function pullWasFromCache(result: PullResult): boolean;
|
|
11
|
+
/** The storage `documentKey` for a push `path`: the path with the `/push/`
|
|
12
|
+
* action prefix stripped (the namespace lives only in the URL). The author
|
|
13
|
+
* signature binds to this key. */
|
|
14
|
+
export declare function stripPushPrefix(path: string): string;
|
|
3
15
|
/** Result of pulling a binary blob from the server. */
|
|
4
16
|
export interface BlobPullResult {
|
|
5
17
|
data: ArrayBuffer;
|
|
@@ -19,6 +31,13 @@ export interface AppendPullOptions {
|
|
|
19
31
|
since?: number;
|
|
20
32
|
/** Return only the last K items (applied after `since` filter). Sent as `?last=`. */
|
|
21
33
|
last?: number;
|
|
34
|
+
/** Return only the last K items. Alias of `last`; sent as `?limit=`. When both
|
|
35
|
+
* are given, `limit` wins. */
|
|
36
|
+
limit?: number;
|
|
37
|
+
/** Explicitly fetch the whole collection (sent as `?full=true`). Mutually
|
|
38
|
+
* exclusive with `since`/`limit`/`last` — the server requires a pull to declare
|
|
39
|
+
* exactly one of {checkpoint, limit/last, full}. */
|
|
40
|
+
full?: boolean;
|
|
22
41
|
}
|
|
23
42
|
/**
|
|
24
43
|
* Options for a structured (non-append) pull.
|
|
@@ -35,6 +54,66 @@ export interface PullOptions {
|
|
|
35
54
|
checkpoint?: number;
|
|
36
55
|
/** Include the sibling `_keyring` document in the response. Defaults to false. */
|
|
37
56
|
withKeyring?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Serve the last-synced cached snapshot immediately (tagged via
|
|
59
|
+
* {@link pullWasFromCache}) and revalidate in the background. Requires a
|
|
60
|
+
* {@link StarfishClientOptions.cache} to be configured; without one the option
|
|
61
|
+
* is a no-op and the pull goes to the network as usual.
|
|
62
|
+
*
|
|
63
|
+
* On a cache hit: returns the stale snapshot at once, kicks a background fetch,
|
|
64
|
+
* and on success writes the fresh snapshot to cache and fires
|
|
65
|
+
* {@link StarfishClientOptions.onRevalidated}. Uses the same dedup set as the
|
|
66
|
+
* {@link StarfishClientOptions.cacheFallbackStatuses} revalidation path — a
|
|
67
|
+
* concurrent error-triggered loop for the same document is not duplicated.
|
|
68
|
+
*
|
|
69
|
+
* On a cache miss: falls through to the normal network-first pull unchanged.
|
|
70
|
+
*/
|
|
71
|
+
staleWhileRevalidate?: boolean;
|
|
72
|
+
}
|
|
73
|
+
/** Per-collection result in a {@link BatchPullResult}: either the pulled
|
|
74
|
+
* document (`data`/`hash`/`timestamp`) or a per-collection `error` string. */
|
|
75
|
+
export interface BatchPullEntry {
|
|
76
|
+
data?: unknown;
|
|
77
|
+
hash?: string;
|
|
78
|
+
timestamp?: number;
|
|
79
|
+
error?: string;
|
|
80
|
+
}
|
|
81
|
+
/** Response of {@link StarfishClient.batchPull}: a map of requested collection
|
|
82
|
+
* name → an ARRAY of {@link BatchPullEntry}, one per requested param-set, in
|
|
83
|
+
* request order. A collection read with no params yields a one-element array. */
|
|
84
|
+
export interface BatchPullResult {
|
|
85
|
+
collections: Record<string, BatchPullEntry[]>;
|
|
86
|
+
}
|
|
87
|
+
/** Options for {@link StarfishClient.batchPull}. */
|
|
88
|
+
export interface BatchPullOptions {
|
|
89
|
+
/** Per-collection path params: collection name → an ARRAY of param-sets, one
|
|
90
|
+
* per document to read from that collection, e.g.
|
|
91
|
+
* `{ profile: [{ identity: "a" }, { identity: "b" }] }` reads two profiles in
|
|
92
|
+
* one round-trip. Serialized to a URL-encoded JSON `params` query. The
|
|
93
|
+
* `{identity}` param is auto-filled by the server from the authenticated
|
|
94
|
+
* caller when a set omits it, so a single self-doc read can pass `[{}]` — or
|
|
95
|
+
* omit the collection from `params` entirely (an unlisted collection reads one
|
|
96
|
+
* auto-filled doc). Results come back under the same name in request order. */
|
|
97
|
+
params?: Record<string, Record<string, string>[]>;
|
|
98
|
+
/**
|
|
99
|
+
* Per-collection append options, index-aligned to `params`. Makes the batch
|
|
100
|
+
* request **append/checkpoint-aware**: each entry returns the bounded tail of
|
|
101
|
+
* that collection's append-only log rather than the full document.
|
|
102
|
+
*
|
|
103
|
+
* Serialized as URL-encoded JSON alongside `params`. Server ignores it for
|
|
104
|
+
* collections that are not append-only (returns `{ error: "append_params_not_supported" }`
|
|
105
|
+
* for those entries). `full` is disallowed in batch (`full_not_allowed` per entry).
|
|
106
|
+
*
|
|
107
|
+
* Example — read the last 5 events for two rooms and the newest item for a third:
|
|
108
|
+
* ```ts
|
|
109
|
+
* await client.batchPull(["events"], {
|
|
110
|
+
* params: { events: [{ room: "a" }, { room: "b" }, { room: "c" }] },
|
|
111
|
+
* appendParams: { events: [{ last: 5 }, { last: 5 }, { last: 1 }] },
|
|
112
|
+
* })
|
|
113
|
+
* ```
|
|
114
|
+
* Each `data[appendField]` in the result is the filtered array for that entry.
|
|
115
|
+
*/
|
|
116
|
+
appendParams?: Record<string, AppendPullOptions[]>;
|
|
38
117
|
}
|
|
39
118
|
/**
|
|
40
119
|
* Low-level HTTP client for the Starfish sync protocol.
|
|
@@ -45,12 +124,29 @@ export declare class StarfishClient {
|
|
|
45
124
|
private readonly namespace?;
|
|
46
125
|
private readonly capProvider?;
|
|
47
126
|
private readonly fetch;
|
|
127
|
+
private readonly cache?;
|
|
128
|
+
private readonly cacheMaxAgeMs?;
|
|
129
|
+
private readonly cacheFallbackStatuses?;
|
|
130
|
+
private readonly onRevalidated?;
|
|
131
|
+
private readonly revalidating;
|
|
132
|
+
/**
|
|
133
|
+
* In-memory mirror of the latest document timestamp written to each cache
|
|
134
|
+
* key via {@link writeCache}. Updated synchronously so {@link revalidateLoop}
|
|
135
|
+
* can guard against stale overwrites without an extra async cache read.
|
|
136
|
+
*/
|
|
137
|
+
private readonly latestCacheTimestamp;
|
|
48
138
|
/**
|
|
49
139
|
* Installed client-side plugins. Currently stored as inert data; no
|
|
50
140
|
* hooks fire yet. Extensions can inspect this list if needed.
|
|
51
141
|
*/
|
|
52
142
|
readonly plugins: ReadonlyArray<import("./types.js").ClientPlugin>;
|
|
53
143
|
constructor(options: StarfishClientOptions);
|
|
144
|
+
/**
|
|
145
|
+
* Mark a `PullResult` as having been served from the offline read-through
|
|
146
|
+
* cache (transport was unreachable). Non-enumerable so it doesn't leak into
|
|
147
|
+
* JSON / equality / re-caching; read via {@link pullWasFromCache}.
|
|
148
|
+
*/
|
|
149
|
+
private tagFromCache;
|
|
54
150
|
/**
|
|
55
151
|
* Resolve the host portion of the URL the client will send to. The host
|
|
56
152
|
* is folded into the signed canonical input as the `h` field so the
|
|
@@ -87,23 +183,131 @@ export declare class StarfishClient {
|
|
|
87
183
|
* The host bound into the signature is derived from `baseUrl` once per call.
|
|
88
184
|
*/
|
|
89
185
|
private buildAuthHeaders;
|
|
186
|
+
/**
|
|
187
|
+
* Build the request-signing headers from an ALREADY-fetched cap context. Split
|
|
188
|
+
* out of {@link buildAuthHeaders} so {@link append} can fetch the cap once and
|
|
189
|
+
* reuse it for BOTH the author signature (over the element data) and the
|
|
190
|
+
* request signature (over the body), without redeeming the cap twice — a
|
|
191
|
+
* second `getCap()` could rotate keys and break the `authorPubkey ===
|
|
192
|
+
* presenter` bind the server checks.
|
|
193
|
+
*/
|
|
194
|
+
private capRequestHeaders;
|
|
195
|
+
/**
|
|
196
|
+
* Resolve the author public key to attach to a signed append: the redeemer's
|
|
197
|
+
* `pubHex` for an audience cap, else the cert subject `cap.sub` for a
|
|
198
|
+
* device/member cap. This is the SAME key that signs the request, so a server
|
|
199
|
+
* enforcing author proof can bind the stored element to its writer. Returns
|
|
200
|
+
* undefined only for a (malformed) cap with neither — the append then goes
|
|
201
|
+
* unsigned and a server requiring signatures rejects it.
|
|
202
|
+
*/
|
|
203
|
+
private appendAuthorKey;
|
|
90
204
|
/** Pull synced data from the server. Returns the raw `PullResult`. */
|
|
91
205
|
pull(path: string, checkpoint?: number): Promise<PullResult>;
|
|
92
206
|
/** Pull synced data with structured options (e.g. `{withKeyring: true}`). */
|
|
93
207
|
pull(path: string, options: PullOptions): Promise<PullResult>;
|
|
94
208
|
/** Pull an append-only collection. Extracts and returns `data[appendField]` as `T[]`. */
|
|
95
209
|
pull<T = unknown>(path: string, options: AppendPullOptions): Promise<T[]>;
|
|
210
|
+
/**
|
|
211
|
+
* Write a pull snapshot to the cache. Fire-and-forget; errors are swallowed
|
|
212
|
+
* so a failing cache never blocks the caller. No-op when no cache is configured.
|
|
213
|
+
*/
|
|
214
|
+
private writeCache;
|
|
215
|
+
/** Build the URL + auth headers for one revalidation GET. Shared between
|
|
216
|
+
* {@link pull} and {@link revalidateLoop} to avoid duplicated fetch setup. */
|
|
217
|
+
private revalidateFetch;
|
|
218
|
+
/**
|
|
219
|
+
* Deduplicated fire-and-forget: starts one revalidation loop per cacheKey.
|
|
220
|
+
* Used by both the {@link cacheFallbackStatuses} error path (delayed first
|
|
221
|
+
* attempt, honoring `Retry-After`) and the {@link PullOptions.staleWhileRevalidate}
|
|
222
|
+
* read path (`immediate: true` — no initial delay on the first attempt). The
|
|
223
|
+
* `revalidating` set deduplicates across both triggers so a concurrent
|
|
224
|
+
* error-triggered loop and an SWR-on-read loop for the same key collapse to one.
|
|
225
|
+
*/
|
|
226
|
+
private scheduleRevalidate;
|
|
227
|
+
/**
|
|
228
|
+
* Background revalidation loop shared by both {@link cacheFallbackStatuses}
|
|
229
|
+
* hits and {@link PullOptions.staleWhileRevalidate} reads.
|
|
230
|
+
*
|
|
231
|
+
* Retries (honoring `Retry-After`) up to {@link MAX_REVALIDATE_ATTEMPTS} times.
|
|
232
|
+
* When `immediate` is true the first attempt fires without any initial delay
|
|
233
|
+
* (SWR-on-read path). On a live 2xx the fresh snapshot is written to cache and
|
|
234
|
+
* {@link onRevalidated} fires. Stops early on a non-fallback status (403/404).
|
|
235
|
+
*/
|
|
236
|
+
private revalidateLoop;
|
|
237
|
+
/**
|
|
238
|
+
* Read the cached snapshot for a document `path` WITHOUT hitting the network —
|
|
239
|
+
* the basis for cache-first paint (seed the UI from the last-synced snapshot,
|
|
240
|
+
* then revalidate with a live {@link pull}). Returns the tagged `PullResult`,
|
|
241
|
+
* or null when no cache is configured / there's no entry. Namespacing matches
|
|
242
|
+
* {@link pull}, so the key lines up with whatever `pull` wrote.
|
|
243
|
+
*/
|
|
244
|
+
peekCache(path: string): Promise<PullResult | null>;
|
|
245
|
+
/** Read + parse a cached pull snapshot, tagged {@link tagFromCache}. Returns
|
|
246
|
+
* null on a miss or an unparseable blob (never throws — a corrupt cache entry
|
|
247
|
+
* must not break a pull, just miss). */
|
|
248
|
+
private readCache;
|
|
249
|
+
/**
|
|
250
|
+
* Pull several documents in one round-trip via `/batch/pull`. `collections` is
|
|
251
|
+
* the list of distinct collection names; `opts.params` supplies, per collection,
|
|
252
|
+
* an ARRAY of path-param sets — one per document to read — so the SAME collection
|
|
253
|
+
* can fan in many documents (e.g. many users' `profile`) in a single request.
|
|
254
|
+
* The server auto-fills the `{identity}` param from the authenticated caller for
|
|
255
|
+
* any set that omits it, so a self-doc collection needs no params. Returns a map
|
|
256
|
+
* of collection name → an ARRAY of pulled documents (or per-document `{ error }`),
|
|
257
|
+
* in request order. Honors the configured namespace.
|
|
258
|
+
*
|
|
259
|
+
* For the common "many docs of one collection" case prefer {@link batchPullMany}.
|
|
260
|
+
*
|
|
261
|
+
* Pass `appendParams` per entry for append-only bounded-tail reads (see {@link batchPullManyAppend}).
|
|
262
|
+
*/
|
|
263
|
+
batchPull(collections: string[], opts?: BatchPullOptions): Promise<BatchPullResult>;
|
|
264
|
+
/**
|
|
265
|
+
* Convenience over {@link batchPull} for reading MANY documents of ONE
|
|
266
|
+
* collection in a single round-trip: pass the per-document param-sets and get
|
|
267
|
+
* back the {@link BatchPullEntry} array aligned to `paramsList` by index (each
|
|
268
|
+
* entry is `{ data, hash, timestamp }` or `{ error }`). An empty `paramsList`
|
|
269
|
+
* issues no request and returns `[]`.
|
|
270
|
+
*/
|
|
271
|
+
batchPullMany(collection: string, paramsList: Record<string, string>[]): Promise<BatchPullEntry[]>;
|
|
272
|
+
/**
|
|
273
|
+
* Convenience over {@link batchPull} for reading append-only bounded tails from
|
|
274
|
+
* MANY entries of ONE collection in a single round-trip.
|
|
275
|
+
*
|
|
276
|
+
* Each request in `requests` carries optional `params` (path params) and
|
|
277
|
+
* `options` (append bounds: `since`/`last`/`limit`/`appendField`). An empty
|
|
278
|
+
* `requests` issues no request and returns `[]`.
|
|
279
|
+
*
|
|
280
|
+
* Returns an array aligned to `requests` by index. Each element is either:
|
|
281
|
+
* - the filtered array `T[]` extracted from `entry.data[appendField]`, or
|
|
282
|
+
* - `{ error: string }` if the server returned a per-entry error.
|
|
283
|
+
*
|
|
284
|
+
* The `appendField` used for extraction defaults to `"items"` and can be
|
|
285
|
+
* overridden per request via `options.appendField`.
|
|
286
|
+
*
|
|
287
|
+
* The `appendField` option is client-side only (used for result extraction, not sent to the server).
|
|
288
|
+
* It must match the collection's server-configured append field and defaults to `"items"`.
|
|
289
|
+
*
|
|
290
|
+
* Note: `full: true` is not supported in batch and is rejected client-side
|
|
291
|
+
* before the request is sent.
|
|
292
|
+
*/
|
|
293
|
+
batchPullManyAppend<T = unknown>(collection: string, requests: {
|
|
294
|
+
params?: Record<string, string>;
|
|
295
|
+
options: AppendPullOptions;
|
|
296
|
+
}[]): Promise<(T[] | {
|
|
297
|
+
error: string;
|
|
298
|
+
})[]>;
|
|
96
299
|
/**
|
|
97
300
|
* Push synced data to the server.
|
|
98
301
|
* @param path - The push endpoint path (e.g. "/push/users/abc/settings")
|
|
99
302
|
* @param data - The full document data to push
|
|
100
303
|
* @param baseHash - Hash of the document this push is based on (null for first push)
|
|
101
304
|
*
|
|
102
|
-
* v3 author
|
|
103
|
-
*
|
|
305
|
+
* v3 author proof (`authorPubkey` + `authorSignature`) is passed via `author`
|
|
306
|
+
* (produced by `SyncManager` when a `signer` is configured) and sent as
|
|
307
|
+
* top-level body siblings of `data`, where the server verifies it.
|
|
104
308
|
* @throws {ConflictError} if the server detects a hash mismatch (409)
|
|
105
309
|
*/
|
|
106
|
-
push(path: string, data: Record<string, unknown>, baseHash: string | null): Promise<PushSuccess>;
|
|
310
|
+
push(path: string, data: Record<string, unknown>, baseHash: string | null, author?: AppendAuthor): Promise<PushSuccess>;
|
|
107
311
|
/**
|
|
108
312
|
* Append an element to an appendOnly (`by_timestamp`) collection.
|
|
109
313
|
*
|
|
@@ -118,12 +322,44 @@ export declare class StarfishClient {
|
|
|
118
322
|
* @param opts.ts - optional client-supplied element timestamp (ms). Must be a
|
|
119
323
|
* non-negative integer strictly greater than the latest stored element's ts
|
|
120
324
|
* (else the server responds 409). Omit to let the server assign one.
|
|
121
|
-
* @throws {StarfishHttpError} on a non-2xx response
|
|
122
|
-
* non-monotonic timestamp
|
|
325
|
+
* @throws {StarfishHttpError} on a non-2xx response — e.g. 409
|
|
326
|
+
* `{ error: "non_monotonic_timestamp" }` for a non-monotonic timestamp, or
|
|
327
|
+
* `{ error: "append_limit_exceeded", limit }` if the collection's `maxItems`
|
|
328
|
+
* cap is reached (partition by a path parameter for higher volume).
|
|
123
329
|
*/
|
|
124
330
|
append(path: string, data: Record<string, unknown>, opts?: {
|
|
125
331
|
ts?: number;
|
|
126
332
|
}): Promise<PushSuccess>;
|
|
333
|
+
/**
|
|
334
|
+
* Append one element to a **public-write** append-only collection with an
|
|
335
|
+
* Ed25519 author proof but **no cap `Authorization` header**.
|
|
336
|
+
*
|
|
337
|
+
* Unlike {@link append}, which always attaches a cap-signed `Authorization`
|
|
338
|
+
* header from the configured `capProvider`, this method signs only the
|
|
339
|
+
* append-author proof (binding the element to the writer's Ed25519 key) and
|
|
340
|
+
* sends the request without authentication headers. This is required for
|
|
341
|
+
* collections with `writeRoles: ["public"]` — the server's cap-scope check
|
|
342
|
+
* would reject a request carrying a cap whose scope does not cover the path.
|
|
343
|
+
*
|
|
344
|
+
* Typical use-case: writing a sealed invitation to another user's
|
|
345
|
+
* public-write inbox collection without needing a cap scoped to the
|
|
346
|
+
* recipient's namespace. The author proof is optional on the server side
|
|
347
|
+
* (`requireAuthorSignature: false` for a public inbox), but signing anyway
|
|
348
|
+
* binds the stored element to the sender's Ed25519 key for verification in
|
|
349
|
+
* the receive path.
|
|
350
|
+
*
|
|
351
|
+
* The element is sent as `{ data, authorPubkey, authorSignature }`.
|
|
352
|
+
*
|
|
353
|
+
* @param path The push path, e.g. `/push/inbox/{userId}/{shard}`.
|
|
354
|
+
* @param element The JSON element to append.
|
|
355
|
+
* @param signer The sender's Ed25519 keypair (signs the author proof).
|
|
356
|
+
*
|
|
357
|
+
* @throws {AppendHttpError} on a non-2xx response.
|
|
358
|
+
*/
|
|
359
|
+
appendAnonymous(path: string, element: Record<string, unknown>, signer: {
|
|
360
|
+
edPubHex: string;
|
|
361
|
+
edPrivHex: string;
|
|
362
|
+
}): Promise<void>;
|
|
127
363
|
/**
|
|
128
364
|
* Pull binary data from a blob collection.
|
|
129
365
|
* Returns raw bytes with the content hash from the ETag header.
|
|
@@ -134,4 +370,33 @@ export declare class StarfishClient {
|
|
|
134
370
|
* Binary collections use last-write-wins (no conflict detection).
|
|
135
371
|
*/
|
|
136
372
|
pushBlob(path: string, data: ArrayBuffer | Uint8Array | Blob, contentType: string): Promise<BlobPushResult>;
|
|
373
|
+
/**
|
|
374
|
+
* Push an Apache Parquet file to a Parquet collection.
|
|
375
|
+
*
|
|
376
|
+
* Thin wrapper over {@link pushBlob} that fixes `Content-Type` to
|
|
377
|
+
* `application/vnd.apache.parquet` so the S3 object is tagged correctly
|
|
378
|
+
* for DuckDB and CDN consumption.
|
|
379
|
+
*
|
|
380
|
+
* @example
|
|
381
|
+
* ```ts
|
|
382
|
+
* const parquetBytes = await generateParquet(rows)
|
|
383
|
+
* const result = await client.pushParquet("/push/analytics/alice/q1.parquet", parquetBytes)
|
|
384
|
+
* console.log("stored hash:", result.hash)
|
|
385
|
+
* ```
|
|
386
|
+
*/
|
|
387
|
+
pushParquet(path: string, data: ArrayBuffer | Uint8Array | Blob): Promise<BlobPushResult>;
|
|
388
|
+
/**
|
|
389
|
+
* Pull an Apache Parquet file from a Parquet collection.
|
|
390
|
+
*
|
|
391
|
+
* Thin wrapper over {@link pullBlob} for API symmetry with
|
|
392
|
+
* {@link pushParquet}.
|
|
393
|
+
*
|
|
394
|
+
* @example
|
|
395
|
+
* ```ts
|
|
396
|
+
* const result = await client.pullParquet("/pull/analytics/alice/q1.parquet")
|
|
397
|
+
* // result.data → ArrayBuffer
|
|
398
|
+
* // result.contentType → "application/vnd.apache.parquet"
|
|
399
|
+
* ```
|
|
400
|
+
*/
|
|
401
|
+
pullParquet(path: string): Promise<BlobPullResult>;
|
|
137
402
|
}
|