@skipruntime/core 0.0.16 → 0.0.18

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.getForkName());
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.forkName);
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,98 @@ 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
+ if (!merged) fork.abortFork();
825
+ throw ex;
826
+ }
827
+ }
828
+
829
+ private async _reload(
830
+ definition: ServiceDefinition,
831
+ changes: ChangeManager,
832
+ ): Promise<string[]> {
833
+ this.refs.setFork(this.forkName);
834
+ const result = this.refs.runWithGC(() => {
835
+ this.refs.changes = this.refs.handles.register(changes);
836
+ const skservicehHdl = this.refs.handles.register(definition);
837
+ const skservice =
838
+ this.refs.binding.SkipRuntime_createService(skservicehHdl);
839
+ const res = this.refs.binding.SkipRuntime_Runtime__reload(skservice);
840
+ this.refs.handles.deleteHandle(this.refs.changes);
841
+ this.refs.changes = null;
842
+ return this.refs.json().importJSON(res, true);
675
843
  });
676
844
  if (Array.isArray(result)) {
677
- const handles = result as Handle<Promise<void>>[];
845
+ const [handles, res] = result as [Handle<Promise<void>>[], string[]];
678
846
  const promises = handles.map((h) => this.refs.handles.deleteHandle(h));
679
- return Promise.all(promises);
847
+ await Promise.all(promises);
848
+ return res;
680
849
  } else {
681
850
  const errorHdl = result as Handle<Error>;
682
- return Promise.reject(this.refs.handles.deleteHandle(errorHdl));
851
+ throw this.refs.handles.deleteHandle(errorHdl);
683
852
  }
684
853
  }
854
+
855
+ /**
856
+ * Fork the service with current specified name.
857
+ * @param name - the name of the fork.
858
+ * @returns The forked ServiceInstance
859
+ */
860
+ private fork(name: string): ServiceInstance {
861
+ if (this.forkName) throw new Error(`Unable to fork ${this.forkName}.`);
862
+ this.refs.setFork(this.forkName);
863
+ this.refs.fork(name);
864
+ return new ServiceInstance(this.refs, name, this.definition);
865
+ }
866
+
867
+ private merge(ignore: string[]): void {
868
+ if (!this.forkName) throw new Error("Unable to merge fork on main.");
869
+ this.refs.setFork(this.forkName);
870
+ this.refs.merge(ignore);
871
+ }
872
+
873
+ private abortFork(): void {
874
+ if (!this.forkName) throw new Error("Unable to abord fork on main.");
875
+ this.refs.setFork(this.forkName);
876
+ this.refs.abortFork();
877
+ }
878
+
879
+ private closeResourceStreams(streams: string[]): void {
880
+ this.refs.setFork(this.forkName);
881
+ const errorHdl = this.refs.runWithGC(() => {
882
+ return this.refs.binding.SkipRuntime_Runtime__closeResourceStreams(
883
+ this.refs.json().exportJSON(streams),
884
+ );
885
+ });
886
+ if (errorHdl) throw this.refs.handles.deleteHandle(errorHdl);
887
+ }
685
888
  }
686
889
 
687
890
  class ValuesImpl<T> implements Values<T> {
@@ -758,17 +961,21 @@ class ValuesImpl<T> implements Values<T> {
758
961
 
759
962
  export class ToBinding {
760
963
  private readonly stack: Stack;
761
- private readonly handles: Handles;
762
964
  private skjson?: JsonConverter;
965
+ private forkName: Nullable<string>;
966
+ readonly handles: Handles;
967
+ changes: Nullable<Handle<ChangeManager>>;
763
968
 
764
969
  constructor(
765
- private binding: FromBinding,
766
- private runWithGC: <T>(fn: () => T) => T,
970
+ public binding: FromBinding,
971
+ public runWithGC: <T>(fn: () => T) => T,
767
972
  private getConverter: () => JsonConverter,
768
973
  private getError: (skExc: Pointer<Internal.Exception>) => Error,
769
974
  ) {
770
975
  this.stack = new Stack();
771
976
  this.handles = new Handles();
977
+ this.forkName = null;
978
+ this.changes = null;
772
979
  }
773
980
 
774
981
  register<T>(v: T): Handle<T> {
@@ -795,17 +1002,29 @@ export class ToBinding {
795
1002
  return this.stack.get();
796
1003
  }
797
1004
 
1005
+ SkipRuntime_getFork(): Nullable<string> {
1006
+ return this.forkName;
1007
+ }
1008
+
1009
+ SkipRuntime_getChangeManager(): number {
1010
+ return this.changes ?? 0;
1011
+ }
1012
+
1013
+ setFork(name: Nullable<string>): void {
1014
+ this.forkName = name;
1015
+ }
1016
+
798
1017
  // Mapper
799
1018
 
800
1019
  SkipRuntime_Mapper__mapEntry(
801
- skmapper: Handle<JSONMapper>,
1020
+ skmapper: Handle<HandlerInfo<JSONMapper>>,
802
1021
  key: Pointer<Internal.CJSON>,
803
1022
  values: Pointer<Internal.NonEmptyIterator>,
804
1023
  ): Pointer<Internal.CJArray> {
805
1024
  const skjson = this.getJsonConverter();
806
1025
  const mapper = this.handles.get(skmapper);
807
- const context = new ContextImpl(this.refs());
808
- const result = mapper.mapEntry(
1026
+ const context = new ContextImpl(this);
1027
+ const result = mapper.object.mapEntry(
809
1028
  skjson.importJSON(key) as Json,
810
1029
  new ValuesImpl<Json>(skjson, this.binding, values),
811
1030
  context,
@@ -813,27 +1032,65 @@ export class ToBinding {
813
1032
  return skjson.exportJSON(Array.from(result));
814
1033
  }
815
1034
 
816
- SkipRuntime_deleteMapper(mapper: Handle<JSONMapper>): void {
1035
+ SkipRuntime_Mapper__getInfo(
1036
+ skmapper: Handle<HandlerInfo<JSONMapper>>,
1037
+ ): Pointer<Internal.CJObject> {
1038
+ return this.getInfo(skmapper);
1039
+ }
1040
+
1041
+ SkipRuntime_Mapper__isEquals(
1042
+ mapper: Handle<HandlerInfo<JSONMapper>>,
1043
+ other: Handle<HandlerInfo<JSONMapper>>,
1044
+ ): number {
1045
+ const object = this.handles.get(mapper);
1046
+ if (this.getChanges()?.needMapperReload(object.name)) {
1047
+ return 0;
1048
+ }
1049
+ return this.isEquals(mapper, other);
1050
+ }
1051
+
1052
+ SkipRuntime_deleteMapper(mapper: Handle<HandlerInfo<JSONMapper>>): void {
817
1053
  this.handles.deleteHandle(mapper);
818
1054
  }
819
1055
 
820
1056
  // LazyCompute
821
1057
 
822
1058
  SkipRuntime_LazyCompute__compute(
823
- sklazyCompute: Handle<JSONLazyCompute>,
1059
+ sklazyCompute: Handle<HandlerInfo<JSONLazyCompute>>,
824
1060
  self: string,
825
1061
  skkey: Pointer<Internal.CJSON>,
826
1062
  ): Pointer<Internal.CJArray> {
827
1063
  const skjson = this.getJsonConverter();
828
1064
  const lazyCompute = this.handles.get(sklazyCompute);
829
- const result = lazyCompute.compute(
830
- new LazyCollectionImpl<Json, Json>(self, this.refs()),
1065
+ const context = new ContextImpl(this);
1066
+ const result = lazyCompute.object.compute(
1067
+ new LazyCollectionImpl<Json, Json>(self, this),
831
1068
  skjson.importJSON(skkey) as Json,
1069
+ context,
832
1070
  );
833
1071
  return skjson.exportJSON(Array.from(result));
834
1072
  }
835
1073
 
836
- SkipRuntime_deleteLazyCompute(lazyCompute: Handle<JSONLazyCompute>): void {
1074
+ SkipRuntime_LazyCompute__getInfo(
1075
+ lazyCompute: Handle<HandlerInfo<JSONLazyCompute>>,
1076
+ ): Pointer<Internal.CJObject> {
1077
+ return this.getInfo(lazyCompute);
1078
+ }
1079
+
1080
+ SkipRuntime_LazyCompute__isEquals(
1081
+ lazyCompute: Handle<HandlerInfo<JSONLazyCompute>>,
1082
+ other: Handle<HandlerInfo<JSONLazyCompute>>,
1083
+ ): number {
1084
+ const object = this.handles.get(lazyCompute);
1085
+ if (this.getChanges()?.needLazyComputeReload(object.name)) {
1086
+ return 0;
1087
+ }
1088
+ return this.isEquals(lazyCompute, other);
1089
+ }
1090
+
1091
+ SkipRuntime_deleteLazyCompute(
1092
+ lazyCompute: Handle<HandlerInfo<JSONLazyCompute>>,
1093
+ ): void {
837
1094
  this.handles.deleteHandle(lazyCompute);
838
1095
  }
839
1096
 
@@ -849,11 +1106,10 @@ export class ToBinding {
849
1106
  const keysIds = skjson.importJSON(skcollections) as {
850
1107
  [key: string]: string;
851
1108
  };
852
- const refs = this.refs();
853
1109
  for (const [key, name] of Object.entries(keysIds)) {
854
- collections[key] = new EagerCollectionImpl<Json, Json>(name, refs);
1110
+ collections[key] = new EagerCollectionImpl<Json, Json>(name, this);
855
1111
  }
856
- const collection = resource.instantiate(collections, new ContextImpl(refs));
1112
+ const collection = resource.instantiate(collections, new ContextImpl(this));
857
1113
  return EagerCollectionImpl.getName(collection);
858
1114
  }
859
1115
 
@@ -861,28 +1117,10 @@ export class ToBinding {
861
1117
  this.handles.deleteHandle(resource);
862
1118
  }
863
1119
 
864
- // ResourceBuilder
1120
+ // ServiceDefinition
865
1121
 
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>,
1122
+ SkipRuntime_ServiceDefinition__createGraph(
1123
+ skservice: Handle<ServiceDefinition>,
886
1124
  skcollections: Pointer<Internal.CJObject>,
887
1125
  ): Pointer<Internal.CJObject> {
888
1126
  const skjson = this.getJsonConverter();
@@ -891,11 +1129,10 @@ export class ToBinding {
891
1129
  const keysIds = skjson.importJSON(skcollections) as {
892
1130
  [key: string]: string;
893
1131
  };
894
- const refs = this.refs();
895
1132
  for (const [key, name] of Object.entries(keysIds)) {
896
- collections[key] = new EagerCollectionImpl<Json, Json>(name, refs);
1133
+ collections[key] = new EagerCollectionImpl<Json, Json>(name, this);
897
1134
  }
898
- const result = service.createGraph(collections, new ContextImpl(refs));
1135
+ const result = service.createGraph(collections, new ContextImpl(this));
899
1136
  const collectionsNames: { [name: string]: string } = {};
900
1137
  for (const [name, collection] of Object.entries(result)) {
901
1138
  collectionsNames[name] = EagerCollectionImpl.getName(collection);
@@ -903,10 +1140,111 @@ export class ToBinding {
903
1140
  return skjson.exportJSON(collectionsNames);
904
1141
  }
905
1142
 
906
- SkipRuntime_deleteService(service: Handle<SkipService>): void {
1143
+ SkipRuntime_ServiceDefinition__inputs(
1144
+ skservice: Handle<ServiceDefinition>,
1145
+ ): Pointer<Internal.CJArray<Internal.CJSON>> {
1146
+ const skjson = this.getJsonConverter();
1147
+ const service = this.handles.get(skservice);
1148
+ return skjson.exportJSON(service.inputs());
1149
+ }
1150
+
1151
+ SkipRuntime_ServiceDefinition__resources(
1152
+ skservice: Handle<ServiceDefinition>,
1153
+ ): Pointer<Internal.CJArray<Internal.CJSON>> {
1154
+ const skjson = this.getJsonConverter();
1155
+ const service = this.handles.get(skservice);
1156
+ return skjson.exportJSON(service.resources());
1157
+ }
1158
+
1159
+ SkipRuntime_ServiceDefinition__initialData(
1160
+ skservice: Handle<ServiceDefinition>,
1161
+ name: string,
1162
+ ): Pointer<Internal.CJArray<Internal.CJSON>> {
1163
+ const skjson = this.getJsonConverter();
1164
+ const service = this.handles.get(skservice);
1165
+ return skjson.exportJSON(service.initialData(name));
1166
+ }
1167
+
1168
+ SkipRuntime_ServiceDefinition__buildResource(
1169
+ skservice: Handle<ServiceDefinition>,
1170
+ name: string,
1171
+ skparams: Pointer<Internal.CJObject>,
1172
+ ): Pointer<Internal.Resource> {
1173
+ const skjson = this.getJsonConverter();
1174
+ const service = this.handles.get(skservice);
1175
+ const resource = service.buildResource(
1176
+ name,
1177
+ skjson.importJSON(skparams) as Json,
1178
+ );
1179
+ return this.binding.SkipRuntime_createResource(
1180
+ this.handles.register(resource),
1181
+ );
1182
+ }
1183
+
1184
+ SkipRuntime_ServiceDefinition__subscribe(
1185
+ skservice: Handle<ServiceDefinition>,
1186
+ external: string,
1187
+ writerId: string,
1188
+ instance: string,
1189
+ resource: string,
1190
+ skparams: Pointer<Internal.CJObject>,
1191
+ ): Handle<Promise<void>> {
1192
+ const skjson = this.getJsonConverter();
1193
+ const service = this.handles.get(skservice);
1194
+ const writer = new CollectionWriter(writerId, this, this.forkName);
1195
+ const params = skjson.importJSON(skparams, true) as Json;
1196
+ return this.handles.register(
1197
+ service.subscribe(external, writer, instance, resource, params),
1198
+ );
1199
+ }
1200
+
1201
+ SkipRuntime_ServiceDefinition__unsubscribe(
1202
+ skservice: Handle<ServiceDefinition>,
1203
+ external: string,
1204
+ instance: string,
1205
+ ): void {
1206
+ const service = this.handles.get(skservice);
1207
+ service.unsubscribe(external, instance);
1208
+ }
1209
+
1210
+ SkipRuntime_ServiceDefinition__shutdown(
1211
+ skservice: Handle<ServiceDefinition>,
1212
+ ): Handle<Promise<unknown>> {
1213
+ const service = this.handles.get(skservice);
1214
+ return this.handles.register(service.shutdown());
1215
+ }
1216
+
1217
+ SkipRuntime_deleteService(service: Handle<ServiceDefinition>): void {
907
1218
  this.handles.deleteHandle(service);
908
1219
  }
909
1220
 
1221
+ // Change manager
1222
+
1223
+ SkipRuntime_ChangeManager__needInputReload(
1224
+ skmanager: Handle<ChangeManager>,
1225
+ name: string,
1226
+ ): number {
1227
+ const manager = this.handles.get(skmanager);
1228
+ return manager.needInputReload(name) ? 1 : 0;
1229
+ }
1230
+
1231
+ SkipRuntime_ChangeManager__needResourceReload(
1232
+ skmanager: Handle<ChangeManager>,
1233
+ name: string,
1234
+ ): number {
1235
+ const manager = this.handles.get(skmanager);
1236
+ return manager.needResourceReload(name);
1237
+ }
1238
+
1239
+ SkipRuntime_ChangeManager__needExternalServiceReload(
1240
+ skmanager: Handle<ChangeManager>,
1241
+ name: string,
1242
+ resource: string,
1243
+ ): number {
1244
+ const manager = this.handles.get(skmanager);
1245
+ return manager.needExternalServiceReload(name, resource) ? 1 : 0;
1246
+ }
1247
+
910
1248
  // Notifier
911
1249
 
912
1250
  SkipRuntime_Notifier__subscribed<K extends Json, V extends Json>(
@@ -948,15 +1286,23 @@ export class ToBinding {
948
1286
 
949
1287
  // Reducer
950
1288
 
1289
+ SkipRuntime_Reducer__init(
1290
+ skreducer: Handle<HandlerInfo<Reducer<Json, Json>>>,
1291
+ ): Pointer<Internal.CJSON> {
1292
+ const skjson = this.getJsonConverter();
1293
+ const reducer = this.handles.get(skreducer);
1294
+ return skjson.exportJSON(reducer.object.initial);
1295
+ }
1296
+
951
1297
  SkipRuntime_Reducer__add(
952
- skreducer: Handle<Reducer<Json, Json>>,
1298
+ skreducer: Handle<HandlerInfo<Reducer<Json, Json>>>,
953
1299
  skacc: Nullable<Pointer<Internal.CJSON>>,
954
1300
  skvalue: Pointer<Internal.CJSON>,
955
1301
  ): Pointer<Internal.CJSON> {
956
1302
  const skjson = this.getJsonConverter();
957
1303
  const reducer = this.handles.get(skreducer);
958
1304
  return skjson.exportJSON(
959
- reducer.add(
1305
+ reducer.object.add(
960
1306
  skacc ? (skjson.importJSON(skacc) as Json) : null,
961
1307
  skjson.importJSON(skvalue) as Json & DepSafe,
962
1308
  ),
@@ -964,165 +1310,216 @@ export class ToBinding {
964
1310
  }
965
1311
 
966
1312
  SkipRuntime_Reducer__remove(
967
- skreducer: Handle<Reducer<Json, Json>>,
1313
+ skreducer: Handle<HandlerInfo<Reducer<Json, Json>>>,
968
1314
  skacc: Pointer<Internal.CJSON>,
969
1315
  skvalue: Pointer<Internal.CJSON>,
970
1316
  ): Nullable<Pointer<Internal.CJSON>> {
971
1317
  const skjson = this.getJsonConverter();
972
1318
  const reducer = this.handles.get(skreducer);
973
1319
  return skjson.exportJSON(
974
- reducer.remove(
1320
+ reducer.object.remove(
975
1321
  skjson.importJSON(skacc) as Json,
976
1322
  skjson.importJSON(skvalue) as Json & DepSafe,
977
1323
  ),
978
1324
  );
979
1325
  }
980
1326
 
981
- SkipRuntime_deleteReducer(reducer: Handle<Reducer<Json, Json>>): void {
982
- this.handles.deleteHandle(reducer);
1327
+ SkipRuntime_Reducer__isEquals(
1328
+ reducer: Handle<HandlerInfo<Reducer<Json, Json>>>,
1329
+ other: Handle<HandlerInfo<Reducer<Json, Json>>>,
1330
+ ): number {
1331
+ const object = this.handles.get(reducer);
1332
+ if (this.getChanges()?.needReducerReload(object.name)) {
1333
+ return 0;
1334
+ }
1335
+ return this.isEquals(reducer, other);
983
1336
  }
984
1337
 
985
- // ExternalService
1338
+ SkipRuntime_Reducer__getInfo(
1339
+ reducer: Handle<HandlerInfo<Reducer<Json, Json>>>,
1340
+ ): Pointer<Internal.CJObject> {
1341
+ return this.getInfo(reducer);
1342
+ }
986
1343
 
987
- SkipRuntime_ExternalService__subscribe(
988
- sksupplier: Handle<ExternalService>,
989
- writerId: string,
990
- instance: string,
991
- resource: string,
992
- skparams: Pointer<Internal.CJObject>,
1344
+ SkipRuntime_deleteReducer(
1345
+ reducer: Handle<HandlerInfo<Reducer<Json, Json>>>,
993
1346
  ): 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);
1347
+ this.handles.deleteHandle(reducer);
1014
1348
  }
1015
1349
 
1016
- SkipRuntime_ExternalService__unsubscribe(
1017
- sksupplier: Handle<ExternalService>,
1018
- instance: string,
1019
- ): void {
1020
- const supplier = this.handles.get(sksupplier);
1021
- supplier.unsubscribe(instance);
1350
+ async initService(service: SkipService): Promise<ServiceInstance> {
1351
+ this.setFork(null);
1352
+ const uuid = crypto.randomUUID();
1353
+ this.fork(uuid);
1354
+ try {
1355
+ this.setFork(uuid);
1356
+ const definition = new ServiceDefinition(service);
1357
+ await this.initService_(definition);
1358
+ this.setFork(uuid);
1359
+ this.merge();
1360
+ return new ServiceInstance(this, null, definition);
1361
+ } catch (ex: unknown) {
1362
+ this.setFork(uuid);
1363
+ this.abortFork();
1364
+ throw ex;
1365
+ }
1022
1366
  }
1023
1367
 
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());
1368
+ private initService_(definition: ServiceDefinition): Promise<void> {
1369
+ return this.runAsync(() => {
1370
+ const skservicehHdl = this.handles.register(definition);
1371
+ const skservice = this.binding.SkipRuntime_createService(skservicehHdl);
1372
+ return this.binding.SkipRuntime_initService(skservice);
1373
+ });
1029
1374
  }
1030
1375
 
1031
- SkipRuntime_deleteExternalService(supplier: Handle<ExternalService>): void {
1032
- this.handles.deleteHandle(supplier);
1376
+ //
1377
+ public getJsonConverter() {
1378
+ if (this.skjson == undefined) {
1379
+ this.skjson = this.getConverter();
1380
+ }
1381
+ return this.skjson;
1033
1382
  }
1034
1383
 
1035
- // Executor
1384
+ public needGC() {
1385
+ return this.SkipRuntime_getContext() == null;
1386
+ }
1036
1387
 
1037
- SkipRuntime_Executor__resolve(skexecutor: Handle<Executor>): void {
1038
- const checker = this.handles.get(skexecutor);
1039
- checker.resolve();
1388
+ public json() {
1389
+ return this.getJsonConverter();
1040
1390
  }
1041
1391
 
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));
1392
+ fork(name: string): void {
1393
+ const errorHdl = this.runWithGC(() =>
1394
+ this.binding.SkipRuntime_Runtime__fork(name),
1395
+ );
1396
+ if (errorHdl) throw this.handles.deleteHandle(errorHdl);
1048
1397
  }
1049
1398
 
1050
- SkipRuntime_deleteExecutor(executor: Handle<Executor>): void {
1051
- this.handles.deleteHandle(executor);
1399
+ merge(ignore: string[] = []): void {
1400
+ const errorHdl = this.runWithGC(() =>
1401
+ this.binding.SkipRuntime_Runtime__merge(this.json().exportJSON(ignore)),
1402
+ );
1403
+ if (errorHdl) throw this.handles.deleteHandle(errorHdl);
1052
1404
  }
1053
1405
 
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));
1406
+ abortFork(): void {
1407
+ const errorHdl = this.runWithGC(() =>
1408
+ this.binding.SkipRuntime_Runtime__abortFork(),
1409
+ );
1410
+ if (errorHdl) throw this.handles.deleteHandle(errorHdl);
1411
+ }
1412
+
1413
+ async runAsync(fn: () => Pointer<Internal.CJSON>): Promise<void> {
1414
+ const result = this.runWithGC(() => {
1415
+ return this.json().importJSON(fn(), true);
1104
1416
  });
1417
+ if (Array.isArray(result)) {
1418
+ const handles = result as Handle<Promise<void>>[];
1419
+ const promises = handles.map((h) => this.handles.deleteHandle(h));
1420
+ await Promise.all(promises);
1421
+ } else {
1422
+ const errorHdl = result as Handle<Error>;
1423
+ throw this.handles.deleteHandle(errorHdl);
1424
+ }
1105
1425
  }
1106
1426
 
1107
- //
1108
- private getJsonConverter() {
1109
- if (this.skjson == undefined) {
1110
- this.skjson = this.getConverter();
1427
+ private deepEquals(a: Nullable<Json>, b: Nullable<Json>) {
1428
+ // Same reference or both NaN
1429
+ if (a === b) return true;
1430
+ if (a !== a && b !== b) return true; // NaN check
1431
+
1432
+ // Different types or one is null
1433
+ if (typeof a !== typeof b || a === null || b === null) return false;
1434
+
1435
+ // Primitives already checked by ===
1436
+ if (typeof a !== "object" || typeof b !== "object") return false;
1437
+
1438
+ // Arrays
1439
+ if (Array.isArray(a)) {
1440
+ if (!Array.isArray(b) || a.length !== b.length) return false;
1441
+ for (let i = 0; i < a.length; i++) {
1442
+ if (!this.deepEquals(a[i]!, b[i]!)) return false;
1443
+ }
1444
+ return true;
1111
1445
  }
1112
- return this.skjson;
1446
+
1447
+ // Different array status
1448
+ if (Array.isArray(b)) return false;
1449
+
1450
+ // Objects
1451
+ const keysA = Object.keys(a);
1452
+ const keysB = Object.keys(b);
1453
+
1454
+ if (keysA.length !== keysB.length) return false;
1455
+
1456
+ for (const key of keysA) {
1457
+ if (
1458
+ !Object.prototype.hasOwnProperty.call(b, key) ||
1459
+ !this.deepEquals(a[key]!, b[key]!)
1460
+ )
1461
+ return false;
1462
+ }
1463
+
1464
+ return true;
1113
1465
  }
1114
1466
 
1115
- private needGC() {
1116
- return this.SkipRuntime_getContext() == null;
1467
+ private getInfo<T>(
1468
+ skmapper: Handle<HandlerInfo<T>>,
1469
+ ): Pointer<Internal.CJObject> {
1470
+ const skjson = this.getJsonConverter();
1471
+ const object = this.handles.get(skmapper);
1472
+ const name = object.name;
1473
+ const parameters = object.params.map((v) => {
1474
+ if (v instanceof EagerCollectionImpl) {
1475
+ return {
1476
+ type: "collection",
1477
+ value: v.collection,
1478
+ };
1479
+ }
1480
+ if (v instanceof LazyCollectionImpl) {
1481
+ return {
1482
+ type: "collection",
1483
+ value: v.lazyCollection,
1484
+ };
1485
+ }
1486
+ return { type: "data", value: v as Json };
1487
+ });
1488
+ return skjson.exportJSON({ name, parameters });
1117
1489
  }
1118
1490
 
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
- );
1491
+ private isEquals<T extends JSONOperator>(
1492
+ mapper: Handle<HandlerInfo<T>>,
1493
+ other: Handle<HandlerInfo<T>>,
1494
+ ): number {
1495
+ const object = this.handles.get(mapper);
1496
+ const oobject = this.handles.get(other);
1497
+ if (object.object.constructor != oobject.object.constructor) {
1498
+ return 0;
1499
+ }
1500
+ if (object.params.length != oobject.params.length) return 0;
1501
+ for (const [i, param] of object.params.entries()) {
1502
+ const oparam = oobject.params[i];
1503
+ if (param instanceof EagerCollectionImpl) {
1504
+ if (oparam instanceof EagerCollectionImpl) {
1505
+ if (param.collection != oparam.collection) return 0;
1506
+ } else {
1507
+ return 0;
1508
+ }
1509
+ } else if (param instanceof LazyCollectionImpl) {
1510
+ if (oparam instanceof LazyCollectionImpl) {
1511
+ if (param.lazyCollection != oparam.lazyCollection) return 0;
1512
+ } else {
1513
+ return 0;
1514
+ }
1515
+ } else if (!this.deepEquals(param as Json, oparam as Json)) {
1516
+ return 0;
1517
+ }
1518
+ }
1519
+ return 1;
1520
+ }
1521
+
1522
+ private getChanges(): Nullable<ChangeManager> {
1523
+ return this.changes ? this.handles.get(this.changes) : null;
1127
1524
  }
1128
1525
  }