@skipruntime/core 0.0.12 → 0.0.14

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
@@ -13,8 +13,7 @@ import type {
13
13
  JsonObject,
14
14
  } from "../skiplang-json/index.js";
15
15
  import {
16
- sk_freeze,
17
- isSkManaged,
16
+ deepFreeze,
18
17
  SkManaged,
19
18
  checkOrCloneParam,
20
19
  } from "../skiplang-json/index.js";
@@ -54,13 +53,28 @@ import {
54
53
  type FromBinding,
55
54
  } from "./binding.js";
56
55
 
57
- export { sk_freeze, isSkManaged };
58
56
  export * from "./api.js";
59
57
  export * from "./errors.js";
60
58
 
61
59
  export type JSONMapper = Mapper<Json, Json, Json, Json>;
62
60
  export type JSONLazyCompute = LazyCompute<Json, Json>;
63
61
 
62
+ function instantiateUserObject<Params extends DepSafe[], Result extends object>(
63
+ what: string,
64
+ ctor: new (...params: Params) => Result,
65
+ params: Params,
66
+ ): Result {
67
+ const checkedParams = params.map(checkOrCloneParam) as Params;
68
+ const obj = new ctor(...checkedParams);
69
+ Object.freeze(obj);
70
+ if (!obj.constructor.name) {
71
+ throw new SkipClassNameError(
72
+ `${what} classes must be defined at top-level.`,
73
+ );
74
+ }
75
+ return obj;
76
+ }
77
+
64
78
  class Handles {
65
79
  private nextID: number = 1;
66
80
  private readonly objects: any[] = [];
@@ -138,15 +152,18 @@ class LazyCollectionImpl<K extends Json, V extends Json>
138
152
  ) as (V & DepSafe)[];
139
153
  }
140
154
 
141
- getUnique(key: K): V & DepSafe {
142
- const v = this.refs.skjson.importOptJSON(
143
- this.refs.binding.SkipRuntime_LazyCollection__getUnique(
144
- this.lazyCollection,
145
- this.refs.skjson.exportJSON(key),
146
- ),
147
- ) as Nullable<V & DepSafe>;
148
- if (v == null) throw new SkipNonUniqueValueError();
149
- return v;
155
+ getUnique(key: K, _default?: { ifNone?: V; ifMany?: V }): V & DepSafe {
156
+ const values = this.getArray(key);
157
+ switch (values.length) {
158
+ case 1:
159
+ return values[0]!;
160
+ case 0:
161
+ if (_default?.ifNone !== undefined) return deepFreeze(_default.ifNone);
162
+ throw new SkipNonUniqueValueError();
163
+ default:
164
+ if (_default?.ifMany !== undefined) return deepFreeze(_default.ifMany);
165
+ throw new SkipNonUniqueValueError();
166
+ }
150
167
  }
151
168
  }
152
169
 
@@ -171,15 +188,18 @@ class EagerCollectionImpl<K extends Json, V extends Json>
171
188
  ) as (V & DepSafe)[];
172
189
  }
173
190
 
174
- getUnique(key: K): V & DepSafe {
175
- const v = this.refs.skjson.importOptJSON(
176
- this.refs.binding.SkipRuntime_Collection__getUnique(
177
- this.collection,
178
- this.refs.skjson.exportJSON(key),
179
- ),
180
- ) as Nullable<V & DepSafe>;
181
- if (v == null) throw new SkipNonUniqueValueError();
182
- return v;
191
+ getUnique(key: K, _default?: { ifNone?: V; ifMany?: V }): V & DepSafe {
192
+ const values = this.getArray(key);
193
+ switch (values.length) {
194
+ case 1:
195
+ return values[0]!;
196
+ case 0:
197
+ if (_default?.ifNone !== undefined) return deepFreeze(_default.ifNone);
198
+ throw new SkipNonUniqueValueError();
199
+ default:
200
+ if (_default?.ifMany !== undefined) return deepFreeze(_default.ifMany);
201
+ throw new SkipNonUniqueValueError();
202
+ }
183
203
  }
184
204
 
185
205
  size = () => {
@@ -212,14 +232,7 @@ class EagerCollectionImpl<K extends Json, V extends Json>
212
232
  mapper: new (...params: Params) => Mapper<K, V, K2, V2>,
213
233
  ...params: Params
214
234
  ): EagerCollection<K2, V2> {
215
- const mapperParams = params.map(checkOrCloneParam) as Params;
216
- const mapperObj = new mapper(...mapperParams);
217
- Object.freeze(mapperObj);
218
- if (!mapperObj.constructor.name) {
219
- throw new SkipClassNameError(
220
- "Mapper classes must be defined at top-level.",
221
- );
222
- }
235
+ const mapperObj = instantiateUserObject("Mapper", mapper, params);
223
236
  const skmapper = this.refs.binding.SkipRuntime_createMapper(
224
237
  this.refs.handles.register(mapperObj),
225
238
  );
@@ -238,25 +251,12 @@ class EagerCollectionImpl<K extends Json, V extends Json>
238
251
  reducer: new (...params: ReducerParams) => Reducer<V2, Accum>,
239
252
  ...reducerParams: ReducerParams
240
253
  ) => {
241
- const mParams = mapperParams.map(checkOrCloneParam) as MapperParams;
242
- const rParams = reducerParams.map(checkOrCloneParam) as ReducerParams;
243
-
244
- const mapperObj = new mapper(...mParams);
245
- const reducerObj = new reducer(...rParams);
246
-
247
- Object.freeze(mapperObj);
248
- Object.freeze(reducerObj);
249
-
250
- if (!mapperObj.constructor.name) {
251
- throw new SkipClassNameError(
252
- "Mapper classes must be defined at top-level.",
253
- );
254
- }
255
- if (!reducerObj.constructor.name) {
256
- throw new SkipClassNameError(
257
- "Reducer classes must be defined at top-level.",
258
- );
259
- }
254
+ const mapperObj = instantiateUserObject("Mapper", mapper, mapperParams);
255
+ const reducerObj = instantiateUserObject(
256
+ "Reducer",
257
+ reducer,
258
+ reducerParams,
259
+ );
260
260
 
261
261
  const skmapper = this.refs.binding.SkipRuntime_createMapper(
262
262
  this.refs.handles.register(mapperObj),
@@ -290,14 +290,7 @@ class EagerCollectionImpl<K extends Json, V extends Json>
290
290
  reducer: new (...params: Params) => Reducer<V, Accum>,
291
291
  ...params: Params
292
292
  ): EagerCollection<K, Accum> {
293
- const reducerParams = params.map(checkOrCloneParam) as Params;
294
- const reducerObj = new reducer(...reducerParams);
295
- Object.freeze(reducerObj);
296
- if (!reducerObj.constructor.name) {
297
- throw new SkipClassNameError(
298
- "Reducer classes must be defined at top-level.",
299
- );
300
- }
293
+ const reducerObj = instantiateUserObject("Reducer", reducer, params);
301
294
  if (sknative in reducerObj && typeof reducerObj[sknative] == "string") {
302
295
  return this.derive<K, Accum>(
303
296
  this.refs.binding.SkipRuntime_Collection__nativeReduce(
@@ -320,8 +313,8 @@ class EagerCollectionImpl<K extends Json, V extends Json>
320
313
  }
321
314
 
322
315
  merge(...others: EagerCollection<K, V>[]): EagerCollection<K, V> {
323
- const otherNames = others.map(
324
- (other) => (other as EagerCollectionImpl<K, V>).collection,
316
+ const otherNames = others.map((other) =>
317
+ EagerCollectionImpl.getName(other),
325
318
  );
326
319
  const mapped = this.refs.binding.SkipRuntime_Collection__merge(
327
320
  this.collection,
@@ -335,6 +328,12 @@ class EagerCollectionImpl<K extends Json, V extends Json>
335
328
  ): EagerCollection<K2, V2> {
336
329
  return new EagerCollectionImpl<K2, V2>(collection, this.refs);
337
330
  }
331
+
332
+ static getName<K extends Json, V extends Json>(
333
+ coll: EagerCollection<K, V>,
334
+ ): string {
335
+ return (coll as EagerCollectionImpl<K, V>).collection;
336
+ }
338
337
  }
339
338
 
340
339
  class CollectionWriter<K extends Json, V extends Json> {
@@ -398,14 +397,7 @@ class ContextImpl extends SkManaged implements Context {
398
397
  compute: new (...params: Params) => LazyCompute<K, V>,
399
398
  ...params: Params
400
399
  ): LazyCollection<K, V> {
401
- const mapperParams = params.map(checkOrCloneParam) as Params;
402
- const computeObj = new compute(...mapperParams);
403
- Object.freeze(computeObj);
404
- if (!computeObj.constructor.name) {
405
- throw new SkipClassNameError(
406
- "LazyCompute classes must be defined at top-level.",
407
- );
408
- }
400
+ const computeObj = instantiateUserObject("LazyCompute", compute, params);
409
401
  const skcompute = this.refs.binding.SkipRuntime_createLazyCompute(
410
402
  this.refs.handles.register(computeObj),
411
403
  );
@@ -728,47 +720,74 @@ export class ServiceInstance {
728
720
  }
729
721
 
730
722
  class ValuesImpl<T> implements Values<T> {
723
+ /* Lazy Iterable/Sequence: values are generated from
724
+ the Iterator pointer and stored in materialized.
725
+ Once finished the pointer is nullified. */
726
+ private readonly materialized: (T & DepSafe)[] = [];
727
+
731
728
  constructor(
732
729
  private readonly skjson: JsonConverter,
733
730
  private readonly binding: FromBinding,
734
- private readonly pointer: Pointer<Internal.NonEmptyIterator>,
731
+ private pointer: Pointer<Internal.NonEmptyIterator> | null,
735
732
  ) {
736
- this.skjson = skjson;
737
- this.binding = binding;
738
733
  this.pointer = pointer;
739
734
  }
740
735
 
741
- next(): Nullable<T & DepSafe> {
742
- return this.skjson.importOptJSON(
736
+ private next(): Nullable<T & DepSafe> {
737
+ if (this.pointer === null) {
738
+ return null;
739
+ }
740
+
741
+ const v = this.skjson.importOptJSON(
743
742
  this.binding.SkipRuntime_NonEmptyIterator__next(this.pointer),
744
743
  ) as Nullable<T & DepSafe>;
744
+
745
+ if (v === null) {
746
+ this.pointer = null;
747
+ } else {
748
+ this.materialized.push(v);
749
+ }
750
+ return v;
745
751
  }
746
752
 
747
- getUnique(): T & DepSafe {
748
- const value = this.skjson.importOptJSON(
749
- this.binding.SkipRuntime_NonEmptyIterator__uniqueValue(this.pointer),
750
- ) as Nullable<T & DepSafe>;
751
- if (value == null) throw new SkipNonUniqueValueError();
752
- return value;
753
+ getUnique(_default?: { ifMany?: T }): T & DepSafe {
754
+ if (this.materialized.length < 1) {
755
+ this.next();
756
+ }
757
+ const first = this.materialized[0];
758
+ if (
759
+ first === undefined /* i.e. this.materialized.length == 0 */ ||
760
+ this.materialized.length >= 2 ||
761
+ this.next() !== null
762
+ ) {
763
+ if (_default?.ifMany !== undefined) return deepFreeze(_default.ifMany);
764
+ throw new SkipNonUniqueValueError();
765
+ }
766
+ return first;
753
767
  }
754
768
 
755
- toArray: () => (T & DepSafe)[] = () => {
756
- return Array.from(this);
757
- };
769
+ toArray(): (T & DepSafe)[] {
770
+ while (this.next() !== null);
771
+ return this.materialized;
772
+ }
758
773
 
759
774
  [Symbol.iterator](): Iterator<T & DepSafe> {
760
- const cloned_iter = new ValuesImpl<T & DepSafe>(
761
- this.skjson,
762
- this.binding,
763
- this.binding.SkipRuntime_NonEmptyIterator__clone(this.pointer),
764
- );
765
-
766
- return {
767
- next() {
768
- const value = cloned_iter.next();
769
- return { value, done: value == null } as IteratorResult<T & DepSafe>;
770
- },
775
+ let i = 0;
776
+ const next = (): IteratorResult<T & DepSafe> => {
777
+ // Invariant: this.materialized.length >= i
778
+ let value = this.materialized[i];
779
+ if (value === undefined /* i.e. this.materialized.length == i */) {
780
+ const next = this.next();
781
+ if (next === null) {
782
+ return { value: null, done: true };
783
+ }
784
+ value = next;
785
+ }
786
+ i++;
787
+ return { value };
771
788
  };
789
+
790
+ return { next };
772
791
  }
773
792
  }
774
793
 
@@ -820,11 +839,19 @@ export class ToBinding {
820
839
  ): Pointer<Internal.CJArray> {
821
840
  const skjson = this.getJsonConverter();
822
841
  const mapper = this.handles.get(skmapper);
823
- const result = mapper.mapEntry(
824
- skjson.importJSON(key) as Json,
825
- new ValuesImpl<Json>(skjson, this.binding, values),
826
- );
827
- return skjson.exportJSON(Array.from(result) as [[Json, Json]]);
842
+ const context = new ContextImpl(this.refs());
843
+ try {
844
+ const result = mapper.mapEntry(
845
+ skjson.importJSON(key) as Json,
846
+ new ValuesImpl<Json>(skjson, this.binding, values),
847
+ context,
848
+ );
849
+ return skjson.exportJSON(Array.from(result));
850
+ } catch (e: unknown) {
851
+ console.error("Uncaught error during Skip runtime reactive update: ", e);
852
+ // Exception in async context will be dropped -- this `throw` is just to appease typechecker
853
+ throw e;
854
+ }
828
855
  }
829
856
 
830
857
  SkipRuntime_deleteMapper(mapper: Handle<JSONMapper>): void {
@@ -868,7 +895,7 @@ export class ToBinding {
868
895
  collections[key] = new EagerCollectionImpl<Json, Json>(name, refs);
869
896
  }
870
897
  const collection = resource.instantiate(collections, new ContextImpl(refs));
871
- return (collection as EagerCollectionImpl<Json, Json>).collection;
898
+ return EagerCollectionImpl.getName(collection);
872
899
  }
873
900
 
874
901
  SkipRuntime_deleteResource(resource: Handle<Resource>): void {
@@ -912,9 +939,7 @@ export class ToBinding {
912
939
  const result = service.createGraph(collections, new ContextImpl(refs));
913
940
  const collectionsNames: { [name: string]: string } = {};
914
941
  for (const [name, collection] of Object.entries(result)) {
915
- collectionsNames[name] = (
916
- collection as EagerCollectionImpl<Json, Json>
917
- ).collection;
942
+ collectionsNames[name] = EagerCollectionImpl.getName(collection);
918
943
  }
919
944
  return skjson.exportJSON(collectionsNames);
920
945
  }