@skipruntime/core 0.0.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.
package/src/index.ts ADDED
@@ -0,0 +1,1134 @@
1
+ import type {
2
+ Pointer,
3
+ Nullable,
4
+ Json,
5
+ JsonConverter,
6
+ JsonObject,
7
+ } from "@skiplang/json";
8
+ import { isObjectProxy, sk_freeze, isSkFrozen } from "@skiplang/json";
9
+ import type * as Internal from "./internal.js";
10
+ import {
11
+ NonUniqueValueException,
12
+ type CollectionUpdate,
13
+ type Context,
14
+ type EagerCollection,
15
+ type Entry,
16
+ type ExternalService,
17
+ type LazyCollection,
18
+ type LazyCompute,
19
+ type Mapper,
20
+ type NamedCollections,
21
+ type NonEmptyIterator,
22
+ type Param,
23
+ type Reducer,
24
+ type Resource,
25
+ type SkipService,
26
+ type SubscriptionID,
27
+ type Watermark,
28
+ } from "@skipruntime/api";
29
+
30
+ import { Frozen } from "@skipruntime/api/internals.js";
31
+ import { UnknownCollectionError } from "./errors.js";
32
+ import {
33
+ ResourceBuilder,
34
+ type Notifier,
35
+ type Checker,
36
+ type Handle,
37
+ type FromBinding,
38
+ } from "./binding.js";
39
+
40
+ export { UnknownCollectionError, sk_freeze, isSkFrozen };
41
+ export { SkipExternalService } from "./remote.js";
42
+ export { Sum, Min, Max, CountMapper } from "./utils.js";
43
+
44
+ export type JSONMapper = Mapper<Json, Json, Json, Json>;
45
+ export type JSONLazyCompute = LazyCompute<Json, Json>;
46
+
47
+ export type Entrypoint = {
48
+ host: string;
49
+ streaming_port: number;
50
+ control_port: number;
51
+ secured?: boolean;
52
+ };
53
+
54
+ abstract class SkFrozen extends Frozen {
55
+ protected freeze() {
56
+ sk_freeze(this);
57
+ }
58
+ }
59
+
60
+ function checkOrCloneParam<T>(value: T): T {
61
+ if (
62
+ typeof value == "string" ||
63
+ typeof value == "number" ||
64
+ typeof value == "boolean"
65
+ )
66
+ return value;
67
+ if (typeof value == "object") {
68
+ if (value === null) return value;
69
+ if (isObjectProxy(value)) return value.clone() as T;
70
+ if (isSkFrozen(value)) return value;
71
+ throw new Error("Invalid object: must be deep-frozen.");
72
+ }
73
+ throw new Error(`'${typeof value}' cannot be deep-frozen.`);
74
+ }
75
+
76
+ /**
77
+ * _Deep-freeze_ an object, returning the same object that was passed in.
78
+ *
79
+ * This function is similar to
80
+ * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze | `Object.freeze()`}
81
+ * but freezes the object and deep-freezes all its properties,
82
+ * recursively. The object is then not only _immutable_ but also
83
+ * _constant_. Note that as a result all objects reachable from the
84
+ * parameter will be frozen and no longer mutable or extensible, even from
85
+ * other references.
86
+ *
87
+ * The argument object and all its properties, recursively, must not already
88
+ * be frozen by `Object.freeze` (or else `deepFreeze` cannot mark them
89
+ * deep-frozen). Undefined, function (and hence class) values cannot be
90
+ * deep-frozen.
91
+ *
92
+ * The primary use for this function is to satisfy the requirement that all
93
+ * parameters to Skip `Mapper` constructors must be deep-frozen: objects
94
+ * that have not been constructed by Skip can be passed to `deepFreeze()`
95
+ * before passing them to a `Mapper` constructor.
96
+ *
97
+ * @param value - The object to deep-freeze.
98
+ * @returns The same object that was passed in.
99
+ */
100
+ export function deepFreeze<T>(value: T): T & Param {
101
+ if (
102
+ typeof value == "bigint" ||
103
+ typeof value == "boolean" ||
104
+ typeof value == "number" ||
105
+ typeof value == "string" ||
106
+ typeof value == "symbol"
107
+ ) {
108
+ return value;
109
+ } else if (typeof value == "object") {
110
+ if (value === null) {
111
+ return value;
112
+ } else if (isSkFrozen(value)) {
113
+ return value;
114
+ } else if (Object.isFrozen(value)) {
115
+ throw new Error(`Cannot deep-freeze an Object.frozen value.`);
116
+ } else if (Array.isArray(value)) {
117
+ for (const elt of value) {
118
+ deepFreeze(elt);
119
+ }
120
+ return Object.freeze(sk_freeze(value));
121
+ } else {
122
+ for (const val of Object.values(value)) {
123
+ deepFreeze(val);
124
+ }
125
+ return Object.freeze(sk_freeze(value));
126
+ }
127
+ } else {
128
+ // typeof value == "function" || typeof value == "undefined"
129
+ throw new Error(`'${typeof value}' values cannot be deep-frozen.`);
130
+ }
131
+ }
132
+
133
+ class Handles {
134
+ private nextID: number = 1;
135
+ private readonly objects: any[] = [];
136
+ private readonly freeIDs: number[] = [];
137
+
138
+ register<T>(v: T): Handle<T> {
139
+ const freeID = this.freeIDs.pop();
140
+ const id = freeID ?? this.nextID++;
141
+ this.objects[id] = v;
142
+ return id as Handle<T>;
143
+ }
144
+
145
+ get<T>(id: Handle<T>): T {
146
+ return this.objects[id] as T;
147
+ }
148
+
149
+ apply<R, P extends any[]>(id: Handle<(..._: P) => R>, parameters: P): R {
150
+ const fn = this.get(id);
151
+ return fn.apply(null, parameters);
152
+ }
153
+
154
+ deleteHandle<T>(id: Handle<T>): T {
155
+ const current = this.get(id);
156
+ this.objects[id] = null;
157
+ this.freeIDs.push(id);
158
+ return current;
159
+ }
160
+ }
161
+
162
+ export class Stack {
163
+ private readonly stack: Pointer<Internal.Context>[] = [];
164
+
165
+ push(pointer: Pointer<Internal.Context>) {
166
+ this.stack.push(pointer);
167
+ }
168
+
169
+ get(): Nullable<Pointer<Internal.Context>> {
170
+ if (this.stack.length == 0) return null;
171
+ return this.stack[this.stack.length - 1]!;
172
+ }
173
+
174
+ pop(): void {
175
+ this.stack.pop();
176
+ }
177
+ }
178
+
179
+ export class Refs {
180
+ constructor(
181
+ public readonly binding: FromBinding,
182
+ public readonly skjson: JsonConverter,
183
+ public readonly handles: Handles,
184
+ public readonly needGC: () => boolean,
185
+ public readonly runWithGC: <T>(fn: () => T) => T,
186
+ ) {}
187
+ }
188
+
189
+ class LazyCollectionImpl<K extends Json, V extends Json>
190
+ extends SkFrozen
191
+ implements LazyCollection<K, V>
192
+ {
193
+ constructor(
194
+ private readonly lazyCollection: string,
195
+ private readonly refs: Refs,
196
+ ) {
197
+ super();
198
+ Object.freeze(this);
199
+ }
200
+
201
+ getArray(key: K): (V & Param)[] {
202
+ return this.refs.skjson.importJSON(
203
+ this.refs.binding.SkipRuntime_LazyCollection__getArray(
204
+ this.lazyCollection,
205
+ this.refs.skjson.exportJSON(key),
206
+ ),
207
+ ) as (V & Param)[];
208
+ }
209
+
210
+ getUnique(key: K): V & Param {
211
+ const v = this.refs.skjson.importOptJSON(
212
+ this.refs.binding.SkipRuntime_LazyCollection__getUnique(
213
+ this.lazyCollection,
214
+ this.refs.skjson.exportJSON(key),
215
+ ),
216
+ ) as Nullable<V & Param>;
217
+ if (v == null) throw new NonUniqueValueException();
218
+ return v;
219
+ }
220
+ }
221
+
222
+ class EagerCollectionImpl<K extends Json, V extends Json>
223
+ extends SkFrozen
224
+ implements EagerCollection<K, V>
225
+ {
226
+ constructor(
227
+ public readonly collection: string,
228
+ private readonly refs: Refs,
229
+ ) {
230
+ super();
231
+ Object.freeze(this);
232
+ }
233
+
234
+ getArray(key: K): (V & Param)[] {
235
+ return this.refs.skjson.importJSON(
236
+ this.refs.binding.SkipRuntime_Collection__getArray(
237
+ this.collection,
238
+ this.refs.skjson.exportJSON(key),
239
+ ),
240
+ ) as (V & Param)[];
241
+ }
242
+
243
+ getUnique(key: K): V & Param {
244
+ const v = this.refs.skjson.importOptJSON(
245
+ this.refs.binding.SkipRuntime_Collection__getUnique(
246
+ this.collection,
247
+ this.refs.skjson.exportJSON(key),
248
+ ),
249
+ ) as Nullable<V & Param>;
250
+ if (v == null) throw new NonUniqueValueException();
251
+ return v;
252
+ }
253
+
254
+ size = () => {
255
+ return Number(
256
+ this.refs.binding.SkipRuntime_Collection__size(this.collection),
257
+ );
258
+ };
259
+
260
+ slice(start: K, end: K): EagerCollection<K, V> {
261
+ return this.slices([start, end]);
262
+ }
263
+
264
+ slices(...ranges: [K, K][]): EagerCollection<K, V> {
265
+ const skcollection = this.refs.binding.SkipRuntime_Collection__slice(
266
+ this.collection,
267
+ this.refs.skjson.exportJSON(ranges),
268
+ );
269
+ return this.derive<K, V>(skcollection);
270
+ }
271
+
272
+ take(limit: number): EagerCollection<K, V> {
273
+ const skcollection = this.refs.binding.SkipRuntime_Collection__take(
274
+ this.collection,
275
+ BigInt(limit),
276
+ );
277
+ return this.derive<K, V>(skcollection);
278
+ }
279
+
280
+ map<K2 extends Json, V2 extends Json, Params extends Param[]>(
281
+ mapper: new (...params: Params) => Mapper<K, V, K2, V2>,
282
+ ...params: Params
283
+ ): EagerCollection<K2, V2> {
284
+ const mapperParams = params.map(checkOrCloneParam) as Params;
285
+ const mapperObj = new mapper(...mapperParams);
286
+ Object.freeze(mapperObj);
287
+ if (!mapperObj.constructor.name) {
288
+ throw new Error("Mapper classes must be defined at top-level.");
289
+ }
290
+ const skmapper = this.refs.binding.SkipRuntime_createMapper(
291
+ this.refs.handles.register(mapperObj),
292
+ );
293
+ const mapped = this.refs.binding.SkipRuntime_Collection__map(
294
+ this.collection,
295
+ skmapper,
296
+ );
297
+ return this.derive<K2, V2>(mapped);
298
+ }
299
+
300
+ mapReduce<K2 extends Json, V2 extends Json, MapperParams extends Param[]>(
301
+ mapper: new (...params: MapperParams) => Mapper<K, V, K2, V2>,
302
+ ...mapperParams: MapperParams
303
+ ) {
304
+ return <Accum extends Json, ReducerParams extends Param[]>(
305
+ reducer: new (...params: ReducerParams) => Reducer<V2, Accum>,
306
+ ...reducerParams: ReducerParams
307
+ ) => {
308
+ const mParams = mapperParams.map(checkOrCloneParam) as MapperParams;
309
+ const rParams = reducerParams.map(checkOrCloneParam) as ReducerParams;
310
+
311
+ const mapperObj = new mapper(...mParams);
312
+ const reducerObj = new reducer(...rParams);
313
+
314
+ Object.freeze(mapperObj);
315
+ Object.freeze(reducerObj);
316
+
317
+ if (!mapperObj.constructor.name) {
318
+ throw new Error("Mapper classes must be defined at top-level.");
319
+ }
320
+ if (!reducerObj.constructor.name) {
321
+ throw new Error("Reducer classes must be defined at top-level.");
322
+ }
323
+
324
+ const skmapper = this.refs.binding.SkipRuntime_createMapper(
325
+ this.refs.handles.register(mapperObj),
326
+ );
327
+ const skreducer = this.refs.binding.SkipRuntime_createReducer(
328
+ this.refs.handles.register(reducerObj),
329
+ this.refs.skjson.exportJSON(reducerObj.default),
330
+ );
331
+ const mapped = this.refs.binding.SkipRuntime_Collection__mapReduce(
332
+ this.collection,
333
+ skmapper,
334
+ skreducer,
335
+ );
336
+ return this.derive<K2, Accum>(mapped);
337
+ };
338
+ }
339
+
340
+ reduce<Accum extends Json, Params extends Param[]>(
341
+ reducer: new (...params: Params) => Reducer<V, Accum>,
342
+ ...params: Params
343
+ ): EagerCollection<K, Accum> {
344
+ const reducerParams = params.map(checkOrCloneParam) as Params;
345
+ const reducerObj = new reducer(...reducerParams);
346
+ Object.freeze(reducerObj);
347
+ if (!reducerObj.constructor.name) {
348
+ throw new Error("Reducer classes must be defined at top-level.");
349
+ }
350
+ const skreducer = this.refs.binding.SkipRuntime_createReducer(
351
+ this.refs.handles.register(reducerObj),
352
+ this.refs.skjson.exportJSON(reducerObj.default),
353
+ );
354
+ return this.derive<K, Accum>(
355
+ this.refs.binding.SkipRuntime_Collection__reduce(
356
+ this.collection,
357
+ skreducer,
358
+ ),
359
+ );
360
+ }
361
+
362
+ merge(...others: EagerCollection<K, V>[]): EagerCollection<K, V> {
363
+ const otherNames = others.map(
364
+ (other) => (other as EagerCollectionImpl<K, V>).collection,
365
+ );
366
+ const mapped = this.refs.binding.SkipRuntime_Collection__merge(
367
+ this.collection,
368
+ this.refs.skjson.exportJSON(otherNames),
369
+ );
370
+ return this.derive<K, V>(mapped);
371
+ }
372
+
373
+ private derive<K2 extends Json, V2 extends Json>(
374
+ collection: string,
375
+ ): EagerCollection<K2, V2> {
376
+ return new EagerCollectionImpl<K2, V2>(collection, this.refs);
377
+ }
378
+ }
379
+
380
+ class CollectionWriter<K extends Json, V extends Json> {
381
+ constructor(
382
+ public readonly collection: string,
383
+ private readonly refs: Refs,
384
+ ) {}
385
+
386
+ update(values: Entry<K, V>[], isInit: boolean): void {
387
+ const update_ = () => {
388
+ return this.refs.binding.SkipRuntime_CollectionWriter__update(
389
+ this.collection,
390
+ this.refs.skjson.exportJSON(values),
391
+ isInit,
392
+ );
393
+ };
394
+ if (this.refs.needGC()) {
395
+ this.refs.runWithGC(update_);
396
+ } else {
397
+ update_();
398
+ }
399
+ }
400
+
401
+ loading(): void {
402
+ const loading_ = () => {
403
+ return this.refs.binding.SkipRuntime_CollectionWriter__loading(
404
+ this.collection,
405
+ );
406
+ };
407
+ if (this.refs.needGC()) this.refs.runWithGC(loading_);
408
+ else loading_();
409
+ }
410
+
411
+ error(error: Json): void {
412
+ const error_ = () => {
413
+ return this.refs.binding.SkipRuntime_CollectionWriter__error(
414
+ this.collection,
415
+ this.refs.skjson.exportJSON(error),
416
+ );
417
+ };
418
+ if (this.refs.needGC()) this.refs.runWithGC(error_);
419
+ else error_();
420
+ }
421
+ }
422
+
423
+ class ContextImpl extends SkFrozen implements Context {
424
+ constructor(private readonly refs: Refs) {
425
+ super();
426
+ Object.freeze(this);
427
+ }
428
+
429
+ createLazyCollection<K extends Json, V extends Json, Params extends Param[]>(
430
+ compute: new (...params: Params) => LazyCompute<K, V>,
431
+ ...params: Params
432
+ ): LazyCollection<K, V> {
433
+ const mapperParams = params.map(checkOrCloneParam) as Params;
434
+ const computeObj = new compute(...mapperParams);
435
+ Object.freeze(computeObj);
436
+ if (!computeObj.constructor.name) {
437
+ throw new Error("LazyCompute classes must be defined at top-level.");
438
+ }
439
+ const skcompute = this.refs.binding.SkipRuntime_createLazyCompute(
440
+ this.refs.handles.register(computeObj),
441
+ );
442
+ const lazyCollection =
443
+ this.refs.binding.SkipRuntime_Context__createLazyCollection(skcompute);
444
+ return new LazyCollectionImpl<K, V>(lazyCollection, this.refs);
445
+ }
446
+
447
+ useExternalResource<K extends Json, V extends Json>(resource: {
448
+ service: string;
449
+ identifier: string;
450
+ params?: { [param: string]: string | number };
451
+ }): EagerCollection<K, V> {
452
+ const collection =
453
+ this.refs.binding.SkipRuntime_Context__useExternalResource(
454
+ resource.service,
455
+ resource.identifier,
456
+ this.refs.skjson.exportJSON(resource.params ?? {}),
457
+ );
458
+ return new EagerCollectionImpl<K, V>(collection, this.refs);
459
+ }
460
+
461
+ jsonExtract(value: JsonObject, pattern: string): Json[] {
462
+ return this.refs.skjson.importJSON(
463
+ this.refs.binding.SkipRuntime_Context__jsonExtract(
464
+ this.refs.skjson.exportJSON(value),
465
+ pattern,
466
+ ),
467
+ ) as Json[];
468
+ }
469
+ }
470
+
471
+ export class ServiceInstanceFactory {
472
+ constructor(private init: (service: SkipService) => ServiceInstance) {}
473
+
474
+ initService(service: SkipService): ServiceInstance {
475
+ return this.init(service);
476
+ }
477
+ }
478
+
479
+ export type GetResult<T> = {
480
+ request?: string;
481
+ payload: T;
482
+ errors: Json[];
483
+ };
484
+
485
+ export type Executor<T> = {
486
+ resolve: (value: T) => void;
487
+ reject: (reason?: any) => void;
488
+ };
489
+
490
+ class AllChecker<K extends Json, V extends Json> implements Checker {
491
+ constructor(
492
+ private readonly service: ServiceInstance,
493
+ private readonly executor: Executor<Entry<K, V>[]>,
494
+ private readonly resource: string,
495
+ private readonly params: { [param: string]: string },
496
+ ) {}
497
+
498
+ check(request: string): void {
499
+ const result = this.service.getAll<K, V>(
500
+ this.resource,
501
+ this.params,
502
+ request,
503
+ );
504
+ if (result.errors.length > 0) {
505
+ this.executor.reject(new Error(JSON.stringify(result.errors)));
506
+ } else {
507
+ this.executor.resolve(result.payload);
508
+ }
509
+ }
510
+ }
511
+
512
+ class OneChecker<K extends Json, V extends Json> implements Checker {
513
+ constructor(
514
+ private readonly service: ServiceInstance,
515
+ private readonly executor: Executor<V[]>,
516
+ private readonly resource: string,
517
+ private readonly params: { [param: string]: string },
518
+ private readonly key: K,
519
+ ) {}
520
+
521
+ check(request: string): void {
522
+ const result = this.service.getArray<K, V>(
523
+ this.resource,
524
+ this.key,
525
+ this.params,
526
+ request,
527
+ );
528
+ if (result.errors.length > 0) {
529
+ this.executor.reject(new Error(JSON.stringify(result.errors)));
530
+ } else {
531
+ this.executor.resolve(result.payload);
532
+ }
533
+ }
534
+ }
535
+
536
+ /**
537
+ * A `ServiceInstance` is a running instance of a `SkipService`, providing access to its resources
538
+ * and operations to manage susbscriptions and the service itself.
539
+ */
540
+ export class ServiceInstance {
541
+ constructor(private readonly refs: Refs) {}
542
+
543
+ /**
544
+ * Instantiate a resource with some parameters and client session authentication token
545
+ * @param identifier - The resource instance identifier
546
+ * @param resource - A resource name, which must correspond to a key in this `SkipService`'s `resources` field
547
+ * @param params - Resource parameters, which will be passed to the resource constructor specified in this `SkipService`'s `resources` field
548
+ */
549
+ instantiateResource(
550
+ identifier: string,
551
+ resource: string,
552
+ params: { [param: string]: string },
553
+ ): void {
554
+ const errorHdl = this.refs.runWithGC(() => {
555
+ return this.refs.binding.SkipRuntime_Runtime__createResource(
556
+ identifier,
557
+ resource,
558
+ this.refs.skjson.exportJSON(params),
559
+ );
560
+ });
561
+ if (errorHdl) throw this.refs.handles.deleteHandle(errorHdl);
562
+ }
563
+
564
+ /**
565
+ * Creates if not exists and get all current values of specified resource
566
+ * @param resource - the resource name corresponding to a key in remotes field of SkipService
567
+ * @param params - the parameters of the resource used to build the resource with the corresponding constructor specified in remotes field of SkipService
568
+ * @returns The current values of the corresponding resource with reactive responce token to allow subscription
569
+ */
570
+ getAll<K extends Json, V extends Json>(
571
+ resource: string,
572
+ params: { [param: string]: string } = {},
573
+ request?: string | Executor<Entry<K, V>[]>,
574
+ ): GetResult<Entry<K, V>[]> {
575
+ const get_ = () => {
576
+ return this.refs.skjson.importJSON(
577
+ this.refs.binding.SkipRuntime_Runtime__getAll(
578
+ resource,
579
+ this.refs.skjson.exportJSON(params),
580
+ request !== undefined
581
+ ? typeof request == "string"
582
+ ? this.refs.binding.SkipRuntime_createIdentifier(request)
583
+ : this.refs.binding.SkipRuntime_createChecker(
584
+ this.refs.handles.register(
585
+ new AllChecker(this, request, resource, params),
586
+ ),
587
+ )
588
+ : null,
589
+ ),
590
+ true,
591
+ );
592
+ };
593
+ const result = this.refs.needGC() ? this.refs.runWithGC(get_) : get_();
594
+ if (typeof result == "number")
595
+ throw this.refs.handles.deleteHandle(result as Handle<Error>);
596
+ return result as GetResult<Entry<K, V>[]>;
597
+ }
598
+
599
+ /**
600
+ * Get the current value of a key in the specified resource instance, creating it if it doesn't already exist
601
+ * @param resource - A resource name, which must correspond to a key in this `SkipService`'s `resources` field
602
+ * @param key - A key to look up in the resource instance
603
+ * @param params - Resource parameters, passed to the resource constructor specified in this `SkipService`'s `resources` field
604
+ * @returns The current value(s) for this key in the specified resource instance
605
+ */
606
+ getArray<K extends Json, V extends Json>(
607
+ resource: string,
608
+ key: K,
609
+ params: { [param: string]: string } = {},
610
+ request?: string | Executor<V[]>,
611
+ ): GetResult<V[]> {
612
+ const get_ = () => {
613
+ return this.refs.skjson.importJSON(
614
+ this.refs.binding.SkipRuntime_Runtime__getForKey(
615
+ resource,
616
+ this.refs.skjson.exportJSON(params),
617
+ this.refs.skjson.exportJSON(key),
618
+ request !== undefined
619
+ ? typeof request == "string"
620
+ ? this.refs.binding.SkipRuntime_createIdentifier(request)
621
+ : this.refs.binding.SkipRuntime_createChecker(
622
+ this.refs.handles.register(
623
+ new OneChecker(this, request, resource, params, key),
624
+ ),
625
+ )
626
+ : null,
627
+ ),
628
+ true,
629
+ );
630
+ };
631
+ const needGC = this.refs.needGC();
632
+ const result = needGC ? this.refs.runWithGC(get_) : get_();
633
+ if (typeof result == "number")
634
+ throw this.refs.handles.deleteHandle(result as Handle<Error>);
635
+ return result as GetResult<V[]>;
636
+ }
637
+
638
+ /**
639
+ * Close the specified resource instance
640
+ * @param resourceInstanceId - The resource identifier
641
+ */
642
+ closeResourceInstance(resourceInstanceId: string): void {
643
+ const errorHdl = this.refs.runWithGC(() => {
644
+ return this.refs.binding.SkipRuntime_Runtime__closeResource(
645
+ resourceInstanceId,
646
+ );
647
+ });
648
+ if (errorHdl) throw this.refs.handles.deleteHandle(errorHdl);
649
+ }
650
+
651
+ /**
652
+ * Initiate reactive subscription on a resource instance
653
+ * @param resourceInstanceId - the resource instance identifier
654
+ * @param notifier - the object containing subscription callbacks
655
+ * @param notifier.subscribed - A callback to execute when subscription effectivly done
656
+ * @param notifier.notify - A callback to execute on collection updates
657
+ * @param notifier.close - A callback to execute on resource close
658
+ * @param watermark - the watermark where to start the subscription
659
+ * @returns A subcription identifier
660
+ */
661
+ subscribe<K extends Json, V extends Json>(
662
+ resourceInstanceId: string,
663
+ notifier: {
664
+ subscribed: () => void;
665
+ notify: (update: CollectionUpdate<K, V>) => void;
666
+ close: () => void;
667
+ },
668
+ watermark?: string,
669
+ ): SubscriptionID {
670
+ const session = this.refs.runWithGC(() => {
671
+ const sknotifier = this.refs.binding.SkipRuntime_createNotifier(
672
+ this.refs.handles.register(notifier),
673
+ );
674
+ return this.refs.binding.SkipRuntime_Runtime__subscribe(
675
+ resourceInstanceId,
676
+ sknotifier,
677
+ watermark ?? null,
678
+ );
679
+ });
680
+ if (session == -1n) {
681
+ throw new UnknownCollectionError(
682
+ `Unknown resource instance '${resourceInstanceId}'`,
683
+ );
684
+ } else if (session < 0n) {
685
+ throw new Error("Unknown error");
686
+ }
687
+ return session as SubscriptionID;
688
+ }
689
+
690
+ /**
691
+ * Terminate a client's subscription to a reactive resource instance
692
+ * @param id - The subcription identifier returned by a call to `subscribe`
693
+ */
694
+ unsubscribe(id: SubscriptionID): void {
695
+ const errorHdl = this.refs.runWithGC(() => {
696
+ return this.refs.binding.SkipRuntime_Runtime__unsubscribe(id);
697
+ });
698
+ if (errorHdl) {
699
+ throw this.refs.handles.deleteHandle(errorHdl);
700
+ }
701
+ }
702
+
703
+ /**
704
+ * Update an input collection
705
+ * @param collection - the name of the input collection to update
706
+ * @param entries - entries to update in the collection.
707
+ */
708
+ update<K extends Json, V extends Json>(
709
+ collection: string,
710
+ entries: Entry<K, V>[],
711
+ ): void {
712
+ const errorHdl = this.refs.runWithGC(() => {
713
+ return this.refs.binding.SkipRuntime_Runtime__update(
714
+ collection,
715
+ this.refs.skjson.exportJSON(entries),
716
+ );
717
+ });
718
+ if (errorHdl) {
719
+ throw this.refs.handles.deleteHandle(errorHdl);
720
+ }
721
+ }
722
+
723
+ /**
724
+ * Close all resources and shut down the service.
725
+ * Any subsequent calls on the service will result in errors.
726
+ */
727
+ close(): void {
728
+ const errorHdl = this.refs.runWithGC(() => {
729
+ return this.refs.binding.SkipRuntime_closeService();
730
+ });
731
+ if (errorHdl) {
732
+ throw this.refs.handles.deleteHandle(errorHdl);
733
+ }
734
+ }
735
+ }
736
+
737
+ export class NonEmptyIteratorImpl<T> implements NonEmptyIterator<T> {
738
+ constructor(
739
+ private readonly skjson: JsonConverter,
740
+ private readonly binding: FromBinding,
741
+ private readonly pointer: Pointer<Internal.NonEmptyIterator>,
742
+ ) {
743
+ this.skjson = skjson;
744
+ this.binding = binding;
745
+ this.pointer = pointer;
746
+ }
747
+
748
+ next(): Nullable<T & Param> {
749
+ return this.skjson.importOptJSON(
750
+ this.binding.SkipRuntime_NonEmptyIterator__next(this.pointer),
751
+ ) as Nullable<T & Param>;
752
+ }
753
+
754
+ getUnique(): T & Param {
755
+ const value = this.skjson.importOptJSON(
756
+ this.binding.SkipRuntime_NonEmptyIterator__uniqueValue(this.pointer),
757
+ ) as Nullable<T & Param>;
758
+ if (value == null) throw new NonUniqueValueException();
759
+ return value;
760
+ }
761
+
762
+ toArray: () => (T & Param)[] = () => {
763
+ return Array.from(this);
764
+ };
765
+
766
+ [Symbol.iterator](): Iterator<T & Param> {
767
+ const cloned_iter = new NonEmptyIteratorImpl<T & Param>(
768
+ this.skjson,
769
+ this.binding,
770
+ this.binding.SkipRuntime_NonEmptyIterator__clone(this.pointer),
771
+ );
772
+
773
+ return {
774
+ next() {
775
+ const value = cloned_iter.next();
776
+ return { value, done: value == null } as IteratorResult<T & Param>;
777
+ },
778
+ };
779
+ }
780
+
781
+ map<U>(f: (value: T & Param, index: number) => U, thisObj?: any): U[] {
782
+ return this.toArray().map(f, thisObj);
783
+ }
784
+ }
785
+
786
+ export class ToBinding {
787
+ private readonly stack: Stack;
788
+ private readonly handles: Handles;
789
+ private skjson?: JsonConverter;
790
+
791
+ constructor(
792
+ private binding: FromBinding,
793
+ private runWithGC: <T>(fn: () => T) => T,
794
+ private getConverter: () => JsonConverter,
795
+ private getError: (skExc: Pointer<Internal.Exception>) => Error,
796
+ ) {
797
+ this.stack = new Stack();
798
+ this.handles = new Handles();
799
+ }
800
+
801
+ register<T>(v: T): Handle<T> {
802
+ return this.handles.register(v);
803
+ }
804
+
805
+ deleteHandle<T>(id: Handle<T>): T {
806
+ return this.handles.deleteHandle(id);
807
+ }
808
+
809
+ SkipRuntime_getErrorHdl(exn: Pointer<Internal.Exception>): Handle<Error> {
810
+ return this.handles.register(this.getError(exn));
811
+ }
812
+
813
+ SkipRuntime_pushContext(context: Pointer<Internal.Context>): void {
814
+ this.stack.push(context);
815
+ }
816
+
817
+ SkipRuntime_popContext(): void {
818
+ this.stack.pop();
819
+ }
820
+
821
+ SkipRuntime_getContext(): Nullable<Pointer<Internal.Context>> {
822
+ return this.stack.get();
823
+ }
824
+
825
+ // Mapper
826
+
827
+ SkipRuntime_Mapper__mapEntry(
828
+ skmapper: Handle<JSONMapper>,
829
+ key: Pointer<Internal.CJSON>,
830
+ values: Pointer<Internal.NonEmptyIterator>,
831
+ ): Pointer<Internal.CJArray> {
832
+ const skjson = this.getJsonConverter();
833
+ const mapper = this.handles.get(skmapper);
834
+ const result = mapper.mapEntry(
835
+ skjson.importJSON(key) as Json,
836
+ new NonEmptyIteratorImpl<Json>(skjson, this.binding, values),
837
+ );
838
+ return skjson.exportJSON(Array.from(result) as [[Json, Json]]);
839
+ }
840
+
841
+ SkipRuntime_deleteMapper(mapper: Handle<JSONMapper>): void {
842
+ this.handles.deleteHandle(mapper);
843
+ }
844
+
845
+ // LazyCompute
846
+
847
+ SkipRuntime_LazyCompute__compute(
848
+ sklazyCompute: Handle<JSONLazyCompute>,
849
+ self: string,
850
+ skkey: Pointer<Internal.CJSON>,
851
+ ): Pointer<Internal.CJArray> {
852
+ const skjson = this.getJsonConverter();
853
+ const lazyCompute = this.handles.get(sklazyCompute);
854
+ const computed = lazyCompute.compute(
855
+ new LazyCollectionImpl<Json, Json>(self, this.refs()),
856
+ skjson.importJSON(skkey) as Json,
857
+ );
858
+ return skjson.exportJSON(computed ? [computed] : []);
859
+ }
860
+
861
+ SkipRuntime_deleteLazyCompute(lazyCompute: Handle<JSONLazyCompute>): void {
862
+ this.handles.deleteHandle(lazyCompute);
863
+ }
864
+
865
+ // Resource
866
+
867
+ SkipRuntime_Resource__instantiate(
868
+ skresource: Handle<Resource>,
869
+ skcollections: Pointer<Internal.CJObject>,
870
+ ): string {
871
+ const skjson = this.getJsonConverter();
872
+ const resource = this.handles.get(skresource);
873
+ const collections: NamedCollections = {};
874
+ const keysIds = skjson.importJSON(skcollections) as {
875
+ [key: string]: string;
876
+ };
877
+ const refs = this.refs();
878
+ for (const [key, name] of Object.entries(keysIds)) {
879
+ collections[key] = new EagerCollectionImpl<Json, Json>(name, refs);
880
+ }
881
+ const collection = resource.instantiate(collections, new ContextImpl(refs));
882
+ return (collection as EagerCollectionImpl<Json, Json>).collection;
883
+ }
884
+
885
+ SkipRuntime_deleteResource(resource: Handle<Resource>): void {
886
+ this.handles.deleteHandle(resource);
887
+ }
888
+
889
+ // ResourceBuilder
890
+
891
+ SkipRuntime_ResourceBuilder__build(
892
+ skbuilder: Handle<ResourceBuilder>,
893
+ skparams: Pointer<Internal.CJObject>,
894
+ ): Pointer<Internal.Resource> {
895
+ const skjson = this.getJsonConverter();
896
+ const builder = this.handles.get(skbuilder);
897
+ const resource = builder.build(
898
+ skjson.importJSON(skparams) as { [param: string]: string },
899
+ );
900
+ return this.binding.SkipRuntime_createResource(
901
+ this.handles.register(resource),
902
+ );
903
+ }
904
+
905
+ SkipRuntime_deleteResourceBuilder(builder: Handle<ResourceBuilder>): void {
906
+ this.handles.deleteHandle(builder);
907
+ }
908
+
909
+ // Service
910
+
911
+ SkipRuntime_Service__createGraph(
912
+ skservice: Handle<SkipService>,
913
+ skcollections: Pointer<Internal.CJObject>,
914
+ ): Pointer<Internal.CJObject> {
915
+ const skjson = this.getJsonConverter();
916
+ const service = this.handles.get(skservice);
917
+ const collections: NamedCollections = {};
918
+ const keysIds = skjson.importJSON(skcollections) as {
919
+ [key: string]: string;
920
+ };
921
+ const refs = this.refs();
922
+ for (const [key, name] of Object.entries(keysIds)) {
923
+ collections[key] = new EagerCollectionImpl<Json, Json>(name, refs);
924
+ }
925
+ const result = service.createGraph(collections, new ContextImpl(refs));
926
+ const collectionsNames: { [name: string]: string } = {};
927
+ for (const [name, collection] of Object.entries(result)) {
928
+ collectionsNames[name] = (
929
+ collection as EagerCollectionImpl<Json, Json>
930
+ ).collection;
931
+ }
932
+ return skjson.exportJSON(collectionsNames);
933
+ }
934
+
935
+ SkipRuntime_deleteService(service: Handle<SkipService>): void {
936
+ this.handles.deleteHandle(service);
937
+ }
938
+
939
+ // Notifier
940
+
941
+ SkipRuntime_Notifier__subscribed<K extends Json, V extends Json>(
942
+ sknotifier: Handle<Notifier<K, V>>,
943
+ ): void {
944
+ const notifier = this.handles.get(sknotifier);
945
+ notifier.subscribed();
946
+ }
947
+
948
+ SkipRuntime_Notifier__notify<K extends Json, V extends Json>(
949
+ sknotifier: Handle<Notifier<K, V>>,
950
+ skvalues: Pointer<Internal.CJArray<Internal.CJArray<Internal.CJSON>>>,
951
+ watermark: Watermark,
952
+ isUpdates: number,
953
+ ) {
954
+ const skjson = this.getJsonConverter();
955
+ const notifier = this.handles.get(sknotifier);
956
+ const values = skjson.importJSON(skvalues, true) as Entry<K, V>[];
957
+ const isInitial = isUpdates ? false : true;
958
+ notifier.notify({
959
+ values,
960
+ watermark,
961
+ isInitial,
962
+ });
963
+ }
964
+
965
+ SkipRuntime_Notifier__close<K extends Json, V extends Json>(
966
+ sknotifier: Handle<Notifier<K, V>>,
967
+ ): void {
968
+ const notifier = this.handles.get(sknotifier);
969
+ notifier.close();
970
+ }
971
+
972
+ SkipRuntime_deleteNotifier<K extends Json, V extends Json>(
973
+ notifier: Handle<Notifier<K, V>>,
974
+ ): void {
975
+ this.handles.deleteHandle(notifier);
976
+ }
977
+
978
+ // Reducer
979
+
980
+ SkipRuntime_Reducer__add(
981
+ skreducer: Handle<Reducer<Json, Json>>,
982
+ skacc: Nullable<Pointer<Internal.CJSON>>,
983
+ skvalue: Pointer<Internal.CJSON>,
984
+ ): Pointer<Internal.CJSON> {
985
+ const skjson = this.getJsonConverter();
986
+ const reducer = this.handles.get(skreducer);
987
+ return skjson.exportJSON(
988
+ reducer.add(
989
+ skacc ? (skjson.importJSON(skacc) as Json) : null,
990
+ skjson.importJSON(skvalue) as Json & Param,
991
+ ),
992
+ );
993
+ }
994
+
995
+ SkipRuntime_Reducer__remove(
996
+ skreducer: Handle<Reducer<Json, Json>>,
997
+ skacc: Pointer<Internal.CJSON>,
998
+ skvalue: Pointer<Internal.CJSON>,
999
+ ): Nullable<Pointer<Internal.CJSON>> {
1000
+ const skjson = this.getJsonConverter();
1001
+ const reducer = this.handles.get(skreducer);
1002
+ return skjson.exportJSON(
1003
+ reducer.remove(
1004
+ skjson.importJSON(skacc) as Json,
1005
+ skjson.importJSON(skvalue) as Json & Param,
1006
+ ),
1007
+ );
1008
+ }
1009
+
1010
+ SkipRuntime_deleteReducer(reducer: Handle<Reducer<Json, Json>>): void {
1011
+ this.handles.deleteHandle(reducer);
1012
+ }
1013
+
1014
+ // ExternalService
1015
+
1016
+ SkipRuntime_ExternalService__subscribe(
1017
+ sksupplier: Handle<ExternalService>,
1018
+ writerId: string,
1019
+ resource: string,
1020
+ skparams: Pointer<Internal.CJObject>,
1021
+ ): void {
1022
+ const skjson = this.getJsonConverter();
1023
+ const supplier = this.handles.get(sksupplier);
1024
+ const writer = new CollectionWriter(writerId, this.refs());
1025
+ const params = skjson.importJSON(skparams, true) as {
1026
+ [param: string]: string;
1027
+ };
1028
+ supplier.subscribe(resource, params, {
1029
+ update: writer.update.bind(writer),
1030
+ error: writer.error.bind(writer),
1031
+ loading: writer.loading.bind(writer),
1032
+ });
1033
+ }
1034
+
1035
+ SkipRuntime_ExternalService__unsubscribe(
1036
+ sksupplier: Handle<ExternalService>,
1037
+ resource: string,
1038
+ skparams: Pointer<Internal.CJObject>,
1039
+ ): void {
1040
+ const skjson = this.getJsonConverter();
1041
+ const supplier = this.handles.get(sksupplier);
1042
+ const params = skjson.importJSON(skparams, true) as {
1043
+ [param: string]: string;
1044
+ };
1045
+ supplier.unsubscribe(resource, params);
1046
+ }
1047
+
1048
+ SkipRuntime_ExternalService__shutdown(
1049
+ sksupplier: Handle<ExternalService>,
1050
+ ): void {
1051
+ const supplier = this.handles.get(sksupplier);
1052
+ supplier.shutdown();
1053
+ }
1054
+
1055
+ SkipRuntime_deleteExternalService(supplier: Handle<ExternalService>): void {
1056
+ this.handles.deleteHandle(supplier);
1057
+ }
1058
+
1059
+ // Checker
1060
+
1061
+ SkipRuntime_Checker__check(
1062
+ skchecker: Handle<Checker>,
1063
+ request: string,
1064
+ ): void {
1065
+ const checker = this.handles.get(skchecker);
1066
+ checker.check(request);
1067
+ }
1068
+
1069
+ SkipRuntime_deleteChecker(checker: Handle<Checker>): void {
1070
+ this.handles.deleteHandle(checker);
1071
+ }
1072
+
1073
+ initService(service: SkipService): ServiceInstance {
1074
+ const refs = this.refs();
1075
+ const errorHdl = refs.runWithGC(() => {
1076
+ const skExternalServices =
1077
+ refs.binding.SkipRuntime_ExternalServiceMap__create();
1078
+ if (service.externalServices) {
1079
+ for (const [name, remote] of Object.entries(service.externalServices)) {
1080
+ const skremote = refs.binding.SkipRuntime_createExternalService(
1081
+ refs.handles.register(remote),
1082
+ );
1083
+ refs.binding.SkipRuntime_ExternalServiceMap__add(
1084
+ skExternalServices,
1085
+ name,
1086
+ skremote,
1087
+ );
1088
+ }
1089
+ }
1090
+ const skresources = refs.binding.SkipRuntime_ResourceBuilderMap__create();
1091
+ for (const [name, builder] of Object.entries(service.resources)) {
1092
+ const skbuilder = refs.binding.SkipRuntime_createResourceBuilder(
1093
+ refs.handles.register(new ResourceBuilder(builder)),
1094
+ );
1095
+ refs.binding.SkipRuntime_ResourceBuilderMap__add(
1096
+ skresources,
1097
+ name,
1098
+ skbuilder,
1099
+ );
1100
+ }
1101
+ const skservice = refs.binding.SkipRuntime_createService(
1102
+ refs.handles.register(service),
1103
+ refs.skjson.exportJSON(service.initialData ?? {}),
1104
+ skresources,
1105
+ skExternalServices,
1106
+ );
1107
+ return refs.binding.SkipRuntime_initService(skservice);
1108
+ });
1109
+ if (errorHdl) throw refs.handles.deleteHandle(errorHdl);
1110
+ return new ServiceInstance(refs);
1111
+ }
1112
+
1113
+ //
1114
+ private getJsonConverter() {
1115
+ if (this.skjson == undefined) {
1116
+ this.skjson = this.getConverter();
1117
+ }
1118
+ return this.skjson;
1119
+ }
1120
+
1121
+ private needGC() {
1122
+ return this.SkipRuntime_getContext() == null;
1123
+ }
1124
+
1125
+ private refs(): Refs {
1126
+ return new Refs(
1127
+ this.binding,
1128
+ this.getConverter(),
1129
+ this.handles,
1130
+ this.needGC.bind(this),
1131
+ this.runWithGC,
1132
+ );
1133
+ }
1134
+ }