@learncard/core 9.4.24 → 9.4.26

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.
@@ -0,0 +1,832 @@
1
+ import { CredentialRecord } from '@learncard/types';
2
+ import { Query } from 'sift';
3
+
4
+ import {
5
+ Plugin,
6
+ LearnCard,
7
+ GetPluginMethods,
8
+ AddImplicitLearnCardArgument,
9
+ } from '../../types/wallet';
10
+ import {
11
+ ControlPlane,
12
+ GetPlanesForPlugins,
13
+ GetPlaneProviders,
14
+ IndexPlane,
15
+ LearnCardReadPlane,
16
+ LearnCardStorePlane,
17
+ LearnCardIndexPlane,
18
+ LearnCardCachePlane,
19
+ LearnCardIdPlane,
20
+ LearnCardContextPlane,
21
+ StorePlane,
22
+ PlaneOptions,
23
+ } from '../../types/planes';
24
+ import {
25
+ findFirstResult,
26
+ pluginImplementsPlane,
27
+ learnCardImplementsPlane,
28
+ mapObject,
29
+ isFulfilledAndNotEmpty,
30
+ uniqBy,
31
+ } from './helpers';
32
+
33
+ const getPlaneProviders = <Plugins extends Plugin[], Plane extends ControlPlane>(
34
+ plugins: Plugins,
35
+ plane: Plane
36
+ ): GetPlaneProviders<Plugins, Plane> => {
37
+ return plugins.reduce((providers, plugin) => {
38
+ if (plane in plugin) {
39
+ providers[plugin.name as keyof typeof providers] = {
40
+ name: plugin.name,
41
+ displayName: plugin.displayName,
42
+ description: plugin.description,
43
+ } as any;
44
+ }
45
+
46
+ return providers;
47
+ }, {} as GetPlaneProviders<Plugins, Plane>);
48
+ };
49
+
50
+ const bindLearnCardToFunctionsObject = (
51
+ learnCard: LearnCard<any, any, any>,
52
+ obj: Record<string, (...args: any[]) => any>
53
+ ) => mapObject(obj, method => method.bind(learnCard, learnCard));
54
+
55
+ const addPluginToLearnCard = async <NewPlugin extends Plugin, Plugins extends Plugin[]>(
56
+ learnCard: LearnCard<Plugins>,
57
+ plugin: NewPlugin
58
+ ): Promise<LearnCard<[...Plugins, NewPlugin]>> => {
59
+ learnCard.debug?.('Adding plugin', plugin.name);
60
+ // eslint-disable-next-line
61
+ return (generateLearnCard as any)({
62
+ plugins: [...learnCard.plugins, plugin],
63
+ debug: learnCard.debug,
64
+ });
65
+ };
66
+
67
+ const generateReadPlane = <
68
+ Plugins extends Plugin[] = [],
69
+ ControlPlanes extends GetPlanesForPlugins<Plugins> = GetPlanesForPlugins<Plugins>,
70
+ PluginMethods = GetPluginMethods<Plugins>
71
+ >(
72
+ learnCard:
73
+ | LearnCard<Plugins, ControlPlanes, PluginMethods>
74
+ | LearnCard<Plugins, 'cache', PluginMethods>
75
+ ): LearnCardReadPlane<Plugins> => {
76
+ return {
77
+ get: async (uri, { cache = 'cache-first' } = {}) => {
78
+ learnCard.debug?.('learnCard.read.get', uri);
79
+
80
+ if (!uri) return undefined;
81
+
82
+ if (cache === 'cache-only' && !learnCardImplementsPlane(learnCard, 'cache')) {
83
+ throw new Error('Cannot read from cache. Cache Plane is not implemented!');
84
+ }
85
+
86
+ if (learnCardImplementsPlane(learnCard, 'cache') && cache !== 'skip-cache') {
87
+ const cachedResponse = await learnCard.cache.getVc(uri);
88
+
89
+ if (cachedResponse) {
90
+ if (cache === 'cache-first' && learnCardImplementsPlane(learnCard, 'read')) {
91
+ learnCard.read
92
+ .get(uri, { cache: 'skip-cache' })
93
+ .then(res => learnCard.cache.setVc(uri, res));
94
+ }
95
+
96
+ return cachedResponse;
97
+ }
98
+ }
99
+
100
+ const results = await Promise.allSettled(
101
+ learnCard.plugins.map(async plugin => {
102
+ if (!pluginImplementsPlane(plugin, 'read')) {
103
+ throw new Error('Plugin is not a Read Plugin');
104
+ }
105
+
106
+ return plugin.read.get(learnCard as any, uri);
107
+ })
108
+ );
109
+
110
+ const vc = results.find(isFulfilledAndNotEmpty)?.value;
111
+
112
+ if (vc && learnCardImplementsPlane(learnCard, 'cache') && cache !== 'skip-cache') {
113
+ await learnCard.cache.setVc(uri, vc);
114
+ }
115
+
116
+ return vc;
117
+ },
118
+ providers: getPlaneProviders(learnCard.plugins, 'read'),
119
+ };
120
+ };
121
+
122
+ const addCachingToStorePlane = <
123
+ ControlPlanes extends ControlPlane = never,
124
+ Methods extends Record<string, (...args: any[]) => any> = Record<never, never>,
125
+ DependentControlPlanes extends ControlPlane = never,
126
+ DependentMethods extends Record<string, (...args: any[]) => any> = Record<never, never>
127
+ >(
128
+ plane: AddImplicitLearnCardArgument<
129
+ StorePlane,
130
+ ControlPlanes,
131
+ Methods,
132
+ DependentControlPlanes,
133
+ DependentMethods
134
+ >
135
+ ): AddImplicitLearnCardArgument<
136
+ StorePlane,
137
+ ControlPlanes,
138
+ Methods,
139
+ DependentControlPlanes,
140
+ DependentMethods
141
+ > => ({
142
+ upload: async (_learnCard, vc, { cache = 'cache-first' } = {}) => {
143
+ const uri = await plane.upload(_learnCard, vc);
144
+
145
+ if (cache !== 'skip-cache' && learnCardImplementsPlane(_learnCard, 'cache')) {
146
+ await _learnCard.cache.setVc(uri, vc);
147
+ }
148
+
149
+ return uri;
150
+ },
151
+ // TODO: Add caching to uploadMany
152
+ ...('uploadMany' in plane ? { uploadMany: plane.uploadMany } : {}),
153
+ ...('uploadEncrypted' in plane
154
+ ? {
155
+ uploadEncrypted: async (_learnCard, vc, params, { cache = 'cache-first' } = {}) => {
156
+ const uri = await plane.uploadEncrypted?.(_learnCard, vc, params);
157
+
158
+ if (cache !== 'skip-cache' && learnCardImplementsPlane(_learnCard, 'cache')) {
159
+ await _learnCard.cache.setVc(uri, vc);
160
+ }
161
+
162
+ return uri;
163
+ },
164
+ }
165
+ : {}),
166
+ ...('delete' in plane
167
+ ? {
168
+ delete: async (_learnCard, uri, { cache = 'cache-first' } = {}) => {
169
+ const result = await plane.delete?.(_learnCard, uri);
170
+
171
+ if (
172
+ result &&
173
+ cache !== 'skip-cache' &&
174
+ learnCardImplementsPlane(_learnCard, 'cache')
175
+ ) {
176
+ await _learnCard.cache.setVc(uri, undefined);
177
+ }
178
+
179
+ return result;
180
+ },
181
+ }
182
+ : {}),
183
+ });
184
+
185
+ const generateStorePlane = <
186
+ Plugins extends Plugin[] = [],
187
+ ControlPlanes extends GetPlanesForPlugins<Plugins> = GetPlanesForPlugins<Plugins>,
188
+ PluginMethods = GetPluginMethods<Plugins>
189
+ >(
190
+ learnCard: LearnCard<Plugins, ControlPlanes, PluginMethods>
191
+ ): LearnCardStorePlane<Plugins> => {
192
+ const pluginPlanes = learnCard.plugins.reduce((planes, plugin) => {
193
+ if (pluginImplementsPlane(plugin, 'store')) {
194
+ planes[plugin.name as keyof typeof planes] = bindLearnCardToFunctionsObject(
195
+ learnCard,
196
+ addCachingToStorePlane(plugin.store)
197
+ ) as any;
198
+ }
199
+
200
+ return planes;
201
+ }, {} as LearnCardStorePlane<Plugins>);
202
+
203
+ return { ...pluginPlanes, providers: getPlaneProviders(learnCard.plugins, 'store') };
204
+ };
205
+
206
+ const addCachingToIndexPlane = <
207
+ ControlPlanes extends ControlPlane = never,
208
+ Methods extends Record<string, (...args: any[]) => any> = Record<never, never>,
209
+ DependentControlPlanes extends ControlPlane = never,
210
+ DependentMethods extends Record<string, (...args: any[]) => any> = Record<never, never>
211
+ >(
212
+ plane: AddImplicitLearnCardArgument<
213
+ IndexPlane,
214
+ ControlPlanes,
215
+ Methods,
216
+ DependentControlPlanes,
217
+ DependentMethods
218
+ >,
219
+ name: string
220
+ ): AddImplicitLearnCardArgument<
221
+ IndexPlane,
222
+ ControlPlanes,
223
+ Methods,
224
+ DependentControlPlanes,
225
+ DependentMethods
226
+ > => ({
227
+ get: async (_learnCard, query, { cache = 'cache-first' } = {}) => {
228
+ if (cache === 'cache-only' && !learnCardImplementsPlane(_learnCard, 'cache')) {
229
+ throw new Error('Cannot read from cache. Cache Plane is not implemented!');
230
+ }
231
+
232
+ if (learnCardImplementsPlane(_learnCard, 'cache') && cache !== 'skip-cache') {
233
+ const cachedResponse = await _learnCard.cache.getIndex(name, query ?? {});
234
+
235
+ if (cachedResponse) {
236
+ if (cache === 'cache-first') {
237
+ plane.get(_learnCard, query, { cache: 'skip-cache' }).then(res => {
238
+ _learnCard.cache.setIndex(name, query ?? {}, res);
239
+ });
240
+ }
241
+
242
+ return cachedResponse;
243
+ }
244
+ }
245
+
246
+ const list = await plane.get(_learnCard, query);
247
+
248
+ if (list && learnCardImplementsPlane(_learnCard, 'cache') && cache !== 'skip-cache') {
249
+ await _learnCard.cache.setIndex(name, query ?? {}, list);
250
+ }
251
+
252
+ return list;
253
+ },
254
+ ...(plane.getPage
255
+ ? {
256
+ getPage: async (
257
+ _learnCard,
258
+ query,
259
+ paginationOptions,
260
+ { cache = 'cache-first' } = {}
261
+ ) => {
262
+ if (cache === 'cache-only' && !learnCardImplementsPlane(_learnCard, 'cache')) {
263
+ throw new Error('Cannot read from cache. Cache Plane is not implemented!');
264
+ }
265
+
266
+ if (learnCardImplementsPlane(_learnCard, 'cache') && cache !== 'skip-cache') {
267
+ const cachedResponse = await _learnCard.cache.getIndexPage(
268
+ name,
269
+ query ?? {},
270
+ paginationOptions
271
+ );
272
+
273
+ if (cachedResponse) {
274
+ if (cache === 'cache-first') {
275
+ plane
276
+ .getPage?.(_learnCard, query, paginationOptions, {
277
+ cache: 'skip-cache',
278
+ })
279
+ .then((res: any) => {
280
+ _learnCard.cache.setIndexPage(
281
+ name,
282
+ query ?? {},
283
+ res,
284
+ paginationOptions
285
+ );
286
+ });
287
+ }
288
+
289
+ return cachedResponse;
290
+ }
291
+ }
292
+
293
+ const result = await plane.getPage?.(_learnCard, query, paginationOptions);
294
+
295
+ if (
296
+ result &&
297
+ learnCardImplementsPlane(_learnCard, 'cache') &&
298
+ cache !== 'skip-cache'
299
+ ) {
300
+ await _learnCard.cache.setIndexPage(
301
+ name,
302
+ query ?? {},
303
+ result,
304
+ paginationOptions
305
+ );
306
+ }
307
+
308
+ return result;
309
+ },
310
+ }
311
+ : {}),
312
+ ...(plane.getCount
313
+ ? {
314
+ getCount: async (_learnCard, query, { cache = 'cache-first' } = {}) => {
315
+ if (cache === 'cache-only' && !learnCardImplementsPlane(_learnCard, 'cache')) {
316
+ throw new Error('Cannot read from cache. Cache Plane is not implemented!');
317
+ }
318
+
319
+ if (learnCardImplementsPlane(_learnCard, 'cache') && cache !== 'skip-cache') {
320
+ const cachedResponse = await _learnCard.cache.getIndexCount?.(
321
+ name,
322
+ query ?? {}
323
+ );
324
+
325
+ if (cachedResponse) {
326
+ if (cache === 'cache-first') {
327
+ plane
328
+ .getCount?.(_learnCard, query, {
329
+ cache: 'skip-cache',
330
+ })
331
+ .then((res: number) =>
332
+ _learnCard.cache.setIndexCount?.(name, query ?? {}, res)
333
+ );
334
+ }
335
+
336
+ return cachedResponse;
337
+ }
338
+ }
339
+
340
+ const result = await plane.getCount?.(_learnCard, query);
341
+
342
+ if (
343
+ result &&
344
+ learnCardImplementsPlane(_learnCard, 'cache') &&
345
+ cache !== 'skip-cache'
346
+ ) {
347
+ await _learnCard.cache.setIndexCount?.(name, query ?? {}, result);
348
+ }
349
+
350
+ return result;
351
+ },
352
+ }
353
+ : {}),
354
+ add: async (_learnCard, record, { cache = 'cache-first' } = {}) => {
355
+ const result = await plane.add(_learnCard, record);
356
+ if (cache !== 'skip-cache' && learnCardImplementsPlane(_learnCard, 'cache')) {
357
+ await _learnCard.cache.flushIndex();
358
+ }
359
+
360
+ return result;
361
+ },
362
+ ...(plane.addMany
363
+ ? {
364
+ addMany: async (_learnCard, records, { cache = 'cache-first' } = {}) => {
365
+ const result = await plane.addMany?.(_learnCard, records);
366
+
367
+ if (cache !== 'skip-cache' && learnCardImplementsPlane(_learnCard, 'cache')) {
368
+ await _learnCard.cache.flushIndex();
369
+ }
370
+
371
+ return result;
372
+ },
373
+ }
374
+ : {}),
375
+ update: async (_learnCard, id, update, { cache = 'cache-first' } = {}) => {
376
+ const result = await plane.update(_learnCard, id, update);
377
+
378
+ if (cache !== 'skip-cache' && learnCardImplementsPlane(_learnCard, 'cache')) {
379
+ await _learnCard.cache.flushIndex();
380
+ }
381
+
382
+ return result;
383
+ },
384
+ remove: async (_learnCard, id, { cache = 'cache-first' } = {}) => {
385
+ const result = await plane.remove(_learnCard, id);
386
+
387
+ if (cache !== 'skip-cache' && learnCardImplementsPlane(_learnCard, 'cache')) {
388
+ await _learnCard.cache.flushIndex();
389
+ }
390
+
391
+ return result;
392
+ },
393
+ ...(plane.removeAll
394
+ ? {
395
+ removeAll: async (_learnCard, { cache = 'cache-first' } = {}) => {
396
+ const result = await plane.removeAll?.(_learnCard);
397
+
398
+ if (cache !== 'skip-cache' && learnCardImplementsPlane(_learnCard, 'cache')) {
399
+ await _learnCard.cache.flushIndex();
400
+ }
401
+
402
+ return result;
403
+ },
404
+ }
405
+ : {}),
406
+ });
407
+
408
+ const generateIndexPlane = <
409
+ Plugins extends Plugin[] = [],
410
+ ControlPlanes extends GetPlanesForPlugins<Plugins> = GetPlanesForPlugins<Plugins>,
411
+ PluginMethods = GetPluginMethods<Plugins>
412
+ >(
413
+ learnCard: LearnCard<Plugins, ControlPlanes, PluginMethods>
414
+ ): LearnCardIndexPlane<Plugins> => {
415
+ const individualPlanes = learnCard.plugins.reduce<LearnCardIndexPlane<Plugins>>(
416
+ (planes, plugin) => {
417
+ if (pluginImplementsPlane(plugin, 'index')) {
418
+ planes[plugin.name as keyof typeof planes] = bindLearnCardToFunctionsObject(
419
+ learnCard,
420
+ addCachingToIndexPlane(plugin.index, plugin.name)
421
+ ) as any;
422
+ }
423
+
424
+ return planes;
425
+ },
426
+ {} as LearnCardIndexPlane<Plugins>
427
+ );
428
+
429
+ const all: Pick<IndexPlane, 'get'> = {
430
+ get: async <Metadata extends Record<string, any> = Record<never, never>>(
431
+ query?: Partial<Query<CredentialRecord<Metadata>>>,
432
+ { cache = 'cache-first' }: PlaneOptions = {}
433
+ ) => {
434
+ learnCard.debug?.('learnCard.index.all.get');
435
+
436
+ if (cache === 'cache-only' && !learnCardImplementsPlane(learnCard, 'cache')) {
437
+ throw new Error('Cannot read from cache. Cache Plane is not implemented!');
438
+ }
439
+
440
+ if (learnCardImplementsPlane(learnCard, 'cache') && cache !== 'skip-cache') {
441
+ const cachedResponse = await learnCard.cache.getIndex<Metadata>('all', query ?? {});
442
+
443
+ if (cachedResponse) {
444
+ if (cache === 'cache-first' && learnCardImplementsPlane(learnCard, 'index')) {
445
+ learnCard.index.all
446
+ .get(query, { cache: 'skip-cache' })
447
+ .then(res => learnCard.cache.setIndex('all', query ?? {}, res));
448
+ }
449
+
450
+ return cachedResponse;
451
+ }
452
+ }
453
+
454
+ const resultsWithDuplicates = (
455
+ await Promise.all(
456
+ learnCard.plugins.map(async plugin => {
457
+ if (!pluginImplementsPlane(plugin, 'index')) return [];
458
+
459
+ return plugin.index.get(learnCard as any, query) as Promise<
460
+ CredentialRecord<Metadata>[]
461
+ >;
462
+ })
463
+ )
464
+ ).flat();
465
+
466
+ const results = uniqBy(resultsWithDuplicates, 'id');
467
+
468
+ if (results && learnCardImplementsPlane(learnCard, 'cache') && cache !== 'skip-cache') {
469
+ await learnCard.cache.setIndex('all', query ?? {}, results);
470
+ }
471
+
472
+ return results;
473
+ },
474
+ };
475
+
476
+ return { ...individualPlanes, all, providers: getPlaneProviders(learnCard.plugins, 'index') };
477
+ };
478
+
479
+ const generateCachePlane = <
480
+ Plugins extends Plugin[] = [],
481
+ ControlPlanes extends GetPlanesForPlugins<Plugins> = GetPlanesForPlugins<Plugins>,
482
+ PluginMethods = GetPluginMethods<Plugins>
483
+ >(
484
+ learnCard: LearnCard<Plugins, ControlPlanes, PluginMethods>
485
+ ): LearnCardCachePlane<Plugins> => {
486
+ return {
487
+ getIndex: async <Metadata extends Record<string, any> = Record<never, never>>(
488
+ name: string,
489
+ query: Partial<Query<CredentialRecord<Metadata>>>
490
+ ) => {
491
+ learnCard.debug?.('learnCard.cache.getIndex');
492
+
493
+ try {
494
+ const results = await Promise.allSettled(
495
+ learnCard.plugins.map(async plugin => {
496
+ if (!pluginImplementsPlane(plugin, 'cache')) {
497
+ throw new Error('Plugin is not a Cache Plugin');
498
+ }
499
+
500
+ return plugin.cache.getIndex(learnCard as any, name, query) as Promise<
501
+ CredentialRecord<Metadata>[]
502
+ >;
503
+ })
504
+ );
505
+
506
+ const index = results.find(isFulfilledAndNotEmpty)?.value;
507
+
508
+ return index;
509
+ } catch (error) {
510
+ return undefined;
511
+ }
512
+ },
513
+ setIndex: async (name, query, value) => {
514
+ learnCard.debug?.('learnCard.cache.setIndex');
515
+
516
+ const result = await Promise.allSettled(
517
+ learnCard.plugins.map(async plugin => {
518
+ if (!pluginImplementsPlane(plugin, 'cache')) {
519
+ throw new Error('Plugin is not a Cache Plugin');
520
+ }
521
+
522
+ return plugin.cache.setIndex(learnCard as any, name, query, value);
523
+ })
524
+ );
525
+
526
+ return result.some(promiseResult => promiseResult.status === 'fulfilled');
527
+ },
528
+ getIndexPage: async <Metadata extends Record<string, any> = Record<never, never>>(
529
+ name: string,
530
+ query: Partial<Query<CredentialRecord<Metadata>>>,
531
+ paginationOptions?: { limit?: number; cursor?: string }
532
+ ) => {
533
+ learnCard.debug?.('learnCard.cache.getIndex');
534
+
535
+ try {
536
+ const results = await Promise.allSettled(
537
+ learnCard.plugins.map(async plugin => {
538
+ if (!pluginImplementsPlane(plugin, 'cache')) {
539
+ throw new Error('Plugin is not a Cache Plugin');
540
+ }
541
+
542
+ return plugin.cache.getIndexPage(
543
+ learnCard as any,
544
+ name,
545
+ query,
546
+ paginationOptions
547
+ ) as Promise<
548
+ | {
549
+ records: CredentialRecord<Metadata>[];
550
+ hasMore: boolean;
551
+ cursor?: string;
552
+ }
553
+ | undefined
554
+ >;
555
+ })
556
+ );
557
+
558
+ const index = results.find(isFulfilledAndNotEmpty)?.value;
559
+
560
+ return index;
561
+ } catch (error) {
562
+ return undefined;
563
+ }
564
+ },
565
+ setIndexPage: async (name, query, value, paginationOptions) => {
566
+ learnCard.debug?.('learnCard.cache.setIndex');
567
+
568
+ const result = await Promise.allSettled(
569
+ learnCard.plugins.map(async plugin => {
570
+ if (!pluginImplementsPlane(plugin, 'cache')) {
571
+ throw new Error('Plugin is not a Cache Plugin');
572
+ }
573
+
574
+ return plugin.cache.setIndexPage(
575
+ learnCard as any,
576
+ name,
577
+ query,
578
+ value,
579
+ paginationOptions
580
+ );
581
+ })
582
+ );
583
+
584
+ return result.some(promiseResult => promiseResult.status === 'fulfilled');
585
+ },
586
+ getIndexCount: async <Metadata extends Record<string, any> = Record<never, never>>(
587
+ name: string,
588
+ query: Partial<Query<CredentialRecord<Metadata>>>
589
+ ) => {
590
+ learnCard.debug?.('learnCard.cache.getIndex');
591
+
592
+ try {
593
+ const results = await Promise.allSettled(
594
+ learnCard.plugins.map(async plugin => {
595
+ if (!pluginImplementsPlane(plugin, 'cache')) {
596
+ throw new Error('Plugin is not a Cache Plugin');
597
+ }
598
+
599
+ return plugin.cache.getIndexCount?.(
600
+ learnCard as any,
601
+ name,
602
+ query
603
+ ) as Promise<number | undefined>;
604
+ })
605
+ );
606
+
607
+ const index = results.find(isFulfilledAndNotEmpty)?.value;
608
+
609
+ return index;
610
+ } catch (error) {
611
+ return undefined;
612
+ }
613
+ },
614
+ setIndexCount: async (name, query, value) => {
615
+ learnCard.debug?.('learnCard.cache.setIndex');
616
+
617
+ const result = await Promise.allSettled(
618
+ learnCard.plugins.map(async plugin => {
619
+ if (!pluginImplementsPlane(plugin, 'cache')) {
620
+ throw new Error('Plugin is not a Cache Plugin');
621
+ }
622
+
623
+ return plugin.cache.setIndexCount?.(learnCard as any, name, query, value);
624
+ })
625
+ );
626
+
627
+ return result.some(promiseResult => promiseResult.status === 'fulfilled');
628
+ },
629
+ flushIndex: async () => {
630
+ learnCard.debug?.('learnCard.cache.flushIndex');
631
+
632
+ const result = await Promise.allSettled(
633
+ learnCard.plugins.map(async plugin => {
634
+ if (!pluginImplementsPlane(plugin, 'cache')) {
635
+ throw new Error('Plugin is not a Cache Plugin');
636
+ }
637
+
638
+ return plugin.cache.flushIndex(learnCard as any);
639
+ })
640
+ );
641
+
642
+ return result.some(promiseResult => promiseResult.status === 'fulfilled');
643
+ },
644
+ getVc: async uri => {
645
+ learnCard.debug?.('learnCard.cache.getVc');
646
+
647
+ try {
648
+ const results = await Promise.allSettled(
649
+ learnCard.plugins.map(async plugin => {
650
+ if (!pluginImplementsPlane(plugin, 'cache')) {
651
+ throw new Error('Plugin is not a Cache Plugin');
652
+ }
653
+
654
+ return plugin.cache.getVc(learnCard as any, uri);
655
+ })
656
+ );
657
+
658
+ const vc = results.find(isFulfilledAndNotEmpty)?.value;
659
+
660
+ return vc;
661
+ } catch (error) {
662
+ return undefined;
663
+ }
664
+ },
665
+ setVc: async (uri, value) => {
666
+ learnCard.debug?.('learnCard.cache.setVc');
667
+
668
+ const result = await Promise.allSettled(
669
+ learnCard.plugins.map(async plugin => {
670
+ if (!pluginImplementsPlane(plugin, 'cache')) {
671
+ throw new Error('Plugin is not a Cache Plugin');
672
+ }
673
+
674
+ return plugin.cache.setVc(learnCard as any, uri, value);
675
+ })
676
+ );
677
+
678
+ return result.some(promiseResult => promiseResult.status === 'fulfilled');
679
+ },
680
+ flushVc: async () => {
681
+ learnCard.debug?.('learnCard.cache.flushVc');
682
+
683
+ const result = await Promise.allSettled(
684
+ learnCard.plugins.map(async plugin => {
685
+ if (!pluginImplementsPlane(plugin, 'cache')) {
686
+ throw new Error('Plugin is not a Cache Plugin');
687
+ }
688
+
689
+ return plugin.cache.flushVc(learnCard as any);
690
+ })
691
+ );
692
+
693
+ return result.some(promiseResult => promiseResult.status === 'fulfilled');
694
+ },
695
+ providers: getPlaneProviders(learnCard.plugins, 'cache'),
696
+ };
697
+ };
698
+
699
+ const generateIdPlane = <
700
+ Plugins extends Plugin[] = [],
701
+ ControlPlanes extends GetPlanesForPlugins<Plugins> = GetPlanesForPlugins<Plugins>,
702
+ PluginMethods = GetPluginMethods<Plugins>
703
+ >(
704
+ learnCard: LearnCard<Plugins, ControlPlanes, PluginMethods>
705
+ ): LearnCardIdPlane<Plugins> => {
706
+ return {
707
+ did: method => {
708
+ learnCard.debug?.('learnCard.id.did', method);
709
+
710
+ const result = findFirstResult([...learnCard.plugins].reverse(), plugin => {
711
+ try {
712
+ if (!pluginImplementsPlane(plugin, 'id')) return undefined;
713
+
714
+ return plugin.id.did(learnCard as any, method);
715
+ } catch (error) {
716
+ return undefined;
717
+ }
718
+ });
719
+
720
+ if (!result) throw new Error(`No plugin supports did method ${method}`);
721
+
722
+ return result;
723
+ },
724
+ keypair: algorithm => {
725
+ learnCard.debug?.('learnCard.id.keypair', algorithm);
726
+
727
+ const result = findFirstResult(learnCard.plugins, plugin => {
728
+ try {
729
+ if (!pluginImplementsPlane(plugin, 'id')) return undefined;
730
+
731
+ return plugin.id.keypair(learnCard as any, algorithm);
732
+ } catch (error) {
733
+ return undefined;
734
+ }
735
+ });
736
+
737
+ if (!result) throw new Error(`No plugin supports keypair type ${algorithm}`);
738
+
739
+ return result;
740
+ },
741
+ providers: getPlaneProviders(learnCard.plugins, 'id'),
742
+ };
743
+ };
744
+
745
+ const generateContextPlane = <
746
+ Plugins extends Plugin[] = [],
747
+ ControlPlanes extends GetPlanesForPlugins<Plugins> = GetPlanesForPlugins<Plugins>,
748
+ PluginMethods = GetPluginMethods<Plugins>
749
+ >(
750
+ learnCard: LearnCard<Plugins, ControlPlanes, PluginMethods>
751
+ ): LearnCardContextPlane<Plugins> => {
752
+ return {
753
+ resolveDocument: async (uri, allowRemote = false) => {
754
+ learnCard.debug?.('learnCard.context.resolveDocument', uri);
755
+
756
+ const staticResults = await Promise.all(
757
+ learnCard.plugins.map(async plugin => {
758
+ if (!pluginImplementsPlane(plugin, 'context')) return undefined;
759
+
760
+ return plugin.context.resolveStaticDocument(learnCard as any, uri);
761
+ })
762
+ );
763
+
764
+ const staticResult = staticResults.find(Boolean);
765
+
766
+ if (staticResult || !allowRemote) return staticResult;
767
+
768
+ const remoteResults = await Promise.all(
769
+ learnCard.plugins.map(async plugin => {
770
+ if (!pluginImplementsPlane(plugin, 'context')) return undefined;
771
+
772
+ return plugin.context.resolveRemoteDocument?.(learnCard as any, uri);
773
+ })
774
+ );
775
+
776
+ return remoteResults.find(Boolean);
777
+ },
778
+ providers: getPlaneProviders(learnCard.plugins, 'context'),
779
+ };
780
+ };
781
+
782
+ const bindMethods = <
783
+ Plugins extends Plugin[] = [],
784
+ ControlPlanes extends GetPlanesForPlugins<Plugins> = GetPlanesForPlugins<Plugins>,
785
+ PluginMethods extends GetPluginMethods<Plugins> = GetPluginMethods<Plugins>
786
+ >(
787
+ learnCard: LearnCard<Plugins, ControlPlanes, PluginMethods>,
788
+ pluginMethods: PluginMethods
789
+ ): PluginMethods => bindLearnCardToFunctionsObject(learnCard, pluginMethods as any) as any;
790
+
791
+ /** @group Universal Wallets */
792
+ export const generateLearnCard = async <
793
+ Plugins extends Plugin[] = [],
794
+ ControlPlanes extends GetPlanesForPlugins<Plugins> = GetPlanesForPlugins<Plugins>,
795
+ PluginMethods extends GetPluginMethods<Plugins> = GetPluginMethods<Plugins>
796
+ >(
797
+ _learnCard: Partial<LearnCard<Plugins, ControlPlanes, PluginMethods>> = {}
798
+ ): Promise<LearnCard<Plugins, ControlPlanes, PluginMethods>> => {
799
+ const { plugins = [] as unknown as Plugins } = _learnCard;
800
+
801
+ const pluginMethods = plugins.reduce<PluginMethods>((cumulativePluginMethods, plugin) => {
802
+ const newPluginMethods = { ...(cumulativePluginMethods as any), ...plugin.methods };
803
+
804
+ return newPluginMethods;
805
+ }, {} as PluginMethods);
806
+
807
+ const learnCard: LearnCard<Plugins, ControlPlanes, PluginMethods> = {
808
+ read: {} as LearnCardReadPlane<Plugins>,
809
+ store: {} as LearnCardStorePlane<Plugins>,
810
+ index: {} as LearnCardIndexPlane<Plugins>,
811
+ cache: {} as LearnCardCachePlane<Plugins>,
812
+ id: {} as LearnCardIdPlane<Plugins>,
813
+ context: {} as LearnCardContextPlane<Plugins>,
814
+ plugins: plugins as Plugins,
815
+ invoke: pluginMethods,
816
+ addPlugin: function (plugin) {
817
+ return addPluginToLearnCard(this as any, plugin);
818
+ },
819
+ debug: _learnCard.debug,
820
+ };
821
+
822
+ (learnCard as any).read = generateReadPlane(learnCard);
823
+ (learnCard as any).store = generateStorePlane(learnCard);
824
+ (learnCard as any).index = generateIndexPlane(learnCard);
825
+ (learnCard as any).cache = generateCachePlane(learnCard);
826
+ (learnCard as any).id = generateIdPlane(learnCard);
827
+ (learnCard as any).context = generateContextPlane(learnCard);
828
+
829
+ if (pluginMethods) learnCard.invoke = bindMethods(learnCard, pluginMethods);
830
+
831
+ return learnCard;
832
+ };