@ersbeth/picoflow 0.1.0 → 0.2.0

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.
Files changed (48) hide show
  1. package/api/doc/picoflow.array.md +55 -0
  2. package/api/doc/picoflow.flowarray._constructor_.md +49 -0
  3. package/api/doc/picoflow.flowarray._lastaction.md +13 -0
  4. package/api/doc/picoflow.flowarray.clear.md +17 -0
  5. package/api/doc/picoflow.flowarray.dispose.md +55 -0
  6. package/api/doc/picoflow.flowarray.get.md +19 -0
  7. package/api/doc/picoflow.flowarray.length.md +13 -0
  8. package/api/doc/picoflow.flowarray.md +273 -0
  9. package/api/doc/picoflow.flowarray.pop.md +17 -0
  10. package/api/doc/picoflow.flowarray.push.md +53 -0
  11. package/api/doc/picoflow.flowarray.set.md +53 -0
  12. package/api/doc/picoflow.flowarray.setitem.md +69 -0
  13. package/api/doc/picoflow.flowarray.shift.md +17 -0
  14. package/api/doc/picoflow.flowarray.splice.md +85 -0
  15. package/api/doc/picoflow.flowarray.unshift.md +53 -0
  16. package/api/doc/picoflow.flowarrayaction.md +37 -0
  17. package/api/doc/picoflow.flowdisposable.dispose.md +55 -0
  18. package/api/doc/picoflow.flowdisposable.md +43 -0
  19. package/api/doc/picoflow.flowsignal.dispose.md +39 -1
  20. package/api/doc/picoflow.flowsignal.md +3 -2
  21. package/api/doc/picoflow.isdisposable.md +55 -0
  22. package/api/doc/picoflow.md +70 -0
  23. package/api/picoflow.public.api.md +63 -2
  24. package/dist/picoflow.js +188 -4
  25. package/dist/types/advanced/array.d.ts +116 -0
  26. package/dist/types/advanced/array.d.ts.map +1 -0
  27. package/dist/types/advanced/index.d.ts +2 -0
  28. package/dist/types/advanced/index.d.ts.map +1 -1
  29. package/dist/types/basic/disposable.d.ts +23 -0
  30. package/dist/types/basic/disposable.d.ts.map +1 -0
  31. package/dist/types/basic/index.d.ts +2 -0
  32. package/dist/types/basic/index.d.ts.map +1 -1
  33. package/dist/types/basic/signal.d.ts +5 -2
  34. package/dist/types/basic/signal.d.ts.map +1 -1
  35. package/dist/types/creators.d.ts +9 -0
  36. package/dist/types/creators.d.ts.map +1 -1
  37. package/dist/types/index.d.ts +4 -3
  38. package/dist/types/index.d.ts.map +1 -1
  39. package/package.json +1 -1
  40. package/src/advanced/array.ts +224 -0
  41. package/src/advanced/index.ts +2 -0
  42. package/src/basic/disposable.ts +27 -0
  43. package/src/basic/index.ts +2 -0
  44. package/src/basic/observable.ts +2 -2
  45. package/src/basic/signal.ts +17 -5
  46. package/src/creators.ts +12 -0
  47. package/src/index.ts +5 -0
  48. package/test/array.test.ts +620 -0
@@ -0,0 +1,224 @@
1
+ import { FlowObservable, type FlowState } from "../basic";
2
+ import { isDisposable } from "../basic";
3
+ import { state } from "../creators";
4
+
5
+ /**
6
+ * Represents the actions that can be performed on a FlowArray.
7
+ * @public
8
+ */
9
+ export type FlowArrayAction<T> =
10
+ | {
11
+ type: "set";
12
+ items: T[];
13
+ }
14
+ | {
15
+ type: "setItem";
16
+ index: number;
17
+ item: T;
18
+ }
19
+ | {
20
+ type: "push";
21
+ item: T;
22
+ }
23
+ | {
24
+ type: "pop";
25
+ }
26
+ | {
27
+ type: "unshift";
28
+ item: T;
29
+ }
30
+ | {
31
+ type: "shift";
32
+ }
33
+ | {
34
+ type: "splice";
35
+ start: number;
36
+ deleteCount: number;
37
+ items: T[];
38
+ }
39
+ | {
40
+ type: "clear";
41
+ };
42
+
43
+ /**
44
+ * Represents a reactive array.
45
+ * @public
46
+ */
47
+ export class FlowArray<T> extends FlowObservable<T[]> {
48
+ /**
49
+ * Last action performed on the FlowArray.
50
+ * @public
51
+ */
52
+ $lastAction: FlowState<FlowArrayAction<T>>;
53
+
54
+ /**
55
+ * Creates an instance of FlowArray.
56
+ * @param value - Initial array value.
57
+ * @public
58
+ */
59
+ constructor(value: T[] = []) {
60
+ super();
61
+ this._value = value;
62
+ this.$lastAction = state<FlowArrayAction<T>>({
63
+ type: "set",
64
+ items: value,
65
+ });
66
+ }
67
+
68
+ /**
69
+ * Gets the current length of the array.
70
+ * @returns The length of the array.
71
+ * @public
72
+ */
73
+ get length(): number {
74
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
75
+ return this._value.length;
76
+ }
77
+
78
+ /**
79
+ * Returns a copy of the internal array.
80
+ * @returns A copy of the array.
81
+ * @public
82
+ */
83
+ get(): T[] {
84
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
85
+ return [...this._value]; // Ensure nobody can modify the original array
86
+ }
87
+
88
+ /**
89
+ * Replaces the entire array with new items.
90
+ * @param items - The new array of items.
91
+ * @public
92
+ */
93
+ set(items: T[]): void {
94
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
95
+ this._value.forEach((item) => {
96
+ if (isDisposable(item)) item.dispose({ self: true });
97
+ });
98
+ this._value = items;
99
+ this._notify();
100
+ this.$lastAction.set({ type: "set", items: items });
101
+ }
102
+
103
+ /**
104
+ * Replaces an item at a specific index.
105
+ * @param index - The index of the item to replace.
106
+ * @param item - The new item.
107
+ * @public
108
+ */
109
+ setItem(index: number, item: T): void {
110
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
111
+ if (index < 0 || index >= this._value.length) {
112
+ throw new Error("[PicoFlow] Index out of bounds");
113
+ }
114
+ this._value[index] = item;
115
+ this._notify();
116
+ this.$lastAction.set({ type: "setItem", index: index, item: item });
117
+ }
118
+
119
+ /**
120
+ * Appends an item to the end of the array.
121
+ * @param item - The item to append.
122
+ * @public
123
+ */
124
+ push(item: T): void {
125
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
126
+ this._value.push(item);
127
+ this._notify();
128
+ this.$lastAction.set({ type: "push", item: item });
129
+ }
130
+
131
+ /**
132
+ * Removes the last item from the array.
133
+ * @public
134
+ */
135
+ pop(): void {
136
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
137
+ const item = this._value.pop();
138
+ if (isDisposable(item)) {
139
+ item.dispose({ self: true });
140
+ }
141
+ this._notify();
142
+ this.$lastAction.set({ type: "pop" });
143
+ }
144
+
145
+ /**
146
+ * Inserts an item at the beginning of the array.
147
+ * @param item - The item to insert.
148
+ * @public
149
+ */
150
+ unshift(item: T): void {
151
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
152
+ this._value.unshift(item);
153
+ this._notify();
154
+ this.$lastAction.set({ type: "unshift", item: item });
155
+ }
156
+
157
+ /**
158
+ * Removes the first item from the array.
159
+ * @public
160
+ */
161
+ shift(): void {
162
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
163
+ const item = this._value.shift();
164
+ if (isDisposable(item)) {
165
+ item.dispose({ self: true });
166
+ }
167
+ this._notify();
168
+ this.$lastAction.set({ type: "shift" });
169
+ }
170
+
171
+ /**
172
+ * Changes the content of the array.
173
+ * @param start - The starting index.
174
+ * @param deleteCount - Number of items to remove.
175
+ * @param newItems - New items to add.
176
+ * @public
177
+ */
178
+ splice(start: number, deleteCount: number, ...newItems: T[]): void {
179
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
180
+ const items = this._value.splice(start, deleteCount, ...newItems);
181
+ items.forEach((item) => {
182
+ if (isDisposable(item)) item.dispose({ self: true });
183
+ });
184
+ this._notify();
185
+ this.$lastAction.set({
186
+ type: "splice",
187
+ start: start,
188
+ deleteCount: deleteCount,
189
+ items: newItems,
190
+ });
191
+ }
192
+
193
+ /**
194
+ * Clears all items from the array.
195
+ * @public
196
+ */
197
+ clear(): void {
198
+ if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
199
+ const items = [...this._value];
200
+ items.forEach((item) => {
201
+ if (isDisposable(item)) item.dispose({ self: true });
202
+ });
203
+ this._value = [];
204
+ this._notify();
205
+ this.$lastAction.set({ type: "clear" });
206
+ }
207
+
208
+ /**
209
+ * Disposes the FlowArray and its items.
210
+ * @param options - Disposal options.
211
+ * @public
212
+ */
213
+ override dispose(options?: { self: boolean }): void {
214
+ super.dispose(options);
215
+ this._value.forEach((item) => {
216
+ if (isDisposable(item)) item.dispose(options);
217
+ });
218
+ this._value = [];
219
+ }
220
+
221
+ /* INTERNAL */
222
+
223
+ /*@internal*/ protected override _value: T[] = [];
224
+ }
@@ -8,3 +8,5 @@ export type {
8
8
  FlowStreamDisposer,
9
9
  FlowStreamSetter,
10
10
  } from "./stream";
11
+ export { FlowArray } from "./array";
12
+ export type { FlowArrayAction } from "./array";
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Represents an object with a disposable lifecycle.
3
+ * @remarks
4
+ * Objects implementing this interface require explicit resource disposal.
5
+ * @public
6
+ */
7
+ export interface FlowDisposable {
8
+ /**
9
+ * Disposes resources held by this object.
10
+ * @param options - Options to specify disposal behavior.
11
+ */
12
+ dispose(options?: { self: boolean }): void;
13
+ }
14
+
15
+ /**
16
+ * Checks whether an object implements the FlowDisposable interface.
17
+ * @param obj - The object to test.
18
+ * @returns True if the object has a dispose method, otherwise false.
19
+ * @public
20
+ */
21
+ export function isDisposable(obj: unknown): obj is FlowDisposable {
22
+ return (
23
+ obj !== null &&
24
+ obj !== undefined &&
25
+ typeof (obj as FlowDisposable).dispose === "function"
26
+ );
27
+ }
@@ -4,5 +4,7 @@ export { FlowObservable } from "./observable";
4
4
  export { FlowDerivation } from "./derivation";
5
5
  export { FlowEffect } from "./effect";
6
6
  export { FlowConstant } from "./constant";
7
+ export { isDisposable } from "./disposable";
7
8
  export type { FlowGetter } from "./observable";
8
9
  export type { FlowWatcher } from "./signal";
10
+ export type { FlowDisposable } from "./disposable";
@@ -12,8 +12,8 @@ export type FlowGetter = <T>(observable: FlowObservable<T>) => T;
12
12
 
13
13
  /**
14
14
  * Represents a reactive observable that holds and tracks a value.
15
- *
16
- *
15
+ *
16
+ *
17
17
  * @remarks Subclasses must implement the {@link FlowObservable.get} method to return the current value.
18
18
  * @typeparam T - The type of the value held by the observable.
19
19
  * @public
@@ -1,3 +1,4 @@
1
+ import type { FlowDisposable } from "./disposable";
1
2
  import type { FlowEffect } from "./effect";
2
3
 
3
4
  /**
@@ -9,12 +10,12 @@ export type FlowWatcher = (signal: FlowSignal) => void;
9
10
 
10
11
  /**
11
12
  * Represents a reactive signal.
12
- *
13
+ *
13
14
  * @remarks Use FlowSignal to create reactive streams that notify listeners and execute associated effects.
14
15
  * Signals can be triggered and disposed. Once disposed, interactions with the signal will throw errors.
15
16
  * @public
16
17
  */
17
- export class FlowSignal {
18
+ export class FlowSignal implements FlowDisposable {
18
19
  /**
19
20
  * Triggers the FlowSignal.
20
21
  * Notifies all registered listeners and schedules execution of associated effects.
@@ -33,10 +34,21 @@ export class FlowSignal {
33
34
  * @throws If the FlowSignal is already disposed.
34
35
  * @public
35
36
  */
36
- public dispose(): void {
37
+ public dispose(options?: { self: boolean }): void {
37
38
  if (this._disposed) throw new Error("[PicoFlow] Primitive is disposed");
38
- Array.from(this._effects).forEach((effect) => effect.dispose());
39
- Array.from(this._listeners).forEach((listener) => listener.dispose());
39
+ if (options?.self) {
40
+ Array.from(this._effects).forEach((effect) =>
41
+ effect._unregisterDependency(this),
42
+ );
43
+ Array.from(this._listeners).forEach((listener) =>
44
+ listener._unregisterDependency(this),
45
+ );
46
+ } else {
47
+ Array.from(this._effects).forEach((effect) => effect.dispose());
48
+ Array.from(this._listeners).forEach((listener) =>
49
+ listener.dispose(),
50
+ );
51
+ }
40
52
  Array.from(this._dependencies).forEach((dependency) => {
41
53
  this._unregisterDependency(dependency);
42
54
  });
package/src/creators.ts CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  FlowStream,
6
6
  FlowStreamAsync,
7
7
  } from "./advanced/";
8
+ import { FlowArray } from "./advanced/array";
8
9
  import {
9
10
  FlowConstant,
10
11
  FlowDerivation,
@@ -138,3 +139,14 @@ export function map<K extends string | number | symbol, V>(
138
139
  new Map<K, V>(initial ? (Object.entries(initial) as [K, V][]) : []),
139
140
  );
140
141
  }
142
+
143
+ /**
144
+ * Creates a new reactive array.
145
+ * @typeparam T - The type of the array elements.
146
+ * @param initial - An optional array of initial values.
147
+ * @returns A new instance of {@link FlowArray}.
148
+ * @public
149
+ */
150
+ export function array<T>(initial?: T[]): FlowArray<T> {
151
+ return new FlowArray<T>(initial);
152
+ }
package/src/index.ts CHANGED
@@ -15,9 +15,11 @@ export {
15
15
  derivation,
16
16
  effect,
17
17
  map,
18
+ array,
18
19
  streamAsync,
19
20
  resourceAsync,
20
21
  } from "./creators";
22
+ export { isDisposable } from "./basic";
21
23
  export type {
22
24
  FlowDerivation,
23
25
  FlowEffect,
@@ -27,6 +29,7 @@ export type {
27
29
  FlowWatcher,
28
30
  FlowState,
29
31
  FlowConstant,
32
+ FlowDisposable,
30
33
  } from "./basic/";
31
34
  export type {
32
35
  FlowResource,
@@ -37,4 +40,6 @@ export type {
37
40
  FlowStreamDisposer,
38
41
  FlowStreamSetter,
39
42
  FlowStreamUpdater,
43
+ FlowArray,
44
+ FlowArrayAction,
40
45
  } from "./advanced/";