@learncard/learn-cloud-plugin 2.3.29 → 2.3.31

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/src/plugin.ts ADDED
@@ -0,0 +1,711 @@
1
+ import { chunk } from 'lodash';
2
+ import { getClient, LearnCloudClient } from '@learncard/learn-cloud-client';
3
+ import { LearnCard } from '@learncard/core';
4
+ import { isEncrypted, resolveStorageReadResult } from '@learncard/helpers';
5
+ import {
6
+ CredentialRecord,
7
+ JWE,
8
+ PaginatedEncryptedRecordsType,
9
+ PaginatedEncryptedCredentialRecordsType,
10
+ StoredCredentialEnvelope,
11
+ StoredCredentialEnvelopeValidator,
12
+ VCValidator,
13
+ VPValidator,
14
+ isStoredCredentialEnvelope,
15
+ } from '@learncard/types';
16
+
17
+ import { LearnCloudPluginDependentMethods, LearnCloudPlugin } from './types';
18
+ import {
19
+ generateEncryptedRecord,
20
+ generateEncryptedFieldsArray,
21
+ generateJWE,
22
+ decryptJWE,
23
+ hash,
24
+ } from './helpers';
25
+
26
+ export * from './types';
27
+
28
+ const uint8ArrayToBase64Url = (bytes: Uint8Array): string => {
29
+ let binary = '';
30
+ for (let i = 0; i < bytes.length; i += 1) binary += String.fromCharCode(bytes[i]!);
31
+ const base64 =
32
+ typeof btoa === 'function'
33
+ ? btoa(binary)
34
+ : Buffer.from(binary, 'binary').toString('base64');
35
+ return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
36
+ };
37
+
38
+ type WireEnvelope = StoredCredentialEnvelope & { data: string };
39
+
40
+ const normalizeEnvelopeForTransport = <T>(
41
+ value: T
42
+ ): Exclude<T, StoredCredentialEnvelope> | WireEnvelope => {
43
+ if (!isStoredCredentialEnvelope(value)) {
44
+ return value as Exclude<T, StoredCredentialEnvelope>;
45
+ }
46
+ if (typeof value.data === 'string') return value as WireEnvelope;
47
+ return { ...value, data: uint8ArrayToBase64Url(value.data) };
48
+ };
49
+
50
+ const getLearnCloudClient = async (
51
+ url: string,
52
+ learnCard: LearnCard<any, 'id', LearnCloudPluginDependentMethods>
53
+ ) => {
54
+ return getClient(url, async challenge => {
55
+ const jwt = await learnCard.invoke.getDidAuthVp({ proofFormat: 'jwt', challenge });
56
+
57
+ if (typeof jwt !== 'string') throw new Error('Error getting DID-Auth-JWT!');
58
+
59
+ return jwt;
60
+ });
61
+ };
62
+
63
+ /**
64
+ * @group Plugins
65
+ */
66
+ export const getLearnCloudPlugin = async (
67
+ initialLearnCard: LearnCard<any, 'id', LearnCloudPluginDependentMethods>,
68
+ url: string,
69
+ unencryptedFields: string[] = [],
70
+ unencryptedCustomFields: string[] = [],
71
+ automaticallyAssociateDids = true
72
+ ): Promise<LearnCloudPlugin> => {
73
+ let learnCard = initialLearnCard;
74
+
75
+ learnCard.debug?.('Adding LearnCloud Plugin');
76
+
77
+ let client = await getLearnCloudClient(url, learnCard);
78
+
79
+ let dids: string[] = [learnCard.id.did()];
80
+
81
+ client.user.getDids.query().then(result => (dids = result));
82
+
83
+ let otherClients: Record<string, LearnCloudClient> = {};
84
+
85
+ const learnCloudDid = client.utilities.getDid.query();
86
+
87
+ const getOtherClient = async (url: string) => {
88
+ if (!otherClients[url]) otherClients[url] = await getLearnCloudClient(url, learnCard);
89
+
90
+ return otherClients[url]!;
91
+ };
92
+
93
+ const updateLearnCard = async (
94
+ _learnCard: LearnCard<any, 'id', LearnCloudPluginDependentMethods>
95
+ ) => {
96
+ const oldDid = learnCard.id.did();
97
+ const newDid = _learnCard.id.did();
98
+
99
+ if (oldDid !== newDid) {
100
+ if (!dids.includes(newDid) && automaticallyAssociateDids) {
101
+ const presentation = await _learnCard.invoke.getDidAuthVp();
102
+
103
+ await client.user.addDid.mutate({ presentation });
104
+
105
+ if (newDid.split(':')[1] === 'web') {
106
+ await client.user.setPrimaryDid.mutate({ presentation });
107
+ }
108
+
109
+ dids = await client.user.getDids.query();
110
+ }
111
+
112
+ client = await getLearnCloudClient(url, _learnCard);
113
+ }
114
+
115
+ learnCard = _learnCard;
116
+ };
117
+
118
+ return {
119
+ name: 'LearnCloud',
120
+ displayName: 'LearnCloud',
121
+ description: 'LearnCloud Integration',
122
+ methods: {
123
+ learnCloudCreate: async (_learnCard, document) => {
124
+ await updateLearnCard(_learnCard);
125
+
126
+ const item = await generateEncryptedRecord(
127
+ _learnCard,
128
+ document,
129
+ unencryptedCustomFields
130
+ );
131
+
132
+ return client.customStorage.create.mutate({
133
+ item: await generateJWE(_learnCard, await learnCloudDid, item),
134
+ });
135
+ },
136
+ learnCloudCreateMany: async (_learnCard, documents) => {
137
+ await updateLearnCard(_learnCard);
138
+
139
+ const items = await Promise.all(
140
+ documents.map(async document =>
141
+ generateEncryptedRecord(_learnCard, document, unencryptedCustomFields)
142
+ )
143
+ );
144
+
145
+ return client.customStorage.createMany.mutate({
146
+ items: await generateJWE(_learnCard, await learnCloudDid, items),
147
+ });
148
+ },
149
+ learnCloudRead: async (_learnCard, query, includeAssociatedDids) => {
150
+ await updateLearnCard(_learnCard);
151
+
152
+ const documents: Record<string, any>[] = [];
153
+
154
+ let result = await _learnCard.invoke.learnCloudReadPage(
155
+ query,
156
+ {},
157
+ includeAssociatedDids
158
+ );
159
+
160
+ documents.push(...result.records);
161
+
162
+ while (result.hasMore) {
163
+ result = await _learnCard.invoke.learnCloudReadPage(
164
+ query,
165
+ {},
166
+ includeAssociatedDids
167
+ );
168
+
169
+ documents.push(...result.records);
170
+ }
171
+
172
+ return documents;
173
+ },
174
+ learnCloudReadPage: async (
175
+ _learnCard,
176
+ query,
177
+ paginationOptions,
178
+ includeAssociatedDids
179
+ ) => {
180
+ await updateLearnCard(_learnCard);
181
+
182
+ if (!query) {
183
+ const jwe: JWE = (await client.customStorage.read.query({
184
+ includeAssociatedDids,
185
+ ...paginationOptions,
186
+ })) as any;
187
+
188
+ const encryptedRecords = isEncrypted(jwe)
189
+ ? await decryptJWE<PaginatedEncryptedRecordsType>(_learnCard, jwe)
190
+ : (jwe as PaginatedEncryptedRecordsType);
191
+
192
+ return {
193
+ ...encryptedRecords,
194
+ records: await Promise.all(
195
+ encryptedRecords.records.map(async record => ({
196
+ ...(await decryptJWE<CredentialRecord>(
197
+ _learnCard,
198
+ record.encryptedRecord
199
+ )),
200
+ _id: record._id,
201
+ }))
202
+ ),
203
+ };
204
+ }
205
+
206
+ const fields = await generateEncryptedFieldsArray(_learnCard, query, [
207
+ ...unencryptedCustomFields,
208
+ '_id',
209
+ ]);
210
+
211
+ const unencryptedEntries = Object.fromEntries(
212
+ Object.entries(query).filter(([key]) =>
213
+ [...unencryptedCustomFields, '_id'].includes(key)
214
+ )
215
+ );
216
+
217
+ const jwe: JWE = (await client.customStorage.read.query({
218
+ query: await generateJWE(_learnCard, await learnCloudDid, {
219
+ ...unencryptedEntries,
220
+ ...(fields.length > 0 ? { fields: { $in: fields } } : {}),
221
+ }),
222
+ ...paginationOptions,
223
+ includeAssociatedDids,
224
+ })) as any;
225
+
226
+ const encryptedRecords = isEncrypted(jwe)
227
+ ? await decryptJWE<PaginatedEncryptedRecordsType>(_learnCard, jwe)
228
+ : (jwe as PaginatedEncryptedRecordsType);
229
+
230
+ return {
231
+ ...encryptedRecords,
232
+ records: await Promise.all(
233
+ encryptedRecords.records.map(async record => ({
234
+ ...(await decryptJWE<CredentialRecord>(
235
+ _learnCard,
236
+ record.encryptedRecord
237
+ )),
238
+ _id: record._id,
239
+ }))
240
+ ),
241
+ };
242
+ },
243
+ learnCloudCount: async (_learnCard, query, includeAssociatedDids) => {
244
+ await updateLearnCard(_learnCard);
245
+
246
+ if (!query) return client.customStorage.count.query({ includeAssociatedDids });
247
+
248
+ const fields = await generateEncryptedFieldsArray(_learnCard, query, [
249
+ ...unencryptedCustomFields,
250
+ '_id',
251
+ ]);
252
+
253
+ const unencryptedEntries = Object.fromEntries(
254
+ Object.entries(query).filter(([key]) =>
255
+ [...unencryptedCustomFields, '_id'].includes(key)
256
+ )
257
+ );
258
+
259
+ return client.customStorage.count.query({
260
+ query: await generateJWE(_learnCard, await learnCloudDid, {
261
+ ...unencryptedEntries,
262
+ ...(fields.length > 0 ? { fields: { $in: fields } } : {}),
263
+ }),
264
+ includeAssociatedDids,
265
+ });
266
+ },
267
+ learnCloudUpdate: async (_learnCard, query, update) => {
268
+ await updateLearnCard(_learnCard);
269
+
270
+ const documents = await _learnCard.invoke.learnCloudRead(query);
271
+
272
+ const updates = await Promise.all(
273
+ documents.map(async document =>
274
+ client.customStorage.update.mutate({
275
+ query: await generateJWE(_learnCard, await learnCloudDid, {
276
+ _id: document._id,
277
+ }),
278
+ update: await generateJWE(
279
+ _learnCard,
280
+ await learnCloudDid,
281
+ await generateEncryptedRecord(
282
+ _learnCard,
283
+ { ...document, ...update },
284
+ unencryptedCustomFields
285
+ )
286
+ ),
287
+ })
288
+ )
289
+ );
290
+
291
+ return updates.reduce((sum, current) => current + sum, 0);
292
+ },
293
+ learnCloudDelete: async (_learnCard, query, includeAssociatedDids) => {
294
+ await updateLearnCard(_learnCard);
295
+
296
+ if (!query) return client.customStorage.delete.mutate({ includeAssociatedDids });
297
+
298
+ const fields = await generateEncryptedFieldsArray(_learnCard, query, [
299
+ ...unencryptedCustomFields,
300
+ '_id',
301
+ ]);
302
+
303
+ const unencryptedEntries = Object.fromEntries(
304
+ Object.entries(query).filter(([key]) =>
305
+ [...unencryptedCustomFields, '_id'].includes(key)
306
+ )
307
+ );
308
+
309
+ return client.customStorage.delete.mutate({
310
+ query: await generateJWE(_learnCard, await learnCloudDid, {
311
+ ...unencryptedEntries,
312
+ ...(fields.length > 0 ? { fields: { $in: fields } } : {}),
313
+ }),
314
+ includeAssociatedDids,
315
+ });
316
+ },
317
+ learnCloudBatchResolve: async (_learnCard, uris) => {
318
+ const results = await client.storage.batchResolve.query({ uris });
319
+
320
+ return Promise.all(
321
+ results.map(async result => {
322
+ if (!result) return null;
323
+
324
+ try {
325
+ const decryptedResult = await _learnCard.invoke.decryptDagJwe(result);
326
+
327
+ const parsed = await VCValidator.or(VPValidator)
328
+ .or(StoredCredentialEnvelopeValidator)
329
+ .parseAsync(decryptedResult);
330
+
331
+ const resolved = resolveStorageReadResult(parsed);
332
+ return resolved === undefined || isStoredCredentialEnvelope(resolved)
333
+ ? null
334
+ : resolved;
335
+ } catch (error) {
336
+ _learnCard.debug?.(error);
337
+
338
+ return null;
339
+ }
340
+ })
341
+ );
342
+ },
343
+ },
344
+ read: {
345
+ get: async (_learnCard, uri) => {
346
+ learnCard.debug?.('LearnCloud read.get', uri);
347
+
348
+ if (!uri) return undefined;
349
+
350
+ const parts = uri.split(':');
351
+
352
+ learnCard.debug?.('LearnCloud read.get parts:', parts);
353
+
354
+ if (parts.length !== 5) return undefined;
355
+
356
+ const [lc, method, uriUrl] = parts as [string, string, string, string, string];
357
+
358
+ if (lc !== 'lc' || method !== 'cloud') {
359
+ learnCard.debug?.('LearnCloud read.get not cloud URI!', { lc, method });
360
+
361
+ return undefined;
362
+ }
363
+
364
+ if (
365
+ uriUrl.replace(/https?:\/\//g, '') !==
366
+ url.replace(/https?:\/\//g, '').replace(/:/g, '%3A')
367
+ ) {
368
+ const fullUrl = (
369
+ uriUrl.startsWith('http')
370
+ ? uriUrl
371
+ : `http${
372
+ uriUrl.includes('http') || uriUrl.includes('localhost') ? '' : 's'
373
+ }://${uriUrl}`
374
+ ).replaceAll('%3A', ':');
375
+
376
+ learnCard.debug?.('LearnCloud read.get different LearnCloud!', {
377
+ uriUrl,
378
+ url,
379
+ fullUrl,
380
+ comparison: {
381
+ a: uriUrl.replace(/https?:\/\//g, ''),
382
+ b: url.replace(/https?:\/\//g, '').replace(/:/g, '%3A'),
383
+ },
384
+ });
385
+
386
+ const otherClient = await getOtherClient(fullUrl);
387
+
388
+ try {
389
+ const result = await otherClient.storage.resolve.query({ uri: uri });
390
+
391
+ learnCard.debug?.('LearnCloud read.get result', result);
392
+
393
+ const decryptedResult = await _learnCard.invoke.decryptDagJwe(result);
394
+
395
+ learnCard.debug?.('LearnCloud read.get decryptedResult', decryptedResult);
396
+
397
+ const parsed = await VCValidator.or(VPValidator)
398
+ .or(StoredCredentialEnvelopeValidator)
399
+ .parseAsync(decryptedResult);
400
+ return resolveStorageReadResult(parsed);
401
+ } catch (error) {
402
+ _learnCard.debug?.(error);
403
+ return undefined;
404
+ }
405
+ }
406
+
407
+ try {
408
+ const result = await client.storage.resolve.query({ uri: uri });
409
+
410
+ learnCard.debug?.('LearnCloud read.get result', result);
411
+
412
+ const decryptedResult = await _learnCard.invoke.decryptDagJwe(result);
413
+
414
+ learnCard.debug?.('LearnCloud read.get decryptedResult', decryptedResult);
415
+
416
+ const parsed = await VCValidator.or(VPValidator)
417
+ .or(StoredCredentialEnvelopeValidator)
418
+ .parseAsync(decryptedResult);
419
+ return resolveStorageReadResult(parsed);
420
+ } catch (error) {
421
+ _learnCard.debug?.(error);
422
+ return undefined;
423
+ }
424
+ },
425
+ },
426
+ store: {
427
+ upload: async (_learnCard, credential) => {
428
+ _learnCard.debug?.("learnCard.store['LearnCard Network'].upload");
429
+
430
+ return client.storage.store.mutate({
431
+ item: normalizeEnvelopeForTransport(credential),
432
+ });
433
+ },
434
+ uploadEncrypted: async (
435
+ _learnCard,
436
+ credential,
437
+ { recipients = [] } = { recipients: [] }
438
+ ) => {
439
+ _learnCard.debug?.("learnCard.store['LearnCard Network'].upload");
440
+
441
+ const jwe = await _learnCard.invoke.createDagJwe(
442
+ normalizeEnvelopeForTransport(credential),
443
+ [_learnCard.id.did(), ...recipients]
444
+ );
445
+
446
+ return client.storage.store.mutate({ item: jwe });
447
+ },
448
+ delete: async (_learnCard, uri) => {
449
+ _learnCard.debug?.("learnCard.store['LearnCloud'].delete", { uri });
450
+
451
+ let deleted = false;
452
+
453
+ try {
454
+ deleted = Boolean(await client.storage.delete.mutate({ uri }));
455
+ } catch (error) {
456
+ _learnCard.debug?.('LearnCloud store.delete storage.delete failed', error);
457
+ }
458
+
459
+ if (!deleted) return false;
460
+
461
+ try {
462
+ const records = await _learnCard.index.LearnCloud.get({ uri });
463
+ for (const record of records) {
464
+ await _learnCard.index.LearnCloud.remove(record.id, {
465
+ cache: 'skip-cache',
466
+ });
467
+ }
468
+ } catch (error) {
469
+ _learnCard.debug?.('LearnCloud store.delete index cleanup failed', error);
470
+ }
471
+
472
+ return true;
473
+ },
474
+ },
475
+ index: {
476
+ get: async (_learnCard, query) => {
477
+ await updateLearnCard(_learnCard);
478
+
479
+ const records: CredentialRecord[] = [];
480
+
481
+ let result: PaginatedEncryptedCredentialRecordsType = await (
482
+ _learnCard.index as any
483
+ ).LearnCloud.getPage(query);
484
+
485
+ records.push(...(result.records as any));
486
+
487
+ while (result.hasMore) {
488
+ result = await (_learnCard.index as any).LearnCloud.getPage(query, {
489
+ cursor: result.cursor,
490
+ });
491
+
492
+ records.push(...(result.records as any));
493
+ }
494
+
495
+ return records;
496
+ },
497
+ getPage: async (_learnCard, query, paginationOptions) => {
498
+ await updateLearnCard(_learnCard);
499
+
500
+ _learnCard.debug?.('LearnCloud index.getPaginated', query, paginationOptions);
501
+
502
+ const options = {
503
+ ...paginationOptions,
504
+ sort: ['newestFirst', 'oldestFirst'].includes(paginationOptions?.sort ?? '')
505
+ ? (paginationOptions?.sort as 'newestFirst' | 'oldestFirst')
506
+ : undefined,
507
+ };
508
+
509
+ if (!query) {
510
+ _learnCard.debug?.('LearnCloud index.get (no query)');
511
+ const jwe: JWE = (await client.index.get.query(options)) as any;
512
+
513
+ _learnCard.debug?.('LearnCloud index.get (no query response)', jwe);
514
+
515
+ const encryptedRecords = isEncrypted(jwe)
516
+ ? await decryptJWE<PaginatedEncryptedCredentialRecordsType>(_learnCard, jwe)
517
+ : (jwe as PaginatedEncryptedCredentialRecordsType);
518
+
519
+ _learnCard.debug?.(
520
+ 'LearnCloud index.get (no query encryptedRecords)',
521
+ encryptedRecords
522
+ );
523
+
524
+ return {
525
+ ...encryptedRecords,
526
+ records: await Promise.all(
527
+ encryptedRecords.records.map(async record =>
528
+ decryptJWE<CredentialRecord>(_learnCard, record.encryptedRecord)
529
+ )
530
+ ),
531
+ };
532
+ }
533
+
534
+ _learnCard.debug?.('LearnCloud index.get (query)');
535
+
536
+ const fields = await generateEncryptedFieldsArray(
537
+ _learnCard,
538
+ query,
539
+ unencryptedFields
540
+ );
541
+
542
+ _learnCard.debug?.('LearnCloud index.get (query fields)', fields);
543
+
544
+ const unencryptedEntries = Object.fromEntries(
545
+ Object.entries(query).filter(([key]) => unencryptedFields.includes(key))
546
+ );
547
+
548
+ _learnCard.debug?.(
549
+ 'LearnCloud index.get (query unencryptedEntries)',
550
+ unencryptedEntries
551
+ );
552
+
553
+ const jwe: JWE = (await client.index.get.query({
554
+ query: await generateJWE(_learnCard, await learnCloudDid, {
555
+ ...unencryptedEntries,
556
+ ...(fields.length > 0 ? { fields: { $in: fields } } : {}),
557
+ }),
558
+ ...options,
559
+ })) as any;
560
+
561
+ _learnCard.debug?.('LearnCloud index.get (query jwe)', jwe);
562
+
563
+ const encryptedRecords = isEncrypted(jwe)
564
+ ? await decryptJWE<PaginatedEncryptedCredentialRecordsType>(_learnCard, jwe)
565
+ : (jwe as PaginatedEncryptedCredentialRecordsType);
566
+
567
+ _learnCard.debug?.(
568
+ 'LearnCloud index.get (query encryptedRecords)',
569
+ encryptedRecords
570
+ );
571
+
572
+ return {
573
+ ...encryptedRecords,
574
+ records: await Promise.all(
575
+ encryptedRecords.records.map(async record =>
576
+ decryptJWE<CredentialRecord>(_learnCard, record.encryptedRecord)
577
+ )
578
+ ),
579
+ };
580
+ },
581
+ getCount: async (_learnCard, query) => {
582
+ await updateLearnCard(_learnCard);
583
+
584
+ if (!query) {
585
+ _learnCard.debug?.('LearnCloud index.count (no query)');
586
+ const jwe = await client.index.count.query();
587
+
588
+ _learnCard.debug?.('LearnCloud index.count (no query response)', jwe);
589
+
590
+ const count = isEncrypted(jwe as any)
591
+ ? await decryptJWE<number>(_learnCard, jwe as JWE)
592
+ : (jwe as number);
593
+
594
+ _learnCard.debug?.('LearnCloud index.count (no query count)', count);
595
+
596
+ return count;
597
+ }
598
+
599
+ _learnCard.debug?.('LearnCloud index.getCount (query)');
600
+
601
+ const fields = await generateEncryptedFieldsArray(
602
+ _learnCard,
603
+ query,
604
+ unencryptedFields
605
+ );
606
+
607
+ _learnCard.debug?.('LearnCloud index.getCount (query fields)', fields);
608
+
609
+ const unencryptedEntries = Object.fromEntries(
610
+ Object.entries(query).filter(([key]) => unencryptedFields.includes(key))
611
+ );
612
+
613
+ _learnCard.debug?.(
614
+ 'LearnCloud index.getCount (query unencryptedEntries)',
615
+ unencryptedEntries
616
+ );
617
+
618
+ const jwe = await client.index.count.query({
619
+ query: await generateJWE(_learnCard, await learnCloudDid, {
620
+ ...unencryptedEntries,
621
+ ...(fields.length > 0 ? { fields: { $in: fields } } : {}),
622
+ }),
623
+ });
624
+
625
+ _learnCard.debug?.('LearnCloud index.count (query response)', jwe);
626
+
627
+ const count = isEncrypted(jwe as any)
628
+ ? await decryptJWE<number>(_learnCard, jwe as JWE)
629
+ : (jwe as number);
630
+
631
+ _learnCard.debug?.('LearnCloud index.count (query count)', count);
632
+
633
+ return count;
634
+ },
635
+ add: async (_learnCard, record) => {
636
+ await updateLearnCard(_learnCard);
637
+
638
+ const id = record.id || _learnCard.invoke.crypto().randomUUID();
639
+
640
+ return client.index.add.mutate({
641
+ record: await generateJWE(_learnCard, await learnCloudDid, {
642
+ ...(await generateEncryptedRecord(
643
+ _learnCard,
644
+ { ...record, id },
645
+ unencryptedFields
646
+ )),
647
+ id: await hash(_learnCard, id),
648
+ }),
649
+ });
650
+ },
651
+ addMany: async (_learnCard, _records) => {
652
+ await updateLearnCard(_learnCard);
653
+
654
+ const results = await Promise.all(
655
+ chunk(_records, 25).map(async batch => {
656
+ const records = await Promise.all(
657
+ batch.map(async record => {
658
+ const id = record.id || _learnCard.invoke.crypto().randomUUID();
659
+
660
+ return {
661
+ ...(await generateEncryptedRecord(
662
+ _learnCard,
663
+ { ...record, id },
664
+ unencryptedFields
665
+ )),
666
+ id: await hash(_learnCard, id),
667
+ };
668
+ })
669
+ );
670
+
671
+ return client.index.addMany.mutate({
672
+ records: await generateJWE(_learnCard, await learnCloudDid, records),
673
+ });
674
+ })
675
+ );
676
+
677
+ return results.every(Boolean);
678
+ },
679
+ update: async (_learnCard, id, updates) => {
680
+ await updateLearnCard(_learnCard);
681
+
682
+ const records = await _learnCard.index.LearnCloud.get({ id });
683
+
684
+ if (records.length !== 1) return false;
685
+
686
+ const record = records[0];
687
+
688
+ const newRecord = { ...record, ...updates };
689
+
690
+ return client.index.update.mutate({
691
+ id: await hash(_learnCard, id),
692
+ updates: await generateJWE(
693
+ _learnCard,
694
+ await learnCloudDid,
695
+ await generateEncryptedRecord(_learnCard, newRecord, unencryptedFields)
696
+ ),
697
+ });
698
+ },
699
+ remove: async (_learnCard, id) => {
700
+ await updateLearnCard(_learnCard);
701
+
702
+ return client.index.remove.mutate({ id: await hash(_learnCard, id) });
703
+ },
704
+ removeAll: async _learnCard => {
705
+ await updateLearnCard(_learnCard);
706
+
707
+ return client.index.removeAll.mutate();
708
+ },
709
+ },
710
+ };
711
+ };