@interop/wallet-core 0.83.1 → 0.85.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.
Files changed (52) hide show
  1. package/README.md +13 -3
  2. package/dist/menders/derive.d.ts +1 -0
  3. package/dist/menders/derive.d.ts.map +1 -1
  4. package/dist/menders/derive.js +4 -1
  5. package/dist/menders/derive.js.map +1 -1
  6. package/dist/menders/ids.d.ts +1 -1
  7. package/dist/menders/ids.d.ts.map +1 -1
  8. package/dist/menders/ids.js +3 -1
  9. package/dist/menders/ids.js.map +1 -1
  10. package/dist/menders/vocabulary.d.ts +4 -2
  11. package/dist/menders/vocabulary.d.ts.map +1 -1
  12. package/dist/menders/vocabulary.js +5 -2
  13. package/dist/menders/vocabulary.js.map +1 -1
  14. package/dist/space/collections.d.ts +17 -2
  15. package/dist/space/collections.d.ts.map +1 -1
  16. package/dist/space/collections.js +26 -2
  17. package/dist/space/collections.js.map +1 -1
  18. package/dist/space/index.d.ts +1 -1
  19. package/dist/space/index.d.ts.map +1 -1
  20. package/dist/space/index.js +1 -1
  21. package/dist/space/index.js.map +1 -1
  22. package/dist/sync/engine.d.ts +11 -0
  23. package/dist/sync/engine.d.ts.map +1 -1
  24. package/dist/sync/engine.js +2 -0
  25. package/dist/sync/engine.js.map +1 -1
  26. package/dist/sync/pull.d.ts +8 -2
  27. package/dist/sync/pull.d.ts.map +1 -1
  28. package/dist/sync/pull.js +55 -4
  29. package/dist/sync/pull.js.map +1 -1
  30. package/dist/sync/push.d.ts +4 -1
  31. package/dist/sync/push.d.ts.map +1 -1
  32. package/dist/sync/push.js +22 -8
  33. package/dist/sync/push.js.map +1 -1
  34. package/dist/sync/types.d.ts +22 -0
  35. package/dist/sync/types.d.ts.map +1 -1
  36. package/dist/writers/entry.d.ts +95 -0
  37. package/dist/writers/entry.d.ts.map +1 -0
  38. package/dist/writers/entry.js +90 -0
  39. package/dist/writers/entry.js.map +1 -0
  40. package/dist/writers/index.d.ts +17 -0
  41. package/dist/writers/index.d.ts.map +1 -0
  42. package/dist/writers/index.js +14 -0
  43. package/dist/writers/index.js.map +1 -0
  44. package/dist/writers/roster.d.ts +152 -0
  45. package/dist/writers/roster.d.ts.map +1 -0
  46. package/dist/writers/roster.js +365 -0
  47. package/dist/writers/roster.js.map +1 -0
  48. package/dist/writers/store.d.ts +71 -0
  49. package/dist/writers/store.d.ts.map +1 -0
  50. package/dist/writers/store.js +55 -0
  51. package/dist/writers/store.js.map +1 -0
  52. package/package.json +10 -4
@@ -0,0 +1,365 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * The writer roster's logic: lazy registration with its throttled liveness
6
+ * touch, the user's rename, the sweep-on-read with two-phase expiry, and the
7
+ * join a history view renders a revision's `writerId` through.
8
+ *
9
+ * Orphaning is the normal lifecycle of a writer: a cleared browser profile
10
+ * cannot deregister. So no cleanup here depends on the dying writer. Any
11
+ * replica that reads the roster sweeps it, every sweep write is conditional
12
+ * on the ETag it read, and a lost race is skipped, so concurrent sweepers
13
+ * converge. Registration waits until the writer is encountered in a second
14
+ * session under the same account, which keeps one-shot writers (an incognito
15
+ * window, a cleared profile) out of the roster altogether.
16
+ */
17
+ import { NotSupportedError } from '@interop/was-client';
18
+ import { parseRegisteredWriterEntry, registeredWriterPolicy, registeredWriterResourceId } from './entry.js';
19
+ const PRECONDITION_FAILED_ERROR_NAME = 'PreconditionFailedError';
20
+ const NOT_FOUND_ERROR_NAME = 'NotFoundError';
21
+ const REGISTRATION_ATTEMPTS = 3;
22
+ /**
23
+ * Whether a store write lost a race: a conditional write refused, or (for a
24
+ * delete) the resource already gone. A put that meets `NotFoundError` has
25
+ * not raced. It is refused, so it propagates. Matched by name, since the
26
+ * store is an injected seam that may raise from a second copy of was-client.
27
+ *
28
+ * @param err {unknown}
29
+ * @param [options] {object}
30
+ * @param [options.deleting] {boolean}
31
+ * @returns {boolean}
32
+ */
33
+ function isLostRace(err, { deleting = false } = {}) {
34
+ const name = err?.name;
35
+ return (name === PRECONDITION_FAILED_ERROR_NAME ||
36
+ (deleting && name === NOT_FOUND_ERROR_NAME));
37
+ }
38
+ /**
39
+ * The ETag a guarded write over an existing resource pins to. A read that
40
+ * carried none would send the write unconditionally and could overwrite a
41
+ * newer touch or rename, so it is refused instead.
42
+ *
43
+ * @param options {object}
44
+ * @param options.resourceId {string}
45
+ * @param [options.etag] {string}
46
+ * @returns {string}
47
+ */
48
+ function requireEtag({ resourceId, etag }) {
49
+ if (etag === undefined) {
50
+ throw new NotSupportedError(`Cannot update registered writer "${resourceId}": its read returned ` +
51
+ 'no ETag, so the write would go out unconditionally. A browser ' +
52
+ "client needs `ETag` in the server's `Access-Control-Expose-Headers`.");
53
+ }
54
+ return etag;
55
+ }
56
+ /**
57
+ * An entry's `lastSeen` as a time no later than `now`. A writer's clock
58
+ * that runs fast would otherwise keep its entry newest, ahead of every
59
+ * eviction.
60
+ *
61
+ * @param options {object}
62
+ * @param options.entry {RegisteredWriterEntry}
63
+ * @param options.now {Date}
64
+ * @returns {number}
65
+ */
66
+ function effectiveLastSeen({ entry, now }) {
67
+ return Math.min(Date.parse(entry.lastSeen), now.getTime());
68
+ }
69
+ /**
70
+ * Reads the writer first-seen record, or `undefined` when it is missing or
71
+ * malformed.
72
+ *
73
+ * @param body {unknown}
74
+ * @returns {WriterFirstSeenRecord | undefined}
75
+ */
76
+ function parseFirstSeenRecord(body) {
77
+ if (body === null || typeof body !== 'object') {
78
+ return undefined;
79
+ }
80
+ const { accountDid, writerId, firstSessionAt, lastTouchedAt } = body;
81
+ if (typeof accountDid !== 'string' ||
82
+ typeof writerId !== 'string' ||
83
+ typeof firstSessionAt !== 'string' ||
84
+ Number.isNaN(Date.parse(firstSessionAt)) ||
85
+ (lastTouchedAt !== undefined &&
86
+ (typeof lastTouchedAt !== 'string' ||
87
+ Number.isNaN(Date.parse(lastTouchedAt))))) {
88
+ return undefined;
89
+ }
90
+ return {
91
+ accountDid,
92
+ writerId,
93
+ firstSessionAt,
94
+ ...(lastTouchedAt !== undefined && { lastTouchedAt })
95
+ };
96
+ }
97
+ /**
98
+ * One stored resource read as an entry of this account's roster: a
99
+ * well-formed entry stored at its own resource id. An entry stored under any
100
+ * other id is skipped, so a copied or misplaced entry labels nothing.
101
+ *
102
+ * @param options {object}
103
+ * @param options.accountDid {string}
104
+ * @param options.stored {StoredRegisteredWriter}
105
+ * @returns {RegisteredWriterEntry | undefined}
106
+ */
107
+ function boundEntry({ accountDid, stored }) {
108
+ const entry = parseRegisteredWriterEntry(stored.body);
109
+ if (entry === undefined ||
110
+ registeredWriterResourceId({ accountDid, writerId: entry.writerId }) !==
111
+ stored.resourceId) {
112
+ return undefined;
113
+ }
114
+ return entry;
115
+ }
116
+ /**
117
+ * Registers this client's writer in the roster once it is encountered in a
118
+ * second session under the account, and touches its `lastSeen` at most once
119
+ * per touch interval after that. Call it on the wallet's connect or sync path;
120
+ * calls within the interval cost no I/O.
121
+ *
122
+ * A new entry takes `label`. A touch keeps the stored label, so a rename
123
+ * stands, and refreshes `signingKeyMultibase` when one is given. Registering
124
+ * a new entry first makes room under the entry cap, deleting the
125
+ * oldest-`lastSeen` entries.
126
+ *
127
+ * @param options {object}
128
+ * @param options.store {RegisteredWritersStore}
129
+ * @param options.local {WriterFirstSeenStore}
130
+ * @param options.accountDid {string} the account whose Space holds the roster
131
+ * @param options.writerId {string} this client's writer
132
+ * @param options.label {string} the coarse platform self-description a new
133
+ * entry takes
134
+ * @param options.sessionStartedAt {Date} when the current session began; a
135
+ * first-seen record written at or after it is this session's own
136
+ * @param [options.signingKeyMultibase] {string} an enrolled wallet client's
137
+ * own signing-key multibase
138
+ * @param [options.now] {Date}
139
+ * @param [options.policy] {Partial<RegisteredWriterPolicy>}
140
+ * @returns {Promise<WriterRegistrationOutcome>}
141
+ */
142
+ export async function registerWriterOnSecondSession({ store, local, accountDid, writerId, label, sessionStartedAt, signingKeyMultibase, now = new Date(), policy }) {
143
+ const resolved = registeredWriterPolicy(policy);
144
+ const firstSeen = parseFirstSeenRecord(await local.get());
145
+ if (firstSeen === undefined ||
146
+ firstSeen.accountDid !== accountDid ||
147
+ firstSeen.writerId !== writerId) {
148
+ await local.put({
149
+ accountDid,
150
+ writerId,
151
+ firstSessionAt: sessionStartedAt.toISOString()
152
+ });
153
+ return 'deferred';
154
+ }
155
+ if (Date.parse(firstSeen.firstSessionAt) >= sessionStartedAt.getTime()) {
156
+ return 'deferred';
157
+ }
158
+ if (firstSeen.lastTouchedAt !== undefined &&
159
+ now.getTime() - Date.parse(firstSeen.lastTouchedAt) <
160
+ resolved.touchIntervalMs) {
161
+ return 'fresh';
162
+ }
163
+ const resourceId = registeredWriterResourceId({ accountDid, writerId });
164
+ const lastSeen = now.toISOString();
165
+ for (let attempt = 0; attempt < REGISTRATION_ATTEMPTS; attempt++) {
166
+ const stored = await store.get({ resourceId });
167
+ const existing = stored === undefined
168
+ ? undefined
169
+ : boundEntry({ accountDid, stored: { resourceId, ...stored } });
170
+ const entry = {
171
+ version: 1,
172
+ writerId,
173
+ ...(signingKeyMultibase !== undefined
174
+ ? { signingKeyMultibase }
175
+ : existing?.signingKeyMultibase !== undefined && {
176
+ signingKeyMultibase: existing.signingKeyMultibase
177
+ }),
178
+ label: existing?.label ?? label,
179
+ lastSeen,
180
+ active: true
181
+ };
182
+ try {
183
+ if (stored === undefined) {
184
+ await sweepStored({
185
+ store,
186
+ accountDid,
187
+ stored: await store.list(),
188
+ now,
189
+ policy: resolved,
190
+ capacity: resolved.maxEntries - 1
191
+ });
192
+ await store.put({ resourceId, entry, ifNoneMatch: true });
193
+ }
194
+ else {
195
+ await store.put({
196
+ resourceId,
197
+ entry,
198
+ ifMatch: requireEtag({ resourceId, etag: stored.etag })
199
+ });
200
+ }
201
+ }
202
+ catch (err) {
203
+ if (isLostRace(err)) {
204
+ continue;
205
+ }
206
+ throw err;
207
+ }
208
+ await local.put({ ...firstSeen, lastTouchedAt: lastSeen });
209
+ return existing === undefined ? 'registered' : 'touched';
210
+ }
211
+ return 'raced';
212
+ }
213
+ /**
214
+ * Renames this client's writer. The rename lands only on a registered entry;
215
+ * before registration there is nothing to rename, and the call returns
216
+ * `false`.
217
+ *
218
+ * @param options {object}
219
+ * @param options.store {RegisteredWritersStore}
220
+ * @param options.accountDid {string}
221
+ * @param options.writerId {string}
222
+ * @param options.label {string} the new label; blank is refused
223
+ * @returns {Promise<boolean>} whether an entry was renamed
224
+ */
225
+ export async function renameRegisteredWriter({ store, accountDid, writerId, label }) {
226
+ const trimmed = label.trim();
227
+ if (!trimmed) {
228
+ throw new TypeError('A writer label must not be blank.');
229
+ }
230
+ const resourceId = registeredWriterResourceId({ accountDid, writerId });
231
+ const stored = await store.get({ resourceId });
232
+ if (stored === undefined) {
233
+ return false;
234
+ }
235
+ const existing = boundEntry({ accountDid, stored: { resourceId, ...stored } });
236
+ if (existing === undefined) {
237
+ return false;
238
+ }
239
+ await store.put({
240
+ resourceId,
241
+ entry: { ...existing, label: trimmed },
242
+ ifMatch: requireEtag({ resourceId, etag: stored.etag })
243
+ });
244
+ return true;
245
+ }
246
+ /**
247
+ * The two-phase expiry over a listing already read. An active entry past the
248
+ * inactivity TTL is marked inactive. Past `capacity` entries, the
249
+ * oldest-`lastSeen` ones are deleted (ties broken by resource id, so every
250
+ * sweeper picks the same ones). A resource that is not a bound entry is left
251
+ * alone and not counted: it may be an entry sealed under an epoch this reader
252
+ * does not hold yet.
253
+ *
254
+ * @param options {object}
255
+ * @param options.store {RegisteredWritersStore}
256
+ * @param options.accountDid {string}
257
+ * @param options.stored {StoredRegisteredWriter[]}
258
+ * @param options.now {Date}
259
+ * @param options.policy {RegisteredWriterPolicy}
260
+ * @param options.capacity {number} the most entries to leave standing
261
+ * @returns {Promise<RegisteredWritersSweep>}
262
+ */
263
+ async function sweepStored({ store, accountDid, stored, now, policy, capacity }) {
264
+ const bound = [];
265
+ for (const item of stored) {
266
+ const entry = boundEntry({ accountDid, stored: item });
267
+ if (entry !== undefined) {
268
+ bound.push({ stored: item, entry });
269
+ }
270
+ }
271
+ bound.sort((left, right) => effectiveLastSeen({ entry: right.entry, now }) -
272
+ effectiveLastSeen({ entry: left.entry, now }) ||
273
+ (left.stored.resourceId < right.stored.resourceId ? -1 : 1));
274
+ const markedInactive = [];
275
+ const deleted = [];
276
+ const entries = [];
277
+ for (const [index, { stored: item, entry }] of bound.entries()) {
278
+ // A resource read without an ETag is never written: an unconditional
279
+ // write could clobber a newer touch.
280
+ if (index >= Math.max(capacity, 0)) {
281
+ if (item.etag === undefined) {
282
+ continue;
283
+ }
284
+ try {
285
+ await store.delete({ resourceId: item.resourceId, ifMatch: item.etag });
286
+ deleted.push(entry.writerId);
287
+ }
288
+ catch (err) {
289
+ if (!isLostRace(err, { deleting: true })) {
290
+ throw err;
291
+ }
292
+ }
293
+ continue;
294
+ }
295
+ const expired = now.getTime() - effectiveLastSeen({ entry, now }) > policy.inactiveAfterMs;
296
+ if (entry.active && expired && item.etag !== undefined) {
297
+ const inactive = { ...entry, active: false };
298
+ try {
299
+ await store.put({
300
+ resourceId: item.resourceId,
301
+ entry: inactive,
302
+ ifMatch: item.etag
303
+ });
304
+ markedInactive.push(entry.writerId);
305
+ entries.push(inactive);
306
+ }
307
+ catch (err) {
308
+ if (!isLostRace(err)) {
309
+ throw err;
310
+ }
311
+ // A newer write won; the entry as read still labels its revisions.
312
+ entries.push(entry);
313
+ }
314
+ continue;
315
+ }
316
+ entries.push(entry);
317
+ }
318
+ return { entries, markedInactive, deleted };
319
+ }
320
+ /**
321
+ * Reads the roster and sweeps it: the sweep-on-read every history view runs.
322
+ * Safe to run from any replica, and concurrently.
323
+ *
324
+ * @param options {object}
325
+ * @param options.store {RegisteredWritersStore}
326
+ * @param options.accountDid {string} the account whose Space holds the roster
327
+ * @param [options.now] {Date}
328
+ * @param [options.policy] {Partial<RegisteredWriterPolicy>}
329
+ * @returns {Promise<RegisteredWritersSweep>}
330
+ */
331
+ export async function sweepRegisteredWriters({ store, accountDid, now = new Date(), policy }) {
332
+ const resolved = registeredWriterPolicy(policy);
333
+ return sweepStored({
334
+ store,
335
+ accountDid,
336
+ stored: await store.list(),
337
+ now,
338
+ policy: resolved,
339
+ capacity: resolved.maxEntries
340
+ });
341
+ }
342
+ /**
343
+ * Resolves a revision's `writerId` against the swept roster.
344
+ *
345
+ * @param options {object}
346
+ * @param options.writerId {string}
347
+ * @param options.entries {RegisteredWriterEntry[]} a sweep's `entries`
348
+ * @param options.enrolledSigningKeys {ReadonlySet<string>} the signing-key
349
+ * multibases of the enrolled clients in the verified account document
350
+ * @returns {ResolvedWriter | undefined}
351
+ */
352
+ export function resolveRegisteredWriter({ writerId, entries, enrolledSigningKeys }) {
353
+ const entry = entries.find(candidate => candidate.writerId === writerId);
354
+ if (entry === undefined) {
355
+ return undefined;
356
+ }
357
+ const linked = entry.signingKeyMultibase !== undefined &&
358
+ enrolledSigningKeys.has(entry.signingKeyMultibase);
359
+ return {
360
+ label: entry.label,
361
+ active: entry.active,
362
+ ...(linked && { clientSigningKeyMultibase: entry.signingKeyMultibase })
363
+ };
364
+ }
365
+ //# sourceMappingURL=roster.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roster.js","sourceRoot":"","sources":["../../src/writers/roster.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ /*!
2
+ * Copyright (c) 2026 Interop Alliance. All rights reserved.
3
+ */
4
+ /**
5
+ * The writer roster's storage seam, and its WAS-backed adapter over the
6
+ * `registered-writers` collection.
7
+ *
8
+ * One resource per writer keeps registration, liveness touches, and sweeps
9
+ * off any shared document. Every write the roster logic makes is conditional
10
+ * on the ETag it read, so two sweepers, or a sweeper and a touching writer,
11
+ * cannot overwrite each other's newer write. A lost race surfaces as an error
12
+ * named `PreconditionFailedError`, which the roster logic matches by name.
13
+ */
14
+ import type { Collection } from '@interop/was-client';
15
+ import type { RegisteredWriterEntry } from './entry.js';
16
+ /**
17
+ * One stored resource: its body as read (`undefined` when it could not be
18
+ * read or opened) and its ETag.
19
+ */
20
+ export interface StoredRegisteredWriter {
21
+ resourceId: string;
22
+ body: unknown;
23
+ etag?: string;
24
+ }
25
+ /**
26
+ * The Space-side seam of the roster.
27
+ */
28
+ export interface RegisteredWritersStore {
29
+ /**
30
+ * Every resource in the collection. A resource that fails to read or
31
+ * decrypt is listed with an `undefined` body rather than failing the list.
32
+ */
33
+ list(): Promise<StoredRegisteredWriter[]>;
34
+ /**
35
+ * One resource, or `undefined` when it does not exist.
36
+ */
37
+ get(options: {
38
+ resourceId: string;
39
+ }): Promise<Omit<StoredRegisteredWriter, 'resourceId'> | undefined>;
40
+ /**
41
+ * Writes one entry: conditional on `ifMatch`, or create-only under
42
+ * `ifNoneMatch`.
43
+ */
44
+ put(options: {
45
+ resourceId: string;
46
+ entry: RegisteredWriterEntry;
47
+ ifMatch?: string;
48
+ ifNoneMatch?: boolean;
49
+ }): Promise<void>;
50
+ /**
51
+ * Deletes one resource, conditional on `ifMatch` when given.
52
+ */
53
+ delete(options: {
54
+ resourceId: string;
55
+ ifMatch?: string;
56
+ }): Promise<void>;
57
+ }
58
+ /**
59
+ * Builds the roster store over the `registered-writers` collection handle.
60
+ *
61
+ * @param options {object}
62
+ * @param options.collection {Collection} the account data Space's
63
+ * `registered-writers` collection, opened through a storage client whose
64
+ * encryption provider seals and opens it like any other encrypted wallet
65
+ * collection (and carrying the invocation capability, if any)
66
+ * @returns {RegisteredWritersStore}
67
+ */
68
+ export declare function wasRegisteredWritersStore({ collection }: {
69
+ collection: Collection;
70
+ }): RegisteredWritersStore;
71
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/writers/store.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAEvD;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,OAAO,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,IAAI,IAAI,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAAA;IACzC;;OAEG;IACH,GAAG,CAAC,OAAO,EAAE;QACX,UAAU,EAAE,MAAM,CAAA;KACnB,GAAG,OAAO,CAAC,IAAI,CAAC,sBAAsB,EAAE,YAAY,CAAC,GAAG,SAAS,CAAC,CAAA;IACnE;;;OAGG;IACH,GAAG,CAAC,OAAO,EAAE;QACX,UAAU,EAAE,MAAM,CAAA;QAClB,KAAK,EAAE,qBAAqB,CAAA;QAC5B,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,WAAW,CAAC,EAAE,OAAO,CAAA;KACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjB;;OAEG;IACH,MAAM,CAAC,OAAO,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACzE;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CAAC,EACxC,UAAU,EACX,EAAE;IACD,UAAU,EAAE,UAAU,CAAA;CACvB,GAAG,sBAAsB,CA+CzB"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Builds the roster store over the `registered-writers` collection handle.
3
+ *
4
+ * @param options {object}
5
+ * @param options.collection {Collection} the account data Space's
6
+ * `registered-writers` collection, opened through a storage client whose
7
+ * encryption provider seals and opens it like any other encrypted wallet
8
+ * collection (and carrying the invocation capability, if any)
9
+ * @returns {RegisteredWritersStore}
10
+ */
11
+ export function wasRegisteredWritersStore({ collection }) {
12
+ async function read(resourceId) {
13
+ const result = await collection.resource(resourceId).getWithEtag();
14
+ if (result === null) {
15
+ return undefined;
16
+ }
17
+ return {
18
+ body: result.data,
19
+ ...(result.etag !== undefined && { etag: result.etag })
20
+ };
21
+ }
22
+ return {
23
+ async list() {
24
+ const ids = [];
25
+ for await (const item of collection.listItems()) {
26
+ ids.push(item.id);
27
+ }
28
+ return Promise.all(ids.map(async (resourceId) => {
29
+ try {
30
+ const stored = await read(resourceId);
31
+ return { resourceId, body: stored?.body, etag: stored?.etag };
32
+ }
33
+ catch {
34
+ return { resourceId, body: undefined };
35
+ }
36
+ }));
37
+ },
38
+ async get({ resourceId }) {
39
+ return read(resourceId);
40
+ },
41
+ async put({ resourceId, entry, ifMatch, ifNoneMatch }) {
42
+ await collection.put(resourceId, entry, {
43
+ contentType: 'application/json',
44
+ ...(ifMatch !== undefined && { ifMatch }),
45
+ ...(ifNoneMatch === true && { ifNoneMatch })
46
+ });
47
+ },
48
+ async delete({ resourceId, ifMatch }) {
49
+ await collection
50
+ .resource(resourceId)
51
+ .delete(ifMatch !== undefined ? { ifMatch } : {});
52
+ }
53
+ };
54
+ }
55
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/writers/store.ts"],"names":[],"mappings":"AAyDA;;;;;;;;;GASG;AACH,MAAM,UAAU,yBAAyB,CAAC,EACxC,UAAU,EAGX;IACC,KAAK,UAAU,IAAI,CACjB,UAAkB;QAElB,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,WAAW,EAAE,CAAA;QAClE,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,OAAO,SAAS,CAAA;QAClB,CAAC;QACD,OAAO;YACL,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;SACxD,CAAA;IACH,CAAC;IAED,OAAO;QACL,KAAK,CAAC,IAAI;YACR,MAAM,GAAG,GAAa,EAAE,CAAA;YACxB,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,UAAU,CAAC,SAAS,EAAE,EAAE,CAAC;gBAChD,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;YACnB,CAAC;YACD,OAAO,OAAO,CAAC,GAAG,CAChB,GAAG,CAAC,GAAG,CAAC,KAAK,EAAC,UAAU,EAAC,EAAE;gBACzB,IAAI,CAAC;oBACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,CAAA;oBACrC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;gBAC/D,CAAC;gBAAC,MAAM,CAAC;oBACP,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,CAAA;gBACxC,CAAC;YACH,CAAC,CAAC,CACH,CAAA;QACH,CAAC;QACD,KAAK,CAAC,GAAG,CAAC,EAAE,UAAU,EAAE;YACtB,OAAO,IAAI,CAAC,UAAU,CAAC,CAAA;QACzB,CAAC;QACD,KAAK,CAAC,GAAG,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE;YACnD,MAAM,UAAU,CAAC,GAAG,CAAC,UAAU,EAAE,KAAK,EAAE;gBACtC,WAAW,EAAE,kBAAkB;gBAC/B,GAAG,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,CAAC;gBACzC,GAAG,CAAC,WAAW,KAAK,IAAI,IAAI,EAAE,WAAW,EAAE,CAAC;aAC7C,CAAC,CAAA;QACJ,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE;YAClC,MAAM,UAAU;iBACb,QAAQ,CAAC,UAAU,CAAC;iBACpB,MAAM,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;QACrD,CAAC;KACF,CAAA;AACH,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@interop/wallet-core",
3
3
  "description": "Shared wallet-domain logic (WAS sync engine core and wallet Space layout contracts) for Interop wallet apps.",
4
- "version": "0.83.1",
4
+ "version": "0.85.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "pnpm run clear && tsc",
@@ -37,8 +37,8 @@
37
37
  "devDependencies": {
38
38
  "@eslint/js": "^10.0.1",
39
39
  "@interop/logger": "^0.2.0",
40
- "@interop/storage-core": "^0.21.0",
41
- "@interop/was-client": "^0.77.0",
40
+ "@interop/storage-core": "^0.22.0",
41
+ "@interop/was-client": "^0.79.0",
42
42
  "@playwright/test": "^1.60.0",
43
43
  "@types/node": "^26.0.1",
44
44
  "@vitest/coverage-v8": "^4.1.7",
@@ -53,7 +53,7 @@
53
53
  "vitest": "^4.1.7"
54
54
  },
55
55
  "peerDependencies": {
56
- "@interop/was-client": ">=0.75.0 <1.0.0"
56
+ "@interop/was-client": ">=0.78.0 <1.0.0"
57
57
  },
58
58
  "files": [
59
59
  "dist",
@@ -199,6 +199,12 @@
199
199
  "import": "./dist/clients/index.js",
200
200
  "default": "./dist/clients/index.js"
201
201
  },
202
+ "./writers": {
203
+ "types": "./dist/writers/index.d.ts",
204
+ "react-native": "./dist/writers/index.js",
205
+ "import": "./dist/writers/index.js",
206
+ "default": "./dist/writers/index.js"
207
+ },
202
208
  "./menders": {
203
209
  "types": "./dist/menders/index.d.ts",
204
210
  "react-native": "./dist/menders/index.js",