@drakkar.software/starfish-client 3.0.0-alpha.4 → 3.0.0-alpha.40
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 +963 -87
- package/dist/bindings/zustand.js.map +4 -4
- package/dist/client.d.ts +283 -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 +14 -7
- package/dist/index.js +1025 -99
- 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 +115 -9
- package/dist/types.js +18 -0
- package/dist/validate.js +28 -0
- package/package.json +12 -3
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { CapCert, PullResult } from "@drakkar.software/starfish-protocol";
|
|
2
2
|
/** Push conflict error (HTTP 409). */
|
|
3
3
|
export declare class ConflictError extends Error {
|
|
4
4
|
constructor();
|
|
@@ -9,6 +9,20 @@ export declare class StarfishHttpError extends Error {
|
|
|
9
9
|
readonly body: string;
|
|
10
10
|
constructor(status: number, body: string);
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Non-2xx HTTP error from an anonymous (cap-less) append call.
|
|
14
|
+
*
|
|
15
|
+
* Distinct from {@link StarfishHttpError} so callers can distinguish "the
|
|
16
|
+
* anonymous write was rejected" (e.g. auth required, payload too large) from
|
|
17
|
+
* other client errors without pattern-matching on the error message.
|
|
18
|
+
*/
|
|
19
|
+
export declare class AppendHttpError extends Error {
|
|
20
|
+
/** HTTP status returned by the server. */
|
|
21
|
+
readonly status: number;
|
|
22
|
+
constructor(
|
|
23
|
+
/** HTTP status returned by the server. */
|
|
24
|
+
status: number, message: string);
|
|
25
|
+
}
|
|
12
26
|
/**
|
|
13
27
|
* v3.0 cap-cert provider for `StarfishClient`. Returns the device's cap-cert and
|
|
14
28
|
* the matching Ed25519 private key (hex). The client calls `getCap()` once per
|
|
@@ -29,25 +43,56 @@ export interface StarfishCapProvider {
|
|
|
29
43
|
* The client then sends it as `X-Starfish-Pub` so the server can verify the
|
|
30
44
|
* request signature against it and check the cap's `aud` allow-list. Omit
|
|
31
45
|
* `pubHex` for device/member caps (the server uses `cap.sub`).
|
|
32
|
-
*
|
|
33
|
-
* `presenterAlg` is the crypto suite of `devEdPrivHex` (the key that signs
|
|
34
|
-
* the request). It matters only for `audience` caps, where the presenter is
|
|
35
|
-
* an arbitrary redeemer whose suite is unrelated to the cap's `issAlg`; the
|
|
36
|
-
* client sends it as `X-Starfish-Alg`. For device/member caps the subject's
|
|
37
|
-
* suite is taken authoritatively from the verified cert, so this is ignored.
|
|
38
|
-
* Defaults to `"ed25519"` when omitted.
|
|
39
46
|
*/
|
|
40
47
|
getCap(): Promise<{
|
|
41
48
|
cap: CapCert;
|
|
42
49
|
devEdPrivHex: string;
|
|
43
50
|
pubHex?: string;
|
|
44
|
-
presenterAlg?: Alg;
|
|
45
51
|
}>;
|
|
46
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* A minimal async key-value store the client uses as a read-through cache for
|
|
55
|
+
* {@link StarfishClient.pull} (offline-first reads). Host-provided so the SDK
|
|
56
|
+
* stays storage-agnostic — back it by `localStorage`, `AsyncStorage`, a file,
|
|
57
|
+
* etc. Shaped like a subset of zustand's `StateStorage` so an existing adapter
|
|
58
|
+
* fits.
|
|
59
|
+
*
|
|
60
|
+
* IMPORTANT — what gets stored: the client caches the RAW server response only
|
|
61
|
+
* (`data`/`hash`/`timestamp`). For E2E (`delegated`) collections that payload is
|
|
62
|
+
* the SEALED ciphertext the server holds — never the decrypted form — so this
|
|
63
|
+
* cache is ciphertext-at-rest by construction. Decryption always happens in
|
|
64
|
+
* memory on read (see {@link SyncManager}). Public/plaintext collections cache
|
|
65
|
+
* their plaintext, exactly as the server stores it.
|
|
66
|
+
*/
|
|
67
|
+
export interface PullCache {
|
|
68
|
+
/** Return the previously-stored string for `key`, or null if absent. Must not throw. */
|
|
69
|
+
get(key: string): Promise<string | null>;
|
|
70
|
+
/** Store `value` under `key`. Must not throw (failures are swallowed by the client). */
|
|
71
|
+
set(key: string, value: string): Promise<void>;
|
|
72
|
+
}
|
|
47
73
|
/** Options for creating a StarfishClient. */
|
|
48
74
|
export interface StarfishClientOptions {
|
|
49
75
|
/** Base URL of the Starfish server (e.g. "https://api.example.com/v1"). */
|
|
50
76
|
baseUrl: string;
|
|
77
|
+
/**
|
|
78
|
+
* Optional namespace for a namespace-mounted server. When set, every request
|
|
79
|
+
* path `/{action}/…` is rewritten to `/v1/{namespace}/{action}/…` for BOTH the
|
|
80
|
+
* URL the client hits AND the canonical path it signs, so the signature the
|
|
81
|
+
* server reconstructs from the namespaced URL verifies (no rewrite layer
|
|
82
|
+
* needed). Mirrors the Python client's `namespace` parameter.
|
|
83
|
+
*
|
|
84
|
+
* Crucially this also rewrites the paths that namespace-unaware SDK helpers
|
|
85
|
+
* build internally (e.g. `starfish-keyring`'s `addCollectionRecipient`, blob
|
|
86
|
+
* uploads), so consumers no longer hand-prefix paths or wrap the client to
|
|
87
|
+
* reach a namespaced deployment. Leave unset (default) for a root-mounted
|
|
88
|
+
* server — paths pass through unchanged, byte-identical to before.
|
|
89
|
+
*
|
|
90
|
+
* Pass the bare namespace name (e.g. `"octochat"`); `baseUrl` then carries only
|
|
91
|
+
* the origin (and any reverse-proxy mount the proxy strips), not the `/v1`
|
|
92
|
+
* version segment. Must match `[A-Za-z0-9_-]+` and not be a reserved route name
|
|
93
|
+
* (`pull`, `push`, `health`, `batch`).
|
|
94
|
+
*/
|
|
95
|
+
namespace?: string;
|
|
51
96
|
/**
|
|
52
97
|
* Cap-cert provider. When set, requests are signed with Ed25519 and carry
|
|
53
98
|
* `Authorization: Cap <…>`. Omit for unauthenticated public-read collections.
|
|
@@ -55,6 +100,67 @@ export interface StarfishClientOptions {
|
|
|
55
100
|
capProvider?: StarfishCapProvider;
|
|
56
101
|
/** Optional fetch implementation (defaults to global fetch). */
|
|
57
102
|
fetch?: typeof fetch;
|
|
103
|
+
/**
|
|
104
|
+
* Optional read-through cache for {@link StarfishClient.pull} — the basis for
|
|
105
|
+
* offline-first reads. When set, every successful non-append pull is written
|
|
106
|
+
* through to the cache (keyed by document path), and a pull that fails because
|
|
107
|
+
* the TRANSPORT is unreachable (offline / DNS / timeout — `fetch` rejects)
|
|
108
|
+
* falls back to the cached response, tagged so callers can tell it's stale.
|
|
109
|
+
*
|
|
110
|
+
* A real HTTP error (404/403/5xx) is a genuine server answer and always
|
|
111
|
+
* propagates — the cache is NOT consulted — so "no document yet" and
|
|
112
|
+
* "access denied" keep their meaning. Caches ciphertext for E2E collections
|
|
113
|
+
* (the server only ever holds sealed payloads); never decrypted data.
|
|
114
|
+
*/
|
|
115
|
+
cache?: PullCache;
|
|
116
|
+
/**
|
|
117
|
+
* Optional max age (ms) for {@link cache} entries. An entry older than this is
|
|
118
|
+
* treated as a cache MISS on every read — both cache-first paint and the
|
|
119
|
+
* offline fallback — so a stale-beyond-policy snapshot is never served (the
|
|
120
|
+
* pull then goes to the network, or rethrows the transport error offline).
|
|
121
|
+
* Each cached snapshot records its write time; expiry is `now - cachedAt >
|
|
122
|
+
* cacheMaxAgeMs`. Omit (default) for entries that never expire — recommended
|
|
123
|
+
* for an offline-first app where any last-synced data beats none.
|
|
124
|
+
*/
|
|
125
|
+
cacheMaxAgeMs?: number;
|
|
126
|
+
/**
|
|
127
|
+
* HTTP status codes for which a structured `pull()` falls back to the
|
|
128
|
+
* last-synced cached snapshot rather than throwing `StarfishHttpError` —
|
|
129
|
+
* a **stale-while-revalidate** strategy for transient server failures.
|
|
130
|
+
*
|
|
131
|
+
* When a pull returns one of these statuses AND a {@link cache} is configured
|
|
132
|
+
* AND a cached snapshot exists for the document, `pull()` returns the cached
|
|
133
|
+
* result immediately (tagged stale via `pullWasFromCache`) and spawns a
|
|
134
|
+
* background revalidation loop that retries until it gets a live response.
|
|
135
|
+
* On success the fresh snapshot is written through and {@link onRevalidated}
|
|
136
|
+
* fires. When no cached snapshot exists the error propagates as usual.
|
|
137
|
+
*
|
|
138
|
+
* Applies only to structured (non-append) pulls. Do NOT include `403` or `404`
|
|
139
|
+
* — they are genuine server answers (access denied / no document yet).
|
|
140
|
+
*
|
|
141
|
+
* Default `undefined` — every non-2xx status throws as before.
|
|
142
|
+
*
|
|
143
|
+
* Recommended set for offline-first apps: `[429, 500, 502, 503, 504]`.
|
|
144
|
+
*/
|
|
145
|
+
cacheFallbackStatuses?: number[];
|
|
146
|
+
/**
|
|
147
|
+
* Called after a background revalidation delivers a fresh snapshot to the
|
|
148
|
+
* cache. Fires for two revalidation paths:
|
|
149
|
+
*
|
|
150
|
+
* 1. **Error-triggered** ({@link cacheFallbackStatuses} hit): the server
|
|
151
|
+
* returned a transient error (429/5xx), `pull()` served the stale cache,
|
|
152
|
+
* and the background retry loop eventually got a live response.
|
|
153
|
+
* 2. **SWR-on-read** ({@link PullOptions.staleWhileRevalidate}): `pull()`
|
|
154
|
+
* served the cache immediately and the background fetch completed.
|
|
155
|
+
*
|
|
156
|
+
* In both cases `result` is the fresh `PullResult` just written to cache.
|
|
157
|
+
* Use this to signal reachability recovery and/or push the fresh data into
|
|
158
|
+
* any store that is showing the stale snapshot.
|
|
159
|
+
*
|
|
160
|
+
* `path` is the namespaced document path (namespace prefix + path + query
|
|
161
|
+
* string, matching the cache key written by {@link StarfishClient.pull}).
|
|
162
|
+
*/
|
|
163
|
+
onRevalidated?: (path: string, result: PullResult) => void;
|
|
58
164
|
/**
|
|
59
165
|
* Optional list of client-side plugins. The list is stored on the client
|
|
60
166
|
* instance but does not fire any hooks yet — the contract is plumbed so
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Push conflict error (HTTP 409). */
|
|
2
|
+
export class ConflictError extends Error {
|
|
3
|
+
constructor() {
|
|
4
|
+
super("hash_mismatch");
|
|
5
|
+
this.name = "ConflictError";
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
/** HTTP error from the Starfish server. */
|
|
9
|
+
export class StarfishHttpError extends Error {
|
|
10
|
+
status;
|
|
11
|
+
body;
|
|
12
|
+
constructor(status, body) {
|
|
13
|
+
super(`HTTP ${status}: ${body}`);
|
|
14
|
+
this.status = status;
|
|
15
|
+
this.body = body;
|
|
16
|
+
this.name = "StarfishHttpError";
|
|
17
|
+
}
|
|
18
|
+
}
|
package/dist/validate.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Error thrown when pre-push validation fails. */
|
|
2
|
+
export class ValidationError extends Error {
|
|
3
|
+
errors;
|
|
4
|
+
constructor(errors) {
|
|
5
|
+
super(`Validation failed: ${errors.join("; ")}`);
|
|
6
|
+
this.errors = errors;
|
|
7
|
+
this.name = "ValidationError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Creates a validator from a JSON Schema object.
|
|
12
|
+
* Requires an Ajv-compatible validate function.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import Ajv from "ajv"
|
|
17
|
+
* const ajv = new Ajv()
|
|
18
|
+
* const validator = createSchemaValidator(ajv, mySchema)
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export function createSchemaValidator(ajv, schema) {
|
|
22
|
+
const validate = ajv.compile(schema);
|
|
23
|
+
return (data) => {
|
|
24
|
+
if (validate(data))
|
|
25
|
+
return true;
|
|
26
|
+
return [ajv.errorsText(validate.errors)];
|
|
27
|
+
};
|
|
28
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drakkar.software/starfish-client",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.40",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/Drakkar-Software/starfish.git",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"description": "TypeScript client SDK for the Starfish sync protocol",
|
|
13
13
|
"type": "module",
|
|
14
|
+
"sideEffects": false,
|
|
14
15
|
"main": "./dist/index.js",
|
|
15
16
|
"types": "./dist/index.d.ts",
|
|
16
17
|
"exports": {
|
|
@@ -26,6 +27,10 @@
|
|
|
26
27
|
"types": "./dist/bindings/legend.d.ts",
|
|
27
28
|
"import": "./dist/bindings/legend.js"
|
|
28
29
|
},
|
|
30
|
+
"./events": {
|
|
31
|
+
"types": "./dist/events.d.ts",
|
|
32
|
+
"import": "./dist/events.js"
|
|
33
|
+
},
|
|
29
34
|
"./fetch": {
|
|
30
35
|
"types": "./dist/fetch.d.ts",
|
|
31
36
|
"import": "./dist/fetch.js"
|
|
@@ -37,6 +42,10 @@
|
|
|
37
42
|
"./testing": {
|
|
38
43
|
"types": "./dist/testing.d.ts",
|
|
39
44
|
"import": "./dist/testing.js"
|
|
45
|
+
},
|
|
46
|
+
"./suspense": {
|
|
47
|
+
"types": "./dist/bindings/suspense.d.ts",
|
|
48
|
+
"import": "./dist/bindings/suspense.js"
|
|
40
49
|
}
|
|
41
50
|
},
|
|
42
51
|
"peerDependencies": {
|
|
@@ -60,7 +69,7 @@
|
|
|
60
69
|
}
|
|
61
70
|
},
|
|
62
71
|
"dependencies": {
|
|
63
|
-
"@drakkar.software/starfish-protocol": "3.0.0-alpha.
|
|
72
|
+
"@drakkar.software/starfish-protocol": "3.0.0-alpha.40"
|
|
64
73
|
},
|
|
65
74
|
"devDependencies": {
|
|
66
75
|
"@legendapp/state": "^2.0.0",
|
|
@@ -74,7 +83,7 @@
|
|
|
74
83
|
"react": "^19.2.4",
|
|
75
84
|
"react-dom": "^19.2.4",
|
|
76
85
|
"typescript": "^5.5.0",
|
|
77
|
-
"vitest": "^3.
|
|
86
|
+
"vitest": "^3.2.6",
|
|
78
87
|
"zustand": "^5.0.11"
|
|
79
88
|
},
|
|
80
89
|
"files": [
|