@vaadin/hilla-react-signals 24.7.0-alpha9 → 24.7.0-beta3

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,14 +1,5 @@
1
1
  import { FullStackSignal } from './FullStackSignal.js';
2
- /**
3
- * A {@link FullStackSignal} that represents a collection of values.
4
- *
5
- * @typeParam T - The type of the values in the collection.
6
- */
7
2
  export declare abstract class CollectionSignal<T> extends FullStackSignal<T> {
8
3
  get value(): T;
9
- /**
10
- * @readonly
11
- */
12
4
  set value(_: never);
13
5
  }
14
- //# sourceMappingURL=CollectionSignal.d.ts.map
@@ -1,16 +1,10 @@
1
- import { FullStackSignal } from "./FullStackSignal.js";
2
- class CollectionSignal extends FullStackSignal {
3
- get value() {
4
- return super.value;
5
- }
6
- /**
7
- * @readonly
8
- */
9
- set value(_) {
10
- throw new Error("Value of the collection signals cannot be set.");
11
- }
1
+ import { FullStackSignal } from './FullStackSignal.js';
2
+ export class CollectionSignal extends FullStackSignal {
3
+ get value() {
4
+ return super.value;
5
+ }
6
+ set value(_) {
7
+ throw new Error('Value of the collection signals cannot be set.');
8
+ }
12
9
  }
13
- export {
14
- CollectionSignal
15
- };
16
- //# sourceMappingURL=CollectionSignal.js.map
10
+ //# sourceMappingURL=CollectionSignal.js.map
@@ -1,7 +1 @@
1
- {
2
- "version": 3,
3
- "sources": ["src/CollectionSignal.ts"],
4
- "sourcesContent": ["import { FullStackSignal } from './FullStackSignal.js';\n\n/**\n * A {@link FullStackSignal} that represents a collection of values.\n *\n * @typeParam T - The type of the values in the collection.\n */\nexport abstract class CollectionSignal<T> extends FullStackSignal<T> {\n override get value(): T {\n return super.value;\n }\n\n /**\n * @readonly\n */\n override set value(_: never) {\n throw new Error('Value of the collection signals cannot be set.');\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,uBAAuB;AAOzB,MAAe,yBAA4B,gBAAmB;AAAA,EACnE,IAAa,QAAW;AACtB,WAAO,MAAM;AAAA,EACf;AAAA;AAAA;AAAA;AAAA,EAKA,IAAa,MAAM,GAAU;AAC3B,UAAM,IAAI,MAAM,gDAAgD;AAAA,EAClE;AACF;",
6
- "names": []
7
- }
1
+ {"version":3,"file":"CollectionSignal.js","sourceRoot":"","sources":["src/CollectionSignal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAOvD,MAAM,OAAgB,gBAAoB,SAAQ,eAAkB;IAClE,IAAa,KAAK;QAChB,OAAO,KAAK,CAAC,KAAK,CAAC;IACrB,CAAC;IAKD,IAAa,KAAK,CAAC,CAAQ;QACzB,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACpE,CAAC;CACF","sourcesContent":["import { FullStackSignal } from './FullStackSignal.js';\n\n/**\n * A {@link FullStackSignal} that represents a collection of values.\n *\n * @typeParam T - The type of the values in the collection.\n */\nexport abstract class CollectionSignal<T> extends FullStackSignal<T> {\n override get value(): T {\n return super.value;\n }\n\n /**\n * @readonly\n */\n override set value(_: never) {\n throw new Error('Value of the collection signals cannot be set.');\n }\n}\n"]}
@@ -1,65 +1,22 @@
1
1
  import type { ConnectClient, Subscription } from '@vaadin/hilla-frontend';
2
2
  import { Signal } from './core.js';
3
3
  import { type StateEvent } from './events.js';
4
- /**
5
- * A return type for signal operations that exposes a `result` property of type
6
- * `Promise`, that resolves when the operation is completed. It allows defining
7
- * callbacks to be run after the operation is completed, or error handling when
8
- * the operation fails.
9
- *
10
- * @example
11
- * ```ts
12
- * const sharedName = NameService.sharedName({ defaultValue: '' });
13
- * sharedName.replace('John').result
14
- * .then(() => console.log('Name updated successfully'))
15
- * .catch((error) => console.error('Failed to update the name:', error));
16
- * ```
17
- */
18
4
  export interface Operation {
19
5
  result: Promise<void>;
20
6
  }
21
- /**
22
- * An abstraction of a signal that tracks the number of subscribers, and calls
23
- * the provided `onSubscribe` and `onUnsubscribe` callbacks for the first
24
- * subscription and the last unsubscription, respectively.
25
- * @internal
26
- */
27
7
  export declare abstract class DependencyTrackingSignal<T> extends Signal<T> {
28
8
  #private;
29
9
  protected constructor(value: T | undefined, onFirstSubscribe: () => void, onLastUnsubscribe: () => void);
30
10
  protected S(node: unknown): void;
31
11
  protected U(node: unknown): void;
32
12
  }
33
- /**
34
- * An object that describes a data object to connect to the signal provider
35
- * service.
36
- */
37
13
  export type ServerConnectionConfig = Readonly<{
38
- /**
39
- * The client instance to be used for communication.
40
- */
41
14
  client: ConnectClient;
42
- /**
43
- * The name of the signal provider service endpoint.
44
- */
45
15
  endpoint: string;
46
- /**
47
- * The name of the signal provider service method.
48
- */
49
16
  method: string;
50
- /**
51
- * Optional object with method call arguments to be sent to the endpoint
52
- * method that provides the signal when subscribing to it.
53
- */
54
17
  params?: Record<string, unknown>;
55
- /**
56
- * The unique identifier of the parent signal in the client.
57
- */
58
18
  parentClientSignalId?: string;
59
19
  }>;
60
- /**
61
- * A server connection manager.
62
- */
63
20
  declare class ServerConnection {
64
21
  #private;
65
22
  readonly config: ServerConnectionConfig;
@@ -86,65 +43,20 @@ export declare const $processServerResponse: unique symbol;
86
43
  export declare const $setValueQuietly: unique symbol;
87
44
  export declare const $resolveOperation: unique symbol;
88
45
  export declare const $createOperation: unique symbol;
89
- /**
90
- * A signal that holds a shared value. Each change to the value is propagated to
91
- * the server-side signal provider. At the same time, each change received from
92
- * the server-side signal provider is propagated to the local signal and it's
93
- * subscribers.
94
- *
95
- * @internal
96
- */
97
46
  export declare abstract class FullStackSignal<T> extends DependencyTrackingSignal<T> {
98
47
  #private;
99
- /**
100
- * The unique identifier of the signal necessary to communicate with the
101
- * server.
102
- */
103
48
  readonly id: string;
104
- /**
105
- * The server connection manager.
106
- */
107
49
  readonly server: ServerConnection;
108
- /**
109
- * Defines whether the signal is currently awaits a server-side response.
110
- */
111
50
  readonly pending: import("@preact/signals-core").ReadonlySignal<boolean>;
112
- /**
113
- * Defines whether the signal has an error.
114
- */
115
51
  readonly error: import("@preact/signals-core").ReadonlySignal<Error | undefined>;
116
52
  constructor(value: T | undefined, config: ServerConnectionConfig, id?: string);
117
53
  protected [$createOperation]({ id, promise }: {
118
54
  id?: string;
119
55
  promise?: Promise<void>;
120
56
  }): Operation;
121
- /**
122
- * Sets the local value of the signal without sending any events to the server
123
- * @param value - The new value.
124
- * @internal
125
- */
126
57
  protected [$setValueQuietly](value: T): void;
127
- /**
128
- * A method to update the server with the new value.
129
- *
130
- * @param event - The event to update the server with.
131
- * @returns The server response promise.
132
- */
133
58
  protected [$update](event: StateEvent): Promise<void>;
134
- /**
135
- * Resolves the operation promise associated with the given event id.
136
- *
137
- * @param eventId - The event id.
138
- * @param reason - The reason to reject the promise (if any).
139
- */
140
59
  protected [$resolveOperation](eventId: string, reason?: string): void;
141
- /**
142
- * A method with to process the server response. The implementation is
143
- * specific for each signal type.
144
- *
145
- * @param event - The server response event.
146
- */
147
60
  protected abstract [$processServerResponse](event: StateEvent): void;
148
61
  }
149
62
  export {};
150
- //# sourceMappingURL=FullStackSignal.d.ts.map
@@ -1,209 +1,165 @@
1
- import { nanoid } from "nanoid";
2
- import { computed, signal, Signal } from "./core.js";
3
- import { createSetStateEvent } from "./events.js";
4
- const ENDPOINT = "SignalsHandler";
5
- class DependencyTrackingSignal extends Signal {
6
- #onFirstSubscribe;
7
- #onLastUnsubscribe;
8
- // -1 means to ignore the first subscription that is created internally in the
9
- // FullStackSignal constructor.
10
- #subscribeCount = -1;
11
- constructor(value, onFirstSubscribe, onLastUnsubscribe) {
12
- super(value);
13
- this.#onFirstSubscribe = onFirstSubscribe;
14
- this.#onLastUnsubscribe = onLastUnsubscribe;
15
- }
16
- S(node) {
17
- super.S(node);
18
- if (this.#subscribeCount === 0) {
19
- this.#onFirstSubscribe();
1
+ import { nanoid } from 'nanoid';
2
+ import { computed, signal, Signal } from './core.js';
3
+ import { createSetStateEvent } from './events.js';
4
+ const ENDPOINT = 'SignalsHandler';
5
+ export class DependencyTrackingSignal extends Signal {
6
+ #onFirstSubscribe;
7
+ #onLastUnsubscribe;
8
+ #subscribeCount = -1;
9
+ constructor(value, onFirstSubscribe, onLastUnsubscribe) {
10
+ super(value);
11
+ this.#onFirstSubscribe = onFirstSubscribe;
12
+ this.#onLastUnsubscribe = onLastUnsubscribe;
20
13
  }
21
- this.#subscribeCount += 1;
22
- }
23
- U(node) {
24
- super.U(node);
25
- this.#subscribeCount -= 1;
26
- if (this.#subscribeCount === 0) {
27
- this.#onLastUnsubscribe();
14
+ S(node) {
15
+ super.S(node);
16
+ if (this.#subscribeCount === 0) {
17
+ this.#onFirstSubscribe();
18
+ }
19
+ this.#subscribeCount += 1;
20
+ }
21
+ U(node) {
22
+ super.U(node);
23
+ this.#subscribeCount -= 1;
24
+ if (this.#subscribeCount === 0) {
25
+ this.#onLastUnsubscribe();
26
+ }
28
27
  }
29
- }
30
28
  }
31
29
  class ServerConnection {
32
- #id;
33
- config;
34
- #subscription;
35
- constructor(id, config) {
36
- this.config = config;
37
- this.#id = id;
38
- }
39
- get subscription() {
40
- return this.#subscription;
41
- }
42
- connect() {
43
- const { client, endpoint, method, params, parentClientSignalId } = this.config;
44
- this.#subscription ??= client.subscribe(ENDPOINT, "subscribe", {
45
- providerEndpoint: endpoint,
46
- providerMethod: method,
47
- clientSignalId: this.#id,
48
- params,
49
- parentClientSignalId
50
- });
51
- return this.#subscription;
52
- }
53
- async update(event) {
54
- const onTheFly = !this.#subscription;
55
- if (onTheFly) {
56
- this.connect();
30
+ #id;
31
+ config;
32
+ #subscription;
33
+ constructor(id, config) {
34
+ this.config = config;
35
+ this.#id = id;
36
+ }
37
+ get subscription() {
38
+ return this.#subscription;
39
+ }
40
+ connect() {
41
+ const { client, endpoint, method, params, parentClientSignalId } = this.config;
42
+ this.#subscription ??= client.subscribe(ENDPOINT, 'subscribe', {
43
+ providerEndpoint: endpoint,
44
+ providerMethod: method,
45
+ clientSignalId: this.#id,
46
+ params,
47
+ parentClientSignalId,
48
+ });
49
+ return this.#subscription;
50
+ }
51
+ async update(event) {
52
+ const onTheFly = !this.#subscription;
53
+ if (onTheFly) {
54
+ this.connect();
55
+ }
56
+ await this.config.client.call(ENDPOINT, 'update', {
57
+ clientSignalId: this.#id,
58
+ event,
59
+ });
60
+ if (onTheFly) {
61
+ this.disconnect();
62
+ }
57
63
  }
58
- await this.config.client.call(ENDPOINT, "update", {
59
- clientSignalId: this.#id,
60
- event
61
- });
62
- if (onTheFly) {
63
- this.disconnect();
64
+ disconnect() {
65
+ this.#subscription?.cancel();
66
+ this.#subscription = undefined;
64
67
  }
65
- }
66
- disconnect() {
67
- this.#subscription?.cancel();
68
- this.#subscription = void 0;
69
- }
70
68
  }
71
- const $update = Symbol("update");
72
- const $processServerResponse = Symbol("processServerResponse");
73
- const $setValueQuietly = Symbol("setValueQuietly");
74
- const $resolveOperation = Symbol("resolveOperation");
75
- const $createOperation = Symbol("createOperation");
76
- class FullStackSignal extends DependencyTrackingSignal {
77
- /**
78
- * The unique identifier of the signal necessary to communicate with the
79
- * server.
80
- */
81
- id;
82
- /**
83
- * The server connection manager.
84
- */
85
- server;
86
- /**
87
- * Defines whether the signal is currently awaits a server-side response.
88
- */
89
- pending = computed(() => this.#pending.value);
90
- /**
91
- * Defines whether the signal has an error.
92
- */
93
- error = computed(() => this.#error.value);
94
- #pending = signal(false);
95
- #error = signal(void 0);
96
- // Paused at the very start to prevent the signal from sending the initial
97
- // value to the server.
98
- #paused = true;
99
- constructor(value, config, id) {
100
- super(
101
- value,
102
- () => this.#connect(),
103
- () => this.#disconnect()
104
- );
105
- this.id = id ?? nanoid();
106
- this.server = new ServerConnection(this.id, config);
107
- this.subscribe((v) => {
108
- if (!this.#paused) {
109
- this.#pending.value = true;
110
- this.#error.value = void 0;
111
- const signalId = config.parentClientSignalId !== void 0 ? this.id : void 0;
112
- this[$update](createSetStateEvent(v, signalId, config.parentClientSignalId));
113
- }
114
- });
115
- this.#paused = false;
116
- }
117
- // stores the promise handlers associated to operations
118
- #operationPromises = /* @__PURE__ */ new Map();
119
- // creates the object to be returned by operations to allow defining callbacks
120
- [$createOperation]({ id, promise }) {
121
- const thens = this.#operationPromises;
122
- const promises = [];
123
- if (promise) {
124
- promises.push(promise);
69
+ export const $update = Symbol('update');
70
+ export const $processServerResponse = Symbol('processServerResponse');
71
+ export const $setValueQuietly = Symbol('setValueQuietly');
72
+ export const $resolveOperation = Symbol('resolveOperation');
73
+ export const $createOperation = Symbol('createOperation');
74
+ export class FullStackSignal extends DependencyTrackingSignal {
75
+ id;
76
+ server;
77
+ pending = computed(() => this.#pending.value);
78
+ error = computed(() => this.#error.value);
79
+ #pending = signal(false);
80
+ #error = signal(undefined);
81
+ #paused = true;
82
+ constructor(value, config, id) {
83
+ super(value, () => this.#connect(), () => this.#disconnect());
84
+ this.id = id ?? nanoid();
85
+ this.server = new ServerConnection(this.id, config);
86
+ this.subscribe((v) => {
87
+ if (!this.#paused) {
88
+ this.#pending.value = true;
89
+ this.#error.value = undefined;
90
+ const signalId = config.parentClientSignalId !== undefined ? this.id : undefined;
91
+ this[$update](createSetStateEvent(v, signalId, config.parentClientSignalId));
92
+ }
93
+ });
94
+ this.#paused = false;
125
95
  }
126
- if (id) {
127
- promises.push(
128
- new Promise((resolve, reject) => {
129
- thens.set(id, { resolve, reject });
130
- })
131
- );
96
+ #operationPromises = new Map();
97
+ [$createOperation]({ id, promise }) {
98
+ const thens = this.#operationPromises;
99
+ const promises = [];
100
+ if (promise) {
101
+ promises.push(promise);
102
+ }
103
+ if (id) {
104
+ promises.push(new Promise((resolve, reject) => {
105
+ thens.set(id, { resolve, reject });
106
+ }));
107
+ }
108
+ if (promises.length === 0) {
109
+ promises.push(Promise.resolve());
110
+ }
111
+ return {
112
+ result: Promise.allSettled(promises).then((results) => {
113
+ const lastResult = results[results.length - 1];
114
+ if (lastResult.status === 'fulfilled') {
115
+ return undefined;
116
+ }
117
+ throw lastResult.reason;
118
+ }),
119
+ };
132
120
  }
133
- if (promises.length === 0) {
134
- promises.push(Promise.resolve());
121
+ [$setValueQuietly](value) {
122
+ this.#paused = true;
123
+ super.value = value;
124
+ this.#paused = false;
135
125
  }
136
- return {
137
- result: Promise.allSettled(promises).then((results) => {
138
- const lastResult = results[results.length - 1];
139
- if (lastResult.status === "fulfilled") {
140
- return void 0;
126
+ async [$update](event) {
127
+ return this.server
128
+ .update(event)
129
+ .catch((error) => {
130
+ this.#error.value = error instanceof Error ? error : new Error(String(error));
131
+ })
132
+ .finally(() => {
133
+ this.#pending.value = false;
134
+ });
135
+ }
136
+ [$resolveOperation](eventId, reason) {
137
+ const operationPromise = this.#operationPromises.get(eventId);
138
+ if (operationPromise) {
139
+ this.#operationPromises.delete(eventId);
140
+ if (reason) {
141
+ operationPromise.reject(reason);
142
+ }
143
+ else {
144
+ operationPromise.resolve();
145
+ }
141
146
  }
142
- throw lastResult.reason;
143
- })
144
- };
145
- }
146
- /**
147
- * Sets the local value of the signal without sending any events to the server
148
- * @param value - The new value.
149
- * @internal
150
- */
151
- [$setValueQuietly](value) {
152
- this.#paused = true;
153
- super.value = value;
154
- this.#paused = false;
155
- }
156
- /**
157
- * A method to update the server with the new value.
158
- *
159
- * @param event - The event to update the server with.
160
- * @returns The server response promise.
161
- */
162
- async [$update](event) {
163
- return this.server.update(event).catch((error) => {
164
- this.#error.value = error instanceof Error ? error : new Error(String(error));
165
- }).finally(() => {
166
- this.#pending.value = false;
167
- });
168
- }
169
- /**
170
- * Resolves the operation promise associated with the given event id.
171
- *
172
- * @param eventId - The event id.
173
- * @param reason - The reason to reject the promise (if any).
174
- */
175
- [$resolveOperation](eventId, reason) {
176
- const operationPromise = this.#operationPromises.get(eventId);
177
- if (operationPromise) {
178
- this.#operationPromises.delete(eventId);
179
- if (reason) {
180
- operationPromise.reject(reason);
181
- } else {
182
- operationPromise.resolve();
183
- }
184
147
  }
185
- }
186
- #connect() {
187
- this.server.connect().onSubscriptionLost(() => "resubscribe").onNext((event) => {
188
- this.#paused = true;
189
- this[$processServerResponse](event);
190
- this.#paused = false;
191
- });
192
- }
193
- #disconnect() {
194
- if (this.server.subscription === void 0) {
195
- return;
148
+ #connect() {
149
+ this.server
150
+ .connect()
151
+ .onSubscriptionLost(() => 'resubscribe')
152
+ .onNext((event) => {
153
+ this.#paused = true;
154
+ this[$processServerResponse](event);
155
+ this.#paused = false;
156
+ });
157
+ }
158
+ #disconnect() {
159
+ if (this.server.subscription === undefined) {
160
+ return;
161
+ }
162
+ this.server.disconnect();
196
163
  }
197
- this.server.disconnect();
198
- }
199
164
  }
200
- export {
201
- $createOperation,
202
- $processServerResponse,
203
- $resolveOperation,
204
- $setValueQuietly,
205
- $update,
206
- DependencyTrackingSignal,
207
- FullStackSignal
208
- };
209
- //# sourceMappingURL=FullStackSignal.js.map
165
+ //# sourceMappingURL=FullStackSignal.js.map
@@ -1,7 +1 @@
1
- {
2
- "version": 3,
3
- "sources": ["src/FullStackSignal.ts"],
4
- "sourcesContent": ["import type { ActionOnLostSubscription, 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 * A return type for signal operations that exposes a `result` property of type\n * `Promise`, that resolves when the operation is completed. It allows defining\n * callbacks to be run after the operation is completed, or error handling when\n * the operation fails.\n *\n * @example\n * ```ts\n * const sharedName = NameService.sharedName({ defaultValue: '' });\n * sharedName.replace('John').result\n * .then(() => console.log('Name updated successfully'))\n * .catch((error) => console.error('Failed to update the name:', error));\n * ```\n */\nexport interface Operation {\n result: Promise<void>;\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 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 * Optional object with method call arguments to be sent to the endpoint\n * method that provides the signal when subscribing to it.\n */\n params?: Record<string, unknown>;\n\n /**\n * The unique identifier of the parent signal in the client.\n */\n parentClientSignalId?: string;\n}>;\n\n/**\n * A server connection manager.\n */\nclass ServerConnection {\n readonly #id: string;\n readonly config: ServerConnectionConfig;\n #subscription?: Subscription<StateEvent>;\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, params, parentClientSignalId } = this.config;\n\n this.#subscription ??= client.subscribe(ENDPOINT, 'subscribe', {\n providerEndpoint: endpoint,\n providerMethod: method,\n clientSignalId: this.#id,\n params,\n parentClientSignalId,\n });\n\n return this.#subscription;\n }\n\n async update(event: StateEvent): Promise<void> {\n const onTheFly = !this.#subscription;\n\n if (onTheFly) {\n this.connect();\n }\n\n await this.config.client.call(ENDPOINT, 'update', {\n clientSignalId: this.#id,\n event,\n });\n\n if (onTheFly) {\n this.disconnect();\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');\nexport const $setValueQuietly = Symbol('setValueQuietly');\nexport const $resolveOperation = Symbol('resolveOperation');\nexport const $createOperation = Symbol('createOperation');\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: string;\n\n /**\n * The server connection manager.\n */\n readonly server: ServerConnection;\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, id?: string) {\n super(\n value,\n () => this.#connect(),\n () => this.#disconnect(),\n );\n this.id = id ?? nanoid();\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 // For internal signals, the provided non-null to the constructor should\n // be used along with the parent client side signal id when sending the\n // set event to the server. For internal signals this combination is\n // needed for addressing the correct parent/child signal instances on\n // the server. For a standalone signal, both of them should be passed in\n // as undefined:\n const signalId = config.parentClientSignalId !== undefined ? this.id : undefined;\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this[$update](createSetStateEvent(v, signalId, config.parentClientSignalId));\n }\n });\n\n this.#paused = false;\n }\n\n // stores the promise handlers associated to operations\n readonly #operationPromises = new Map<\n string,\n {\n resolve(value: PromiseLike<void> | void): void;\n reject(reason?: any): void;\n }\n >();\n\n // creates the object to be returned by operations to allow defining callbacks\n protected [$createOperation]({ id, promise }: { id?: string; promise?: Promise<void> }): Operation {\n const thens = this.#operationPromises;\n const promises: Array<Promise<void>> = [];\n\n if (promise) {\n // Add the provided promise to the list of promises\n promises.push(promise);\n }\n\n if (id) {\n // Create a promise to be associated to the provided id\n promises.push(\n new Promise<void>((resolve, reject) => {\n thens.set(id, { resolve, reject });\n }),\n );\n }\n\n if (promises.length === 0) {\n // If no promises were added, return a resolved promise\n promises.push(Promise.resolve());\n }\n\n return {\n result: Promise.allSettled(promises).then((results) => {\n const lastResult = results[results.length - 1];\n if (lastResult.status === 'fulfilled') {\n return undefined;\n }\n throw lastResult.reason;\n }),\n };\n }\n\n /**\n * Sets the local value of the signal without sending any events to the server\n * @param value - The new value.\n * @internal\n */\n protected [$setValueQuietly](value: T): void {\n this.#paused = true;\n super.value = value;\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 * @returns The server response promise.\n */\n protected async [$update](event: StateEvent): Promise<void> {\n return 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 * Resolves the operation promise associated with the given event id.\n *\n * @param eventId - The event id.\n * @param reason - The reason to reject the promise (if any).\n */\n protected [$resolveOperation](eventId: string, reason?: string): void {\n const operationPromise = this.#operationPromises.get(eventId);\n if (operationPromise) {\n this.#operationPromises.delete(eventId);\n if (reason) {\n operationPromise.reject(reason);\n } else {\n operationPromise.resolve();\n }\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): void;\n\n #connect() {\n this.server\n .connect()\n .onSubscriptionLost(() => 'resubscribe' as ActionOnLostSubscription)\n .onNext((event: StateEvent) => {\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": "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",
6
- "names": []
7
- }
1
+ {"version":3,"file":"FullStackSignal.js","sourceRoot":"","sources":["src/FullStackSignal.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAmB,MAAM,aAAa,CAAC;AAEnE,MAAM,QAAQ,GAAG,gBAAgB,CAAC;AA0BlC,MAAM,OAAgB,wBAA4B,SAAQ,MAAS;IACxD,iBAAiB,CAAa;IAC9B,kBAAkB,CAAa;IAIxC,eAAe,GAAG,CAAC,CAAC,CAAC;IAErB,YAAsB,KAAoB,EAAE,gBAA4B,EAAE,iBAA6B;QACrG,KAAK,CAAC,KAAK,CAAC,CAAC;QACb,IAAI,CAAC,iBAAiB,GAAG,gBAAgB,CAAC;QAC1C,IAAI,CAAC,kBAAkB,GAAG,iBAAiB,CAAC;IAC9C,CAAC;IAEkB,CAAC,CAAC,IAAa;QAChC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACd,IAAI,IAAI,CAAC,eAAe,KAAK,CAAC,EAAE,CAAC;YAC/B,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC3B,CAAC;QACD,IAAI,CAAC,eAAe,IAAI,CAAC,CAAC;IAC5B,CAAC;IAEkB,CAAC,CAAC,IAAa;QAChC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACd,IAAI,CAAC,eAAe,IAAI,CAAC,CAAC;QAC1B,IAAI,IAAI,CAAC,eAAe,KAAK,CAAC,EAAE,CAAC;YAC/B,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC5B,CAAC;IACH,CAAC;CACF;AAqCD,MAAM,gBAAgB;IACX,GAAG,CAAS;IACZ,MAAM,CAAyB;IACxC,aAAa,CAA4B;IAEzC,YAAY,EAAU,EAAE,MAA8B;QACpD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC;IAChB,CAAC;IAED,IAAI,YAAY;QACd,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAED,OAAO;QACL,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,oBAAoB,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC;QAE/E,IAAI,CAAC,aAAa,KAAK,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,WAAW,EAAE;YAC7D,gBAAgB,EAAE,QAAQ;YAC1B,cAAc,EAAE,MAAM;YACtB,cAAc,EAAE,IAAI,CAAC,GAAG;YACxB,MAAM;YACN,oBAAoB;SACrB,CAAC,CAAC;QAEH,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAAiB;QAC5B,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC;QAErC,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,CAAC;QAED,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE;YAChD,cAAc,EAAE,IAAI,CAAC,GAAG;YACxB,KAAK;SACN,CAAC,CAAC;QAEH,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,CAAC,UAAU,EAAE,CAAC;QACpB,CAAC;IACH,CAAC;IAED,UAAU;QACR,IAAI,CAAC,aAAa,EAAE,MAAM,EAAE,CAAC;QAC7B,IAAI,CAAC,aAAa,GAAG,SAAS,CAAC;IACjC,CAAC;CACF;AAED,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;AACxC,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC,uBAAuB,CAAC,CAAC;AACtE,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;AAC1D,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,CAAC,kBAAkB,CAAC,CAAC;AAC5D,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;AAU1D,MAAM,OAAgB,eAAmB,SAAQ,wBAA2B;IAKjE,EAAE,CAAS;IAKX,MAAM,CAAmB;IAKzB,OAAO,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAK9C,KAAK,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAE1C,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACzB,MAAM,GAAG,MAAM,CAAoB,SAAS,CAAC,CAAC;IAIvD,OAAO,GAAG,IAAI,CAAC;IAEf,YAAY,KAAoB,EAAE,MAA8B,EAAE,EAAW;QAC3E,KAAK,CACH,KAAK,EACL,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,EACrB,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CACzB,CAAC;QACF,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,MAAM,EAAE,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QAEpD,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE;YACnB,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;gBAClB,IAAI,CAAC,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC;gBAC3B,IAAI,CAAC,MAAM,CAAC,KAAK,GAAG,SAAS,CAAC;gBAO9B,MAAM,QAAQ,GAAG,MAAM,CAAC,oBAAoB,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;gBAEjF,IAAI,CAAC,OAAO,CAAC,CAAC,mBAAmB,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC;YAC/E,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;IACvB,CAAC;IAGQ,kBAAkB,GAAG,IAAI,GAAG,EAMlC,CAAC;IAGM,CAAC,gBAAgB,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,EAA4C;QACpF,MAAM,KAAK,GAAG,IAAI,CAAC,kBAAkB,CAAC;QACtC,MAAM,QAAQ,GAAyB,EAAE,CAAC;QAE1C,IAAI,OAAO,EAAE,CAAC;YAEZ,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;QAED,IAAI,EAAE,EAAE,CAAC;YAEP,QAAQ,CAAC,IAAI,CACX,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBACpC,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;YACrC,CAAC,CAAC,CACH,CAAC;QACJ,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAE1B,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;QACnC,CAAC;QAED,OAAO;YACL,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE;gBACpD,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;gBAC/C,IAAI,UAAU,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;oBACtC,OAAO,SAAS,CAAC;gBACnB,CAAC;gBACD,MAAM,UAAU,CAAC,MAAM,CAAC;YAC1B,CAAC,CAAC;SACH,CAAC;IACJ,CAAC;IAOS,CAAC,gBAAgB,CAAC,CAAC,KAAQ;QACnC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;IACvB,CAAC;IAQS,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,KAAiB;QACzC,OAAO,IAAI,CAAC,MAAM;aACf,MAAM,CAAC,KAAK,CAAC;aACb,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YACxB,IAAI,CAAC,MAAM,CAAC,KAAK,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAChF,CAAC,CAAC;aACD,OAAO,CAAC,GAAG,EAAE;YACZ,IAAI,CAAC,QAAQ,CAAC,KAAK,GAAG,KAAK,CAAC;QAC9B,CAAC,CAAC,CAAC;IACP,CAAC;IAQS,CAAC,iBAAiB,CAAC,CAAC,OAAe,EAAE,MAAe;QAC5D,MAAM,gBAAgB,GAAG,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC9D,IAAI,gBAAgB,EAAE,CAAC;YACrB,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YACxC,IAAI,MAAM,EAAE,CAAC;gBACX,gBAAgB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAClC,CAAC;iBAAM,CAAC;gBACN,gBAAgB,CAAC,OAAO,EAAE,CAAC;YAC7B,CAAC;QACH,CAAC;IACH,CAAC;IAUD,QAAQ;QACN,IAAI,CAAC,MAAM;aACR,OAAO,EAAE;aACT,kBAAkB,CAAC,GAAG,EAAE,CAAC,aAAyC,CAAC;aACnE,MAAM,CAAC,CAAC,KAAiB,EAAE,EAAE;YAC5B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACpB,IAAI,CAAC,sBAAsB,CAAC,CAAC,KAAK,CAAC,CAAC;YACpC,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACvB,CAAC,CAAC,CAAC;IACP,CAAC;IAED,WAAW;QACT,IAAI,IAAI,CAAC,MAAM,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;YAC3C,OAAO;QACT,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;IAC3B,CAAC;CACF","sourcesContent":["import type { ActionOnLostSubscription, 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 * A return type for signal operations that exposes a `result` property of type\n * `Promise`, that resolves when the operation is completed. It allows defining\n * callbacks to be run after the operation is completed, or error handling when\n * the operation fails.\n *\n * @example\n * ```ts\n * const sharedName = NameService.sharedName({ defaultValue: '' });\n * sharedName.replace('John').result\n * .then(() => console.log('Name updated successfully'))\n * .catch((error) => console.error('Failed to update the name:', error));\n * ```\n */\nexport interface Operation {\n result: Promise<void>;\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 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 * Optional object with method call arguments to be sent to the endpoint\n * method that provides the signal when subscribing to it.\n */\n params?: Record<string, unknown>;\n\n /**\n * The unique identifier of the parent signal in the client.\n */\n parentClientSignalId?: string;\n}>;\n\n/**\n * A server connection manager.\n */\nclass ServerConnection {\n readonly #id: string;\n readonly config: ServerConnectionConfig;\n #subscription?: Subscription<StateEvent>;\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, params, parentClientSignalId } = this.config;\n\n this.#subscription ??= client.subscribe(ENDPOINT, 'subscribe', {\n providerEndpoint: endpoint,\n providerMethod: method,\n clientSignalId: this.#id,\n params,\n parentClientSignalId,\n });\n\n return this.#subscription;\n }\n\n async update(event: StateEvent): Promise<void> {\n const onTheFly = !this.#subscription;\n\n if (onTheFly) {\n this.connect();\n }\n\n await this.config.client.call(ENDPOINT, 'update', {\n clientSignalId: this.#id,\n event,\n });\n\n if (onTheFly) {\n this.disconnect();\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');\nexport const $setValueQuietly = Symbol('setValueQuietly');\nexport const $resolveOperation = Symbol('resolveOperation');\nexport const $createOperation = Symbol('createOperation');\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: string;\n\n /**\n * The server connection manager.\n */\n readonly server: ServerConnection;\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, id?: string) {\n super(\n value,\n () => this.#connect(),\n () => this.#disconnect(),\n );\n this.id = id ?? nanoid();\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 // For internal signals, the provided non-null to the constructor should\n // be used along with the parent client side signal id when sending the\n // set event to the server. For internal signals this combination is\n // needed for addressing the correct parent/child signal instances on\n // the server. For a standalone signal, both of them should be passed in\n // as undefined:\n const signalId = config.parentClientSignalId !== undefined ? this.id : undefined;\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this[$update](createSetStateEvent(v, signalId, config.parentClientSignalId));\n }\n });\n\n this.#paused = false;\n }\n\n // stores the promise handlers associated to operations\n readonly #operationPromises = new Map<\n string,\n {\n resolve(value: PromiseLike<void> | void): void;\n reject(reason?: any): void;\n }\n >();\n\n // creates the object to be returned by operations to allow defining callbacks\n protected [$createOperation]({ id, promise }: { id?: string; promise?: Promise<void> }): Operation {\n const thens = this.#operationPromises;\n const promises: Array<Promise<void>> = [];\n\n if (promise) {\n // Add the provided promise to the list of promises\n promises.push(promise);\n }\n\n if (id) {\n // Create a promise to be associated to the provided id\n promises.push(\n new Promise<void>((resolve, reject) => {\n thens.set(id, { resolve, reject });\n }),\n );\n }\n\n if (promises.length === 0) {\n // If no promises were added, return a resolved promise\n promises.push(Promise.resolve());\n }\n\n return {\n result: Promise.allSettled(promises).then((results) => {\n const lastResult = results[results.length - 1];\n if (lastResult.status === 'fulfilled') {\n return undefined;\n }\n throw lastResult.reason;\n }),\n };\n }\n\n /**\n * Sets the local value of the signal without sending any events to the server\n * @param value - The new value.\n * @internal\n */\n protected [$setValueQuietly](value: T): void {\n this.#paused = true;\n super.value = value;\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 * @returns The server response promise.\n */\n protected async [$update](event: StateEvent): Promise<void> {\n return 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 * Resolves the operation promise associated with the given event id.\n *\n * @param eventId - The event id.\n * @param reason - The reason to reject the promise (if any).\n */\n protected [$resolveOperation](eventId: string, reason?: string): void {\n const operationPromise = this.#operationPromises.get(eventId);\n if (operationPromise) {\n this.#operationPromises.delete(eventId);\n if (reason) {\n operationPromise.reject(reason);\n } else {\n operationPromise.resolve();\n }\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): void;\n\n #connect() {\n this.server\n .connect()\n .onSubscriptionLost(() => 'resubscribe' as ActionOnLostSubscription)\n .onNext((event: StateEvent) => {\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"]}
package/ListSignal.d.ts CHANGED
@@ -2,32 +2,10 @@ import { CollectionSignal } from './CollectionSignal.js';
2
2
  import { type StateEvent } from './events.js';
3
3
  import { $processServerResponse, type Operation, type ServerConnectionConfig } from './FullStackSignal.js';
4
4
  import { ValueSignal } from './ValueSignal.js';
5
- /**
6
- * A {@link FullStackSignal} that represents a shared list of values, where each
7
- * value is represented by a {@link ValueSignal}.
8
- * The list can be modified by calling the defined methods to insert or remove
9
- * items, but the `value` property of a `ListSignal` instance is read-only and
10
- * cannot be assigned directly.
11
- * The value of each item in the list can be manipulated similar to a regular
12
- * {@link ValueSignal}.
13
- *
14
- * @typeParam T - The type of the values in the list.
15
- */
16
5
  export declare class ListSignal<T> extends CollectionSignal<ReadonlyArray<ValueSignal<T>>> {
17
6
  #private;
18
7
  constructor(config: ServerConnectionConfig);
19
8
  protected [$processServerResponse](event: StateEvent): void;
20
- /**
21
- * Inserts a new value at the end of the list.
22
- * @param value - The value to insert.
23
- * @returns An operation object that allows to perform additional actions.
24
- */
25
9
  insertLast(value: T): Operation;
26
- /**
27
- * Removes the given item from the list.
28
- * @param item - The item to remove.
29
- * @returns An operation object that allows to perform additional actions.
30
- */
31
10
  remove(item: ValueSignal<T>): Operation;
32
11
  }
33
- //# sourceMappingURL=ListSignal.d.ts.map