@skipruntime/core 0.0.2 → 0.0.3

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
@@ -1,3 +1,10 @@
1
+ /**
2
+ * The @skipruntime/core package contains internal implementation detail for the Skip Framework and should not need to be used directly. See the public API exposed by the @skipruntime/helpers package.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+
7
+ import type { Opaque } from "@skiplang/std";
1
8
  import type {
2
9
  Pointer,
3
10
  Nullable,
@@ -5,7 +12,12 @@ import type {
5
12
  JsonConverter,
6
13
  JsonObject,
7
14
  } from "@skiplang/json";
8
- import { isObjectProxy, sk_freeze, isSkFrozen } from "@skiplang/json";
15
+ import {
16
+ sk_freeze,
17
+ isSkManaged,
18
+ SkManaged,
19
+ checkOrCloneParam,
20
+ } from "@skiplang/json";
9
21
  import type * as Internal from "./internal.js";
10
22
  import {
11
23
  NonUniqueValueException,
@@ -18,16 +30,14 @@ import {
18
30
  type LazyCompute,
19
31
  type Mapper,
20
32
  type NamedCollections,
21
- type NonEmptyIterator,
22
- type Param,
33
+ type Values,
34
+ type DepSafe,
23
35
  type Reducer,
24
36
  type Resource,
25
37
  type SkipService,
26
- type SubscriptionID,
27
38
  type Watermark,
28
39
  } from "@skipruntime/api";
29
40
 
30
- import { Frozen } from "@skipruntime/api/internals.js";
31
41
  import { UnknownCollectionError } from "./errors.js";
32
42
  import {
33
43
  ResourceBuilder,
@@ -37,99 +47,40 @@ import {
37
47
  type FromBinding,
38
48
  } from "./binding.js";
39
49
 
40
- export { UnknownCollectionError, sk_freeze, isSkFrozen };
50
+ export { UnknownCollectionError, sk_freeze, isSkManaged };
41
51
  export { SkipExternalService } from "./remote.js";
42
- export { Sum, Min, Max, CountMapper } from "./utils.js";
52
+ export { Sum, Min, Max, Count, CountMapper } from "./utils.js";
43
53
 
44
54
  export type JSONMapper = Mapper<Json, Json, Json, Json>;
45
55
  export type JSONLazyCompute = LazyCompute<Json, Json>;
46
56
 
57
+ /**
58
+ * An entry point of a Skip reactive service.
59
+ *
60
+ * URLs for the service's control and streaming APIs can be constructed from an `Entrypoint`.
61
+ */
47
62
  export type Entrypoint = {
63
+ /**
64
+ * Hostname of the service.
65
+ */
48
66
  host: string;
67
+
68
+ /**
69
+ * Port to use for the service's streaming interface.
70
+ */
49
71
  streaming_port: number;
72
+
73
+ /**
74
+ * Port to use for the service's control interface.
75
+ */
50
76
  control_port: number;
77
+
78
+ /**
79
+ * Flag that when set indicates that https should be used instead of http.
80
+ */
51
81
  secured?: boolean;
52
82
  };
53
83
 
54
- abstract class SkFrozen extends Frozen {
55
- protected freeze() {
56
- sk_freeze(this);
57
- }
58
- }
59
-
60
- function checkOrCloneParam<T>(value: T): T {
61
- if (
62
- typeof value == "string" ||
63
- typeof value == "number" ||
64
- typeof value == "boolean"
65
- )
66
- return value;
67
- if (typeof value == "object") {
68
- if (value === null) return value;
69
- if (isObjectProxy(value)) return value.clone() as T;
70
- if (isSkFrozen(value)) return value;
71
- throw new Error("Invalid object: must be deep-frozen.");
72
- }
73
- throw new Error(`'${typeof value}' cannot be deep-frozen.`);
74
- }
75
-
76
- /**
77
- * _Deep-freeze_ an object, returning the same object that was passed in.
78
- *
79
- * This function is similar to
80
- * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze | `Object.freeze()`}
81
- * but freezes the object and deep-freezes all its properties,
82
- * recursively. The object is then not only _immutable_ but also
83
- * _constant_. Note that as a result all objects reachable from the
84
- * parameter will be frozen and no longer mutable or extensible, even from
85
- * other references.
86
- *
87
- * The argument object and all its properties, recursively, must not already
88
- * be frozen by `Object.freeze` (or else `deepFreeze` cannot mark them
89
- * deep-frozen). Undefined, function (and hence class) values cannot be
90
- * deep-frozen.
91
- *
92
- * The primary use for this function is to satisfy the requirement that all
93
- * parameters to Skip `Mapper` constructors must be deep-frozen: objects
94
- * that have not been constructed by Skip can be passed to `deepFreeze()`
95
- * before passing them to a `Mapper` constructor.
96
- *
97
- * @param value - The object to deep-freeze.
98
- * @returns The same object that was passed in.
99
- */
100
- export function deepFreeze<T>(value: T): T & Param {
101
- if (
102
- typeof value == "bigint" ||
103
- typeof value == "boolean" ||
104
- typeof value == "number" ||
105
- typeof value == "string" ||
106
- typeof value == "symbol"
107
- ) {
108
- return value;
109
- } else if (typeof value == "object") {
110
- if (value === null) {
111
- return value;
112
- } else if (isSkFrozen(value)) {
113
- return value;
114
- } else if (Object.isFrozen(value)) {
115
- throw new Error(`Cannot deep-freeze an Object.frozen value.`);
116
- } else if (Array.isArray(value)) {
117
- for (const elt of value) {
118
- deepFreeze(elt);
119
- }
120
- return Object.freeze(sk_freeze(value));
121
- } else {
122
- for (const val of Object.values(value)) {
123
- deepFreeze(val);
124
- }
125
- return Object.freeze(sk_freeze(value));
126
- }
127
- } else {
128
- // typeof value == "function" || typeof value == "undefined"
129
- throw new Error(`'${typeof value}' values cannot be deep-frozen.`);
130
- }
131
- }
132
-
133
84
  class Handles {
134
85
  private nextID: number = 1;
135
86
  private readonly objects: any[] = [];
@@ -187,7 +138,7 @@ export class Refs {
187
138
  }
188
139
 
189
140
  class LazyCollectionImpl<K extends Json, V extends Json>
190
- extends SkFrozen
141
+ extends SkManaged
191
142
  implements LazyCollection<K, V>
192
143
  {
193
144
  constructor(
@@ -198,29 +149,29 @@ class LazyCollectionImpl<K extends Json, V extends Json>
198
149
  Object.freeze(this);
199
150
  }
200
151
 
201
- getArray(key: K): (V & Param)[] {
152
+ getArray(key: K): (V & DepSafe)[] {
202
153
  return this.refs.skjson.importJSON(
203
154
  this.refs.binding.SkipRuntime_LazyCollection__getArray(
204
155
  this.lazyCollection,
205
156
  this.refs.skjson.exportJSON(key),
206
157
  ),
207
- ) as (V & Param)[];
158
+ ) as (V & DepSafe)[];
208
159
  }
209
160
 
210
- getUnique(key: K): V & Param {
161
+ getUnique(key: K): V & DepSafe {
211
162
  const v = this.refs.skjson.importOptJSON(
212
163
  this.refs.binding.SkipRuntime_LazyCollection__getUnique(
213
164
  this.lazyCollection,
214
165
  this.refs.skjson.exportJSON(key),
215
166
  ),
216
- ) as Nullable<V & Param>;
167
+ ) as Nullable<V & DepSafe>;
217
168
  if (v == null) throw new NonUniqueValueException();
218
169
  return v;
219
170
  }
220
171
  }
221
172
 
222
173
  class EagerCollectionImpl<K extends Json, V extends Json>
223
- extends SkFrozen
174
+ extends SkManaged
224
175
  implements EagerCollection<K, V>
225
176
  {
226
177
  constructor(
@@ -231,22 +182,22 @@ class EagerCollectionImpl<K extends Json, V extends Json>
231
182
  Object.freeze(this);
232
183
  }
233
184
 
234
- getArray(key: K): (V & Param)[] {
185
+ getArray(key: K): (V & DepSafe)[] {
235
186
  return this.refs.skjson.importJSON(
236
187
  this.refs.binding.SkipRuntime_Collection__getArray(
237
188
  this.collection,
238
189
  this.refs.skjson.exportJSON(key),
239
190
  ),
240
- ) as (V & Param)[];
191
+ ) as (V & DepSafe)[];
241
192
  }
242
193
 
243
- getUnique(key: K): V & Param {
194
+ getUnique(key: K): V & DepSafe {
244
195
  const v = this.refs.skjson.importOptJSON(
245
196
  this.refs.binding.SkipRuntime_Collection__getUnique(
246
197
  this.collection,
247
198
  this.refs.skjson.exportJSON(key),
248
199
  ),
249
- ) as Nullable<V & Param>;
200
+ ) as Nullable<V & DepSafe>;
250
201
  if (v == null) throw new NonUniqueValueException();
251
202
  return v;
252
203
  }
@@ -277,7 +228,7 @@ class EagerCollectionImpl<K extends Json, V extends Json>
277
228
  return this.derive<K, V>(skcollection);
278
229
  }
279
230
 
280
- map<K2 extends Json, V2 extends Json, Params extends Param[]>(
231
+ map<K2 extends Json, V2 extends Json, Params extends DepSafe[]>(
281
232
  mapper: new (...params: Params) => Mapper<K, V, K2, V2>,
282
233
  ...params: Params
283
234
  ): EagerCollection<K2, V2> {
@@ -297,11 +248,11 @@ class EagerCollectionImpl<K extends Json, V extends Json>
297
248
  return this.derive<K2, V2>(mapped);
298
249
  }
299
250
 
300
- mapReduce<K2 extends Json, V2 extends Json, MapperParams extends Param[]>(
251
+ mapReduce<K2 extends Json, V2 extends Json, MapperParams extends DepSafe[]>(
301
252
  mapper: new (...params: MapperParams) => Mapper<K, V, K2, V2>,
302
253
  ...mapperParams: MapperParams
303
254
  ) {
304
- return <Accum extends Json, ReducerParams extends Param[]>(
255
+ return <Accum extends Json, ReducerParams extends DepSafe[]>(
305
256
  reducer: new (...params: ReducerParams) => Reducer<V2, Accum>,
306
257
  ...reducerParams: ReducerParams
307
258
  ) => {
@@ -326,7 +277,7 @@ class EagerCollectionImpl<K extends Json, V extends Json>
326
277
  );
327
278
  const skreducer = this.refs.binding.SkipRuntime_createReducer(
328
279
  this.refs.handles.register(reducerObj),
329
- this.refs.skjson.exportJSON(reducerObj.default),
280
+ this.refs.skjson.exportJSON(reducerObj.initial),
330
281
  );
331
282
  const mapped = this.refs.binding.SkipRuntime_Collection__mapReduce(
332
283
  this.collection,
@@ -337,7 +288,7 @@ class EagerCollectionImpl<K extends Json, V extends Json>
337
288
  };
338
289
  }
339
290
 
340
- reduce<Accum extends Json, Params extends Param[]>(
291
+ reduce<Accum extends Json, Params extends DepSafe[]>(
341
292
  reducer: new (...params: Params) => Reducer<V, Accum>,
342
293
  ...params: Params
343
294
  ): EagerCollection<K, Accum> {
@@ -349,7 +300,7 @@ class EagerCollectionImpl<K extends Json, V extends Json>
349
300
  }
350
301
  const skreducer = this.refs.binding.SkipRuntime_createReducer(
351
302
  this.refs.handles.register(reducerObj),
352
- this.refs.skjson.exportJSON(reducerObj.default),
303
+ this.refs.skjson.exportJSON(reducerObj.initial),
353
304
  );
354
305
  return this.derive<K, Accum>(
355
306
  this.refs.binding.SkipRuntime_Collection__reduce(
@@ -420,13 +371,17 @@ class CollectionWriter<K extends Json, V extends Json> {
420
371
  }
421
372
  }
422
373
 
423
- class ContextImpl extends SkFrozen implements Context {
374
+ class ContextImpl extends SkManaged implements Context {
424
375
  constructor(private readonly refs: Refs) {
425
376
  super();
426
377
  Object.freeze(this);
427
378
  }
428
379
 
429
- createLazyCollection<K extends Json, V extends Json, Params extends Param[]>(
380
+ createLazyCollection<
381
+ K extends Json,
382
+ V extends Json,
383
+ Params extends DepSafe[],
384
+ >(
430
385
  compute: new (...params: Params) => LazyCompute<K, V>,
431
386
  ...params: Params
432
387
  ): LazyCollection<K, V> {
@@ -447,7 +402,7 @@ class ContextImpl extends SkFrozen implements Context {
447
402
  useExternalResource<K extends Json, V extends Json>(resource: {
448
403
  service: string;
449
404
  identifier: string;
450
- params?: { [param: string]: string | number };
405
+ params?: Json;
451
406
  }): EagerCollection<K, V> {
452
407
  const collection =
453
408
  this.refs.binding.SkipRuntime_Context__useExternalResource(
@@ -492,7 +447,7 @@ class AllChecker<K extends Json, V extends Json> implements Checker {
492
447
  private readonly service: ServiceInstance,
493
448
  private readonly executor: Executor<Entry<K, V>[]>,
494
449
  private readonly resource: string,
495
- private readonly params: { [param: string]: string },
450
+ private readonly params: Json,
496
451
  ) {}
497
452
 
498
453
  check(request: string): void {
@@ -514,7 +469,7 @@ class OneChecker<K extends Json, V extends Json> implements Checker {
514
469
  private readonly service: ServiceInstance,
515
470
  private readonly executor: Executor<V[]>,
516
471
  private readonly resource: string,
517
- private readonly params: { [param: string]: string },
472
+ private readonly params: Json,
518
473
  private readonly key: K,
519
474
  ) {}
520
475
 
@@ -533,6 +488,8 @@ class OneChecker<K extends Json, V extends Json> implements Checker {
533
488
  }
534
489
  }
535
490
 
491
+ export type SubscriptionID = Opaque<bigint, "subscription">;
492
+
536
493
  /**
537
494
  * A `ServiceInstance` is a running instance of a `SkipService`, providing access to its resources
538
495
  * and operations to manage susbscriptions and the service itself.
@@ -549,7 +506,7 @@ export class ServiceInstance {
549
506
  instantiateResource(
550
507
  identifier: string,
551
508
  resource: string,
552
- params: { [param: string]: string },
509
+ params: Json,
553
510
  ): void {
554
511
  const errorHdl = this.refs.runWithGC(() => {
555
512
  return this.refs.binding.SkipRuntime_Runtime__createResource(
@@ -569,7 +526,7 @@ export class ServiceInstance {
569
526
  */
570
527
  getAll<K extends Json, V extends Json>(
571
528
  resource: string,
572
- params: { [param: string]: string } = {},
529
+ params: Json = {},
573
530
  request?: string | Executor<Entry<K, V>[]>,
574
531
  ): GetResult<Entry<K, V>[]> {
575
532
  const get_ = () => {
@@ -606,7 +563,7 @@ export class ServiceInstance {
606
563
  getArray<K extends Json, V extends Json>(
607
564
  resource: string,
608
565
  key: K,
609
- params: { [param: string]: string } = {},
566
+ params: Json = {},
610
567
  request?: string | Executor<V[]>,
611
568
  ): GetResult<V[]> {
612
569
  const get_ = () => {
@@ -734,7 +691,7 @@ export class ServiceInstance {
734
691
  }
735
692
  }
736
693
 
737
- export class NonEmptyIteratorImpl<T> implements NonEmptyIterator<T> {
694
+ class ValuesImpl<T> implements Values<T> {
738
695
  constructor(
739
696
  private readonly skjson: JsonConverter,
740
697
  private readonly binding: FromBinding,
@@ -745,26 +702,26 @@ export class NonEmptyIteratorImpl<T> implements NonEmptyIterator<T> {
745
702
  this.pointer = pointer;
746
703
  }
747
704
 
748
- next(): Nullable<T & Param> {
705
+ next(): Nullable<T & DepSafe> {
749
706
  return this.skjson.importOptJSON(
750
707
  this.binding.SkipRuntime_NonEmptyIterator__next(this.pointer),
751
- ) as Nullable<T & Param>;
708
+ ) as Nullable<T & DepSafe>;
752
709
  }
753
710
 
754
- getUnique(): T & Param {
711
+ getUnique(): T & DepSafe {
755
712
  const value = this.skjson.importOptJSON(
756
713
  this.binding.SkipRuntime_NonEmptyIterator__uniqueValue(this.pointer),
757
- ) as Nullable<T & Param>;
714
+ ) as Nullable<T & DepSafe>;
758
715
  if (value == null) throw new NonUniqueValueException();
759
716
  return value;
760
717
  }
761
718
 
762
- toArray: () => (T & Param)[] = () => {
719
+ toArray: () => (T & DepSafe)[] = () => {
763
720
  return Array.from(this);
764
721
  };
765
722
 
766
- [Symbol.iterator](): Iterator<T & Param> {
767
- const cloned_iter = new NonEmptyIteratorImpl<T & Param>(
723
+ [Symbol.iterator](): Iterator<T & DepSafe> {
724
+ const cloned_iter = new ValuesImpl<T & DepSafe>(
768
725
  this.skjson,
769
726
  this.binding,
770
727
  this.binding.SkipRuntime_NonEmptyIterator__clone(this.pointer),
@@ -773,12 +730,12 @@ export class NonEmptyIteratorImpl<T> implements NonEmptyIterator<T> {
773
730
  return {
774
731
  next() {
775
732
  const value = cloned_iter.next();
776
- return { value, done: value == null } as IteratorResult<T & Param>;
733
+ return { value, done: value == null } as IteratorResult<T & DepSafe>;
777
734
  },
778
735
  };
779
736
  }
780
737
 
781
- map<U>(f: (value: T & Param, index: number) => U, thisObj?: any): U[] {
738
+ map<U>(f: (value: T & DepSafe, index: number) => U, thisObj?: any): U[] {
782
739
  return this.toArray().map(f, thisObj);
783
740
  }
784
741
  }
@@ -833,7 +790,7 @@ export class ToBinding {
833
790
  const mapper = this.handles.get(skmapper);
834
791
  const result = mapper.mapEntry(
835
792
  skjson.importJSON(key) as Json,
836
- new NonEmptyIteratorImpl<Json>(skjson, this.binding, values),
793
+ new ValuesImpl<Json>(skjson, this.binding, values),
837
794
  );
838
795
  return skjson.exportJSON(Array.from(result) as [[Json, Json]]);
839
796
  }
@@ -851,11 +808,11 @@ export class ToBinding {
851
808
  ): Pointer<Internal.CJArray> {
852
809
  const skjson = this.getJsonConverter();
853
810
  const lazyCompute = this.handles.get(sklazyCompute);
854
- const computed = lazyCompute.compute(
811
+ const result = lazyCompute.compute(
855
812
  new LazyCollectionImpl<Json, Json>(self, this.refs()),
856
813
  skjson.importJSON(skkey) as Json,
857
814
  );
858
- return skjson.exportJSON(computed ? [computed] : []);
815
+ return skjson.exportJSON(Array.from(result));
859
816
  }
860
817
 
861
818
  SkipRuntime_deleteLazyCompute(lazyCompute: Handle<JSONLazyCompute>): void {
@@ -894,9 +851,7 @@ export class ToBinding {
894
851
  ): Pointer<Internal.Resource> {
895
852
  const skjson = this.getJsonConverter();
896
853
  const builder = this.handles.get(skbuilder);
897
- const resource = builder.build(
898
- skjson.importJSON(skparams) as { [param: string]: string },
899
- );
854
+ const resource = builder.build(skjson.importJSON(skparams) as Json);
900
855
  return this.binding.SkipRuntime_createResource(
901
856
  this.handles.register(resource),
902
857
  );
@@ -987,7 +942,7 @@ export class ToBinding {
987
942
  return skjson.exportJSON(
988
943
  reducer.add(
989
944
  skacc ? (skjson.importJSON(skacc) as Json) : null,
990
- skjson.importJSON(skvalue) as Json & Param,
945
+ skjson.importJSON(skvalue) as Json & DepSafe,
991
946
  ),
992
947
  );
993
948
  }
@@ -1002,7 +957,7 @@ export class ToBinding {
1002
957
  return skjson.exportJSON(
1003
958
  reducer.remove(
1004
959
  skjson.importJSON(skacc) as Json,
1005
- skjson.importJSON(skvalue) as Json & Param,
960
+ skjson.importJSON(skvalue) as Json & DepSafe,
1006
961
  ),
1007
962
  );
1008
963
  }
@@ -1022,9 +977,7 @@ export class ToBinding {
1022
977
  const skjson = this.getJsonConverter();
1023
978
  const supplier = this.handles.get(sksupplier);
1024
979
  const writer = new CollectionWriter(writerId, this.refs());
1025
- const params = skjson.importJSON(skparams, true) as {
1026
- [param: string]: string;
1027
- };
980
+ const params = skjson.importJSON(skparams, true) as Json;
1028
981
  supplier.subscribe(resource, params, {
1029
982
  update: writer.update.bind(writer),
1030
983
  error: writer.error.bind(writer),
@@ -1039,9 +992,7 @@ export class ToBinding {
1039
992
  ): void {
1040
993
  const skjson = this.getJsonConverter();
1041
994
  const supplier = this.handles.get(sksupplier);
1042
- const params = skjson.importJSON(skparams, true) as {
1043
- [param: string]: string;
1044
- };
995
+ const params = skjson.importJSON(skparams, true) as Json;
1045
996
  supplier.unsubscribe(resource, params);
1046
997
  }
1047
998
 
package/src/remote.ts CHANGED
@@ -10,14 +10,29 @@ interface Closable {
10
10
  close(): void;
11
11
  }
12
12
 
13
+ /**
14
+ * An external Skip reactive service.
15
+ *
16
+ * `SkipExternalService` provides an implementation of `ExternalService` for an external Skip service.
17
+ */
13
18
  export class SkipExternalService implements ExternalService {
14
19
  private readonly resources = new Map<string, Closable>();
15
20
 
21
+ /**
22
+ * @param url - URL to use for the service's streaming interface.
23
+ * @param control_url - URL to use for the service's control interface.
24
+ */
16
25
  constructor(
17
26
  private readonly url: string,
18
27
  private readonly control_url: string,
19
28
  ) {}
20
29
 
30
+ /**
31
+ * Constructor accepting an `Entrypoint`.
32
+ *
33
+ * @param entrypoint - The entry point for the external Skip service.
34
+ * @returns An `ExternalService` to interact with the service running at `entrypoint`.
35
+ */
21
36
  // TODO: Support Skip external services going through a gateway.
22
37
  static direct(entrypoint: Entrypoint): SkipExternalService {
23
38
  let url = `http://${entrypoint.host}:${entrypoint.streaming_port.toString()}`;
@@ -31,7 +46,7 @@ export class SkipExternalService implements ExternalService {
31
46
 
32
47
  subscribe(
33
48
  resource: string,
34
- params: { [param: string]: string },
49
+ params: Json,
35
50
  callbacks: {
36
51
  update: (updates: Entry<Json, Json>[], isInitial: boolean) => void;
37
52
  // FIXME: What is `error()` used for?
@@ -72,7 +87,7 @@ export class SkipExternalService implements ExternalService {
72
87
  });
73
88
  }
74
89
 
75
- unsubscribe(resource: string, params: { [param: string]: string }) {
90
+ unsubscribe(resource: string, params: Json) {
76
91
  const closable = this.resources.get(this.toId(resource, params));
77
92
  if (closable) closable.close();
78
93
  }
@@ -83,12 +98,12 @@ export class SkipExternalService implements ExternalService {
83
98
  }
84
99
  }
85
100
 
86
- private toId(resource: string, params: { [param: string]: string }): string {
87
- // TODO: This is equivalent to `querystring.encode(params, ',', ':')`.
88
- const strparams: string[] = [];
89
- for (const key of Object.keys(params).sort()) {
90
- strparams.push(`${key}:${params[key]}`);
91
- }
92
- return `${resource}[${strparams.join(",")}]`;
101
+ private toId(resource: string, params: Json): string {
102
+ if (typeof params == "object") {
103
+ const strparams = Object.entries(params)
104
+ .map(([key, value]) => `${key}:${btoa(JSON.stringify(value))}`)
105
+ .sort();
106
+ return `${resource}[${strparams.join(",")}]`;
107
+ } else return `${resource}[${btoa(JSON.stringify(params))}]`;
93
108
  }
94
109
  }
package/src/utils.ts CHANGED
@@ -1,48 +1,88 @@
1
1
  import type { Nullable } from "@skip-wasm/std";
2
2
  import { ManyToOneMapper } from "@skipruntime/api";
3
- import type { Reducer, NonEmptyIterator, Json } from "@skipruntime/api";
3
+ import type { Reducer, Values, Json } from "@skipruntime/api";
4
4
 
5
+ /**
6
+ * `Reducer` to maintain the sum of input values.
7
+ *
8
+ * A `Reducer` that maintains the sum of values as they are added and removed from a collection.
9
+ */
5
10
  export class Sum implements Reducer<number, number> {
6
- default = 0;
11
+ initial = 0;
7
12
 
8
- add(acc: number, value: number): number {
9
- return acc + value;
13
+ add(accum: number, value: number): number {
14
+ return accum + value;
10
15
  }
11
16
 
12
- remove(acc: number, value: number): Nullable<number> {
13
- return acc - value;
17
+ remove(accum: number, value: number): Nullable<number> {
18
+ return accum - value;
14
19
  }
15
20
  }
16
21
 
22
+ /**
23
+ * `Reducer` to maintain the minimum of input values.
24
+ *
25
+ * A `Reducer` that maintains the minimum of values as they are added and removed from a collection.
26
+ */
17
27
  export class Min implements Reducer<number, number> {
18
- default = null;
28
+ initial = null;
19
29
 
20
- add(acc: Nullable<number>, value: number): number {
21
- return acc === null ? value : Math.min(acc, value);
30
+ add(accum: Nullable<number>, value: number): number {
31
+ return accum === null ? value : Math.min(accum, value);
22
32
  }
23
33
 
24
- remove(acc: number, value: number): Nullable<number> {
25
- return value > acc ? acc : null;
34
+ remove(accum: number, value: number): Nullable<number> {
35
+ return value > accum ? accum : null;
26
36
  }
27
37
  }
28
38
 
39
+ /**
40
+ * `Reducer` to maintain the maximum of input values.
41
+ *
42
+ * A `Reducer` that maintains the maximum of values as they are added and removed from a collection.
43
+ */
29
44
  export class Max implements Reducer<number, number> {
30
- default = null;
45
+ initial = null;
31
46
 
32
- add(acc: Nullable<number>, value: number): number {
33
- return acc === null ? value : Math.max(acc, value);
47
+ add(accum: Nullable<number>, value: number): number {
48
+ return accum === null ? value : Math.max(accum, value);
34
49
  }
35
50
 
36
- remove(acc: number, value: number): Nullable<number> {
37
- return value < acc ? acc : null;
51
+ remove(accum: number, value: number): Nullable<number> {
52
+ return value < accum ? accum : null;
38
53
  }
39
54
  }
40
55
 
56
+ /**
57
+ * `Reducer` to maintain the count of input values.
58
+ *
59
+ * A `Reducer` that maintains the number of values as they are added and removed from a collection.
60
+ */
61
+ export class Count<T extends Json> implements Reducer<T, number> {
62
+ initial = 0;
63
+
64
+ add(accum: number): number {
65
+ return accum + 1;
66
+ }
67
+
68
+ remove(accum: number): Nullable<number> {
69
+ return accum - 1;
70
+ }
71
+ }
72
+
73
+ /**
74
+ * `Mapper` to count input values.
75
+ *
76
+ * A `Mapper` that associates each key in the output with the number of values it is associated with in the input.
77
+ *
78
+ * @remarks
79
+ * If there are many values associated to keys in a collection, and they are updated frequently, using the `Count` `Reducer` instead could be more efficient.
80
+ */
41
81
  export class CountMapper<
42
82
  K extends Json,
43
83
  V extends Json,
44
84
  > extends ManyToOneMapper<K, V, number> {
45
- mapValues(values: NonEmptyIterator<V>): number {
85
+ mapValues(values: Values<V>): number {
46
86
  return values.toArray().length;
47
87
  }
48
88
  }