@skipruntime/core 0.0.16 → 0.0.17

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 CHANGED
@@ -26,7 +26,6 @@ import {
26
26
  type Context,
27
27
  type EagerCollection,
28
28
  type Entry,
29
- type ExternalService,
30
29
  type LazyCollection,
31
30
  type LazyCompute,
32
31
  type Mapper,
@@ -37,6 +36,7 @@ import {
37
36
  type Resource,
38
37
  type SkipService,
39
38
  type Watermark,
39
+ type ExternalService,
40
40
  } from "./api.js";
41
41
 
42
42
  import {
@@ -46,25 +46,26 @@ import {
46
46
  SkipResourceInstanceInUseError,
47
47
  SkipUnknownCollectionError,
48
48
  } from "./errors.js";
49
- import {
50
- ResourceBuilder,
51
- type Notifier,
52
- type Executor,
53
- type Handle,
54
- type FromBinding,
55
- } from "./binding.js";
49
+ import { type Notifier, type Handle, type FromBinding } from "./binding.js";
56
50
 
57
51
  export * from "./api.js";
58
52
  export * from "./errors.js";
59
53
 
60
54
  export type JSONMapper = Mapper<Json, Json, Json, Json>;
61
55
  export type JSONLazyCompute = LazyCompute<Json, Json>;
56
+ export type JSONOperator = JSONMapper | JSONLazyCompute | Reducer<Json, Json>;
57
+
58
+ export type HandlerInfo<P> = {
59
+ object: P;
60
+ name: string;
61
+ params: DepSafe[];
62
+ };
62
63
 
63
64
  function instantiateUserObject<Params extends DepSafe[], Result extends object>(
64
65
  what: string,
65
66
  ctor: new (...params: Params) => Result,
66
67
  params: Params,
67
- ): Result {
68
+ ): HandlerInfo<Result> {
68
69
  const checkedParams = params.map(checkOrCloneParam) as Params;
69
70
  const obj = new ctor(...checkedParams);
70
71
  Object.freeze(obj);
@@ -73,7 +74,118 @@ function instantiateUserObject<Params extends DepSafe[], Result extends object>(
73
74
  `${what} classes must be defined at top-level.`,
74
75
  );
75
76
  }
76
- return obj;
77
+ return {
78
+ object: obj,
79
+ name: obj.constructor.name,
80
+ params: checkedParams,
81
+ };
82
+ }
83
+
84
+ export enum LoadStatus {
85
+ Incompatible,
86
+ Changed,
87
+ Same,
88
+ }
89
+
90
+ export interface ChangeManager {
91
+ needInputReload(name: string): boolean;
92
+ needResourceReload(name: string): LoadStatus;
93
+ needExternalServiceReload(name: string, resource: string): boolean;
94
+ needMapperReload(name: string): boolean;
95
+ needReducerReload(name: string): boolean;
96
+ needLazyComputeReload(name: string): boolean;
97
+ }
98
+
99
+ export class ServiceDefinition {
100
+ constructor(
101
+ private service: SkipService,
102
+ private readonly externals: Map<string, ExternalService> = new Map(),
103
+ ) {}
104
+
105
+ buildResource(name: string, parameters: Json): Resource {
106
+ const builder = this.service.resources[name];
107
+ if (!builder) throw new Error(`Resource '${name}' not exist.`);
108
+ return new builder(parameters);
109
+ }
110
+
111
+ inputs(): string[] {
112
+ return this.service.initialData
113
+ ? Object.keys(this.service.initialData)
114
+ : [];
115
+ }
116
+
117
+ resources(): string[] {
118
+ return Object.keys(this.service.resources);
119
+ }
120
+
121
+ initialData(name: string): Entry<Json, Json>[] {
122
+ if (!this.service.initialData) throw new Error(`No initial data defined.`);
123
+ const data = this.service.initialData[name];
124
+ if (!data) throw new Error(`Initial data '${name}' not exist.`);
125
+ return data;
126
+ }
127
+
128
+ createGraph(
129
+ inputCollections: NamedCollections,
130
+ context: Context,
131
+ ): NamedCollections {
132
+ return this.service.createGraph(inputCollections, context);
133
+ }
134
+
135
+ subscribe(
136
+ external: string,
137
+ writer: CollectionWriter<Json, Json>,
138
+ instance: string,
139
+ resource: string,
140
+ params: Json,
141
+ ): Promise<void> {
142
+ if (!this.service.externalServices)
143
+ throw new Error(`No external services defined.`);
144
+ const supplier = this.service.externalServices[external];
145
+ if (!supplier)
146
+ throw new Error(`External services '${external}' not exist.`);
147
+ this.externals.set(`${external}/${instance}`, supplier);
148
+ // Ensure notification is made outside the current context update
149
+ return new Promise((resolve, reject) => {
150
+ setTimeout(() => {
151
+ supplier
152
+ .subscribe(instance, resource, params, {
153
+ update: writer.update.bind(writer),
154
+ error: (_) => {},
155
+ })
156
+ .then(resolve)
157
+ .catch(reject);
158
+ }, 0);
159
+ });
160
+ }
161
+
162
+ unsubscribe(external: string, instance: string) {
163
+ if (!this.service.externalServices)
164
+ throw new Error(`No external services defined.`);
165
+ const supplier = this.externals.get(`${external}/${instance}`);
166
+ if (!supplier)
167
+ throw new Error(`External services '${external}/${instance}' not exist.`);
168
+ supplier.unsubscribe(instance);
169
+ this.externals.delete(`${external}/${instance}`);
170
+ }
171
+
172
+ async shutdown(): Promise<void> {
173
+ const promises: Promise<void>[] = [];
174
+ const uniqueServices = new Set(this.externals.values());
175
+ if (this.service.externalServices) {
176
+ for (const es of Object.values(this.service.externalServices)) {
177
+ uniqueServices.add(es);
178
+ }
179
+ }
180
+ for (const es of uniqueServices) {
181
+ promises.push(es.shutdown());
182
+ }
183
+ await Promise.all(promises);
184
+ }
185
+
186
+ derive(service: SkipService): ServiceDefinition {
187
+ return new ServiceDefinition(service, new Map(this.externals));
188
+ }
77
189
  }
78
190
 
79
191
  class Handles {
@@ -122,35 +234,27 @@ export class Stack {
122
234
  }
123
235
  }
124
236
 
125
- export class Refs {
126
- constructor(
127
- public readonly binding: FromBinding,
128
- public readonly skjson: JsonConverter,
129
- public readonly handles: Handles,
130
- public readonly needGC: () => boolean,
131
- public readonly runWithGC: <T>(fn: () => T) => T,
132
- ) {}
133
- }
134
-
135
237
  class LazyCollectionImpl<K extends Json, V extends Json>
136
238
  extends SkManaged
137
239
  implements LazyCollection<K, V>
138
240
  {
139
241
  constructor(
140
- private readonly lazyCollection: string,
141
- private readonly refs: Refs,
242
+ readonly lazyCollection: string,
243
+ private readonly refs: ToBinding,
142
244
  ) {
143
245
  super();
144
246
  Object.freeze(this);
145
247
  }
146
248
 
147
249
  getArray(key: K): (V & DepSafe)[] {
148
- return this.refs.skjson.importJSON(
149
- this.refs.binding.SkipRuntime_LazyCollection__getArray(
150
- this.lazyCollection,
151
- this.refs.skjson.exportJSON(key),
152
- ),
153
- ) as (V & DepSafe)[];
250
+ return this.refs
251
+ .json()
252
+ .importJSON(
253
+ this.refs.binding.SkipRuntime_LazyCollection__getArray(
254
+ this.lazyCollection,
255
+ this.refs.json().exportJSON(key),
256
+ ),
257
+ ) as (V & DepSafe)[];
154
258
  }
155
259
 
156
260
  getUnique(key: K, _default?: { ifNone?: V; ifMany?: V }): V & DepSafe {
@@ -174,19 +278,21 @@ class EagerCollectionImpl<K extends Json, V extends Json>
174
278
  {
175
279
  constructor(
176
280
  public readonly collection: string,
177
- private readonly refs: Refs,
281
+ private readonly refs: ToBinding,
178
282
  ) {
179
283
  super();
180
284
  Object.freeze(this);
181
285
  }
182
286
 
183
287
  getArray(key: K): (V & DepSafe)[] {
184
- return this.refs.skjson.importJSON(
185
- this.refs.binding.SkipRuntime_Collection__getArray(
186
- this.collection,
187
- this.refs.skjson.exportJSON(key),
188
- ),
189
- ) as (V & DepSafe)[];
288
+ return this.refs
289
+ .json()
290
+ .importJSON(
291
+ this.refs.binding.SkipRuntime_Collection__getArray(
292
+ this.collection,
293
+ this.refs.json().exportJSON(key),
294
+ ),
295
+ ) as (V & DepSafe)[];
190
296
  }
191
297
 
192
298
  getUnique(key: K, _default?: { ifNone?: V; ifMany?: V }): V & DepSafe {
@@ -216,7 +322,7 @@ class EagerCollectionImpl<K extends Json, V extends Json>
216
322
  slices(...ranges: [K, K][]): EagerCollection<K, V> {
217
323
  const skcollection = this.refs.binding.SkipRuntime_Collection__slice(
218
324
  this.collection,
219
- this.refs.skjson.exportJSON(ranges),
325
+ this.refs.json().exportJSON(ranges),
220
326
  );
221
327
  return this.derive<K, V>(skcollection);
222
328
  }
@@ -263,18 +369,20 @@ class EagerCollectionImpl<K extends Json, V extends Json>
263
369
  this.refs.handles.register(mapperObj),
264
370
  );
265
371
 
266
- if (sknative in reducerObj && typeof reducerObj[sknative] == "string") {
372
+ if (
373
+ sknative in reducerObj.object &&
374
+ typeof reducerObj.object[sknative] == "string"
375
+ ) {
267
376
  return this.derive<K2, Accum>(
268
377
  this.refs.binding.SkipRuntime_Collection__nativeMapReduce(
269
378
  this.collection,
270
379
  skmapper,
271
- reducerObj[sknative],
380
+ reducerObj.object[sknative],
272
381
  ),
273
382
  );
274
383
  } else {
275
384
  const skreducer = this.refs.binding.SkipRuntime_createReducer(
276
385
  this.refs.handles.register(reducerObj),
277
- this.refs.skjson.exportJSON(reducerObj.initial),
278
386
  );
279
387
  return this.derive<K2, Accum>(
280
388
  this.refs.binding.SkipRuntime_Collection__mapReduce(
@@ -292,17 +400,19 @@ class EagerCollectionImpl<K extends Json, V extends Json>
292
400
  ...params: Params
293
401
  ): EagerCollection<K, Accum> {
294
402
  const reducerObj = instantiateUserObject("Reducer", reducer, params);
295
- if (sknative in reducerObj && typeof reducerObj[sknative] == "string") {
403
+ if (
404
+ sknative in reducerObj.object &&
405
+ typeof reducerObj.object[sknative] == "string"
406
+ ) {
296
407
  return this.derive<K, Accum>(
297
408
  this.refs.binding.SkipRuntime_Collection__nativeReduce(
298
409
  this.collection,
299
- reducerObj[sknative],
410
+ reducerObj.object[sknative],
300
411
  ),
301
412
  );
302
413
  } else {
303
414
  const skreducer = this.refs.binding.SkipRuntime_createReducer(
304
415
  this.refs.handles.register(reducerObj),
305
- this.refs.skjson.exportJSON(reducerObj.initial),
306
416
  );
307
417
  return this.derive<K, Accum>(
308
418
  this.refs.binding.SkipRuntime_Collection__reduce(
@@ -319,7 +429,7 @@ class EagerCollectionImpl<K extends Json, V extends Json>
319
429
  );
320
430
  const mapped = this.refs.binding.SkipRuntime_Collection__merge(
321
431
  this.collection,
322
- this.refs.skjson.exportJSON(otherNames),
432
+ this.refs.json().exportJSON(otherNames),
323
433
  );
324
434
  return this.derive<K, V>(mapped);
325
435
  }
@@ -340,72 +450,71 @@ class EagerCollectionImpl<K extends Json, V extends Json>
340
450
  class CollectionWriter<K extends Json, V extends Json> {
341
451
  constructor(
342
452
  public readonly collection: string,
343
- private readonly refs: Refs,
453
+ private readonly refs: ToBinding,
454
+ private forkName: Nullable<string>,
344
455
  ) {}
345
456
 
346
457
  async update(values: Entry<K, V>[], isInit: boolean): Promise<void> {
347
- await new Promise<void>((resolve, reject) => {
348
- if (!this.refs.needGC()) {
349
- reject(new SkipError("CollectionWriter.update cannot be performed."));
350
- }
351
- const errorHdl = this.refs.runWithGC(() => {
352
- const exHdl = this.refs.handles.register({
353
- resolve,
354
- reject: (ex: Error) => reject(ex),
355
- });
356
- return this.refs.binding.SkipRuntime_CollectionWriter__update(
357
- this.collection,
358
- this.refs.skjson.exportJSON(values),
359
- isInit,
360
- this.refs.binding.SkipRuntime_createExecutor(exHdl),
361
- );
362
- });
363
- if (errorHdl) reject(this.refs.handles.deleteHandle(errorHdl));
364
- });
458
+ this.refs.setFork(this.forkName);
459
+ const uuid = crypto.randomUUID();
460
+ const fork = this.fork(uuid);
461
+ try {
462
+ await fork.update_(values, isInit);
463
+ fork.merge();
464
+ } catch (ex: unknown) {
465
+ fork.abortFork();
466
+ throw ex;
467
+ }
365
468
  }
366
469
 
367
- error(error: unknown): void {
470
+ private update_(values: Entry<K, V>[], isInit: boolean): Promise<void> {
471
+ this.refs.setFork(this.getForkName());
368
472
  if (!this.refs.needGC()) {
369
473
  throw new SkipError("CollectionWriter.update cannot be performed.");
370
474
  }
371
- const errorHdl = this.refs.runWithGC(() =>
372
- this.refs.binding.SkipRuntime_CollectionWriter__error(
475
+ return this.refs.runAsync(() =>
476
+ this.refs.binding.SkipRuntime_CollectionWriter__update(
373
477
  this.collection,
374
- this.refs.skjson.exportJSON(this.toJSONError(error)),
478
+ this.refs.json().exportJSON(values),
479
+ isInit,
375
480
  ),
376
481
  );
377
- if (errorHdl) throw this.refs.handles.deleteHandle(errorHdl);
378
482
  }
379
483
 
380
- initialized(error?: unknown): void {
381
- if (!this.refs.needGC()) {
382
- throw new SkipError("CollectionWriter.update cannot be performed.");
383
- }
384
- const errorHdl = this.refs.runWithGC(() =>
385
- this.refs.binding.SkipRuntime_CollectionWriter__initialized(
386
- this.collection,
387
- this.refs.skjson.exportJSON(error ? this.toJSONError(error) : null),
388
- ),
389
- );
390
- if (errorHdl) throw this.refs.handles.deleteHandle(errorHdl);
484
+ private fork(name: string): CollectionWriter<K, V> {
485
+ this.refs.setFork(this.forkName);
486
+ this.refs.fork(name);
487
+ return new CollectionWriter(this.collection, this.refs, name);
391
488
  }
392
489
 
393
- private toJSONError(error: unknown): Json {
394
- if (error instanceof Error) return error.message;
395
- if (typeof error == "number") return error;
396
- if (typeof error == "boolean") return error;
397
- if (typeof error == "string") return error;
398
- return JSON.parse(
399
- JSON.stringify(error, Object.getOwnPropertyNames(error)),
400
- ) as Json;
490
+ private merge(): void {
491
+ if (!this.forkName) throw new Error("Unable to merge fork on main.");
492
+ this.refs.setFork(this.forkName);
493
+ this.refs.merge();
401
494
  }
402
- }
403
495
 
404
- class ContextImpl extends SkManaged implements Context {
405
- constructor(private readonly refs: Refs) {
406
- super();
407
- Object.freeze(this);
496
+ private abortFork(): void {
497
+ if (!this.forkName) throw new Error("Unable to abord fork on main.");
498
+ this.refs.setFork(this.forkName);
499
+ this.refs.abortFork();
500
+ }
501
+
502
+ private getForkName(): Nullable<string> {
503
+ const forkName = this.forkName;
504
+ if (!forkName) return null;
505
+ if (
506
+ !this.refs.runWithGC(() =>
507
+ this.refs.binding.SkipRuntime_Runtime__forkExists(forkName),
508
+ )
509
+ ) {
510
+ this.forkName = null;
511
+ }
512
+ return this.forkName;
408
513
  }
514
+ }
515
+
516
+ class ContextImpl implements Context {
517
+ constructor(private readonly refs: ToBinding) {}
409
518
 
410
519
  createLazyCollection<
411
520
  K extends Json,
@@ -433,15 +542,16 @@ class ContextImpl extends SkManaged implements Context {
433
542
  this.refs.binding.SkipRuntime_Context__useExternalResource(
434
543
  resource.service,
435
544
  resource.identifier,
436
- this.refs.skjson.exportJSON(resource.params ?? {}),
545
+ this.refs.json().exportJSON(resource.params ?? {}),
437
546
  );
438
547
  return new EagerCollectionImpl<K, V>(collection, this.refs);
439
548
  }
440
549
 
441
550
  jsonExtract(value: JsonObject, pattern: string): Json[] {
442
- return this.refs.skjson.importJSON(
551
+ const skjson = this.refs.json();
552
+ return skjson.importJSON(
443
553
  this.refs.binding.SkipRuntime_Context__jsonExtract(
444
- this.refs.skjson.exportJSON(value),
554
+ skjson.exportJSON(value),
445
555
  pattern,
446
556
  ),
447
557
  ) as Json[];
@@ -456,46 +566,39 @@ export class ServiceInstanceFactory {
456
566
  }
457
567
  }
458
568
 
459
- type GetResult<T> = {
460
- payload: T;
461
- errors: Json[];
462
- };
463
-
464
- export type SubscriptionID = Opaque<bigint, "subscription">;
569
+ export type SubscriptionID = Opaque<string, "subscription">;
465
570
 
466
571
  /**
467
572
  * A `ServiceInstance` is a running instance of a `SkipService`, providing access to its resources
468
573
  * and operations to manage subscriptions and the service itself.
469
574
  */
470
575
  export class ServiceInstance {
471
- constructor(private readonly refs: Refs) {}
576
+ constructor(
577
+ private readonly refs: ToBinding,
578
+ readonly forkName: Nullable<string>,
579
+ private definition: ServiceDefinition,
580
+ ) {}
472
581
 
473
582
  /**
474
583
  * Instantiate a resource with some parameters and client session authentication token
475
584
  * @param identifier - The resource instance identifier
476
585
  * @param resource - A resource name, which must correspond to a key in this `SkipService`'s `resources` field
477
586
  * @param params - Resource parameters, which will be passed to the resource constructor specified in this `SkipService`'s `resources` field
587
+ * @returns The resulting promise
478
588
  */
479
589
  instantiateResource(
480
590
  identifier: string,
481
591
  resource: string,
482
592
  params: Json,
483
593
  ): Promise<void> {
484
- return new Promise((resolve, reject) => {
485
- const errorHdl = this.refs.runWithGC(() => {
486
- const exHdl = this.refs.handles.register({
487
- resolve,
488
- reject: (ex: Error) => reject(ex),
489
- });
490
- return this.refs.binding.SkipRuntime_Runtime__createResource(
491
- identifier,
492
- resource,
493
- this.refs.skjson.exportJSON(params),
494
- this.refs.binding.SkipRuntime_createExecutor(exHdl),
495
- );
496
- });
497
- if (errorHdl) reject(this.refs.handles.deleteHandle(errorHdl));
498
- });
594
+ this.refs.setFork(this.forkName);
595
+ return this.refs.runAsync(() =>
596
+ this.refs.binding.SkipRuntime_Runtime__createResource(
597
+ identifier,
598
+ resource,
599
+ this.refs.json().exportJSON(params),
600
+ ),
601
+ );
499
602
  }
500
603
 
501
604
  /**
@@ -511,21 +614,21 @@ export class ServiceInstance {
511
614
  const uuid = crypto.randomUUID();
512
615
  await this.instantiateResource(uuid, resource, params);
513
616
  try {
617
+ this.refs.setFork(this.forkName);
514
618
  const result = this.refs.runWithGC(() => {
515
- return this.refs.skjson.importJSON(
516
- this.refs.binding.SkipRuntime_Runtime__getAll(
517
- resource,
518
- this.refs.skjson.exportJSON(params),
519
- ),
520
- true,
521
- );
619
+ return this.refs
620
+ .json()
621
+ .importJSON(
622
+ this.refs.binding.SkipRuntime_Runtime__getAll(
623
+ resource,
624
+ this.refs.json().exportJSON(params),
625
+ ),
626
+ true,
627
+ );
522
628
  });
523
629
  if (typeof result == "number")
524
630
  throw this.refs.handles.deleteHandle(result as Handle<Error>);
525
- const info = result as GetResult<Entry<K, V>[]>;
526
- if (info.errors.length > 0)
527
- throw new SkipError(JSON.stringify(info.errors));
528
- return info.payload;
631
+ return result as Entry<K, V>[];
529
632
  } finally {
530
633
  this.closeResourceInstance(uuid);
531
634
  }
@@ -546,22 +649,21 @@ export class ServiceInstance {
546
649
  const uuid = crypto.randomUUID();
547
650
  await this.instantiateResource(uuid, resource, params);
548
651
  try {
652
+ this.refs.setFork(this.forkName);
653
+ const skjson = this.refs.json();
549
654
  const result = this.refs.runWithGC(() => {
550
- return this.refs.skjson.importJSON(
655
+ return skjson.importJSON(
551
656
  this.refs.binding.SkipRuntime_Runtime__getForKey(
552
657
  resource,
553
- this.refs.skjson.exportJSON(params),
554
- this.refs.skjson.exportJSON(key),
658
+ skjson.exportJSON(params),
659
+ skjson.exportJSON(key),
555
660
  ),
556
661
  true,
557
662
  );
558
663
  });
559
664
  if (typeof result == "number")
560
665
  throw this.refs.handles.deleteHandle(result as Handle<Error>);
561
- const info = result as GetResult<V[]>;
562
- if (info.errors.length > 0)
563
- throw new SkipError(JSON.stringify(info.errors));
564
- return info.payload;
666
+ return result as V[];
565
667
  } finally {
566
668
  this.closeResourceInstance(uuid);
567
669
  }
@@ -572,6 +674,7 @@ export class ServiceInstance {
572
674
  * @param resourceInstanceId - The resource identifier
573
675
  */
574
676
  closeResourceInstance(resourceInstanceId: string): void {
677
+ this.refs.setFork(this.forkName);
575
678
  const errorHdl = this.refs.runWithGC(() => {
576
679
  return this.refs.binding.SkipRuntime_Runtime__closeResource(
577
680
  resourceInstanceId,
@@ -599,6 +702,7 @@ export class ServiceInstance {
599
702
  },
600
703
  watermark?: string,
601
704
  ): SubscriptionID {
705
+ this.refs.setFork(this.forkName);
602
706
  const session = this.refs.runWithGC(() => {
603
707
  const sknotifier = this.refs.binding.SkipRuntime_createNotifier(
604
708
  this.refs.handles.register(notifier),
@@ -620,7 +724,7 @@ export class ServiceInstance {
620
724
  } else if (session < 0n) {
621
725
  throw this.refs.handles.deleteHandle(Number(-session) as Handle<Error>);
622
726
  }
623
- return session as SubscriptionID;
727
+ return resourceInstanceId as SubscriptionID;
624
728
  }
625
729
 
626
730
  /**
@@ -628,6 +732,7 @@ export class ServiceInstance {
628
732
  * @param id - The subscription identifier returned by a call to `subscribe`
629
733
  */
630
734
  unsubscribe(id: SubscriptionID): void {
735
+ this.refs.setFork(this.forkName);
631
736
  const errorHdl = this.refs.runWithGC(() => {
632
737
  return this.refs.binding.SkipRuntime_Runtime__unsubscribe(id);
633
738
  });
@@ -641,24 +746,45 @@ export class ServiceInstance {
641
746
  * @param collection - the name of the input collection to update
642
747
  * @param entries - entries to update in the collection.
643
748
  */
644
- update<K extends Json, V extends Json>(
749
+ async update<K extends Json, V extends Json>(
645
750
  collection: string,
646
751
  entries: Entry<K, V>[],
647
752
  ): Promise<void> {
648
- return new Promise((resolve, reject) => {
649
- const errorHdl = this.refs.runWithGC(() => {
650
- const exHdl = this.refs.handles.register({
651
- resolve,
652
- reject: (ex: Error) => reject(ex),
653
- });
654
- return this.refs.binding.SkipRuntime_Runtime__update(
753
+ this.refs.setFork(this.forkName);
754
+ const uuid = crypto.randomUUID();
755
+ const fork = this.fork(uuid);
756
+ try {
757
+ await fork.update_(collection, entries);
758
+ fork.merge([]);
759
+ } catch (ex: unknown) {
760
+ fork.abortFork();
761
+ throw ex;
762
+ }
763
+ }
764
+
765
+ private async update_<K extends Json, V extends Json>(
766
+ collection: string,
767
+ entries: Entry<K, V>[],
768
+ ): Promise<void> {
769
+ this.refs.setFork(this.forkName);
770
+ const result = this.refs.runWithGC(() => {
771
+ const json = this.refs.json();
772
+ return json.importJSON(
773
+ this.refs.binding.SkipRuntime_Runtime__update(
655
774
  collection,
656
- this.refs.skjson.exportJSON(entries),
657
- this.refs.binding.SkipRuntime_createExecutor(exHdl),
658
- );
659
- });
660
- if (errorHdl) reject(this.refs.handles.deleteHandle(errorHdl));
775
+ this.refs.json().exportJSON(entries),
776
+ ),
777
+ true,
778
+ );
661
779
  });
780
+ if (Array.isArray(result)) {
781
+ const handles = result as Handle<Promise<void>>[];
782
+ const promises = handles.map((h) => this.refs.handles.deleteHandle(h));
783
+ await Promise.all(promises);
784
+ } else {
785
+ const errorHdl = result as Handle<Error>;
786
+ throw this.refs.handles.deleteHandle(errorHdl);
787
+ }
662
788
  }
663
789
 
664
790
  /**
@@ -667,21 +793,99 @@ export class ServiceInstance {
667
793
  * @returns The promise of externals services shutdowns
668
794
  */
669
795
  close(): Promise<unknown> {
796
+ this.refs.setFork(this.forkName);
670
797
  const result = this.refs.runWithGC(() => {
671
- return this.refs.skjson.importJSON(
672
- this.refs.binding.SkipRuntime_closeService(),
673
- true,
674
- );
798
+ return this.refs.binding.SkipRuntime_closeService();
799
+ });
800
+ if (result >= 0) {
801
+ return this.refs.handles.deleteHandle(result as Handle<Promise<unknown>>);
802
+ } else {
803
+ const errorHdl = -(result as number) as Handle<Error>;
804
+ return Promise.reject(this.refs.handles.deleteHandle(errorHdl));
805
+ }
806
+ }
807
+
808
+ async reload(service: SkipService, changes: ChangeManager): Promise<void> {
809
+ if (this.forkName) {
810
+ throw new SkipError("Reload cannot be called in transaction.");
811
+ }
812
+ const definition = this.definition.derive(service);
813
+ this.refs.setFork(this.forkName);
814
+ const uuid = crypto.randomUUID();
815
+ const fork = this.fork(uuid);
816
+ let merged = false;
817
+ try {
818
+ const streamsToClose = await fork._reload(definition, changes);
819
+ fork.merge(streamsToClose);
820
+ merged = true;
821
+ this.closeResourceStreams(streamsToClose);
822
+ this.definition = definition;
823
+ } catch (ex: unknown) {
824
+ console.error(ex);
825
+ if (!merged) fork.abortFork();
826
+ throw ex;
827
+ }
828
+ }
829
+
830
+ private async _reload(
831
+ definition: ServiceDefinition,
832
+ changes: ChangeManager,
833
+ ): Promise<string[]> {
834
+ this.refs.setFork(this.forkName);
835
+ const result = this.refs.runWithGC(() => {
836
+ this.refs.changes = this.refs.handles.register(changes);
837
+ const skservicehHdl = this.refs.handles.register(definition);
838
+ const skservice =
839
+ this.refs.binding.SkipRuntime_createService(skservicehHdl);
840
+ const res = this.refs.binding.SkipRuntime_Runtime__reload(skservice);
841
+ this.refs.handles.deleteHandle(this.refs.changes);
842
+ this.refs.changes = null;
843
+ return this.refs.json().importJSON(res, true);
675
844
  });
676
845
  if (Array.isArray(result)) {
677
- const handles = result as Handle<Promise<void>>[];
846
+ const [handles, res] = result as [Handle<Promise<void>>[], string[]];
678
847
  const promises = handles.map((h) => this.refs.handles.deleteHandle(h));
679
- return Promise.all(promises);
848
+ await Promise.all(promises);
849
+ return res;
680
850
  } else {
681
851
  const errorHdl = result as Handle<Error>;
682
- return Promise.reject(this.refs.handles.deleteHandle(errorHdl));
852
+ throw this.refs.handles.deleteHandle(errorHdl);
683
853
  }
684
854
  }
855
+
856
+ /**
857
+ * Fork the service with current specified name.
858
+ * @param name - the name of the fork.
859
+ * @returns The forked ServiceInstance
860
+ */
861
+ private fork(name: string): ServiceInstance {
862
+ if (this.forkName) throw new Error(`Unable to fork ${this.forkName}.`);
863
+ this.refs.setFork(this.forkName);
864
+ this.refs.fork(name);
865
+ return new ServiceInstance(this.refs, name, this.definition);
866
+ }
867
+
868
+ private merge(ignore: string[]): void {
869
+ if (!this.forkName) throw new Error("Unable to merge fork on main.");
870
+ this.refs.setFork(this.forkName);
871
+ this.refs.merge(ignore);
872
+ }
873
+
874
+ private abortFork(): void {
875
+ if (!this.forkName) throw new Error("Unable to abord fork on main.");
876
+ this.refs.setFork(this.forkName);
877
+ this.refs.abortFork();
878
+ }
879
+
880
+ private closeResourceStreams(streams: string[]): void {
881
+ this.refs.setFork(this.forkName);
882
+ const errorHdl = this.refs.runWithGC(() => {
883
+ return this.refs.binding.SkipRuntime_Runtime__closeResourceStreams(
884
+ this.refs.json().exportJSON(streams),
885
+ );
886
+ });
887
+ if (errorHdl) throw this.refs.handles.deleteHandle(errorHdl);
888
+ }
685
889
  }
686
890
 
687
891
  class ValuesImpl<T> implements Values<T> {
@@ -758,17 +962,21 @@ class ValuesImpl<T> implements Values<T> {
758
962
 
759
963
  export class ToBinding {
760
964
  private readonly stack: Stack;
761
- private readonly handles: Handles;
762
965
  private skjson?: JsonConverter;
966
+ private forkName: Nullable<string>;
967
+ readonly handles: Handles;
968
+ changes: Nullable<Handle<ChangeManager>>;
763
969
 
764
970
  constructor(
765
- private binding: FromBinding,
766
- private runWithGC: <T>(fn: () => T) => T,
971
+ public binding: FromBinding,
972
+ public runWithGC: <T>(fn: () => T) => T,
767
973
  private getConverter: () => JsonConverter,
768
974
  private getError: (skExc: Pointer<Internal.Exception>) => Error,
769
975
  ) {
770
976
  this.stack = new Stack();
771
977
  this.handles = new Handles();
978
+ this.forkName = null;
979
+ this.changes = null;
772
980
  }
773
981
 
774
982
  register<T>(v: T): Handle<T> {
@@ -795,17 +1003,29 @@ export class ToBinding {
795
1003
  return this.stack.get();
796
1004
  }
797
1005
 
1006
+ SkipRuntime_getFork(): Nullable<string> {
1007
+ return this.forkName;
1008
+ }
1009
+
1010
+ SkipRuntime_getChangeManager(): number {
1011
+ return this.changes ?? 0;
1012
+ }
1013
+
1014
+ setFork(name: Nullable<string>): void {
1015
+ this.forkName = name;
1016
+ }
1017
+
798
1018
  // Mapper
799
1019
 
800
1020
  SkipRuntime_Mapper__mapEntry(
801
- skmapper: Handle<JSONMapper>,
1021
+ skmapper: Handle<HandlerInfo<JSONMapper>>,
802
1022
  key: Pointer<Internal.CJSON>,
803
1023
  values: Pointer<Internal.NonEmptyIterator>,
804
1024
  ): Pointer<Internal.CJArray> {
805
1025
  const skjson = this.getJsonConverter();
806
1026
  const mapper = this.handles.get(skmapper);
807
- const context = new ContextImpl(this.refs());
808
- const result = mapper.mapEntry(
1027
+ const context = new ContextImpl(this);
1028
+ const result = mapper.object.mapEntry(
809
1029
  skjson.importJSON(key) as Json,
810
1030
  new ValuesImpl<Json>(skjson, this.binding, values),
811
1031
  context,
@@ -813,27 +1033,65 @@ export class ToBinding {
813
1033
  return skjson.exportJSON(Array.from(result));
814
1034
  }
815
1035
 
816
- SkipRuntime_deleteMapper(mapper: Handle<JSONMapper>): void {
1036
+ SkipRuntime_Mapper__getInfo(
1037
+ skmapper: Handle<HandlerInfo<JSONMapper>>,
1038
+ ): Pointer<Internal.CJObject> {
1039
+ return this.getInfo(skmapper);
1040
+ }
1041
+
1042
+ SkipRuntime_Mapper__isEquals(
1043
+ mapper: Handle<HandlerInfo<JSONMapper>>,
1044
+ other: Handle<HandlerInfo<JSONMapper>>,
1045
+ ): number {
1046
+ const object = this.handles.get(mapper);
1047
+ if (this.getChanges()?.needMapperReload(object.name)) {
1048
+ return 0;
1049
+ }
1050
+ return this.isEquals(mapper, other);
1051
+ }
1052
+
1053
+ SkipRuntime_deleteMapper(mapper: Handle<HandlerInfo<JSONMapper>>): void {
817
1054
  this.handles.deleteHandle(mapper);
818
1055
  }
819
1056
 
820
1057
  // LazyCompute
821
1058
 
822
1059
  SkipRuntime_LazyCompute__compute(
823
- sklazyCompute: Handle<JSONLazyCompute>,
1060
+ sklazyCompute: Handle<HandlerInfo<JSONLazyCompute>>,
824
1061
  self: string,
825
1062
  skkey: Pointer<Internal.CJSON>,
826
1063
  ): Pointer<Internal.CJArray> {
827
1064
  const skjson = this.getJsonConverter();
828
1065
  const lazyCompute = this.handles.get(sklazyCompute);
829
- const result = lazyCompute.compute(
830
- new LazyCollectionImpl<Json, Json>(self, this.refs()),
1066
+ const context = new ContextImpl(this);
1067
+ const result = lazyCompute.object.compute(
1068
+ new LazyCollectionImpl<Json, Json>(self, this),
831
1069
  skjson.importJSON(skkey) as Json,
1070
+ context,
832
1071
  );
833
1072
  return skjson.exportJSON(Array.from(result));
834
1073
  }
835
1074
 
836
- SkipRuntime_deleteLazyCompute(lazyCompute: Handle<JSONLazyCompute>): void {
1075
+ SkipRuntime_LazyCompute__getInfo(
1076
+ lazyCompute: Handle<HandlerInfo<JSONLazyCompute>>,
1077
+ ): Pointer<Internal.CJObject> {
1078
+ return this.getInfo(lazyCompute);
1079
+ }
1080
+
1081
+ SkipRuntime_LazyCompute__isEquals(
1082
+ lazyCompute: Handle<HandlerInfo<JSONLazyCompute>>,
1083
+ other: Handle<HandlerInfo<JSONLazyCompute>>,
1084
+ ): number {
1085
+ const object = this.handles.get(lazyCompute);
1086
+ if (this.getChanges()?.needLazyComputeReload(object.name)) {
1087
+ return 0;
1088
+ }
1089
+ return this.isEquals(lazyCompute, other);
1090
+ }
1091
+
1092
+ SkipRuntime_deleteLazyCompute(
1093
+ lazyCompute: Handle<HandlerInfo<JSONLazyCompute>>,
1094
+ ): void {
837
1095
  this.handles.deleteHandle(lazyCompute);
838
1096
  }
839
1097
 
@@ -849,11 +1107,10 @@ export class ToBinding {
849
1107
  const keysIds = skjson.importJSON(skcollections) as {
850
1108
  [key: string]: string;
851
1109
  };
852
- const refs = this.refs();
853
1110
  for (const [key, name] of Object.entries(keysIds)) {
854
- collections[key] = new EagerCollectionImpl<Json, Json>(name, refs);
1111
+ collections[key] = new EagerCollectionImpl<Json, Json>(name, this);
855
1112
  }
856
- const collection = resource.instantiate(collections, new ContextImpl(refs));
1113
+ const collection = resource.instantiate(collections, new ContextImpl(this));
857
1114
  return EagerCollectionImpl.getName(collection);
858
1115
  }
859
1116
 
@@ -861,28 +1118,10 @@ export class ToBinding {
861
1118
  this.handles.deleteHandle(resource);
862
1119
  }
863
1120
 
864
- // ResourceBuilder
1121
+ // ServiceDefinition
865
1122
 
866
- SkipRuntime_ResourceBuilder__build(
867
- skbuilder: Handle<ResourceBuilder>,
868
- skparams: Pointer<Internal.CJObject>,
869
- ): Pointer<Internal.Resource> {
870
- const skjson = this.getJsonConverter();
871
- const builder = this.handles.get(skbuilder);
872
- const resource = builder.build(skjson.importJSON(skparams) as Json);
873
- return this.binding.SkipRuntime_createResource(
874
- this.handles.register(resource),
875
- );
876
- }
877
-
878
- SkipRuntime_deleteResourceBuilder(builder: Handle<ResourceBuilder>): void {
879
- this.handles.deleteHandle(builder);
880
- }
881
-
882
- // Service
883
-
884
- SkipRuntime_Service__createGraph(
885
- skservice: Handle<SkipService>,
1123
+ SkipRuntime_ServiceDefinition__createGraph(
1124
+ skservice: Handle<ServiceDefinition>,
886
1125
  skcollections: Pointer<Internal.CJObject>,
887
1126
  ): Pointer<Internal.CJObject> {
888
1127
  const skjson = this.getJsonConverter();
@@ -891,11 +1130,10 @@ export class ToBinding {
891
1130
  const keysIds = skjson.importJSON(skcollections) as {
892
1131
  [key: string]: string;
893
1132
  };
894
- const refs = this.refs();
895
1133
  for (const [key, name] of Object.entries(keysIds)) {
896
- collections[key] = new EagerCollectionImpl<Json, Json>(name, refs);
1134
+ collections[key] = new EagerCollectionImpl<Json, Json>(name, this);
897
1135
  }
898
- const result = service.createGraph(collections, new ContextImpl(refs));
1136
+ const result = service.createGraph(collections, new ContextImpl(this));
899
1137
  const collectionsNames: { [name: string]: string } = {};
900
1138
  for (const [name, collection] of Object.entries(result)) {
901
1139
  collectionsNames[name] = EagerCollectionImpl.getName(collection);
@@ -903,10 +1141,111 @@ export class ToBinding {
903
1141
  return skjson.exportJSON(collectionsNames);
904
1142
  }
905
1143
 
906
- SkipRuntime_deleteService(service: Handle<SkipService>): void {
1144
+ SkipRuntime_ServiceDefinition__inputs(
1145
+ skservice: Handle<ServiceDefinition>,
1146
+ ): Pointer<Internal.CJArray<Internal.CJSON>> {
1147
+ const skjson = this.getJsonConverter();
1148
+ const service = this.handles.get(skservice);
1149
+ return skjson.exportJSON(service.inputs());
1150
+ }
1151
+
1152
+ SkipRuntime_ServiceDefinition__resources(
1153
+ skservice: Handle<ServiceDefinition>,
1154
+ ): Pointer<Internal.CJArray<Internal.CJSON>> {
1155
+ const skjson = this.getJsonConverter();
1156
+ const service = this.handles.get(skservice);
1157
+ return skjson.exportJSON(service.resources());
1158
+ }
1159
+
1160
+ SkipRuntime_ServiceDefinition__initialData(
1161
+ skservice: Handle<ServiceDefinition>,
1162
+ name: string,
1163
+ ): Pointer<Internal.CJArray<Internal.CJSON>> {
1164
+ const skjson = this.getJsonConverter();
1165
+ const service = this.handles.get(skservice);
1166
+ return skjson.exportJSON(service.initialData(name));
1167
+ }
1168
+
1169
+ SkipRuntime_ServiceDefinition__buildResource(
1170
+ skservice: Handle<ServiceDefinition>,
1171
+ name: string,
1172
+ skparams: Pointer<Internal.CJObject>,
1173
+ ): Pointer<Internal.Resource> {
1174
+ const skjson = this.getJsonConverter();
1175
+ const service = this.handles.get(skservice);
1176
+ const resource = service.buildResource(
1177
+ name,
1178
+ skjson.importJSON(skparams) as Json,
1179
+ );
1180
+ return this.binding.SkipRuntime_createResource(
1181
+ this.handles.register(resource),
1182
+ );
1183
+ }
1184
+
1185
+ SkipRuntime_ServiceDefinition__subscribe(
1186
+ skservice: Handle<ServiceDefinition>,
1187
+ external: string,
1188
+ writerId: string,
1189
+ instance: string,
1190
+ resource: string,
1191
+ skparams: Pointer<Internal.CJObject>,
1192
+ ): Handle<Promise<void>> {
1193
+ const skjson = this.getJsonConverter();
1194
+ const service = this.handles.get(skservice);
1195
+ const writer = new CollectionWriter(writerId, this, this.forkName);
1196
+ const params = skjson.importJSON(skparams, true) as Json;
1197
+ return this.handles.register(
1198
+ service.subscribe(external, writer, instance, resource, params),
1199
+ );
1200
+ }
1201
+
1202
+ SkipRuntime_ServiceDefinition__unsubscribe(
1203
+ skservice: Handle<ServiceDefinition>,
1204
+ external: string,
1205
+ instance: string,
1206
+ ): void {
1207
+ const service = this.handles.get(skservice);
1208
+ service.unsubscribe(external, instance);
1209
+ }
1210
+
1211
+ SkipRuntime_ServiceDefinition__shutdown(
1212
+ skservice: Handle<ServiceDefinition>,
1213
+ ): Handle<Promise<unknown>> {
1214
+ const service = this.handles.get(skservice);
1215
+ return this.handles.register(service.shutdown());
1216
+ }
1217
+
1218
+ SkipRuntime_deleteService(service: Handle<ServiceDefinition>): void {
907
1219
  this.handles.deleteHandle(service);
908
1220
  }
909
1221
 
1222
+ // Change manager
1223
+
1224
+ SkipRuntime_ChangeManager__needInputReload(
1225
+ skmanager: Handle<ChangeManager>,
1226
+ name: string,
1227
+ ): number {
1228
+ const manager = this.handles.get(skmanager);
1229
+ return manager.needInputReload(name) ? 1 : 0;
1230
+ }
1231
+
1232
+ SkipRuntime_ChangeManager__needResourceReload(
1233
+ skmanager: Handle<ChangeManager>,
1234
+ name: string,
1235
+ ): number {
1236
+ const manager = this.handles.get(skmanager);
1237
+ return manager.needResourceReload(name);
1238
+ }
1239
+
1240
+ SkipRuntime_ChangeManager__needExternalServiceReload(
1241
+ skmanager: Handle<ChangeManager>,
1242
+ name: string,
1243
+ resource: string,
1244
+ ): number {
1245
+ const manager = this.handles.get(skmanager);
1246
+ return manager.needExternalServiceReload(name, resource) ? 1 : 0;
1247
+ }
1248
+
910
1249
  // Notifier
911
1250
 
912
1251
  SkipRuntime_Notifier__subscribed<K extends Json, V extends Json>(
@@ -948,15 +1287,23 @@ export class ToBinding {
948
1287
 
949
1288
  // Reducer
950
1289
 
1290
+ SkipRuntime_Reducer__init(
1291
+ skreducer: Handle<HandlerInfo<Reducer<Json, Json>>>,
1292
+ ): Pointer<Internal.CJSON> {
1293
+ const skjson = this.getJsonConverter();
1294
+ const reducer = this.handles.get(skreducer);
1295
+ return skjson.exportJSON(reducer.object.initial);
1296
+ }
1297
+
951
1298
  SkipRuntime_Reducer__add(
952
- skreducer: Handle<Reducer<Json, Json>>,
1299
+ skreducer: Handle<HandlerInfo<Reducer<Json, Json>>>,
953
1300
  skacc: Nullable<Pointer<Internal.CJSON>>,
954
1301
  skvalue: Pointer<Internal.CJSON>,
955
1302
  ): Pointer<Internal.CJSON> {
956
1303
  const skjson = this.getJsonConverter();
957
1304
  const reducer = this.handles.get(skreducer);
958
1305
  return skjson.exportJSON(
959
- reducer.add(
1306
+ reducer.object.add(
960
1307
  skacc ? (skjson.importJSON(skacc) as Json) : null,
961
1308
  skjson.importJSON(skvalue) as Json & DepSafe,
962
1309
  ),
@@ -964,165 +1311,216 @@ export class ToBinding {
964
1311
  }
965
1312
 
966
1313
  SkipRuntime_Reducer__remove(
967
- skreducer: Handle<Reducer<Json, Json>>,
1314
+ skreducer: Handle<HandlerInfo<Reducer<Json, Json>>>,
968
1315
  skacc: Pointer<Internal.CJSON>,
969
1316
  skvalue: Pointer<Internal.CJSON>,
970
1317
  ): Nullable<Pointer<Internal.CJSON>> {
971
1318
  const skjson = this.getJsonConverter();
972
1319
  const reducer = this.handles.get(skreducer);
973
1320
  return skjson.exportJSON(
974
- reducer.remove(
1321
+ reducer.object.remove(
975
1322
  skjson.importJSON(skacc) as Json,
976
1323
  skjson.importJSON(skvalue) as Json & DepSafe,
977
1324
  ),
978
1325
  );
979
1326
  }
980
1327
 
981
- SkipRuntime_deleteReducer(reducer: Handle<Reducer<Json, Json>>): void {
982
- this.handles.deleteHandle(reducer);
1328
+ SkipRuntime_Reducer__isEquals(
1329
+ reducer: Handle<HandlerInfo<Reducer<Json, Json>>>,
1330
+ other: Handle<HandlerInfo<Reducer<Json, Json>>>,
1331
+ ): number {
1332
+ const object = this.handles.get(reducer);
1333
+ if (this.getChanges()?.needReducerReload(object.name)) {
1334
+ return 0;
1335
+ }
1336
+ return this.isEquals(reducer, other);
983
1337
  }
984
1338
 
985
- // ExternalService
1339
+ SkipRuntime_Reducer__getInfo(
1340
+ reducer: Handle<HandlerInfo<Reducer<Json, Json>>>,
1341
+ ): Pointer<Internal.CJObject> {
1342
+ return this.getInfo(reducer);
1343
+ }
986
1344
 
987
- SkipRuntime_ExternalService__subscribe(
988
- sksupplier: Handle<ExternalService>,
989
- writerId: string,
990
- instance: string,
991
- resource: string,
992
- skparams: Pointer<Internal.CJObject>,
1345
+ SkipRuntime_deleteReducer(
1346
+ reducer: Handle<HandlerInfo<Reducer<Json, Json>>>,
993
1347
  ): void {
994
- const skjson = this.getJsonConverter();
995
- const supplier = this.handles.get(sksupplier);
996
- const writer = new CollectionWriter(writerId, this.refs());
997
- const params = skjson.importJSON(skparams, true) as Json;
998
- // Ensure notification is made outside the current context update
999
- setTimeout(() => {
1000
- supplier
1001
- .subscribe(instance, resource, params, {
1002
- update: writer.update.bind(writer),
1003
- error: writer.error.bind(writer),
1004
- })
1005
- .then(() => writer.initialized())
1006
- .catch((e: unknown) =>
1007
- writer.initialized(
1008
- e instanceof Error
1009
- ? e.message
1010
- : JSON.stringify(e, Object.getOwnPropertyNames(e)),
1011
- ),
1012
- );
1013
- }, 0);
1348
+ this.handles.deleteHandle(reducer);
1014
1349
  }
1015
1350
 
1016
- SkipRuntime_ExternalService__unsubscribe(
1017
- sksupplier: Handle<ExternalService>,
1018
- instance: string,
1019
- ): void {
1020
- const supplier = this.handles.get(sksupplier);
1021
- supplier.unsubscribe(instance);
1351
+ async initService(service: SkipService): Promise<ServiceInstance> {
1352
+ this.setFork(null);
1353
+ const uuid = crypto.randomUUID();
1354
+ this.fork(uuid);
1355
+ try {
1356
+ this.setFork(uuid);
1357
+ const definition = new ServiceDefinition(service);
1358
+ await this.initService_(definition);
1359
+ this.setFork(uuid);
1360
+ this.merge();
1361
+ return new ServiceInstance(this, null, definition);
1362
+ } catch (ex: unknown) {
1363
+ this.setFork(uuid);
1364
+ this.abortFork();
1365
+ throw ex;
1366
+ }
1022
1367
  }
1023
1368
 
1024
- SkipRuntime_ExternalService__shutdown(
1025
- sksupplier: Handle<ExternalService>,
1026
- ): Handle<Promise<void>> {
1027
- const supplier = this.handles.get(sksupplier);
1028
- return this.handles.register(supplier.shutdown());
1369
+ private initService_(definition: ServiceDefinition): Promise<void> {
1370
+ return this.runAsync(() => {
1371
+ const skservicehHdl = this.handles.register(definition);
1372
+ const skservice = this.binding.SkipRuntime_createService(skservicehHdl);
1373
+ return this.binding.SkipRuntime_initService(skservice);
1374
+ });
1029
1375
  }
1030
1376
 
1031
- SkipRuntime_deleteExternalService(supplier: Handle<ExternalService>): void {
1032
- this.handles.deleteHandle(supplier);
1377
+ //
1378
+ public getJsonConverter() {
1379
+ if (this.skjson == undefined) {
1380
+ this.skjson = this.getConverter();
1381
+ }
1382
+ return this.skjson;
1033
1383
  }
1034
1384
 
1035
- // Executor
1385
+ public needGC() {
1386
+ return this.SkipRuntime_getContext() == null;
1387
+ }
1036
1388
 
1037
- SkipRuntime_Executor__resolve(skexecutor: Handle<Executor>): void {
1038
- const checker = this.handles.get(skexecutor);
1039
- checker.resolve();
1389
+ public json() {
1390
+ return this.getJsonConverter();
1040
1391
  }
1041
1392
 
1042
- SkipRuntime_Executor__reject(
1043
- skexecutor: Handle<Executor>,
1044
- error: Handle<Error>,
1045
- ): void {
1046
- const checker = this.handles.get(skexecutor);
1047
- checker.reject(this.handles.deleteHandle(error));
1393
+ fork(name: string): void {
1394
+ const errorHdl = this.runWithGC(() =>
1395
+ this.binding.SkipRuntime_Runtime__fork(name),
1396
+ );
1397
+ if (errorHdl) throw this.handles.deleteHandle(errorHdl);
1048
1398
  }
1049
1399
 
1050
- SkipRuntime_deleteExecutor(executor: Handle<Executor>): void {
1051
- this.handles.deleteHandle(executor);
1400
+ merge(ignore: string[] = []): void {
1401
+ const errorHdl = this.runWithGC(() =>
1402
+ this.binding.SkipRuntime_Runtime__merge(this.json().exportJSON(ignore)),
1403
+ );
1404
+ if (errorHdl) throw this.handles.deleteHandle(errorHdl);
1052
1405
  }
1053
1406
 
1054
- initService(service: SkipService): Promise<ServiceInstance> {
1055
- return new Promise((resolve, reject) => {
1056
- const refs = this.refs();
1057
- const errorHdl = refs.runWithGC(() => {
1058
- const skExternalServices =
1059
- refs.binding.SkipRuntime_ExternalServiceMap__create();
1060
- if (service.externalServices) {
1061
- for (const [name, remote] of Object.entries(
1062
- service.externalServices,
1063
- )) {
1064
- const skremote = refs.binding.SkipRuntime_createExternalService(
1065
- refs.handles.register(remote),
1066
- );
1067
- refs.binding.SkipRuntime_ExternalServiceMap__add(
1068
- skExternalServices,
1069
- name,
1070
- skremote,
1071
- );
1072
- }
1073
- }
1074
- const skresources =
1075
- refs.binding.SkipRuntime_ResourceBuilderMap__create();
1076
- for (const [name, builder] of Object.entries(service.resources)) {
1077
- const skbuilder = refs.binding.SkipRuntime_createResourceBuilder(
1078
- refs.handles.register(new ResourceBuilder(builder)),
1079
- );
1080
- refs.binding.SkipRuntime_ResourceBuilderMap__add(
1081
- skresources,
1082
- name,
1083
- skbuilder,
1084
- );
1085
- }
1086
- const skservice = refs.binding.SkipRuntime_createService(
1087
- refs.handles.register(service),
1088
- refs.skjson.exportJSON(service.initialData ?? {}),
1089
- skresources,
1090
- skExternalServices,
1091
- );
1092
- const exHdl = refs.handles.register({
1093
- resolve: () => {
1094
- resolve(new ServiceInstance(refs));
1095
- },
1096
- reject: (ex: Error) => reject(ex),
1097
- });
1098
- return refs.binding.SkipRuntime_initService(
1099
- skservice,
1100
- refs.binding.SkipRuntime_createExecutor(exHdl),
1101
- );
1102
- });
1103
- if (errorHdl) reject(refs.handles.deleteHandle(errorHdl));
1407
+ abortFork(): void {
1408
+ const errorHdl = this.runWithGC(() =>
1409
+ this.binding.SkipRuntime_Runtime__abortFork(),
1410
+ );
1411
+ if (errorHdl) throw this.handles.deleteHandle(errorHdl);
1412
+ }
1413
+
1414
+ async runAsync(fn: () => Pointer<Internal.CJSON>): Promise<void> {
1415
+ const result = this.runWithGC(() => {
1416
+ return this.json().importJSON(fn(), true);
1104
1417
  });
1418
+ if (Array.isArray(result)) {
1419
+ const handles = result as Handle<Promise<void>>[];
1420
+ const promises = handles.map((h) => this.handles.deleteHandle(h));
1421
+ await Promise.all(promises);
1422
+ } else {
1423
+ const errorHdl = result as Handle<Error>;
1424
+ throw this.handles.deleteHandle(errorHdl);
1425
+ }
1105
1426
  }
1106
1427
 
1107
- //
1108
- private getJsonConverter() {
1109
- if (this.skjson == undefined) {
1110
- this.skjson = this.getConverter();
1428
+ private deepEquals(a: Nullable<Json>, b: Nullable<Json>) {
1429
+ // Same reference or both NaN
1430
+ if (a === b) return true;
1431
+ if (a !== a && b !== b) return true; // NaN check
1432
+
1433
+ // Different types or one is null
1434
+ if (typeof a !== typeof b || a === null || b === null) return false;
1435
+
1436
+ // Primitives already checked by ===
1437
+ if (typeof a !== "object" || typeof b !== "object") return false;
1438
+
1439
+ // Arrays
1440
+ if (Array.isArray(a)) {
1441
+ if (!Array.isArray(b) || a.length !== b.length) return false;
1442
+ for (let i = 0; i < a.length; i++) {
1443
+ if (!this.deepEquals(a[i]!, b[i]!)) return false;
1444
+ }
1445
+ return true;
1111
1446
  }
1112
- return this.skjson;
1447
+
1448
+ // Different array status
1449
+ if (Array.isArray(b)) return false;
1450
+
1451
+ // Objects
1452
+ const keysA = Object.keys(a);
1453
+ const keysB = Object.keys(b);
1454
+
1455
+ if (keysA.length !== keysB.length) return false;
1456
+
1457
+ for (const key of keysA) {
1458
+ if (
1459
+ !Object.prototype.hasOwnProperty.call(b, key) ||
1460
+ !this.deepEquals(a[key]!, b[key]!)
1461
+ )
1462
+ return false;
1463
+ }
1464
+
1465
+ return true;
1113
1466
  }
1114
1467
 
1115
- private needGC() {
1116
- return this.SkipRuntime_getContext() == null;
1468
+ private getInfo<T>(
1469
+ skmapper: Handle<HandlerInfo<T>>,
1470
+ ): Pointer<Internal.CJObject> {
1471
+ const skjson = this.getJsonConverter();
1472
+ const object = this.handles.get(skmapper);
1473
+ const name = object.name;
1474
+ const parameters = object.params.map((v) => {
1475
+ if (v instanceof EagerCollectionImpl) {
1476
+ return {
1477
+ type: "collection",
1478
+ value: v.collection,
1479
+ };
1480
+ }
1481
+ if (v instanceof LazyCollectionImpl) {
1482
+ return {
1483
+ type: "collection",
1484
+ value: v.lazyCollection,
1485
+ };
1486
+ }
1487
+ return { type: "data", value: v as Json };
1488
+ });
1489
+ return skjson.exportJSON({ name, parameters });
1117
1490
  }
1118
1491
 
1119
- private refs(): Refs {
1120
- return new Refs(
1121
- this.binding,
1122
- this.getConverter(),
1123
- this.handles,
1124
- this.needGC.bind(this),
1125
- this.runWithGC,
1126
- );
1492
+ private isEquals<T extends JSONOperator>(
1493
+ mapper: Handle<HandlerInfo<T>>,
1494
+ other: Handle<HandlerInfo<T>>,
1495
+ ): number {
1496
+ const object = this.handles.get(mapper);
1497
+ const oobject = this.handles.get(other);
1498
+ if (object.object.constructor != oobject.object.constructor) {
1499
+ return 0;
1500
+ }
1501
+ if (object.params.length != oobject.params.length) return 0;
1502
+ for (const [i, param] of object.params.entries()) {
1503
+ const oparam = oobject.params[i];
1504
+ if (param instanceof EagerCollectionImpl) {
1505
+ if (oparam instanceof EagerCollectionImpl) {
1506
+ if (param.collection != oparam.collection) return 0;
1507
+ } else {
1508
+ return 0;
1509
+ }
1510
+ } else if (param instanceof LazyCollectionImpl) {
1511
+ if (oparam instanceof LazyCollectionImpl) {
1512
+ if (param.lazyCollection != oparam.lazyCollection) return 0;
1513
+ } else {
1514
+ return 0;
1515
+ }
1516
+ } else if (!this.deepEquals(param as Json, oparam as Json)) {
1517
+ return 0;
1518
+ }
1519
+ }
1520
+ return 1;
1521
+ }
1522
+
1523
+ private getChanges(): Nullable<ChangeManager> {
1524
+ return this.changes ? this.handles.get(this.changes) : null;
1127
1525
  }
1128
1526
  }