@vaadin/hilla-react-signals 24.5.0-alpha11 → 24.5.0-alpha13

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.
@@ -1,20 +1,6 @@
1
1
  import type { ConnectClient, Subscription } from '@vaadin/hilla-frontend';
2
2
  import { Signal } from './core.js';
3
- /**
4
- * Types of changes that can be produced or processed by a signal.
5
- */
6
- export declare enum StateEventType {
7
- SET = "set",
8
- SNAPSHOT = "snapshot"
9
- }
10
- /**
11
- * An object that describes the change of the signal state.
12
- */
13
- export type StateEvent<T> = Readonly<{
14
- id: string;
15
- type: StateEventType;
16
- value: T;
17
- }>;
3
+ import { type StateEvent } from './events.js';
18
4
  /**
19
5
  * An abstraction of a signal that tracks the number of subscribers, and calls
20
6
  * the provided `onSubscribe` and `onUnsubscribe` callbacks for the first
@@ -51,19 +37,13 @@ export type ServerConnectionConfig = Readonly<{
51
37
  declare class ServerConnection<T> {
52
38
  #private;
53
39
  constructor(id: string, config: ServerConnectionConfig);
54
- get subscription(): Subscription<Readonly<{
55
- id: string;
56
- type: StateEventType;
57
- value: T;
58
- }>> | undefined;
59
- connect(): Subscription<Readonly<{
60
- id: string;
61
- type: StateEventType;
62
- value: T;
63
- }>>;
40
+ get subscription(): Subscription<StateEvent<T>> | undefined;
41
+ connect(): Subscription<StateEvent<T>>;
64
42
  update(event: StateEvent<T>): Promise<void>;
65
43
  disconnect(): void;
66
44
  }
45
+ export declare const $update: unique symbol;
46
+ export declare const $processServerResponse: unique symbol;
67
47
  /**
68
48
  * A signal that holds a shared value. Each change to the value is propagated to
69
49
  * the server-side signal provider. At the same time, each change received from
@@ -92,6 +72,19 @@ export declare abstract class FullStackSignal<T> extends DependencyTrackingSigna
92
72
  */
93
73
  readonly error: import("@preact/signals-core").ReadonlySignal<Error | undefined>;
94
74
  constructor(value: T | undefined, config: ServerConnectionConfig);
75
+ /**
76
+ * A method to update the server with the new value.
77
+ *
78
+ * @param event - The event to update the server with.
79
+ */
80
+ protected [$update](event: StateEvent<T>): void;
81
+ /**
82
+ * A method with to process the server response. The implementation is
83
+ * specific for each signal type.
84
+ *
85
+ * @param event - The server response event.
86
+ */
87
+ protected abstract [$processServerResponse](event: StateEvent<T>): void;
95
88
  }
96
89
  export {};
97
90
  //# sourceMappingURL=FullStackSignal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FullStackSignal.d.ts","sourceRoot":"","sources":["src/FullStackSignal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE1E,OAAO,EAAoB,MAAM,EAAE,MAAM,WAAW,CAAC;AAIrD;;GAEG;AACH,oBAAY,cAAc;IACxB,GAAG,QAAQ;IACX,QAAQ,aAAa;CACtB;AAED;;GAEG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,QAAQ,CAAC;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,cAAc,CAAC;IACrB,KAAK,EAAE,CAAC,CAAC;CACV,CAAC,CAAC;AAEH;;;;;GAKG;AACH,8BAAsB,wBAAwB,CAAC,CAAC,CAAE,SAAQ,MAAM,CAAC,CAAC,CAAC;;IAQjE,SAAS,aAAa,KAAK,EAAE,CAAC,GAAG,SAAS,EAAE,gBAAgB,EAAE,MAAM,IAAI,EAAE,iBAAiB,EAAE,MAAM,IAAI;IAMvG,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAUhC,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;CASjC;AAED;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C;;OAEG;IACH,MAAM,EAAE,aAAa,CAAC;IAEtB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC,CAAC;AAEH;;GAEG;AACH,cAAM,gBAAgB,CAAC,CAAC;;gBAKV,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,sBAAsB;IAKtD,IAAI,YAAY;YAhFZ,MAAM;cACJ,cAAc;;oBAiFnB;IAED,OAAO;YApFH,MAAM;cACJ,cAAc;;;IA+Fd,MAAM,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAOjD,UAAU;CAIX;AAED;;;;;;;GAOG;AACH,8BAAsB,eAAe,CAAC,CAAC,CAAE,SAAQ,wBAAwB,CAAC,CAAC,CAAC;;IAC1E;;;OAGG;IACH,QAAQ,CAAC,EAAE,SAAY;IAEvB;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAErC;;OAEG;IACH,QAAQ,CAAC,OAAO,yDAAuC;IAEvD;;OAEG;IACH,QAAQ,CAAC,KAAK,mEAAqC;gBASvC,KAAK,EAAE,CAAC,GAAG,SAAS,EAAE,MAAM,EAAE,sBAAsB;CA8CjE"}
1
+ {"version":3,"file":"FullStackSignal.d.ts","sourceRoot":"","sources":["src/FullStackSignal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE1E,OAAO,EAAoB,MAAM,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EAAuB,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAInE;;;;;GAKG;AACH,8BAAsB,wBAAwB,CAAC,CAAC,CAAE,SAAQ,MAAM,CAAC,CAAC,CAAC;;IAQjE,SAAS,aAAa,KAAK,EAAE,CAAC,GAAG,SAAS,EAAE,gBAAgB,EAAE,MAAM,IAAI,EAAE,iBAAiB,EAAE,MAAM,IAAI;cAMpF,CAAC,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;cAQtB,CAAC,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;CAO1C;AAED;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C;;OAEG;IACH,MAAM,EAAE,aAAa,CAAC;IAEtB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC,CAAC;AAEH;;GAEG;AACH,cAAM,gBAAgB,CAAC,CAAC;;gBAKV,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,sBAAsB;IAKtD,IAAI,YAAY,4CAEf;IAED,OAAO;IAYD,MAAM,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAOjD,UAAU;CAIX;AAED,eAAO,MAAM,OAAO,eAAmB,CAAC;AACxC,eAAO,MAAM,sBAAsB,eAAkC,CAAC;AAEtE;;;;;;;GAOG;AACH,8BAAsB,eAAe,CAAC,CAAC,CAAE,SAAQ,wBAAwB,CAAC,CAAC,CAAC;;IAC1E;;;OAGG;IACH,QAAQ,CAAC,EAAE,SAAY;IAEvB;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAErC;;OAEG;IACH,QAAQ,CAAC,OAAO,yDAAuC;IAEvD;;OAEG;IACH,QAAQ,CAAC,KAAK,mEAAqC;gBASvC,KAAK,EAAE,CAAC,GAAG,SAAS,EAAE,MAAM,EAAE,sBAAsB;IAmBhE;;;;OAIG;IACH,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI;IAW/C;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,CAAC,CAAC,sBAAsB,CAAC,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI;CAgBxE"}
@@ -1,11 +1,7 @@
1
1
  import { nanoid } from "nanoid";
2
2
  import { computed, signal, Signal } from "./core.js";
3
+ import { createSetStateEvent } from "./events.js";
3
4
  const ENDPOINT = "SignalsHandler";
4
- var StateEventType = /* @__PURE__ */ ((StateEventType2) => {
5
- StateEventType2["SET"] = "set";
6
- StateEventType2["SNAPSHOT"] = "snapshot";
7
- return StateEventType2;
8
- })(StateEventType || {});
9
5
  class DependencyTrackingSignal extends Signal {
10
6
  #onFirstSubscribe;
11
7
  #onLastUnsubscribe;
@@ -63,6 +59,8 @@ class ServerConnection {
63
59
  this.#subscription = void 0;
64
60
  }
65
61
  }
62
+ const $update = Symbol("update");
63
+ const $processServerResponse = Symbol("processServerResponse");
66
64
  class FullStackSignal extends DependencyTrackingSignal {
67
65
  /**
68
66
  * The unique identifier of the signal necessary to communicate with the
@@ -97,26 +95,28 @@ class FullStackSignal extends DependencyTrackingSignal {
97
95
  if (!this.#paused) {
98
96
  this.#pending.value = true;
99
97
  this.#error.value = void 0;
100
- this.server.update({
101
- id: nanoid(),
102
- type: "set" /* SET */,
103
- value: v
104
- }).catch((error) => {
105
- this.#error.value = error instanceof Error ? error : new Error(String(error));
106
- }).finally(() => {
107
- this.#pending.value = false;
108
- });
98
+ this[$update](createSetStateEvent(v));
109
99
  }
110
100
  });
111
101
  this.#paused = false;
112
102
  }
103
+ /**
104
+ * A method to update the server with the new value.
105
+ *
106
+ * @param event - The event to update the server with.
107
+ */
108
+ [$update](event) {
109
+ this.server.update(event).catch((error) => {
110
+ this.#error.value = error instanceof Error ? error : new Error(String(error));
111
+ }).finally(() => {
112
+ this.#pending.value = false;
113
+ });
114
+ }
113
115
  #connect() {
114
116
  this.server.connect().onNext((event) => {
115
- if (event.type === "snapshot" /* SNAPSHOT */) {
116
- this.#paused = true;
117
- this.value = event.value;
118
- this.#paused = false;
119
- }
117
+ this.#paused = true;
118
+ this[$processServerResponse](event);
119
+ this.#paused = false;
120
120
  });
121
121
  }
122
122
  #disconnect() {
@@ -127,8 +127,9 @@ class FullStackSignal extends DependencyTrackingSignal {
127
127
  }
128
128
  }
129
129
  export {
130
+ $processServerResponse,
131
+ $update,
130
132
  DependencyTrackingSignal,
131
- FullStackSignal,
132
- StateEventType
133
+ FullStackSignal
133
134
  };
134
135
  //# sourceMappingURL=FullStackSignal.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["src/FullStackSignal.ts"],
4
- "sourcesContent": ["import type { ConnectClient, Subscription } from '@vaadin/hilla-frontend';\nimport { nanoid } from 'nanoid';\nimport { computed, signal, Signal } from './core.js';\n\nconst ENDPOINT = 'SignalsHandler';\n\n/**\n * Types of changes that can be produced or processed by a signal.\n */\nexport enum StateEventType {\n SET = 'set',\n SNAPSHOT = 'snapshot',\n}\n\n/**\n * An object that describes the change of the signal state.\n */\nexport type StateEvent<T> = Readonly<{\n id: string;\n type: StateEventType;\n value: T;\n}>;\n\n/**\n * An abstraction of a signal that tracks the number of subscribers, and calls\n * the provided `onSubscribe` and `onUnsubscribe` callbacks for the first\n * subscription and the last unsubscription, respectively.\n * @internal\n */\nexport abstract class DependencyTrackingSignal<T> extends Signal<T> {\n readonly #onFirstSubscribe: () => void;\n readonly #onLastUnsubscribe: () => void;\n\n // -1 means to ignore the first subscription that is created internally in the\n // FullStackSignal constructor.\n #subscribeCount = -1;\n\n protected constructor(value: T | undefined, onFirstSubscribe: () => void, onLastUnsubscribe: () => void) {\n super(value);\n this.#onFirstSubscribe = onFirstSubscribe;\n this.#onLastUnsubscribe = onLastUnsubscribe;\n }\n\n protected S(node: unknown): void {\n // @ts-expect-error: We use the protected method from the base class.\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call\n super.S(node);\n if (this.#subscribeCount === 0) {\n this.#onFirstSubscribe();\n }\n this.#subscribeCount += 1;\n }\n\n protected U(node: unknown): void {\n // @ts-expect-error: We use the protected method from the base class.\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call\n super.U(node);\n this.#subscribeCount -= 1;\n if (this.#subscribeCount === 0) {\n this.#onLastUnsubscribe();\n }\n }\n}\n\n/**\n * An object that describes a data object to connect to the signal provider\n * service.\n */\nexport type ServerConnectionConfig = Readonly<{\n /**\n * The client instance to be used for communication.\n */\n client: ConnectClient;\n\n /**\n * The name of the signal provider service endpoint.\n */\n endpoint: string;\n\n /**\n * The name of the signal provider service method.\n */\n method: string;\n}>;\n\n/**\n * A server connection manager.\n */\nclass ServerConnection<T> {\n readonly #id: string;\n readonly #config: ServerConnectionConfig;\n #subscription?: Subscription<StateEvent<T>>;\n\n constructor(id: string, config: ServerConnectionConfig) {\n this.#config = config;\n this.#id = id;\n }\n\n get subscription() {\n return this.#subscription;\n }\n\n connect() {\n const { client, endpoint, method } = this.#config;\n\n this.#subscription ??= client.subscribe(ENDPOINT, 'subscribe', {\n providerEndpoint: endpoint,\n providerMethod: method,\n clientSignalId: this.#id,\n });\n\n return this.#subscription;\n }\n\n async update(event: StateEvent<T>): Promise<void> {\n await this.#config.client.call(ENDPOINT, 'update', {\n clientSignalId: this.#id,\n event,\n });\n }\n\n disconnect() {\n this.#subscription?.cancel();\n this.#subscription = undefined;\n }\n}\n\n/**\n * A signal that holds a shared value. Each change to the value is propagated to\n * the server-side signal provider. At the same time, each change received from\n * the server-side signal provider is propagated to the local signal and it's\n * subscribers.\n *\n * @internal\n */\nexport abstract class FullStackSignal<T> extends DependencyTrackingSignal<T> {\n /**\n * The unique identifier of the signal necessary to communicate with the\n * server.\n */\n readonly id = nanoid();\n\n /**\n * The server connection manager.\n */\n readonly server: ServerConnection<T>;\n\n /**\n * Defines whether the signal is currently awaits a server-side response.\n */\n readonly pending = computed(() => this.#pending.value);\n\n /**\n * Defines whether the signal has an error.\n */\n readonly error = computed(() => this.#error.value);\n\n readonly #pending = signal(false);\n readonly #error = signal<Error | undefined>(undefined);\n\n // Paused at the very start to prevent the signal from sending the initial\n // value to the server.\n #paused = true;\n\n constructor(value: T | undefined, config: ServerConnectionConfig) {\n super(\n value,\n () => this.#connect(),\n () => this.#disconnect(),\n );\n this.server = new ServerConnection(this.id, config);\n\n this.subscribe((v) => {\n if (!this.#paused) {\n this.#pending.value = true;\n this.#error.value = undefined;\n this.server\n .update({\n id: nanoid(),\n type: StateEventType.SET,\n value: v,\n })\n .catch((error: unknown) => {\n this.#error.value = error instanceof Error ? error : new Error(String(error));\n })\n .finally(() => {\n this.#pending.value = false;\n });\n }\n });\n\n this.#paused = false;\n }\n\n #connect() {\n this.server.connect().onNext((event: StateEvent<T>) => {\n if (event.type === StateEventType.SNAPSHOT) {\n this.#paused = true;\n this.value = event.value;\n this.#paused = false;\n }\n });\n }\n\n #disconnect() {\n if (this.server.subscription === undefined) {\n return;\n }\n this.server.disconnect();\n }\n}\n"],
5
- "mappings": "AACA,SAAS,cAAc;AACvB,SAAS,UAAU,QAAQ,cAAc;AAEzC,MAAM,WAAW;AAKV,IAAK,iBAAL,kBAAKA,oBAAL;AACL,EAAAA,gBAAA,SAAM;AACN,EAAAA,gBAAA,cAAW;AAFD,SAAAA;AAAA,GAAA;AAoBL,MAAe,iCAAoC,OAAU;AAAA,EACzD;AAAA,EACA;AAAA;AAAA;AAAA,EAIT,kBAAkB;AAAA,EAER,YAAY,OAAsB,kBAA8B,mBAA+B;AACvG,UAAM,KAAK;AACX,SAAK,oBAAoB;AACzB,SAAK,qBAAqB;AAAA,EAC5B;AAAA,EAEU,EAAE,MAAqB;AAG/B,UAAM,EAAE,IAAI;AACZ,QAAI,KAAK,oBAAoB,GAAG;AAC9B,WAAK,kBAAkB;AAAA,IACzB;AACA,SAAK,mBAAmB;AAAA,EAC1B;AAAA,EAEU,EAAE,MAAqB;AAG/B,UAAM,EAAE,IAAI;AACZ,SAAK,mBAAmB;AACxB,QAAI,KAAK,oBAAoB,GAAG;AAC9B,WAAK,mBAAmB;AAAA,IAC1B;AAAA,EACF;AACF;AA0BA,MAAM,iBAAoB;AAAA,EACf;AAAA,EACA;AAAA,EACT;AAAA,EAEA,YAAY,IAAY,QAAgC;AACtD,SAAK,UAAU;AACf,SAAK,MAAM;AAAA,EACb;AAAA,EAEA,IAAI,eAAe;AACjB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,UAAU;AACR,UAAM,EAAE,QAAQ,UAAU,OAAO,IAAI,KAAK;AAE1C,SAAK,kBAAkB,OAAO,UAAU,UAAU,aAAa;AAAA,MAC7D,kBAAkB;AAAA,MAClB,gBAAgB;AAAA,MAChB,gBAAgB,KAAK;AAAA,IACvB,CAAC;AAED,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,OAAO,OAAqC;AAChD,UAAM,KAAK,QAAQ,OAAO,KAAK,UAAU,UAAU;AAAA,MACjD,gBAAgB,KAAK;AAAA,MACrB;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,aAAa;AACX,SAAK,eAAe,OAAO;AAC3B,SAAK,gBAAgB;AAAA,EACvB;AACF;AAUO,MAAe,wBAA2B,yBAA4B;AAAA;AAAA;AAAA;AAAA;AAAA,EAKlE,KAAK,OAAO;AAAA;AAAA;AAAA;AAAA,EAKZ;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,SAAS,MAAM,KAAK,SAAS,KAAK;AAAA;AAAA;AAAA;AAAA,EAK5C,QAAQ,SAAS,MAAM,KAAK,OAAO,KAAK;AAAA,EAExC,WAAW,OAAO,KAAK;AAAA,EACvB,SAAS,OAA0B,MAAS;AAAA;AAAA;AAAA,EAIrD,UAAU;AAAA,EAEV,YAAY,OAAsB,QAAgC;AAChE;AAAA,MACE;AAAA,MACA,MAAM,KAAK,SAAS;AAAA,MACpB,MAAM,KAAK,YAAY;AAAA,IACzB;AACA,SAAK,SAAS,IAAI,iBAAiB,KAAK,IAAI,MAAM;AAElD,SAAK,UAAU,CAAC,MAAM;AACpB,UAAI,CAAC,KAAK,SAAS;AACjB,aAAK,SAAS,QAAQ;AACtB,aAAK,OAAO,QAAQ;AACpB,aAAK,OACF,OAAO;AAAA,UACN,IAAI,OAAO;AAAA,UACX,MAAM;AAAA,UACN,OAAO;AAAA,QACT,CAAC,EACA,MAAM,CAAC,UAAmB;AACzB,eAAK,OAAO,QAAQ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAAA,QAC9E,CAAC,EACA,QAAQ,MAAM;AACb,eAAK,SAAS,QAAQ;AAAA,QACxB,CAAC;AAAA,MACL;AAAA,IACF,CAAC;AAED,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,WAAW;AACT,SAAK,OAAO,QAAQ,EAAE,OAAO,CAAC,UAAyB;AACrD,UAAI,MAAM,SAAS,2BAAyB;AAC1C,aAAK,UAAU;AACf,aAAK,QAAQ,MAAM;AACnB,aAAK,UAAU;AAAA,MACjB;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,cAAc;AACZ,QAAI,KAAK,OAAO,iBAAiB,QAAW;AAC1C;AAAA,IACF;AACA,SAAK,OAAO,WAAW;AAAA,EACzB;AACF;",
6
- "names": ["StateEventType"]
4
+ "sourcesContent": ["import type { ConnectClient, Subscription } from '@vaadin/hilla-frontend';\nimport { nanoid } from 'nanoid';\nimport { computed, signal, Signal } from './core.js';\nimport { createSetStateEvent, type StateEvent } from './events.js';\n\nconst ENDPOINT = 'SignalsHandler';\n\n/**\n * An abstraction of a signal that tracks the number of subscribers, and calls\n * the provided `onSubscribe` and `onUnsubscribe` callbacks for the first\n * subscription and the last unsubscription, respectively.\n * @internal\n */\nexport abstract class DependencyTrackingSignal<T> extends Signal<T> {\n readonly #onFirstSubscribe: () => void;\n readonly #onLastUnsubscribe: () => void;\n\n // -1 means to ignore the first subscription that is created internally in the\n // FullStackSignal constructor.\n #subscribeCount = -1;\n\n protected constructor(value: T | undefined, onFirstSubscribe: () => void, onLastUnsubscribe: () => void) {\n super(value);\n this.#onFirstSubscribe = onFirstSubscribe;\n this.#onLastUnsubscribe = onLastUnsubscribe;\n }\n\n protected override S(node: unknown): void {\n super.S(node);\n if (this.#subscribeCount === 0) {\n this.#onFirstSubscribe();\n }\n this.#subscribeCount += 1;\n }\n\n protected override U(node: unknown): void {\n super.U(node);\n this.#subscribeCount -= 1;\n if (this.#subscribeCount === 0) {\n this.#onLastUnsubscribe();\n }\n }\n}\n\n/**\n * An object that describes a data object to connect to the signal provider\n * service.\n */\nexport type ServerConnectionConfig = Readonly<{\n /**\n * The client instance to be used for communication.\n */\n client: ConnectClient;\n\n /**\n * The name of the signal provider service endpoint.\n */\n endpoint: string;\n\n /**\n * The name of the signal provider service method.\n */\n method: string;\n}>;\n\n/**\n * A server connection manager.\n */\nclass ServerConnection<T> {\n readonly #id: string;\n readonly #config: ServerConnectionConfig;\n #subscription?: Subscription<StateEvent<T>>;\n\n constructor(id: string, config: ServerConnectionConfig) {\n this.#config = config;\n this.#id = id;\n }\n\n get subscription() {\n return this.#subscription;\n }\n\n connect() {\n const { client, endpoint, method } = this.#config;\n\n this.#subscription ??= client.subscribe(ENDPOINT, 'subscribe', {\n providerEndpoint: endpoint,\n providerMethod: method,\n clientSignalId: this.#id,\n });\n\n return this.#subscription;\n }\n\n async update(event: StateEvent<T>): Promise<void> {\n await this.#config.client.call(ENDPOINT, 'update', {\n clientSignalId: this.#id,\n event,\n });\n }\n\n disconnect() {\n this.#subscription?.cancel();\n this.#subscription = undefined;\n }\n}\n\nexport const $update = Symbol('update');\nexport const $processServerResponse = Symbol('processServerResponse');\n\n/**\n * A signal that holds a shared value. Each change to the value is propagated to\n * the server-side signal provider. At the same time, each change received from\n * the server-side signal provider is propagated to the local signal and it's\n * subscribers.\n *\n * @internal\n */\nexport abstract class FullStackSignal<T> extends DependencyTrackingSignal<T> {\n /**\n * The unique identifier of the signal necessary to communicate with the\n * server.\n */\n readonly id = nanoid();\n\n /**\n * The server connection manager.\n */\n readonly server: ServerConnection<T>;\n\n /**\n * Defines whether the signal is currently awaits a server-side response.\n */\n readonly pending = computed(() => this.#pending.value);\n\n /**\n * Defines whether the signal has an error.\n */\n readonly error = computed(() => this.#error.value);\n\n readonly #pending = signal(false);\n readonly #error = signal<Error | undefined>(undefined);\n\n // Paused at the very start to prevent the signal from sending the initial\n // value to the server.\n #paused = true;\n\n constructor(value: T | undefined, config: ServerConnectionConfig) {\n super(\n value,\n () => this.#connect(),\n () => this.#disconnect(),\n );\n this.server = new ServerConnection(this.id, config);\n\n this.subscribe((v) => {\n if (!this.#paused) {\n this.#pending.value = true;\n this.#error.value = undefined;\n this[$update](createSetStateEvent(v));\n }\n });\n\n this.#paused = false;\n }\n\n /**\n * A method to update the server with the new value.\n *\n * @param event - The event to update the server with.\n */\n protected [$update](event: StateEvent<T>): void {\n this.server\n .update(event)\n .catch((error: unknown) => {\n this.#error.value = error instanceof Error ? error : new Error(String(error));\n })\n .finally(() => {\n this.#pending.value = false;\n });\n }\n\n /**\n * A method with to process the server response. The implementation is\n * specific for each signal type.\n *\n * @param event - The server response event.\n */\n protected abstract [$processServerResponse](event: StateEvent<T>): void;\n\n #connect() {\n this.server.connect().onNext((event: StateEvent<T>) => {\n this.#paused = true;\n this[$processServerResponse](event);\n this.#paused = false;\n });\n }\n\n #disconnect() {\n if (this.server.subscription === undefined) {\n return;\n }\n this.server.disconnect();\n }\n}\n"],
5
+ "mappings": "AACA,SAAS,cAAc;AACvB,SAAS,UAAU,QAAQ,cAAc;AACzC,SAAS,2BAA4C;AAErD,MAAM,WAAW;AAQV,MAAe,iCAAoC,OAAU;AAAA,EACzD;AAAA,EACA;AAAA;AAAA;AAAA,EAIT,kBAAkB;AAAA,EAER,YAAY,OAAsB,kBAA8B,mBAA+B;AACvG,UAAM,KAAK;AACX,SAAK,oBAAoB;AACzB,SAAK,qBAAqB;AAAA,EAC5B;AAAA,EAEmB,EAAE,MAAqB;AACxC,UAAM,EAAE,IAAI;AACZ,QAAI,KAAK,oBAAoB,GAAG;AAC9B,WAAK,kBAAkB;AAAA,IACzB;AACA,SAAK,mBAAmB;AAAA,EAC1B;AAAA,EAEmB,EAAE,MAAqB;AACxC,UAAM,EAAE,IAAI;AACZ,SAAK,mBAAmB;AACxB,QAAI,KAAK,oBAAoB,GAAG;AAC9B,WAAK,mBAAmB;AAAA,IAC1B;AAAA,EACF;AACF;AA0BA,MAAM,iBAAoB;AAAA,EACf;AAAA,EACA;AAAA,EACT;AAAA,EAEA,YAAY,IAAY,QAAgC;AACtD,SAAK,UAAU;AACf,SAAK,MAAM;AAAA,EACb;AAAA,EAEA,IAAI,eAAe;AACjB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,UAAU;AACR,UAAM,EAAE,QAAQ,UAAU,OAAO,IAAI,KAAK;AAE1C,SAAK,kBAAkB,OAAO,UAAU,UAAU,aAAa;AAAA,MAC7D,kBAAkB;AAAA,MAClB,gBAAgB;AAAA,MAChB,gBAAgB,KAAK;AAAA,IACvB,CAAC;AAED,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,OAAO,OAAqC;AAChD,UAAM,KAAK,QAAQ,OAAO,KAAK,UAAU,UAAU;AAAA,MACjD,gBAAgB,KAAK;AAAA,MACrB;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,aAAa;AACX,SAAK,eAAe,OAAO;AAC3B,SAAK,gBAAgB;AAAA,EACvB;AACF;AAEO,MAAM,UAAU,OAAO,QAAQ;AAC/B,MAAM,yBAAyB,OAAO,uBAAuB;AAU7D,MAAe,wBAA2B,yBAA4B;AAAA;AAAA;AAAA;AAAA;AAAA,EAKlE,KAAK,OAAO;AAAA;AAAA;AAAA;AAAA,EAKZ;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,SAAS,MAAM,KAAK,SAAS,KAAK;AAAA;AAAA;AAAA;AAAA,EAK5C,QAAQ,SAAS,MAAM,KAAK,OAAO,KAAK;AAAA,EAExC,WAAW,OAAO,KAAK;AAAA,EACvB,SAAS,OAA0B,MAAS;AAAA;AAAA;AAAA,EAIrD,UAAU;AAAA,EAEV,YAAY,OAAsB,QAAgC;AAChE;AAAA,MACE;AAAA,MACA,MAAM,KAAK,SAAS;AAAA,MACpB,MAAM,KAAK,YAAY;AAAA,IACzB;AACA,SAAK,SAAS,IAAI,iBAAiB,KAAK,IAAI,MAAM;AAElD,SAAK,UAAU,CAAC,MAAM;AACpB,UAAI,CAAC,KAAK,SAAS;AACjB,aAAK,SAAS,QAAQ;AACtB,aAAK,OAAO,QAAQ;AACpB,aAAK,OAAO,EAAE,oBAAoB,CAAC,CAAC;AAAA,MACtC;AAAA,IACF,CAAC;AAED,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,CAAW,OAAO,EAAE,OAA4B;AAC9C,SAAK,OACF,OAAO,KAAK,EACZ,MAAM,CAAC,UAAmB;AACzB,WAAK,OAAO,QAAQ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAAA,IAC9E,CAAC,EACA,QAAQ,MAAM;AACb,WAAK,SAAS,QAAQ;AAAA,IACxB,CAAC;AAAA,EACL;AAAA,EAUA,WAAW;AACT,SAAK,OAAO,QAAQ,EAAE,OAAO,CAAC,UAAyB;AACrD,WAAK,UAAU;AACf,WAAK,sBAAsB,EAAE,KAAK;AAClC,WAAK,UAAU;AAAA,IACjB,CAAC;AAAA,EACH;AAAA,EAEA,cAAc;AACZ,QAAI,KAAK,OAAO,iBAAiB,QAAW;AAC1C;AAAA,IACF;AACA,SAAK,OAAO,WAAW;AAAA,EACzB;AACF;",
6
+ "names": []
7
7
  }
package/Signals.d.ts CHANGED
@@ -1,9 +1,4 @@
1
- import { FullStackSignal } from './FullStackSignal.js';
2
- /**
3
- * A full-stack signal that holds an arbitrary value.
4
- */
5
- export declare class ValueSignal<T> extends FullStackSignal<T> {
6
- }
1
+ import { type OperationSubscription, ValueSignal } from './ValueSignal.js';
7
2
  /**
8
3
  * A signal that holds a number value. The underlying
9
4
  * value of this signal is stored and updated as a
@@ -28,5 +23,62 @@ export declare class ValueSignal<T> extends FullStackSignal<T> {
28
23
  * ```
29
24
  */
30
25
  export declare class NumberSignal extends ValueSignal<number> {
26
+ /**
27
+ * Increments the value by the specified delta. The delta can be negative to
28
+ * decrease the value. If no delta is provided, the value increases by 1.
29
+ *
30
+ * This method differs from using the `++` or `+=` operators directly on the
31
+ * signal instance. It performs an atomic operation to prevent conflicts from
32
+ * concurrent changes, ensuring that other users' modifications are not
33
+ * accidentally overwritten.
34
+ *
35
+ * By default, if there are concurrent changes on the server, this operation
36
+ * will keep retrying until it succeeds. The returned `OperationSubscription`
37
+ * can be used to cancel the operation, if needed.
38
+ * To disable automatic retries, set `retryOnFailure` to `false`. Note that
39
+ * when `retryOnFailure` is `false`, the returned `OperationSubscription` will
40
+ * not be cancellable.
41
+ *
42
+ * @remarks
43
+ * **IMPORTANT:** please note that if the operation has already succeeded on
44
+ * the server, calling `cancel` will not prevent or undo the operation.The
45
+ * `cancel` method only stops further retry attempts if a previous attempt was
46
+ * rejected by the server.
47
+ *
48
+ * @param delta - The delta to increment the value by. The delta can be
49
+ * negative. Defaults to 1.
50
+ * @param retryOnFailure - Whether to retry the operation in case of a
51
+ * concurrent value change on the server or not. Defaults to `true`.
52
+ *
53
+ * @returns An object that can be used to cancel the operation.
54
+ */
55
+ increment(delta?: number, retryOnFailure?: boolean): OperationSubscription;
56
+ /**
57
+ * Adds the provided delta to the value. The delta can be negative to perform
58
+ * a subtraction.
59
+ * This operation does not retry in case failure due to concurrent value
60
+ * changes from other clients have already applied to the shared value on the
61
+ * server. If you want the operation to be retried until it succeeds, use the
62
+ * overload with retry functionality.
63
+ *
64
+ * @param delta - The delta to add to the value. The delta can be negative to
65
+ * perform a subtraction.
66
+ * @see {@link NumberSignal.add|add(delta: number, retryOnFailure: boolean)}
67
+ */
68
+ add(delta: number): void;
69
+ /**
70
+ * Adds the provided delta to the value. The delta can be negative to perform
71
+ * a subtraction.
72
+ * If `true` passed to the `retryOnFailure` parameter, the operation is
73
+ * retried in case of failures due to concurrent value changes on the server
74
+ * until it succeeds. Note that the returned operation subscription can be
75
+ * used to cancel the operation.
76
+ *
77
+ * @param delta - The delta to add to the value. The delta can be negative.
78
+ * @param retryOnFailure - Whether to retry the operation in case of failures
79
+ * due to concurrent value changes on the server or not.
80
+ * @returns An operation subscription that can be canceled.
81
+ */
82
+ add(delta: number, retryOnFailure: boolean): OperationSubscription;
31
83
  }
32
84
  //# sourceMappingURL=Signals.d.ts.map
package/Signals.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Signals.d.ts","sourceRoot":"","sources":["src/Signals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD;;GAEG;AACH,qBAAa,WAAW,CAAC,CAAC,CAAE,SAAQ,eAAe,CAAC,CAAC,CAAC;CAAG;AAEzD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,YAAa,SAAQ,WAAW,CAAC,MAAM,CAAC;CAAG"}
1
+ {"version":3,"file":"Signals.d.ts","sourceRoot":"","sources":["src/Signals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,qBAAqB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE3E;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,YAAa,SAAQ,WAAW,CAAC,MAAM,CAAC;IACnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,SAAS,CAAC,KAAK,GAAE,MAAU,EAAE,cAAc,GAAE,OAAc,GAAG,qBAAqB;IAanF;;;;;;;;;;;OAWG;IACH,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAExB;;;;;;;;;;;;OAYG;IACH,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,GAAG,qBAAqB;CAanE"}
package/Signals.js CHANGED
@@ -1,10 +1,59 @@
1
- import { FullStackSignal } from "./FullStackSignal.js";
2
- class ValueSignal extends FullStackSignal {
3
- }
1
+ import { ValueSignal } from "./ValueSignal.js";
4
2
  class NumberSignal extends ValueSignal {
3
+ /**
4
+ * Increments the value by the specified delta. The delta can be negative to
5
+ * decrease the value. If no delta is provided, the value increases by 1.
6
+ *
7
+ * This method differs from using the `++` or `+=` operators directly on the
8
+ * signal instance. It performs an atomic operation to prevent conflicts from
9
+ * concurrent changes, ensuring that other users' modifications are not
10
+ * accidentally overwritten.
11
+ *
12
+ * By default, if there are concurrent changes on the server, this operation
13
+ * will keep retrying until it succeeds. The returned `OperationSubscription`
14
+ * can be used to cancel the operation, if needed.
15
+ * To disable automatic retries, set `retryOnFailure` to `false`. Note that
16
+ * when `retryOnFailure` is `false`, the returned `OperationSubscription` will
17
+ * not be cancellable.
18
+ *
19
+ * @remarks
20
+ * **IMPORTANT:** please note that if the operation has already succeeded on
21
+ * the server, calling `cancel` will not prevent or undo the operation.The
22
+ * `cancel` method only stops further retry attempts if a previous attempt was
23
+ * rejected by the server.
24
+ *
25
+ * @param delta - The delta to increment the value by. The delta can be
26
+ * negative. Defaults to 1.
27
+ * @param retryOnFailure - Whether to retry the operation in case of a
28
+ * concurrent value change on the server or not. Defaults to `true`.
29
+ *
30
+ * @returns An object that can be used to cancel the operation.
31
+ */
32
+ increment(delta = 1, retryOnFailure = true) {
33
+ const noopCancelSubscription = { cancel: () => {
34
+ } };
35
+ if (delta === 0) {
36
+ return noopCancelSubscription;
37
+ }
38
+ if (!retryOnFailure) {
39
+ const expected = this.value;
40
+ this.replace(expected, expected + delta);
41
+ return noopCancelSubscription;
42
+ }
43
+ return this.update((value) => value + delta);
44
+ }
45
+ add(delta, retryOnFailure) {
46
+ if (delta !== 0) {
47
+ if (retryOnFailure) {
48
+ return this.increment(delta, retryOnFailure);
49
+ }
50
+ const expected = this.value;
51
+ this.replace(expected, expected + delta);
52
+ }
53
+ return;
54
+ }
5
55
  }
6
56
  export {
7
- NumberSignal,
8
- ValueSignal
57
+ NumberSignal
9
58
  };
10
59
  //# sourceMappingURL=Signals.js.map
package/Signals.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["src/Signals.ts"],
4
- "sourcesContent": ["import { FullStackSignal } from './FullStackSignal.js';\n\n/**\n * A full-stack signal that holds an arbitrary value.\n */\nexport class ValueSignal<T> extends FullStackSignal<T> {}\n\n/**\n * A signal that holds a number value. The underlying\n * value of this signal is stored and updated as a\n * shared value on the server.\n *\n * After obtaining the NumberSignal instance from\n * a server-side service that returns one, the value\n * can be updated using the `value` property,\n * and it can be read with or without the\n * `value` property (similar to a normal signal):\n *\n * @example\n * ```tsx\n * const counter = CounterService.counter();\n *\n * return (\n * <Button onClick={() => counter++)}>\n * Click count: { counter }\n * </Button>\n * <Button onClick={() => counter.value = 0}>Reset</Button>\n * );\n * ```\n */\nexport class NumberSignal extends ValueSignal<number> {}\n"],
5
- "mappings": "AAAA,SAAS,uBAAuB;AAKzB,MAAM,oBAAuB,gBAAmB;AAAC;AAyBjD,MAAM,qBAAqB,YAAoB;AAAC;",
4
+ "sourcesContent": ["import { type OperationSubscription, ValueSignal } from './ValueSignal.js';\n\n/**\n * A signal that holds a number value. The underlying\n * value of this signal is stored and updated as a\n * shared value on the server.\n *\n * After obtaining the NumberSignal instance from\n * a server-side service that returns one, the value\n * can be updated using the `value` property,\n * and it can be read with or without the\n * `value` property (similar to a normal signal):\n *\n * @example\n * ```tsx\n * const counter = CounterService.counter();\n *\n * return (\n * <Button onClick={() => counter++)}>\n * Click count: { counter }\n * </Button>\n * <Button onClick={() => counter.value = 0}>Reset</Button>\n * );\n * ```\n */\nexport class NumberSignal extends ValueSignal<number> {\n /**\n * Increments the value by the specified delta. The delta can be negative to\n * decrease the value. If no delta is provided, the value increases by 1.\n *\n * This method differs from using the `++` or `+=` operators directly on the\n * signal instance. It performs an atomic operation to prevent conflicts from\n * concurrent changes, ensuring that other users' modifications are not\n * accidentally overwritten.\n *\n * By default, if there are concurrent changes on the server, this operation\n * will keep retrying until it succeeds. The returned `OperationSubscription`\n * can be used to cancel the operation, if needed.\n * To disable automatic retries, set `retryOnFailure` to `false`. Note that\n * when `retryOnFailure` is `false`, the returned `OperationSubscription` will\n * not be cancellable.\n *\n * @remarks\n * **IMPORTANT:** please note that if the operation has already succeeded on\n * the server, calling `cancel` will not prevent or undo the operation.The\n * `cancel` method only stops further retry attempts if a previous attempt was\n * rejected by the server.\n *\n * @param delta - The delta to increment the value by. The delta can be\n * negative. Defaults to 1.\n * @param retryOnFailure - Whether to retry the operation in case of a\n * concurrent value change on the server or not. Defaults to `true`.\n *\n * @returns An object that can be used to cancel the operation.\n */\n increment(delta: number = 1, retryOnFailure: boolean = true): OperationSubscription {\n const noopCancelSubscription = { cancel: () => {} };\n if (delta === 0) {\n return noopCancelSubscription;\n }\n if (!retryOnFailure) {\n const expected = this.value;\n this.replace(expected, expected + delta);\n return noopCancelSubscription;\n }\n return this.update((value) => value + delta);\n }\n\n /**\n * Adds the provided delta to the value. The delta can be negative to perform\n * a subtraction.\n * This operation does not retry in case failure due to concurrent value\n * changes from other clients have already applied to the shared value on the\n * server. If you want the operation to be retried until it succeeds, use the\n * overload with retry functionality.\n *\n * @param delta - The delta to add to the value. The delta can be negative to\n * perform a subtraction.\n * @see {@link NumberSignal.add|add(delta: number, retryOnFailure: boolean)}\n */\n add(delta: number): void;\n\n /**\n * Adds the provided delta to the value. The delta can be negative to perform\n * a subtraction.\n * If `true` passed to the `retryOnFailure` parameter, the operation is\n * retried in case of failures due to concurrent value changes on the server\n * until it succeeds. Note that the returned operation subscription can be\n * used to cancel the operation.\n *\n * @param delta - The delta to add to the value. The delta can be negative.\n * @param retryOnFailure - Whether to retry the operation in case of failures\n * due to concurrent value changes on the server or not.\n * @returns An operation subscription that can be canceled.\n */\n add(delta: number, retryOnFailure: boolean): OperationSubscription;\n\n add(delta: number, retryOnFailure?: boolean): OperationSubscription | void {\n if (delta !== 0) {\n if (retryOnFailure) {\n return this.increment(delta, retryOnFailure);\n }\n const expected = this.value;\n this.replace(expected, expected + delta);\n }\n // eslint-disable-next-line no-useless-return, consistent-return\n return;\n }\n}\n"],
5
+ "mappings": "AAAA,SAAqC,mBAAmB;AAyBjD,MAAM,qBAAqB,YAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA8BpD,UAAU,QAAgB,GAAG,iBAA0B,MAA6B;AAClF,UAAM,yBAAyB,EAAE,QAAQ,MAAM;AAAA,IAAC,EAAE;AAClD,QAAI,UAAU,GAAG;AACf,aAAO;AAAA,IACT;AACA,QAAI,CAAC,gBAAgB;AACnB,YAAM,WAAW,KAAK;AACtB,WAAK,QAAQ,UAAU,WAAW,KAAK;AACvC,aAAO;AAAA,IACT;AACA,WAAO,KAAK,OAAO,CAAC,UAAU,QAAQ,KAAK;AAAA,EAC7C;AAAA,EA+BA,IAAI,OAAe,gBAAwD;AACzE,QAAI,UAAU,GAAG;AACf,UAAI,gBAAgB;AAClB,eAAO,KAAK,UAAU,OAAO,cAAc;AAAA,MAC7C;AACA,YAAM,WAAW,KAAK;AACtB,WAAK,QAAQ,UAAU,WAAW,KAAK;AAAA,IACzC;AAEA;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,49 @@
1
+ import { type StateEvent } from './events.js';
2
+ import { $processServerResponse, FullStackSignal } from './FullStackSignal.js';
3
+ /**
4
+ * An operation subscription that can be canceled.
5
+ */
6
+ export interface OperationSubscription {
7
+ cancel(): void;
8
+ }
9
+ /**
10
+ * A full-stack signal that holds an arbitrary value.
11
+ */
12
+ export declare class ValueSignal<T> extends FullStackSignal<T> {
13
+ #private;
14
+ /**
15
+ * Sets the value.
16
+ * Note that the value change event that is propagated to the server as the
17
+ * result of this operation is not taking the last seen value into account and
18
+ * will overwrite the shared value on the server unconditionally (AKA: "Last
19
+ * Write Wins"). If you need to perform a conditional update, use the
20
+ * `replace` method instead.
21
+ *
22
+ * @param value - The new value.
23
+ */
24
+ set(value: T): void;
25
+ /**
26
+ * Replaces the value with a new one only if the current value is equal to the
27
+ * expected value.
28
+ *
29
+ * @param expected - The expected value.
30
+ * @param newValue - The new value.
31
+ */
32
+ replace(expected: T, newValue: T): void;
33
+ /**
34
+ * Tries to update the value by applying the callback function to the current
35
+ * value. In case of a concurrent change, the callback is run again with an
36
+ * updated input value. This is repeated until the result can be applied
37
+ * without concurrent changes, or the operation is canceled.
38
+ *
39
+ * Note that there is no guarantee that cancel() will be effective always,
40
+ * since a succeeding operation might already be on its way to the server.
41
+ *
42
+ * @param callback - The function that is applied on the current value to
43
+ * produce the new value.
44
+ * @returns An operation subscription that can be canceled.
45
+ */
46
+ update(callback: (value: T) => T): OperationSubscription;
47
+ protected [$processServerResponse](event: StateEvent<T>): void;
48
+ }
49
+ //# sourceMappingURL=ValueSignal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ValueSignal.d.ts","sourceRoot":"","sources":["src/ValueSignal.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,sBAAsB,EAAW,eAAe,EAAE,MAAM,sBAAsB,CAAC;AASxF;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,MAAM,IAAI,IAAI,CAAC;CAChB;AAED;;GAEG;AACH,qBAAa,WAAW,CAAC,CAAC,CAAE,SAAQ,eAAe,CAAC,CAAC,CAAC;;IAGpD;;;;;;;;;OASG;IACH,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI;IAInB;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI;IAIvC;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,qBAAqB;cAerC,CAAC,sBAAsB,CAAC,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI;CAoBxE"}
package/ValueSignal.js ADDED
@@ -0,0 +1,76 @@
1
+ import { createReplaceStateEvent } from "./events.js";
2
+ import { $processServerResponse, $update, FullStackSignal } from "./FullStackSignal.js";
3
+ class ValueSignal extends FullStackSignal {
4
+ #pendingRequests = /* @__PURE__ */ new Map();
5
+ /**
6
+ * Sets the value.
7
+ * Note that the value change event that is propagated to the server as the
8
+ * result of this operation is not taking the last seen value into account and
9
+ * will overwrite the shared value on the server unconditionally (AKA: "Last
10
+ * Write Wins"). If you need to perform a conditional update, use the
11
+ * `replace` method instead.
12
+ *
13
+ * @param value - The new value.
14
+ */
15
+ set(value) {
16
+ this.value = value;
17
+ }
18
+ /**
19
+ * Replaces the value with a new one only if the current value is equal to the
20
+ * expected value.
21
+ *
22
+ * @param expected - The expected value.
23
+ * @param newValue - The new value.
24
+ */
25
+ replace(expected, newValue) {
26
+ this[$update](createReplaceStateEvent(expected, newValue));
27
+ }
28
+ /**
29
+ * Tries to update the value by applying the callback function to the current
30
+ * value. In case of a concurrent change, the callback is run again with an
31
+ * updated input value. This is repeated until the result can be applied
32
+ * without concurrent changes, or the operation is canceled.
33
+ *
34
+ * Note that there is no guarantee that cancel() will be effective always,
35
+ * since a succeeding operation might already be on its way to the server.
36
+ *
37
+ * @param callback - The function that is applied on the current value to
38
+ * produce the new value.
39
+ * @returns An operation subscription that can be canceled.
40
+ */
41
+ update(callback) {
42
+ const newValue = callback(this.value);
43
+ const event = createReplaceStateEvent(this.value, newValue);
44
+ this[$update](event);
45
+ const waiter = Promise.withResolvers();
46
+ const pendingRequest = { callback, waiter, canceled: false };
47
+ this.#pendingRequests.set(event.id, pendingRequest);
48
+ return {
49
+ cancel: () => {
50
+ pendingRequest.canceled = true;
51
+ pendingRequest.waiter.resolve();
52
+ }
53
+ };
54
+ }
55
+ [$processServerResponse](event) {
56
+ const record = this.#pendingRequests.get(event.id);
57
+ if (record) {
58
+ this.#pendingRequests.delete(event.id);
59
+ }
60
+ if (event.type === "snapshot") {
61
+ if (record) {
62
+ record.waiter.resolve();
63
+ }
64
+ this.value = event.value;
65
+ }
66
+ if (event.type === "reject" && record) {
67
+ if (!record.canceled) {
68
+ this.update(record.callback);
69
+ }
70
+ }
71
+ }
72
+ }
73
+ export {
74
+ ValueSignal
75
+ };
76
+ //# sourceMappingURL=ValueSignal.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["src/ValueSignal.ts"],
4
+ "sourcesContent": ["import { createReplaceStateEvent, type StateEvent } from './events.js';\nimport { $processServerResponse, $update, FullStackSignal } from './FullStackSignal.js';\n\ntype PromiseWithResolvers = ReturnType<typeof Promise.withResolvers<void>>;\ntype PendingRequestsRecord<T> = Readonly<{\n waiter: PromiseWithResolvers;\n callback(value: T): T;\n canceled: boolean;\n}>;\n\n/**\n * An operation subscription that can be canceled.\n */\nexport interface OperationSubscription {\n cancel(): void;\n}\n\n/**\n * A full-stack signal that holds an arbitrary value.\n */\nexport class ValueSignal<T> extends FullStackSignal<T> {\n readonly #pendingRequests = new Map<string, PendingRequestsRecord<T>>();\n\n /**\n * Sets the value.\n * Note that the value change event that is propagated to the server as the\n * result of this operation is not taking the last seen value into account and\n * will overwrite the shared value on the server unconditionally (AKA: \"Last\n * Write Wins\"). If you need to perform a conditional update, use the\n * `replace` method instead.\n *\n * @param value - The new value.\n */\n set(value: T): void {\n this.value = value;\n }\n\n /**\n * Replaces the value with a new one only if the current value is equal to the\n * expected value.\n *\n * @param expected - The expected value.\n * @param newValue - The new value.\n */\n replace(expected: T, newValue: T): void {\n this[$update](createReplaceStateEvent(expected, newValue));\n }\n\n /**\n * Tries to update the value by applying the callback function to the current\n * value. In case of a concurrent change, the callback is run again with an\n * updated input value. This is repeated until the result can be applied\n * without concurrent changes, or the operation is canceled.\n *\n * Note that there is no guarantee that cancel() will be effective always,\n * since a succeeding operation might already be on its way to the server.\n *\n * @param callback - The function that is applied on the current value to\n * produce the new value.\n * @returns An operation subscription that can be canceled.\n */\n update(callback: (value: T) => T): OperationSubscription {\n const newValue = callback(this.value);\n const event = createReplaceStateEvent(this.value, newValue);\n this[$update](event);\n const waiter = Promise.withResolvers<void>();\n const pendingRequest = { callback, waiter, canceled: false };\n this.#pendingRequests.set(event.id, pendingRequest);\n return {\n cancel: () => {\n pendingRequest.canceled = true;\n pendingRequest.waiter.resolve();\n },\n };\n }\n\n protected override [$processServerResponse](event: StateEvent<T>): void {\n const record = this.#pendingRequests.get(event.id);\n if (record) {\n this.#pendingRequests.delete(event.id);\n }\n\n if (event.type === 'snapshot') {\n if (record) {\n record.waiter.resolve();\n }\n this.value = event.value;\n }\n\n if (event.type === 'reject' && record) {\n if (!record.canceled) {\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.update(record.callback);\n }\n }\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,+BAAgD;AACzD,SAAS,wBAAwB,SAAS,uBAAuB;AAmB1D,MAAM,oBAAuB,gBAAmB;AAAA,EAC5C,mBAAmB,oBAAI,IAAsC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYtE,IAAI,OAAgB;AAClB,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,QAAQ,UAAa,UAAmB;AACtC,SAAK,OAAO,EAAE,wBAAwB,UAAU,QAAQ,CAAC;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,OAAO,UAAkD;AACvD,UAAM,WAAW,SAAS,KAAK,KAAK;AACpC,UAAM,QAAQ,wBAAwB,KAAK,OAAO,QAAQ;AAC1D,SAAK,OAAO,EAAE,KAAK;AACnB,UAAM,SAAS,QAAQ,cAAoB;AAC3C,UAAM,iBAAiB,EAAE,UAAU,QAAQ,UAAU,MAAM;AAC3D,SAAK,iBAAiB,IAAI,MAAM,IAAI,cAAc;AAClD,WAAO;AAAA,MACL,QAAQ,MAAM;AACZ,uBAAe,WAAW;AAC1B,uBAAe,OAAO,QAAQ;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AAAA,EAEA,CAAoB,sBAAsB,EAAE,OAA4B;AACtE,UAAM,SAAS,KAAK,iBAAiB,IAAI,MAAM,EAAE;AACjD,QAAI,QAAQ;AACV,WAAK,iBAAiB,OAAO,MAAM,EAAE;AAAA,IACvC;AAEA,QAAI,MAAM,SAAS,YAAY;AAC7B,UAAI,QAAQ;AACV,eAAO,OAAO,QAAQ;AAAA,MACxB;AACA,WAAK,QAAQ,MAAM;AAAA,IACrB;AAEA,QAAI,MAAM,SAAS,YAAY,QAAQ;AACrC,UAAI,CAAC,OAAO,UAAU;AAEpB,aAAK,OAAO,OAAO,QAAQ;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
package/events.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ import type { Simplify } from 'type-fest';
2
+ /**
3
+ * Creates a new state event type.
4
+ */
5
+ type CreateStateEventType<V, T extends string, C extends Record<string, unknown> = Record<never, never>> = Simplify<Readonly<{
6
+ id: string;
7
+ type: T;
8
+ value: V;
9
+ }> & Readonly<C>>;
10
+ /**
11
+ * A state event received from the server describing the current state of the
12
+ * signal.
13
+ */
14
+ export type SnapshotStateEvent<T> = CreateStateEventType<T, 'snapshot'>;
15
+ export type RejectStateEvent = CreateStateEventType<never, 'reject'>;
16
+ /**
17
+ * A state event defines a new value of the signal shared with the server. The
18
+ *
19
+ */
20
+ export type SetStateEvent<T> = CreateStateEventType<T, 'set'>;
21
+ export declare function createSetStateEvent<T>(value: T): SetStateEvent<T>;
22
+ export type ReplaceStateEvent<T> = CreateStateEventType<T, 'replace', {
23
+ expected: T;
24
+ }>;
25
+ export declare function createReplaceStateEvent<T>(expected: T, value: T): ReplaceStateEvent<T>;
26
+ /**
27
+ * An object that describes the change of the signal state.
28
+ */
29
+ export type StateEvent<T> = RejectStateEvent | ReplaceStateEvent<T> | SetStateEvent<T> | SnapshotStateEvent<T>;
30
+ export {};
31
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["src/events.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE1C;;GAEG;AACH,KAAK,oBAAoB,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,QAAQ,CACjH,QAAQ,CAAC;IACP,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,CAAC,CAAC;IACR,KAAK,EAAE,CAAC,CAAC;CACV,CAAC,GACA,QAAQ,CAAC,CAAC,CAAC,CACd,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,kBAAkB,CAAC,CAAC,IAAI,oBAAoB,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;AAExE,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;AAErE;;;GAGG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI,oBAAoB,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AAE9D,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAMjE;AAED,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAAI,oBAAoB,CAAC,CAAC,EAAE,SAAS,EAAE;IAAE,QAAQ,EAAE,CAAC,CAAA;CAAE,CAAC,CAAC;AAEvF,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAOtF;AAED;;GAEG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,gBAAgB,GAAG,iBAAiB,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAC"}
package/events.js ADDED
@@ -0,0 +1,21 @@
1
+ import { nanoid } from "nanoid";
2
+ function createSetStateEvent(value) {
3
+ return {
4
+ id: nanoid(),
5
+ type: "set",
6
+ value
7
+ };
8
+ }
9
+ function createReplaceStateEvent(expected, value) {
10
+ return {
11
+ id: nanoid(),
12
+ type: "replace",
13
+ value,
14
+ expected
15
+ };
16
+ }
17
+ export {
18
+ createReplaceStateEvent,
19
+ createSetStateEvent
20
+ };
21
+ //# sourceMappingURL=events.js.map
package/events.js.map ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["src/events.ts"],
4
+ "sourcesContent": ["import { nanoid } from 'nanoid';\nimport type { Simplify } from 'type-fest';\n\n/**\n * Creates a new state event type.\n */\ntype CreateStateEventType<V, T extends string, C extends Record<string, unknown> = Record<never, never>> = Simplify<\n Readonly<{\n id: string;\n type: T;\n value: V;\n }> &\n Readonly<C>\n>;\n\n/**\n * A state event received from the server describing the current state of the\n * signal.\n */\nexport type SnapshotStateEvent<T> = CreateStateEventType<T, 'snapshot'>;\n\nexport type RejectStateEvent = CreateStateEventType<never, 'reject'>;\n\n/**\n * A state event defines a new value of the signal shared with the server. The\n *\n */\nexport type SetStateEvent<T> = CreateStateEventType<T, 'set'>;\n\nexport function createSetStateEvent<T>(value: T): SetStateEvent<T> {\n return {\n id: nanoid(),\n type: 'set',\n value,\n };\n}\n\nexport type ReplaceStateEvent<T> = CreateStateEventType<T, 'replace', { expected: T }>;\n\nexport function createReplaceStateEvent<T>(expected: T, value: T): ReplaceStateEvent<T> {\n return {\n id: nanoid(),\n type: 'replace',\n value,\n expected,\n };\n}\n\n/**\n * An object that describes the change of the signal state.\n */\nexport type StateEvent<T> = RejectStateEvent | ReplaceStateEvent<T> | SetStateEvent<T> | SnapshotStateEvent<T>;\n"],
5
+ "mappings": "AAAA,SAAS,cAAc;AA6BhB,SAAS,oBAAuB,OAA4B;AACjE,SAAO;AAAA,IACL,IAAI,OAAO;AAAA,IACX,MAAM;AAAA,IACN;AAAA,EACF;AACF;AAIO,SAAS,wBAA2B,UAAa,OAAgC;AACtF,SAAO;AAAA,IACL,IAAI,OAAO;AAAA,IACX,MAAM;AAAA,IACN;AAAA,IACA;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
package/index.d.ts CHANGED
@@ -1,4 +1,7 @@
1
+ import './polyfills.js';
1
2
  export * from './core.js';
2
3
  export { NumberSignal } from './Signals.js';
4
+ export { ValueSignal } from './ValueSignal.js';
5
+ export type { OperationSubscription } from './ValueSignal.js';
3
6
  export { FullStackSignal } from './FullStackSignal.js';
4
7
  //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":"AACA,cAAc,WAAW,CAAC;AAC1B,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":"AACA,OAAO,gBAAgB,CAAC;AAGxB,cAAc,WAAW,CAAC;AAC1B,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
package/index.js CHANGED
@@ -1,8 +1,11 @@
1
+ import "./polyfills.js";
1
2
  export * from "./core.js";
2
3
  import { NumberSignal } from "./Signals.js";
4
+ import { ValueSignal } from "./ValueSignal.js";
3
5
  import { FullStackSignal } from "./FullStackSignal.js";
4
6
  export {
5
7
  FullStackSignal,
6
- NumberSignal
8
+ NumberSignal,
9
+ ValueSignal
7
10
  };
8
11
  //# sourceMappingURL=index.js.map
package/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["src/index.ts"],
4
- "sourcesContent": ["// eslint-disable-next-line import/export\nexport * from './core.js';\nexport { NumberSignal } from './Signals.js';\nexport { FullStackSignal } from './FullStackSignal.js';\n"],
5
- "mappings": "AACA,cAAc;AACd,SAAS,oBAAoB;AAC7B,SAAS,uBAAuB;",
4
+ "sourcesContent": ["// eslint-disable-next-line import/no-unassigned-import\nimport './polyfills.js';\n\n// eslint-disable-next-line import/export\nexport * from './core.js';\nexport { NumberSignal } from './Signals.js';\nexport { ValueSignal } from './ValueSignal.js';\nexport type { OperationSubscription } from './ValueSignal.js';\nexport { FullStackSignal } from './FullStackSignal.js';\n"],
5
+ "mappings": "AACA,OAAO;AAGP,cAAc;AACd,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB;AAE5B,SAAS,uBAAuB;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/hilla-react-signals",
3
- "version": "24.5.0-alpha11",
3
+ "version": "24.5.0-alpha13",
4
4
  "description": "Signals for Hilla React",
5
5
  "main": "index.js",
6
6
  "module": "index.js",
@@ -47,7 +47,7 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "@preact/signals-react": "^2.0.0",
50
- "@vaadin/hilla-frontend": "24.5.0-alpha11",
50
+ "@vaadin/hilla-frontend": "24.5.0-alpha13",
51
51
  "nanoid": "^5.0.7"
52
52
  },
53
53
  "peerDependencies": {
package/polyfills.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ declare global {
2
+ interface PromiseConstructor {
3
+ withResolvers<T>(): {
4
+ resolve(value: T): void;
5
+ reject(reason?: unknown): void;
6
+ promise: Promise<T>;
7
+ };
8
+ }
9
+ }
10
+ export {};
11
+ //# sourceMappingURL=polyfills.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"polyfills.d.ts","sourceRoot":"","sources":["src/polyfills.ts"],"names":[],"mappings":"AACA,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,kBAAkB;QAC1B,aAAa,CAAC,CAAC,KAAK;YAClB,OAAO,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;YACxB,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;YAC/B,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;SACrB,CAAC;KACH;CACF;AAkBD,OAAO,EAAE,CAAC"}
package/polyfills.js ADDED
@@ -0,0 +1,15 @@
1
+ if (!("withResolvers" in Promise)) {
2
+ Object.defineProperty(Promise, "withResolvers", {
3
+ configurable: true,
4
+ value() {
5
+ let resolve;
6
+ let reject;
7
+ const promise = new Promise((_resolve, _reject) => {
8
+ resolve = _resolve;
9
+ reject = _reject;
10
+ });
11
+ return { resolve, reject, promise };
12
+ }
13
+ });
14
+ }
15
+ //# sourceMappingURL=polyfills.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["src/polyfills.ts"],
4
+ "sourcesContent": ["// TODO: Remove this polyfill when we move to ECMA2024\ndeclare global {\n interface PromiseConstructor {\n withResolvers<T>(): {\n resolve(value: T): void;\n reject(reason?: unknown): void;\n promise: Promise<T>;\n };\n }\n}\n\nif (!('withResolvers' in Promise)) {\n // eslint-disable-next-line no-extend-native\n Object.defineProperty(Promise, 'withResolvers', {\n configurable: true,\n value<T>() {\n let resolve: (value: T) => void;\n let reject: (reason?: unknown) => void;\n const promise = new Promise<T>((_resolve, _reject) => {\n resolve = _resolve;\n reject = _reject;\n });\n return { resolve: resolve!, reject: reject!, promise };\n },\n });\n}\n\nexport {};\n"],
5
+ "mappings": "AAWA,IAAI,EAAE,mBAAmB,UAAU;AAEjC,SAAO,eAAe,SAAS,iBAAiB;AAAA,IAC9C,cAAc;AAAA,IACd,QAAW;AACT,UAAI;AACJ,UAAI;AACJ,YAAM,UAAU,IAAI,QAAW,CAAC,UAAU,YAAY;AACpD,kBAAU;AACV,iBAAS;AAAA,MACX,CAAC;AACD,aAAO,EAAE,SAAmB,QAAiB,QAAQ;AAAA,IACvD;AAAA,EACF,CAAC;AACH;",
6
+ "names": []
7
+ }
package/types.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { Signal as _Signal } from '@preact/signals-react';
2
+
3
+ declare module './core.js' {
4
+ export declare class Signal<T> extends _Signal<T> {
5
+ protected S(node: unknown): void;
6
+ protected U(node: unknown): void;
7
+ }
8
+ }
9
+
10
+ export {};
package/utils.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { ReadonlySignal } from '@preact/signals-react';
2
+ export declare function createPromiseFromSignal<T, U, E>(signal: ReadonlySignal<T>, callback: (value: T, resolve: (value: PromiseLike<U> | U) => void, reject: (reason?: E) => void) => void): Promise<U>;
3
+ //# sourceMappingURL=utils.d.ts.map
package/utils.d.ts.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,wBAAsB,uBAAuB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EACnD,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,EACzB,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,IAAI,KAAK,IAAI,GACvG,OAAO,CAAC,CAAC,CAAC,CAWZ"}
package/utils.js ADDED
@@ -0,0 +1,15 @@
1
+ async function createPromiseFromSignal(signal, callback) {
2
+ return new Promise((resolve, reject) => {
3
+ const unsubscribe = signal.subscribe((value) => {
4
+ if (!value) {
5
+ return;
6
+ }
7
+ unsubscribe();
8
+ callback(value, resolve, reject);
9
+ });
10
+ });
11
+ }
12
+ export {
13
+ createPromiseFromSignal
14
+ };
15
+ //# sourceMappingURL=utils.js.map
package/utils.js.map ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["src/utils.ts"],
4
+ "sourcesContent": ["import type { ReadonlySignal } from '@preact/signals-react';\n\nexport async function createPromiseFromSignal<T, U, E>(\n signal: ReadonlySignal<T>,\n callback: (value: T, resolve: (value: PromiseLike<U> | U) => void, reject: (reason?: E) => void) => void,\n): Promise<U> {\n return new Promise((resolve, reject) => {\n const unsubscribe = signal.subscribe((value) => {\n if (!value) {\n return;\n }\n\n unsubscribe();\n callback(value, resolve, reject);\n });\n });\n}\n"],
5
+ "mappings": "AAEA,eAAsB,wBACpB,QACA,UACY;AACZ,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,cAAc,OAAO,UAAU,CAAC,UAAU;AAC9C,UAAI,CAAC,OAAO;AACV;AAAA,MACF;AAEA,kBAAY;AACZ,eAAS,OAAO,SAAS,MAAM;AAAA,IACjC,CAAC;AAAA,EACH,CAAC;AACH;",
6
+ "names": []
7
+ }