@bandeira-tech/b3nd-web 0.2.7 → 0.3.1

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.
@@ -1,14 +1,16 @@
1
- import {
2
- MemoryClient
3
- } from "./chunk-GIMIWVPX.js";
4
1
  import {
5
2
  WalletServerCore
6
- } from "./chunk-F3W5GZU6.js";
3
+ } from "./chunk-B4VAPGAO.js";
7
4
  import {
8
5
  exportPrivateKeyPem,
9
6
  generateEncryptionKeyPair,
10
- generateSigningKeyPair
7
+ generateSigningKeyPair,
8
+ signWithHex
11
9
  } from "./chunk-JN75UL5C.js";
10
+ import {
11
+ MemoryClient,
12
+ createTestSchema
13
+ } from "./chunk-O53KW746.js";
12
14
 
13
15
  // wallet/client.ts
14
16
  var WalletClient = class {
@@ -142,19 +144,34 @@ var WalletClient = class {
142
144
  throw new Error("Use resetPasswordWithToken(appKey, username, resetToken, newPassword)");
143
145
  }
144
146
  /**
145
- * Sign up with app token (scoped to an app)
147
+ * Sign up with session keypair (scoped to an app)
148
+ *
149
+ * The session must be approved by the app beforehand:
150
+ * 1. Client writes request to: immutable://inbox/{appKey}/sessions/{sessionPubkey} = 1
151
+ * 2. App approves by writing: mutable://accounts/{appKey}/sessions/{sessionPubkey} = 1
152
+ * 3. Client calls this method with the session keypair
153
+ *
154
+ * @param appKey - The app's public key
155
+ * @param session - Session keypair (generated via generateSessionKeypair)
156
+ * @param credentials - User credentials (username/password)
146
157
  */
147
- async signupWithToken(appKey, tokenOrCredentials, maybeCredentials) {
148
- const credentials = typeof tokenOrCredentials === "string" ? maybeCredentials : tokenOrCredentials;
149
- if (!credentials) throw new Error("credentials are required");
158
+ async signupWithToken(appKey, session, credentials) {
159
+ if (!session?.publicKeyHex || !session?.privateKeyHex) {
160
+ throw new Error("session keypair is required");
161
+ }
162
+ const payloadToSign = {
163
+ sessionPubkey: session.publicKeyHex,
164
+ type: "password",
165
+ username: credentials.username,
166
+ password: credentials.password
167
+ };
168
+ const sessionSignature = await signWithHex(session.privateKeyHex, payloadToSign);
150
169
  const response = await this.fetchImpl(this.buildAppKeyUrl("/auth/signup", appKey), {
151
170
  method: "POST",
152
171
  headers: { "Content-Type": "application/json" },
153
172
  body: JSON.stringify({
154
- token: typeof tokenOrCredentials === "string" ? tokenOrCredentials : void 0,
155
- type: "password",
156
- username: credentials.username,
157
- password: credentials.password
173
+ ...payloadToSign,
174
+ sessionSignature
158
175
  })
159
176
  });
160
177
  const data = await response.json();
@@ -164,21 +181,34 @@ var WalletClient = class {
164
181
  return { username: data.username, token: data.token, expiresIn: data.expiresIn };
165
182
  }
166
183
  /**
167
- * Login with app token and session (scoped to an app)
184
+ * Login with session keypair (scoped to an app)
185
+ *
186
+ * The session must be approved by the app beforehand:
187
+ * 1. Client writes request to: immutable://inbox/{appKey}/sessions/{sessionPubkey} = 1
188
+ * 2. App approves by writing: mutable://accounts/{appKey}/sessions/{sessionPubkey} = 1
189
+ * 3. Client calls this method with the session keypair
190
+ *
191
+ * @param appKey - The app's public key
192
+ * @param session - Session keypair (generated via generateSessionKeypair)
193
+ * @param credentials - User credentials (username/password)
168
194
  */
169
- async loginWithTokenSession(appKey, tokenOrSession, sessionOrCredentials, maybeCredentials) {
170
- const session = typeof sessionOrCredentials === "string" && maybeCredentials ? sessionOrCredentials : tokenOrSession;
171
- const credentials = maybeCredentials || sessionOrCredentials;
172
- if (!session || typeof session !== "string") throw new Error("session is required");
195
+ async loginWithTokenSession(appKey, session, credentials) {
196
+ if (!session?.publicKeyHex || !session?.privateKeyHex) {
197
+ throw new Error("session keypair is required");
198
+ }
199
+ const payloadToSign = {
200
+ sessionPubkey: session.publicKeyHex,
201
+ type: "password",
202
+ username: credentials.username,
203
+ password: credentials.password
204
+ };
205
+ const sessionSignature = await signWithHex(session.privateKeyHex, payloadToSign);
173
206
  const response = await this.fetchImpl(this.buildAppKeyUrl("/auth/login", appKey), {
174
207
  method: "POST",
175
208
  headers: { "Content-Type": "application/json" },
176
209
  body: JSON.stringify({
177
- token: typeof tokenOrSession === "string" && maybeCredentials ? tokenOrSession : void 0,
178
- session,
179
- type: "password",
180
- username: credentials.username,
181
- password: credentials.password
210
+ ...payloadToSign,
211
+ sessionSignature
182
212
  })
183
213
  });
184
214
  const data = await response.json();
@@ -254,6 +284,8 @@ var WalletClient = class {
254
284
  * Proxy a write request through the wallet server
255
285
  * The server signs the write with its identity key
256
286
  * Requires active authentication session
287
+ *
288
+ * @returns ProxyWriteResponse - check `success` field for result
257
289
  */
258
290
  async proxyWrite(request) {
259
291
  if (!this.currentSession) {
@@ -272,17 +304,14 @@ var WalletClient = class {
272
304
  })
273
305
  });
274
306
  const data = await response.json();
275
- if (!response.ok || !data.success) {
276
- throw new Error(
277
- data.error || `Proxy write failed: ${response.statusText}`
278
- );
279
- }
280
307
  return data;
281
308
  }
282
309
  /**
283
310
  * Proxy a read request through the wallet server
284
311
  * The server decrypts encrypted data using user's encryption key
285
312
  * Requires active authentication session
313
+ *
314
+ * @returns ProxyReadResponse - check `success` field for result, `decrypted` for decrypted data
286
315
  */
287
316
  async proxyRead(request) {
288
317
  if (!this.currentSession) {
@@ -297,13 +326,33 @@ var WalletClient = class {
297
326
  }
298
327
  });
299
328
  const data = await response.json();
300
- if (!response.ok || !data.success) {
301
- throw new Error(
302
- data.error || `Proxy read failed: ${response.statusText}`
303
- );
304
- }
305
329
  return data;
306
330
  }
331
+ /**
332
+ * Proxy multiple read requests through the wallet server
333
+ * Reads multiple URIs in a single request (max 50 URIs)
334
+ * The server decrypts encrypted data using user's encryption key
335
+ * Requires active authentication session
336
+ *
337
+ * @returns ProxyReadMultiResponse - check `success` for overall result, `results` for per-URI results
338
+ */
339
+ async proxyReadMulti(request) {
340
+ if (!this.currentSession) {
341
+ throw new Error("Not authenticated. Please login first.");
342
+ }
343
+ const response = await this.fetchImpl(
344
+ `${this.walletServerUrl}${this.apiBasePath}/proxy/read-multi`,
345
+ {
346
+ method: "POST",
347
+ headers: {
348
+ "Content-Type": "application/json",
349
+ Authorization: `Bearer ${this.currentSession.token}`
350
+ },
351
+ body: JSON.stringify({ uris: request.uris })
352
+ }
353
+ );
354
+ return await response.json();
355
+ }
307
356
  /**
308
357
  * Convenience method: Get current user's public keys
309
358
  * Requires active authentication session
@@ -361,22 +410,37 @@ var WalletClient = class {
361
410
  };
362
411
  }
363
412
  /**
364
- * Login with Google OAuth (scoped to app token and session)
413
+ * Login with Google OAuth (scoped to app token and session keypair)
365
414
  * Returns session data with Google profile info - call setSession() to activate it
366
415
  *
416
+ * The session must be approved by the app beforehand.
417
+ *
418
+ * @param appKey - The app's public key
367
419
  * @param token - App token from app server
368
- * @param session - Session key from app server
420
+ * @param session - Session keypair (generated via generateSessionKeypair)
369
421
  * @param googleIdToken - Google ID token from Google Sign-In
370
422
  * @returns GoogleAuthSession with username, JWT token, and Google profile info
371
423
  */
372
424
  async loginWithGoogle(appKey, token, session, googleIdToken) {
373
425
  if (!token) throw new Error("token is required");
374
- if (!session) throw new Error("session is required");
426
+ if (!session?.publicKeyHex || !session?.privateKeyHex) {
427
+ throw new Error("session keypair is required");
428
+ }
375
429
  if (!googleIdToken) throw new Error("googleIdToken is required");
430
+ const payloadToSign = {
431
+ token,
432
+ sessionPubkey: session.publicKeyHex,
433
+ type: "google",
434
+ googleIdToken
435
+ };
436
+ const sessionSignature = await signWithHex(session.privateKeyHex, payloadToSign);
376
437
  const response = await this.fetchImpl(this.buildAppKeyUrl("/auth/login", appKey), {
377
438
  method: "POST",
378
439
  headers: { "Content-Type": "application/json" },
379
- body: JSON.stringify({ token, session, type: "google", googleIdToken })
440
+ body: JSON.stringify({
441
+ ...payloadToSign,
442
+ sessionSignature
443
+ })
380
444
  });
381
445
  const data = await response.json();
382
446
  if (!response.ok || !data.success) {
@@ -392,6 +456,13 @@ var WalletClient = class {
392
456
  };
393
457
  }
394
458
  };
459
+ async function generateSessionKeypair() {
460
+ const keyPair = await generateSigningKeyPair();
461
+ return {
462
+ publicKeyHex: keyPair.publicKeyHex,
463
+ privateKeyHex: keyPair.privateKeyHex
464
+ };
465
+ }
395
466
 
396
467
  // wallet/memory-client.ts
397
468
  async function generateTestServerKeys() {
@@ -546,14 +617,32 @@ var MemoryWalletClient = class _MemoryWalletClient {
546
617
  async login(_credentials) {
547
618
  throw new Error("Use loginWithTokenSession(appKey, session, credentials) \u2014 app token + session required");
548
619
  }
549
- async signupWithToken(appKey, tokenOrCredentials, maybeCredentials) {
550
- const credentials = typeof tokenOrCredentials === "string" ? maybeCredentials : tokenOrCredentials;
551
- if (!credentials) throw new Error("credentials are required");
552
- const response = await this.request("POST", `/auth/signup/${appKey}`, {
553
- token: typeof tokenOrCredentials === "string" ? tokenOrCredentials : void 0,
620
+ /**
621
+ * Sign up with session keypair (scoped to an app)
622
+ *
623
+ * The session must be approved by the app beforehand:
624
+ * 1. Client writes request to: immutable://inbox/{appKey}/sessions/{sessionPubkey} = 1
625
+ * 2. App approves by writing: mutable://accounts/{appKey}/sessions/{sessionPubkey} = 1
626
+ * 3. Client calls this method with the session keypair
627
+ *
628
+ * @param appKey - The app's public key
629
+ * @param session - Session keypair (generated via generateSessionKeypair)
630
+ * @param credentials - User credentials (username/password)
631
+ */
632
+ async signupWithToken(appKey, session, credentials) {
633
+ if (!session?.publicKeyHex || !session?.privateKeyHex) {
634
+ throw new Error("session keypair is required");
635
+ }
636
+ const payloadToSign = {
637
+ sessionPubkey: session.publicKeyHex,
554
638
  type: "password",
555
639
  username: credentials.username,
556
640
  password: credentials.password
641
+ };
642
+ const sessionSignature = await signWithHex(session.privateKeyHex, payloadToSign);
643
+ const response = await this.request("POST", `/auth/signup/${appKey}`, {
644
+ ...payloadToSign,
645
+ sessionSignature
557
646
  });
558
647
  const data = await response.json();
559
648
  if (!response.ok || !data.success) {
@@ -565,16 +654,32 @@ var MemoryWalletClient = class _MemoryWalletClient {
565
654
  expiresIn: data.expiresIn
566
655
  };
567
656
  }
568
- async loginWithTokenSession(appKey, tokenOrSession, sessionOrCredentials, maybeCredentials) {
569
- const session = typeof sessionOrCredentials === "string" && maybeCredentials ? sessionOrCredentials : tokenOrSession;
570
- const credentials = maybeCredentials || sessionOrCredentials;
571
- if (!session || typeof session !== "string") throw new Error("session is required");
572
- const response = await this.request("POST", `/auth/login/${appKey}`, {
573
- token: typeof tokenOrSession === "string" && maybeCredentials ? tokenOrSession : void 0,
574
- session,
657
+ /**
658
+ * Login with session keypair (scoped to an app)
659
+ *
660
+ * The session must be approved by the app beforehand:
661
+ * 1. Client writes request to: immutable://inbox/{appKey}/sessions/{sessionPubkey} = 1
662
+ * 2. App approves by writing: mutable://accounts/{appKey}/sessions/{sessionPubkey} = 1
663
+ * 3. Client calls this method with the session keypair
664
+ *
665
+ * @param appKey - The app's public key
666
+ * @param session - Session keypair (generated via generateSessionKeypair)
667
+ * @param credentials - User credentials (username/password)
668
+ */
669
+ async loginWithTokenSession(appKey, session, credentials) {
670
+ if (!session?.publicKeyHex || !session?.privateKeyHex) {
671
+ throw new Error("session keypair is required");
672
+ }
673
+ const payloadToSign = {
674
+ sessionPubkey: session.publicKeyHex,
575
675
  type: "password",
576
676
  username: credentials.username,
577
677
  password: credentials.password
678
+ };
679
+ const sessionSignature = await signWithHex(session.privateKeyHex, payloadToSign);
680
+ const response = await this.request("POST", `/auth/login/${appKey}`, {
681
+ ...payloadToSign,
682
+ sessionSignature
578
683
  });
579
684
  const data = await response.json();
580
685
  if (!response.ok || !data.success) {
@@ -672,20 +777,20 @@ var MemoryWalletClient = class _MemoryWalletClient {
672
777
  encrypt: request.encrypt
673
778
  });
674
779
  const data = await response.json();
675
- if (!response.ok || !data.success) {
676
- throw new Error(data.error || `Proxy write failed: ${response.statusText}`);
677
- }
678
780
  return data;
679
781
  }
680
782
  async proxyRead(request) {
681
783
  const url = `/proxy/read?uri=${encodeURIComponent(request.uri)}`;
682
784
  const response = await this.authRequest("GET", url);
683
785
  const data = await response.json();
684
- if (!response.ok || !data.success) {
685
- throw new Error(data.error || `Proxy read failed: ${response.statusText}`);
686
- }
687
786
  return data;
688
787
  }
788
+ async proxyReadMulti(request) {
789
+ const response = await this.authRequest("POST", "/proxy/read-multi", {
790
+ uris: request.uris
791
+ });
792
+ return await response.json();
793
+ }
689
794
  // ============================================================
690
795
  // Google OAuth (for completeness - may not work without real Google)
691
796
  // ============================================================
@@ -710,15 +815,34 @@ var MemoryWalletClient = class _MemoryWalletClient {
710
815
  picture: data.picture
711
816
  };
712
817
  }
818
+ /**
819
+ * Login with Google OAuth (scoped to app token and session keypair)
820
+ * Returns session data with Google profile info - call setSession() to activate it
821
+ *
822
+ * The session must be approved by the app beforehand.
823
+ *
824
+ * @param appKey - The app's public key
825
+ * @param token - App token from app server
826
+ * @param session - Session keypair (generated via generateSessionKeypair)
827
+ * @param googleIdToken - Google ID token from Google Sign-In
828
+ * @returns GoogleAuthSession with username, JWT token, and Google profile info
829
+ */
713
830
  async loginWithGoogle(appKey, token, session, googleIdToken) {
714
831
  if (!token) throw new Error("token is required");
715
- if (!session) throw new Error("session is required");
832
+ if (!session?.publicKeyHex || !session?.privateKeyHex) {
833
+ throw new Error("session keypair is required");
834
+ }
716
835
  if (!googleIdToken) throw new Error("googleIdToken is required");
717
- const response = await this.request("POST", `/auth/login/${appKey}`, {
836
+ const payloadToSign = {
718
837
  token,
719
- session,
838
+ sessionPubkey: session.publicKeyHex,
720
839
  type: "google",
721
840
  googleIdToken
841
+ };
842
+ const sessionSignature = await signWithHex(session.privateKeyHex, payloadToSign);
843
+ const response = await this.request("POST", `/auth/login/${appKey}`, {
844
+ ...payloadToSign,
845
+ sessionSignature
722
846
  });
723
847
  const data = await response.json();
724
848
  if (!response.ok || !data.success) {
@@ -750,8 +874,58 @@ var MemoryWalletClient = class _MemoryWalletClient {
750
874
  }
751
875
  };
752
876
 
877
+ // wallet/testing.ts
878
+ async function createTestEnvironment(config = {}) {
879
+ const schema = config.schema || createTestSchema();
880
+ const serverKeys = config.serverKeys || await generateTestServerKeys();
881
+ const backend = new MemoryClient({ schema });
882
+ const walletConfig = {
883
+ serverKeys,
884
+ jwtSecret: config.jwtSecret,
885
+ backend
886
+ };
887
+ const wallet = await MemoryWalletClient.create(walletConfig);
888
+ return {
889
+ backend,
890
+ wallet,
891
+ serverKeys,
892
+ async signupTestUser(appKey, username, password) {
893
+ const keypair = await generateSigningKeyPair();
894
+ const sessionKeypair = {
895
+ publicKeyHex: keypair.publicKeyHex,
896
+ privateKeyHex: keypair.privateKeyHex
897
+ };
898
+ const sessionUri = `mutable://accounts/${appKey}/sessions/${sessionKeypair.publicKeyHex}`;
899
+ await backend.write(sessionUri, 1);
900
+ const session = await wallet.signupWithToken(appKey, sessionKeypair, { username, password });
901
+ wallet.setSession(session);
902
+ const keys = await wallet.getPublicKeys(appKey);
903
+ return { session, keys, sessionKeypair };
904
+ },
905
+ async loginTestUser(appKey, username, password) {
906
+ const keypair = await generateSigningKeyPair();
907
+ const sessionKeypair = {
908
+ publicKeyHex: keypair.publicKeyHex,
909
+ privateKeyHex: keypair.privateKeyHex
910
+ };
911
+ const sessionUri = `mutable://accounts/${appKey}/sessions/${sessionKeypair.publicKeyHex}`;
912
+ await backend.write(sessionUri, 1);
913
+ const session = await wallet.loginWithTokenSession(appKey, sessionKeypair, { username, password });
914
+ wallet.setSession(session);
915
+ const keys = await wallet.getPublicKeys(appKey);
916
+ return { session, keys, sessionKeypair };
917
+ },
918
+ async cleanup() {
919
+ await backend.cleanup();
920
+ wallet.logout();
921
+ }
922
+ };
923
+ }
924
+
753
925
  export {
754
926
  WalletClient,
927
+ generateSessionKeypair,
755
928
  generateTestServerKeys,
756
- MemoryWalletClient
929
+ MemoryWalletClient,
930
+ createTestEnvironment
757
931
  };
@@ -1,4 +1,7 @@
1
1
  // clients/memory/mod.ts
2
+ function validateSchemaKey(key) {
3
+ return /^[a-z]+:\/\/[a-z0-9-]+$/.test(key);
4
+ }
2
5
  function target(uri, schema, storage) {
3
6
  const url = URL.parse(uri);
4
7
  const program = `${url.protocol}//${url.hostname}`;
@@ -19,7 +22,19 @@ function target(uri, schema, storage) {
19
22
  };
20
23
  }
21
24
  var MemoryClient = class {
25
+ /**
26
+ * Create a new MemoryClient
27
+ *
28
+ * @param config - Configuration with schema mapping
29
+ * @throws Error if schema keys are not in "protocol://hostname" format
30
+ */
22
31
  constructor(config) {
32
+ const invalidKeys = Object.keys(config.schema).filter((key) => !validateSchemaKey(key));
33
+ if (invalidKeys.length > 0) {
34
+ throw new Error(
35
+ `Invalid schema key format: ${invalidKeys.map((k) => `"${k}"`).join(", ")}. Keys must be in "protocol://hostname" format (e.g., "mutable://accounts", "immutable://data").`
36
+ );
37
+ }
23
38
  this.schema = config.schema;
24
39
  this.storage = config.storage || /* @__PURE__ */ new Map();
25
40
  this.cleanup();
@@ -86,6 +101,30 @@ var MemoryClient = class {
86
101
  record: current.value
87
102
  });
88
103
  }
104
+ async readMulti(uris) {
105
+ if (uris.length > 50) {
106
+ return {
107
+ success: false,
108
+ results: [],
109
+ summary: { total: uris.length, succeeded: 0, failed: uris.length }
110
+ };
111
+ }
112
+ const results = await Promise.all(
113
+ uris.map(async (uri) => {
114
+ const result = await this.read(uri);
115
+ if (result.success && result.record) {
116
+ return { uri, success: true, record: result.record };
117
+ }
118
+ return { uri, success: false, error: result.error || "Read failed" };
119
+ })
120
+ );
121
+ const succeeded = results.filter((r) => r.success).length;
122
+ return {
123
+ success: succeeded > 0,
124
+ results,
125
+ summary: { total: uris.length, succeeded, failed: uris.length - succeeded }
126
+ };
127
+ }
89
128
  list(uri, options) {
90
129
  const result = target(uri, this.schema, this.storage);
91
130
  if (!result.success) {
@@ -115,7 +154,7 @@ var MemoryClient = class {
115
154
  });
116
155
  }
117
156
  let items = Array.from(current.children.entries()).map(([key, child]) => ({
118
- uri: path.endsWith("/") ? `${program}://${path}${key}` : `${program}://${path}/${key}`,
157
+ uri: path.endsWith("/") ? `${program}${path}${key}` : `${program}${path}/${key}`,
119
158
  type: child.value ? "file" : "directory"
120
159
  }));
121
160
  if (options?.pattern) {
@@ -196,7 +235,19 @@ var MemoryClient = class {
196
235
  });
197
236
  }
198
237
  };
238
+ function createTestSchema() {
239
+ const acceptAll = async () => ({ valid: true });
240
+ return {
241
+ "mutable://accounts": acceptAll,
242
+ "mutable://open": acceptAll,
243
+ "mutable://data": acceptAll,
244
+ "immutable://accounts": acceptAll,
245
+ "immutable://open": acceptAll,
246
+ "immutable://data": acceptAll
247
+ };
248
+ }
199
249
 
200
250
  export {
201
- MemoryClient
251
+ MemoryClient,
252
+ createTestSchema
202
253
  };
@@ -102,6 +102,51 @@ var HttpClient = class {
102
102
  };
103
103
  }
104
104
  }
105
+ async readMulti(uris) {
106
+ if (uris.length > 50) {
107
+ return {
108
+ success: false,
109
+ results: [],
110
+ summary: { total: uris.length, succeeded: 0, failed: uris.length }
111
+ };
112
+ }
113
+ try {
114
+ const response = await this.request("/api/v1/read-multi", {
115
+ method: "POST",
116
+ body: JSON.stringify({ uris })
117
+ });
118
+ if (!response.ok) {
119
+ if (response.status === 404) {
120
+ return this.readMultiFallback(uris);
121
+ }
122
+ return {
123
+ success: false,
124
+ results: uris.map((uri) => ({ uri, success: false, error: response.statusText })),
125
+ summary: { total: uris.length, succeeded: 0, failed: uris.length }
126
+ };
127
+ }
128
+ return await response.json();
129
+ } catch (error) {
130
+ return this.readMultiFallback(uris);
131
+ }
132
+ }
133
+ async readMultiFallback(uris) {
134
+ const results = await Promise.all(
135
+ uris.map(async (uri) => {
136
+ const result = await this.read(uri);
137
+ if (result.success && result.record) {
138
+ return { uri, success: true, record: result.record };
139
+ }
140
+ return { uri, success: false, error: result.error || "Read failed" };
141
+ })
142
+ );
143
+ const succeeded = results.filter((r) => r.success).length;
144
+ return {
145
+ success: succeeded > 0,
146
+ results,
147
+ summary: { total: uris.length, succeeded, failed: uris.length - succeeded }
148
+ };
149
+ }
105
150
  async list(uri, options) {
106
151
  try {
107
152
  const { protocol, domain, path } = this.parseUri(uri);
@@ -104,6 +104,30 @@ var LocalStorageClient = class {
104
104
  };
105
105
  }
106
106
  }
107
+ async readMulti(uris) {
108
+ if (uris.length > 50) {
109
+ return {
110
+ success: false,
111
+ results: [],
112
+ summary: { total: uris.length, succeeded: 0, failed: uris.length }
113
+ };
114
+ }
115
+ const results = await Promise.all(
116
+ uris.map(async (uri) => {
117
+ const result = await this.read(uri);
118
+ if (result.success && result.record) {
119
+ return { uri, success: true, record: result.record };
120
+ }
121
+ return { uri, success: false, error: result.error || "Read failed" };
122
+ })
123
+ );
124
+ const succeeded = results.filter((r) => r.success).length;
125
+ return {
126
+ success: succeeded > 0,
127
+ results,
128
+ summary: { total: uris.length, succeeded, failed: uris.length - succeeded }
129
+ };
130
+ }
107
131
  async list(uri, options) {
108
132
  try {
109
133
  const { page = 1, limit = 50, pattern, sortBy = "name", sortOrder = "asc" } = options || {};
@@ -195,6 +195,35 @@ var WebSocketClient = class {
195
195
  };
196
196
  }
197
197
  }
198
+ async readMulti(uris) {
199
+ if (uris.length > 50) {
200
+ return {
201
+ success: false,
202
+ results: [],
203
+ summary: { total: uris.length, succeeded: 0, failed: uris.length }
204
+ };
205
+ }
206
+ try {
207
+ const result = await this.sendRequest("readMulti", { uris });
208
+ return result;
209
+ } catch {
210
+ const results = await Promise.all(
211
+ uris.map(async (uri) => {
212
+ const result = await this.read(uri);
213
+ if (result.success && result.record) {
214
+ return { uri, success: true, record: result.record };
215
+ }
216
+ return { uri, success: false, error: result.error || "Read failed" };
217
+ })
218
+ );
219
+ const succeeded = results.filter((r) => r.success).length;
220
+ return {
221
+ success: succeeded > 0,
222
+ results,
223
+ summary: { total: uris.length, succeeded, failed: uris.length - succeeded }
224
+ };
225
+ }
226
+ }
198
227
  async list(uri, options) {
199
228
  try {
200
229
  const result = await this.sendRequest("list", { uri, options });