@tinycloud/replica 0.1.0-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/dist/index.cjs +940 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +325 -0
- package/dist/index.d.ts +325 -0
- package/dist/index.js +897 -0
- package/dist/index.js.map +1 -0
- package/dist/sqlite.cjs +1005 -0
- package/dist/sqlite.cjs.map +1 -0
- package/dist/sqlite.d.cts +94 -0
- package/dist/sqlite.d.ts +94 -0
- package/dist/sqlite.js +975 -0
- package/dist/sqlite.js.map +1 -0
- package/dist/types-BNu2RYwE.d.cts +264 -0
- package/dist/types-BNu2RYwE.d.ts +264 -0
- package/package.json +54 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { C as Coverage, A as AuthorityState, R as ReplicaStore, a as ReplicaTransport, G as GrantRecord, L as ListOpts, b as ReplicaStatus, c as AuthorityWindow, d as LocalReadPolicy, e as Change } from './types-BNu2RYwE.js';
|
|
2
|
+
export { F as FetchedContent, f as LeaseToken, g as LocalEntry, h as ReplicaConfig, i as ReplicaLastError, j as ReplicaState, S as SyncPage, k as SyncSource, V as VerifiedChange, l as VerifiedPage } from './types-BNu2RYwE.js';
|
|
3
|
+
|
|
4
|
+
/** Metadata every local read carries: the replica reports observed state, never finality. */
|
|
5
|
+
type ReadMeta = {
|
|
6
|
+
consistency: "observed";
|
|
7
|
+
source: {
|
|
8
|
+
host: string;
|
|
9
|
+
nodeDid: string | null;
|
|
10
|
+
space: string;
|
|
11
|
+
prefix: string;
|
|
12
|
+
};
|
|
13
|
+
asOf: string | null;
|
|
14
|
+
coverage: Coverage;
|
|
15
|
+
/** `expired` while reads continue under a node-attested retention. */
|
|
16
|
+
authority: AuthorityState;
|
|
17
|
+
};
|
|
18
|
+
type ReplicaReadResult = {
|
|
19
|
+
status: "present";
|
|
20
|
+
key: string;
|
|
21
|
+
value: Uint8Array;
|
|
22
|
+
etag: string;
|
|
23
|
+
metadata: Record<string, string>;
|
|
24
|
+
meta: ReadMeta;
|
|
25
|
+
} | {
|
|
26
|
+
status: "content_missing";
|
|
27
|
+
key: string;
|
|
28
|
+
etag: string;
|
|
29
|
+
metadata: Record<string, string>;
|
|
30
|
+
meta: ReadMeta;
|
|
31
|
+
} | {
|
|
32
|
+
status: "deleted" | "absent";
|
|
33
|
+
key: string;
|
|
34
|
+
meta: ReadMeta;
|
|
35
|
+
} | {
|
|
36
|
+
status: "coverage_incomplete" | "not_covered";
|
|
37
|
+
key: string;
|
|
38
|
+
meta: ReadMeta;
|
|
39
|
+
};
|
|
40
|
+
type ReplicaListEntry = {
|
|
41
|
+
key: string;
|
|
42
|
+
etag: string;
|
|
43
|
+
metadata: Record<string, string>;
|
|
44
|
+
content: boolean;
|
|
45
|
+
};
|
|
46
|
+
type ReplicaListResult = {
|
|
47
|
+
entries: ReplicaListEntry[];
|
|
48
|
+
meta: ReadMeta;
|
|
49
|
+
};
|
|
50
|
+
type SyncReport = {
|
|
51
|
+
pages: number;
|
|
52
|
+
changes: number;
|
|
53
|
+
deleted: number;
|
|
54
|
+
fetched: number;
|
|
55
|
+
contentMissing: number;
|
|
56
|
+
repaired: number;
|
|
57
|
+
resets: number;
|
|
58
|
+
coverage: Coverage;
|
|
59
|
+
cursorAdvanced: boolean;
|
|
60
|
+
promotedGrant: boolean;
|
|
61
|
+
blobsCollected: number;
|
|
62
|
+
};
|
|
63
|
+
type ReplicaOptions = {
|
|
64
|
+
store: ReplicaStore;
|
|
65
|
+
/** One transport for whichever grant the replica syncs under (tests, single-grant hosts). */
|
|
66
|
+
transport?: ReplicaTransport;
|
|
67
|
+
/**
|
|
68
|
+
* A transport that invokes with exactly `grant`. Lets the engine try a
|
|
69
|
+
* pending grant and fall back to the active one if the node refuses it.
|
|
70
|
+
*/
|
|
71
|
+
transportFor?: (grant: GrantRecord) => ReplicaTransport;
|
|
72
|
+
now?: () => number;
|
|
73
|
+
leaseTtlMs?: number;
|
|
74
|
+
};
|
|
75
|
+
/** `"blake3-" + hex(blake3(bytes))`, the strong ETag the node attests for content. */
|
|
76
|
+
declare function contentHash(bytes: Uint8Array): string;
|
|
77
|
+
/**
|
|
78
|
+
* A durable, read-only local replica of one KV prefix. Reads never touch the
|
|
79
|
+
* network; `sync` pulls `tinycloud.kv/sync` pages, verifies every byte
|
|
80
|
+
* against the node-attested ETag, and commits each page with its cursor.
|
|
81
|
+
*/
|
|
82
|
+
declare class Replica {
|
|
83
|
+
#private;
|
|
84
|
+
constructor(options: ReplicaOptions);
|
|
85
|
+
get(key: string, options?: {
|
|
86
|
+
verify?: boolean;
|
|
87
|
+
}): Promise<ReplicaReadResult>;
|
|
88
|
+
/** Live keys under `prefix` (a plain string prefix within the replica's scope). */
|
|
89
|
+
list(options?: ListOpts): Promise<ReplicaListResult>;
|
|
90
|
+
status(): Promise<ReplicaStatus>;
|
|
91
|
+
/** Clear entries, blobs, cursor and the source pin; keep the configuration and grant. */
|
|
92
|
+
reset(reason: string): Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* Sync under the pending grant if there is one, else the active grant. If
|
|
95
|
+
* the node refuses a pending grant before anything committed under it, the
|
|
96
|
+
* active grant serves: a revoked pending grant is discarded, any other
|
|
97
|
+
* refusal is recorded on it. A revocation of the grant serving the replica,
|
|
98
|
+
* learned anywhere in the sync, purges the replica before this returns.
|
|
99
|
+
*/
|
|
100
|
+
sync(options?: {
|
|
101
|
+
limit?: number;
|
|
102
|
+
signal?: AbortSignal;
|
|
103
|
+
}): Promise<SyncReport>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Typed replica errors. Every failure a caller can act on has its own code;
|
|
108
|
+
* hosts (CLI, worker) map codes to their own exit codes or UI states.
|
|
109
|
+
*/
|
|
110
|
+
declare const ReplicaErrorCode: {
|
|
111
|
+
/** Another process holds the sync lease. */
|
|
112
|
+
readonly BUSY: "REPLICA_BUSY";
|
|
113
|
+
/** No replica with this name exists. */
|
|
114
|
+
readonly NOT_FOUND: "REPLICA_NOT_FOUND";
|
|
115
|
+
/** The replica exists but its stored configuration differs from the request. */
|
|
116
|
+
readonly CONFIG_MISMATCH: "REPLICA_CONFIG_MISMATCH";
|
|
117
|
+
/** The runtime has no supported durable store (e.g. Node < 22.13). */
|
|
118
|
+
readonly RUNTIME_UNSUPPORTED: "RUNTIME_UNSUPPORTED";
|
|
119
|
+
/** The local store failed (I/O, corruption, schema). */
|
|
120
|
+
readonly STORAGE_ERROR: "STORAGE_ERROR";
|
|
121
|
+
/** The local disk is full. */
|
|
122
|
+
readonly STORAGE_FULL: "STORAGE_FULL";
|
|
123
|
+
/** The key is outside the replica's prefix. */
|
|
124
|
+
readonly NOT_COVERED: "NOT_COVERED";
|
|
125
|
+
/** Syncing the secrets space or the vault namespace needs an explicit opt-in. */
|
|
126
|
+
readonly SECRETS_OPT_IN_REQUIRED: "SECRETS_OPT_IN_REQUIRED";
|
|
127
|
+
/** No grant is installed, or none covers the requested scope. */
|
|
128
|
+
readonly GRANT_MISSING: "GRANT_MISSING";
|
|
129
|
+
/** The grant bytes are not a verifiable UCAN, or its signature is wrong. */
|
|
130
|
+
readonly GRANT_INVALID: "GRANT_INVALID";
|
|
131
|
+
/** SIWE/CACAO and other non-UCAN grants cannot be used for a replica. */
|
|
132
|
+
readonly GRANT_FORMAT_UNSUPPORTED: "GRANT_FORMAT_UNSUPPORTED";
|
|
133
|
+
/** The grant was issued to a different device. */
|
|
134
|
+
readonly GRANT_AUDIENCE_MISMATCH: "GRANT_AUDIENCE_MISMATCH";
|
|
135
|
+
/** The grant lacks `tinycloud.kv/sync` or `tinycloud.kv/get` on the prefix. */
|
|
136
|
+
readonly GRANT_NOT_COVERING: "GRANT_NOT_COVERING";
|
|
137
|
+
readonly GRANT_NOT_YET_VALID: "GRANT_NOT_YET_VALID";
|
|
138
|
+
readonly GRANT_EXPIRED: "GRANT_EXPIRED";
|
|
139
|
+
readonly GRANT_REVOKED: "GRANT_REVOKED";
|
|
140
|
+
/** The node refused the invocation for a reason other than revocation. */
|
|
141
|
+
readonly GRANT_UNAUTHORIZED: "GRANT_UNAUTHORIZED";
|
|
142
|
+
/** The node refused the retention grant. */
|
|
143
|
+
readonly RETENTION_GRANT_REFUSED: "RETENTION_GRANT_REFUSED";
|
|
144
|
+
/** The node could not be reached. */
|
|
145
|
+
readonly NETWORK_ERROR: "NETWORK_ERROR";
|
|
146
|
+
/** The node answered with an unexpected status. */
|
|
147
|
+
readonly NODE_ERROR: "NODE_ERROR";
|
|
148
|
+
/** The node's answer violates the wire contract. */
|
|
149
|
+
readonly PROTOCOL_ERROR: "PROTOCOL_ERROR";
|
|
150
|
+
/** The node asked for a fresh bootstrap (410). Handled inside sync. */
|
|
151
|
+
readonly RESET_REQUIRED: "RESET_REQUIRED";
|
|
152
|
+
/** The feed now comes from a different node than the one pinned. */
|
|
153
|
+
readonly SOURCE_CHANGED: "SOURCE_CHANGED";
|
|
154
|
+
/** The feed delivered a key outside the replica's prefix. */
|
|
155
|
+
readonly SCOPE_VIOLATION: "SCOPE_VIOLATION";
|
|
156
|
+
/** Fetched bytes do not hash to the ETag the node attested for them. */
|
|
157
|
+
readonly CONTENT_MISMATCH: "CONTENT_MISMATCH";
|
|
158
|
+
/** A stored blob no longer hashes to its name. */
|
|
159
|
+
readonly INTEGRITY_ERROR: "INTEGRITY_ERROR";
|
|
160
|
+
};
|
|
161
|
+
type ReplicaErrorCode = (typeof ReplicaErrorCode)[keyof typeof ReplicaErrorCode];
|
|
162
|
+
/**
|
|
163
|
+
* The package ships separate entry bundles (`.`, `./sqlite`), each with its own
|
|
164
|
+
* copy of this class, so identity is a registered symbol, not `instanceof`.
|
|
165
|
+
*/
|
|
166
|
+
declare const REPLICA_ERROR: unique symbol;
|
|
167
|
+
declare class ReplicaError extends Error {
|
|
168
|
+
readonly [REPLICA_ERROR] = true;
|
|
169
|
+
readonly code: ReplicaErrorCode;
|
|
170
|
+
readonly detail?: Record<string, unknown>;
|
|
171
|
+
constructor(code: ReplicaErrorCode, message: string, detail?: Record<string, unknown>, options?: {
|
|
172
|
+
cause?: unknown;
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
declare function isReplicaError(error: unknown, code?: ReplicaErrorCode): error is ReplicaError;
|
|
176
|
+
|
|
177
|
+
/** A compact UCAN grant whose Ed25519 signature checked out against its issuer. */
|
|
178
|
+
type ParsedUcanGrant = GrantRecord & {
|
|
179
|
+
/** The signed attenuation: resource → ability → caveats. */
|
|
180
|
+
att: Record<string, Record<string, unknown>>;
|
|
181
|
+
/** Signed proof CIDs. */
|
|
182
|
+
prf: string[];
|
|
183
|
+
/** The compact JWT, without any `Bearer ` prefix. */
|
|
184
|
+
jwt: string;
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* The canonical TinyCloud delegation CID of a compact UCAN: CIDv1, raw codec
|
|
188
|
+
* (0x55), blake3-256 multihash over the JWT's UTF-8 bytes, base32 multibase.
|
|
189
|
+
* Matches `computeCid(bytes, 0x55)` in the SDK's WASM bindings.
|
|
190
|
+
*/
|
|
191
|
+
declare function ucanCid(jwt: string): string;
|
|
192
|
+
/**
|
|
193
|
+
* Parse and verify a compact UCAN grant. Worker-safe (no Node APIs).
|
|
194
|
+
* Everything returned — audience, window, attenuation, proofs — comes from
|
|
195
|
+
* the signed payload, never from wrapper fields around it. Verifying the
|
|
196
|
+
* signature proves the issuer signed exactly these claims; whether the chain
|
|
197
|
+
* holds is the node's call on every sync.
|
|
198
|
+
*/
|
|
199
|
+
declare function parseUcanGrant(input: Uint8Array | string): ParsedUcanGrant;
|
|
200
|
+
/** True when the signed attenuation grants `ability` on a KV path covering `prefix`. */
|
|
201
|
+
declare function grantCovers(grant: Pick<ParsedUcanGrant, "att">, space: string, prefix: string, ability: string): boolean;
|
|
202
|
+
/** The space ids (native form) for which the grant carries `tinycloud.kv/sync`. */
|
|
203
|
+
declare function syncGrantSpaces(grant: Pick<ParsedUcanGrant, "att">): string[];
|
|
204
|
+
/**
|
|
205
|
+
* Install-time checks: the grant must name this device, cover both
|
|
206
|
+
* `tinycloud.kv/sync` and `tinycloud.kv/get` on the prefix, and be inside its
|
|
207
|
+
* own signed window. The node re-checks the whole chain on every sync.
|
|
208
|
+
*/
|
|
209
|
+
declare function assertGrantInstallable(grant: ParsedUcanGrant, input: {
|
|
210
|
+
deviceDid: string;
|
|
211
|
+
space: string;
|
|
212
|
+
prefix: string;
|
|
213
|
+
now: number;
|
|
214
|
+
}): void;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* The node's `kv_prefix_covers`: whole path segments, byte-exact. `notes/`
|
|
218
|
+
* covers everything under `notes/` but not `notes`; `notes` covers `notes`
|
|
219
|
+
* and `notes/…` but not `notes-secret/x`.
|
|
220
|
+
*/
|
|
221
|
+
declare function kvPrefixCovers(prefix: string, key: string): boolean;
|
|
222
|
+
/**
|
|
223
|
+
* Syncing the owner's secrets space, or any prefix overlapping the vault
|
|
224
|
+
* namespace (including bare `vault`), copies encrypted secret material to
|
|
225
|
+
* disk and needs an explicit opt-in (TC-733).
|
|
226
|
+
*/
|
|
227
|
+
declare function requiresSecretsOptIn(space: string, prefix: string): boolean;
|
|
228
|
+
/** `"blake3-<hex>"` (quoted or not) → the 64-hex hash, or undefined. */
|
|
229
|
+
declare function hashFromEtag(etag: string): string | undefined;
|
|
230
|
+
type EffectiveAuthority = {
|
|
231
|
+
state: AuthorityState;
|
|
232
|
+
notBefore: string | null;
|
|
233
|
+
expiresAt: string | null;
|
|
234
|
+
retainUntil: string | null;
|
|
235
|
+
/** The retention grant was revoked: after expiry, reads are revoked rather than expired. */
|
|
236
|
+
retentionRevoked: boolean;
|
|
237
|
+
};
|
|
238
|
+
/**
|
|
239
|
+
* The window local reads are enforced against: the node-attested window from
|
|
240
|
+
* the last successful sync, bounded by the installed leaf grant (an upper
|
|
241
|
+
* bound only — a multi-parent ancestor can end the chain earlier, which only
|
|
242
|
+
* the node can see).
|
|
243
|
+
*/
|
|
244
|
+
declare function effectiveAuthority(input: {
|
|
245
|
+
window: AuthorityWindow | null;
|
|
246
|
+
grant: GrantRecord | null;
|
|
247
|
+
revoked: string | null;
|
|
248
|
+
retentionRevoked?: string | null;
|
|
249
|
+
policy: LocalReadPolicy;
|
|
250
|
+
now: number;
|
|
251
|
+
}): EffectiveAuthority;
|
|
252
|
+
/**
|
|
253
|
+
* Throws unless local reads are allowed now. Returns the reported authority
|
|
254
|
+
* state: `valid`, or `expired` while a node-attested retention holds.
|
|
255
|
+
*/
|
|
256
|
+
declare function assertReadable(authority: EffectiveAuthority, now: number): AuthorityState;
|
|
257
|
+
|
|
258
|
+
/** The sdk-services `ServiceError` fields the transport reads. */
|
|
259
|
+
type ServiceError = {
|
|
260
|
+
code: string;
|
|
261
|
+
message: string;
|
|
262
|
+
meta?: Record<string, unknown>;
|
|
263
|
+
};
|
|
264
|
+
type Result<T> = {
|
|
265
|
+
ok: true;
|
|
266
|
+
data: T;
|
|
267
|
+
} | {
|
|
268
|
+
ok: false;
|
|
269
|
+
error: ServiceError;
|
|
270
|
+
};
|
|
271
|
+
type KVChangesResponse = {
|
|
272
|
+
changes: Change[];
|
|
273
|
+
more: boolean;
|
|
274
|
+
cursor: string;
|
|
275
|
+
source: {
|
|
276
|
+
nodeDid: string;
|
|
277
|
+
space: string;
|
|
278
|
+
prefix: string;
|
|
279
|
+
};
|
|
280
|
+
authority: AuthorityWindow;
|
|
281
|
+
};
|
|
282
|
+
type KVValue = {
|
|
283
|
+
data: unknown;
|
|
284
|
+
headers: {
|
|
285
|
+
etag?: string;
|
|
286
|
+
};
|
|
287
|
+
};
|
|
288
|
+
/**
|
|
289
|
+
* The slice of sdk-services `KVService` a replica needs: the `tinycloud.kv/sync`
|
|
290
|
+
* feed (`changes`, TC-736) and binary reads. Typed structurally so this
|
|
291
|
+
* package never imports an SDK entry point (web-sdk defines custom elements
|
|
292
|
+
* on load; node-sdk pulls in Node-only code).
|
|
293
|
+
*/
|
|
294
|
+
interface KVSyncClient {
|
|
295
|
+
changes(options: {
|
|
296
|
+
prefix: string;
|
|
297
|
+
cursor?: string;
|
|
298
|
+
limit?: number;
|
|
299
|
+
retentionGrant?: string;
|
|
300
|
+
signal?: AbortSignal;
|
|
301
|
+
}): Promise<Result<KVChangesResponse>>;
|
|
302
|
+
get(key: string, options: {
|
|
303
|
+
binary: true;
|
|
304
|
+
signal?: AbortSignal;
|
|
305
|
+
}): Promise<Result<KVValue>>;
|
|
306
|
+
batchGet(keys: string[], options: {
|
|
307
|
+
binary: true;
|
|
308
|
+
signal?: AbortSignal;
|
|
309
|
+
}): Promise<Result<{
|
|
310
|
+
results: Array<{
|
|
311
|
+
key: string;
|
|
312
|
+
result: Result<KVValue>;
|
|
313
|
+
}>;
|
|
314
|
+
}>>;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Map an SDK failure to the replica's typed errors. `request` is the SDK's
|
|
318
|
+
* own description of the call (`Failed to get key "k"`), which lets a read's
|
|
319
|
+
* 401 body be told apart from the key it names.
|
|
320
|
+
*/
|
|
321
|
+
declare function replicaErrorFromService(error: ServiceError, context: string, request?: string): ReplicaError;
|
|
322
|
+
/** A `ReplicaTransport` over a KV service bound to the replica's space and grant. */
|
|
323
|
+
declare function kvSyncTransport(kv: KVSyncClient): ReplicaTransport;
|
|
324
|
+
|
|
325
|
+
export { AuthorityState, AuthorityWindow, Change, Coverage, type EffectiveAuthority, GrantRecord, type KVSyncClient, ListOpts, LocalReadPolicy, type ParsedUcanGrant, type ReadMeta, Replica, ReplicaError, ReplicaErrorCode, type ReplicaListEntry, type ReplicaListResult, type ReplicaOptions, type ReplicaReadResult, ReplicaStatus, ReplicaStore, ReplicaTransport, type SyncReport, assertGrantInstallable, assertReadable, contentHash, effectiveAuthority, grantCovers, hashFromEtag, isReplicaError, kvPrefixCovers, kvSyncTransport, parseUcanGrant, replicaErrorFromService, requiresSecretsOptIn, syncGrantSpaces, ucanCid };
|