@tstdl/base 0.93.74 → 0.93.76
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.
|
@@ -34,7 +34,7 @@ let DocumentManagementObservationService = DocumentManagementObservationService_
|
|
|
34
34
|
collectionChange(ids, transactionOrSession) {
|
|
35
35
|
const transaction = tryGetTstdlTransaction(transactionOrSession);
|
|
36
36
|
if (isDefined(transaction)) {
|
|
37
|
-
transaction.afterCommit
|
|
37
|
+
transaction.afterCommit(() => this.collectionChange(ids));
|
|
38
38
|
}
|
|
39
39
|
else {
|
|
40
40
|
for (const id of toArray(ids)) {
|
|
@@ -45,7 +45,7 @@ let DocumentManagementObservationService = DocumentManagementObservationService_
|
|
|
45
45
|
documentChange(ids, transactionOrSession) {
|
|
46
46
|
const transaction = tryGetTstdlTransaction(transactionOrSession);
|
|
47
47
|
if (isDefined(transaction)) {
|
|
48
|
-
transaction.afterCommit
|
|
48
|
+
transaction.afterCommit(() => this.documentChange(ids));
|
|
49
49
|
}
|
|
50
50
|
else {
|
|
51
51
|
for (const id of toArray(ids)) {
|
|
@@ -56,7 +56,7 @@ let DocumentManagementObservationService = DocumentManagementObservationService_
|
|
|
56
56
|
workflowChange(ids, transactionOrSession) {
|
|
57
57
|
const transaction = tryGetTstdlTransaction(transactionOrSession);
|
|
58
58
|
if (isDefined(transaction)) {
|
|
59
|
-
transaction.afterCommit
|
|
59
|
+
transaction.afterCommit(() => this.workflowChange(ids));
|
|
60
60
|
}
|
|
61
61
|
else {
|
|
62
62
|
for (const id of toArray(ids)) {
|
|
@@ -67,7 +67,7 @@ let DocumentManagementObservationService = DocumentManagementObservationService_
|
|
|
67
67
|
requestChange(ids, transactionOrSession) {
|
|
68
68
|
const transaction = tryGetTstdlTransaction(transactionOrSession);
|
|
69
69
|
if (isDefined(transaction)) {
|
|
70
|
-
transaction.afterCommit
|
|
70
|
+
transaction.afterCommit(() => this.requestChange(ids));
|
|
71
71
|
}
|
|
72
72
|
else {
|
|
73
73
|
for (const id of toArray(ids)) {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { PgTransaction as DrizzlePgTransaction, type PgQueryResultHKT, type PgTransactionConfig } from 'drizzle-orm/pg-core';
|
|
2
|
-
import { DeferredPromise } from '../../promise/deferred-promise.js';
|
|
3
2
|
import type { Record } from '../../types/index.js';
|
|
4
3
|
import type { Database } from './database.js';
|
|
5
4
|
export type PgTransaction = DrizzlePgTransaction<PgQueryResultHKT, Record, Record>;
|
|
@@ -7,12 +6,13 @@ export { DrizzlePgTransaction };
|
|
|
7
6
|
export type TransactionConfig = PgTransactionConfig;
|
|
8
7
|
export declare abstract class Transaction implements AsyncDisposable {
|
|
9
8
|
#private;
|
|
10
|
-
readonly afterCommit
|
|
9
|
+
readonly afterCommit: import("../../utils/async-hook/index.js").AsyncHook<never, never, unknown>;
|
|
11
10
|
manualCommit: boolean;
|
|
12
11
|
[Symbol.asyncDispose](): Promise<void>;
|
|
13
12
|
withManualCommit(): void;
|
|
14
13
|
/**
|
|
15
|
-
*
|
|
14
|
+
* Executes the handler within the transaction scope.
|
|
15
|
+
* Commits automatically on success if manual commit is disabled, or rolls back on error.
|
|
16
16
|
*/
|
|
17
17
|
use<T>(handler: () => Promise<T>): Promise<T>;
|
|
18
18
|
commit(): Promise<void>;
|
|
@@ -21,11 +21,11 @@ export declare abstract class Transaction implements AsyncDisposable {
|
|
|
21
21
|
protected abstract _rollback(): void | Promise<void>;
|
|
22
22
|
}
|
|
23
23
|
export declare class DrizzleTransaction extends Transaction {
|
|
24
|
+
#private;
|
|
24
25
|
readonly pgTransaction: PgTransaction;
|
|
25
|
-
|
|
26
|
-
readonly pgTransactionPromise: Promise<void>;
|
|
27
|
-
constructor(pgTransaction: PgTransaction, pgTransactionPromise: Promise<void>);
|
|
26
|
+
constructor(pgTransaction: PgTransaction);
|
|
28
27
|
static create(session: Database | PgTransaction, config?: TransactionConfig): Promise<DrizzleTransaction>;
|
|
29
28
|
protected _commit(): Promise<void>;
|
|
30
|
-
protected _rollback(): void
|
|
29
|
+
protected _rollback(): Promise<void>;
|
|
30
|
+
private setTransactionResultPromise;
|
|
31
31
|
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { PgTransaction as DrizzlePgTransaction } from 'drizzle-orm/pg-core';
|
|
2
|
-
import { Subject } from 'rxjs';
|
|
3
2
|
import { DeferredPromise } from '../../promise/deferred-promise.js';
|
|
3
|
+
import { asyncHook } from '../../utils/async-hook/index.js';
|
|
4
4
|
export { DrizzlePgTransaction };
|
|
5
5
|
export class Transaction {
|
|
6
|
-
#afterCommitSubject = new Subject();
|
|
7
6
|
#useCounter = 0;
|
|
8
7
|
#done = false;
|
|
9
|
-
afterCommit
|
|
8
|
+
afterCommit = asyncHook();
|
|
10
9
|
manualCommit = false;
|
|
11
10
|
async [Symbol.asyncDispose]() {
|
|
12
11
|
if (!this.#done) {
|
|
@@ -17,65 +16,92 @@ export class Transaction {
|
|
|
17
16
|
this.manualCommit = true;
|
|
18
17
|
}
|
|
19
18
|
/**
|
|
20
|
-
*
|
|
19
|
+
* Executes the handler within the transaction scope.
|
|
20
|
+
* Commits automatically on success if manual commit is disabled, or rolls back on error.
|
|
21
21
|
*/
|
|
22
22
|
async use(handler) {
|
|
23
|
+
if (this.#done) {
|
|
24
|
+
throw new Error('Transaction is already closed');
|
|
25
|
+
}
|
|
23
26
|
this.#useCounter++;
|
|
24
27
|
try {
|
|
25
28
|
return await handler();
|
|
26
29
|
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (!this.#done) {
|
|
32
|
+
try {
|
|
33
|
+
await this.rollback();
|
|
34
|
+
}
|
|
35
|
+
catch { /* ignore */ }
|
|
36
|
+
}
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
27
39
|
finally {
|
|
28
40
|
this.#useCounter--;
|
|
29
|
-
if (
|
|
41
|
+
if (!this.#done && !this.manualCommit && (this.#useCounter == 0)) {
|
|
30
42
|
await this.commit();
|
|
31
43
|
}
|
|
32
44
|
}
|
|
33
45
|
}
|
|
34
46
|
async commit() {
|
|
47
|
+
if (this.#done) {
|
|
48
|
+
throw new Error('Transaction is already closed');
|
|
49
|
+
}
|
|
35
50
|
this.#done = true;
|
|
36
51
|
await this._commit();
|
|
37
|
-
this
|
|
38
|
-
this.#afterCommitSubject.complete();
|
|
52
|
+
await this.afterCommit.trigger();
|
|
39
53
|
}
|
|
40
54
|
async rollback() {
|
|
55
|
+
if (this.#done) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
41
58
|
this.#done = true;
|
|
42
|
-
this.#afterCommitSubject.complete();
|
|
43
59
|
await this._rollback();
|
|
44
60
|
}
|
|
45
61
|
}
|
|
46
62
|
export class DrizzleTransaction extends Transaction {
|
|
63
|
+
#deferPromise = new DeferredPromise();
|
|
47
64
|
pgTransaction;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
constructor(pgTransaction, pgTransactionPromise) {
|
|
65
|
+
#pgTransactionResultPromise;
|
|
66
|
+
constructor(pgTransaction) {
|
|
51
67
|
super();
|
|
52
68
|
this.pgTransaction = pgTransaction;
|
|
53
|
-
this.pgTransactionPromise = pgTransactionPromise;
|
|
54
69
|
}
|
|
55
70
|
static async create(session, config) {
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
const transaction = new DrizzleTransaction(tx
|
|
59
|
-
|
|
60
|
-
await transaction
|
|
71
|
+
const instancePromise = new DeferredPromise();
|
|
72
|
+
const pgTransactionResultPromise = session.transaction(async (tx) => {
|
|
73
|
+
const transaction = new DrizzleTransaction(tx);
|
|
74
|
+
instancePromise.resolve(transaction);
|
|
75
|
+
await transaction.#deferPromise;
|
|
61
76
|
}, config);
|
|
62
|
-
|
|
63
|
-
if (
|
|
64
|
-
|
|
77
|
+
pgTransactionResultPromise.catch((error) => {
|
|
78
|
+
if (instancePromise.pending) {
|
|
79
|
+
instancePromise.reject(error);
|
|
65
80
|
}
|
|
66
81
|
});
|
|
67
|
-
|
|
82
|
+
const transaction = await instancePromise;
|
|
83
|
+
transaction.setTransactionResultPromise(pgTransactionResultPromise);
|
|
84
|
+
return transaction;
|
|
68
85
|
}
|
|
69
86
|
async _commit() {
|
|
70
|
-
this
|
|
71
|
-
await this
|
|
87
|
+
this.#deferPromise.resolve();
|
|
88
|
+
await this.#pgTransactionResultPromise;
|
|
72
89
|
}
|
|
73
|
-
_rollback() {
|
|
90
|
+
async _rollback() {
|
|
74
91
|
try {
|
|
75
92
|
this.pgTransaction.rollback();
|
|
76
93
|
}
|
|
77
94
|
catch (error) {
|
|
78
|
-
this
|
|
95
|
+
if (this.#deferPromise.pending) {
|
|
96
|
+
this.#deferPromise.reject(error);
|
|
97
|
+
}
|
|
79
98
|
}
|
|
99
|
+
try {
|
|
100
|
+
await this.#pgTransactionResultPromise;
|
|
101
|
+
}
|
|
102
|
+
catch { /* expect rejection during rollback */ }
|
|
103
|
+
}
|
|
104
|
+
setTransactionResultPromise(promise) {
|
|
105
|
+
this.#pgTransactionResultPromise = promise;
|
|
80
106
|
}
|
|
81
107
|
}
|
package/package.json
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defines the signature for a handler function that can be registered with an `AsyncHook`.
|
|
3
|
+
* The function can be synchronous (returning `R`) or asynchronous (returning `Promise<R>`).
|
|
4
|
+
*
|
|
5
|
+
* The signature is conditional based on the value type `T` and context type `C`.
|
|
6
|
+
*
|
|
7
|
+
* @template T The type of the value passed to the handler.
|
|
8
|
+
* @template C The type of the optional context object.
|
|
9
|
+
* @template R The expected return type of the handler.
|
|
10
|
+
*/
|
|
11
|
+
export type AsyncHookHandler<T, C, R> = [
|
|
12
|
+
C
|
|
13
|
+
] extends [never] ? [T] extends [never] ? () => R | Promise<R> : (value: T) => R | Promise<R> : [T] extends [never] ? (value: undefined, context: C) => R | Promise<R> : (value: T, context: C) => R | Promise<R>;
|
|
14
|
+
/**
|
|
15
|
+
* Defines the signature for the trigger method.
|
|
16
|
+
*
|
|
17
|
+
* @template T The type of the value passed to the trigger.
|
|
18
|
+
* @template C The type of the optional context object.
|
|
19
|
+
* @template R The return type of an individual handler.
|
|
20
|
+
*/
|
|
21
|
+
export type AsyncHookTrigger<T, C, R> = [
|
|
22
|
+
C
|
|
23
|
+
] extends [never] ? [T] extends [never] ? () => Promise<R[]> : (value: T) => Promise<R[]> : [T] extends [never] ? (value: undefined, context: C) => Promise<R[]> : (value: T, context: C) => Promise<R[]>;
|
|
1
24
|
/**
|
|
2
25
|
* Represents the public interface for an asynchronous hook.
|
|
3
26
|
*
|
|
@@ -6,38 +29,35 @@
|
|
|
6
29
|
* @template R The return type of an individual handler. Defaults to `unknown`.
|
|
7
30
|
*/
|
|
8
31
|
export type AsyncHook<T, C = never, R = unknown> = {
|
|
32
|
+
/**
|
|
33
|
+
* Registers a handler function via the callable interface.
|
|
34
|
+
* @param handler The async handler function to register.
|
|
35
|
+
*/
|
|
36
|
+
(handler: AsyncHookHandler<T, C, R>): AsyncHookHandlerRegistration;
|
|
9
37
|
/**
|
|
10
38
|
* Registers a handler function to be called when the hook is triggered.
|
|
11
39
|
* @param handler The async handler function to register.
|
|
12
|
-
* @returns A registration object with an `unregister` method to remove the handler.
|
|
13
40
|
*/
|
|
14
41
|
register: (handler: AsyncHookHandler<T, C, R>) => AsyncHookHandlerRegistration;
|
|
15
42
|
/**
|
|
16
43
|
* Triggers the hook, executing all registered handlers in sequence.
|
|
44
|
+
* If any handler throws an error, execution stops and the promise rejects.
|
|
17
45
|
*
|
|
18
46
|
* The signature of this function is conditional:
|
|
19
|
-
* -
|
|
20
|
-
* -
|
|
47
|
+
* - `T=never, C=never`: `trigger()`
|
|
48
|
+
* - `T=Type, C=never`: `trigger(value)`
|
|
49
|
+
* - `T=never, C=Type`: `trigger(undefined, context)`
|
|
50
|
+
* - `T=Type, C=Type`: `trigger(value, context)`
|
|
21
51
|
*
|
|
22
|
-
* @param value The value to pass to all handlers.
|
|
23
|
-
* @param context The context object to pass to all handlers (only required if `C` is not `never`).
|
|
24
52
|
* @returns A promise that resolves to an array of results from all handlers.
|
|
25
53
|
*/
|
|
26
|
-
trigger:
|
|
54
|
+
trigger: AsyncHookTrigger<T, C, R>;
|
|
55
|
+
/**
|
|
56
|
+
* Removes all registered handlers.
|
|
57
|
+
* Useful for cleanup logic or resetting state in tests.
|
|
58
|
+
*/
|
|
59
|
+
removeAll: () => void;
|
|
27
60
|
};
|
|
28
|
-
/**
|
|
29
|
-
* Defines the signature for a handler function that can be registered with an `AsyncHook`.
|
|
30
|
-
* The function can be synchronous (returning `R`) or asynchronous (returning `Promise<R>`).
|
|
31
|
-
*
|
|
32
|
-
* The signature is conditional based on the context type `C`:
|
|
33
|
-
* - If `C` is `never`, the handler receives only the `value` argument.
|
|
34
|
-
* - If `C` is any other type, the handler receives both `value` and `context`.
|
|
35
|
-
*
|
|
36
|
-
* @template T The type of the value passed to the handler.
|
|
37
|
-
* @template C The type of the optional context object.
|
|
38
|
-
* @template R The expected return type of the handler.
|
|
39
|
-
*/
|
|
40
|
-
export type AsyncHookHandler<T, C, R> = [C] extends [never] ? ((value: T) => R | Promise<R>) : ((value: T, context: C) => R | Promise<R>);
|
|
41
61
|
/**
|
|
42
62
|
* Represents the object returned when a handler is registered,
|
|
43
63
|
* allowing for its subsequent unregistration.
|
|
@@ -52,67 +72,40 @@ export type AsyncHookHandlerRegistration = {
|
|
|
52
72
|
* Creates a new asynchronous hook.
|
|
53
73
|
*
|
|
54
74
|
* An async hook is a system that allows you to register multiple "handler" functions
|
|
55
|
-
* that will be executed in
|
|
56
|
-
* for creating extensible, plugin-like architectures.
|
|
57
|
-
* or asynchronous.
|
|
75
|
+
* that will be executed in **sequential order** when a "trigger" event occurs.
|
|
76
|
+
* This is useful for creating extensible, plugin-like architectures.
|
|
77
|
+
* Handlers can be synchronous or asynchronous.
|
|
58
78
|
*
|
|
59
|
-
* @template T The type of the primary value that the hook is triggered with.
|
|
79
|
+
* @template T The type of the primary value that the hook is triggered with. Defaults to `never` (no value).
|
|
60
80
|
* @template C The type of the optional context object passed to the hook's trigger and handlers. Defaults to `never`.
|
|
61
81
|
* @template R The return type of an individual handler. The `trigger` method will resolve with an array of these values (`R[]`). Defaults to `unknown`.
|
|
62
|
-
* @returns {AsyncHook<T, C, R>} An object with `register` and `trigger` methods.
|
|
63
82
|
*
|
|
64
83
|
* @example
|
|
65
84
|
* ```ts
|
|
66
|
-
* // Simple hook
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* onTaskStart.register(taskName => {
|
|
71
|
-
* console.log(`[Logger] Task started: ${taskName}`);
|
|
72
|
-
* });
|
|
73
|
-
*
|
|
74
|
-
* const registration = onTaskStart.register(async taskName => {
|
|
75
|
-
* await new Promise(resolve => setTimeout(resolve, 50));
|
|
76
|
-
* console.log(`[Notifier] Notifying that task started: ${taskName}`);
|
|
77
|
-
* });
|
|
78
|
-
*
|
|
79
|
-
* await onTaskStart.trigger('Process Data');
|
|
80
|
-
* // [Logger] Task started: Process Data
|
|
81
|
-
* // [Notifier] Notifying that task started: Process Data
|
|
82
|
-
*
|
|
83
|
-
* registration.unregister();
|
|
84
|
-
* console.log('Notifier unregistered.');
|
|
85
|
-
*
|
|
86
|
-
* await onTaskStart.trigger('Finalize Report');
|
|
87
|
-
* // [Logger] Task started: Finalize Report
|
|
88
|
-
* }
|
|
85
|
+
* // 1. Simple hook (Signal only, no data)
|
|
86
|
+
* const onInit = asyncHook();
|
|
87
|
+
* onInit(() => console.log('Initialized'));
|
|
88
|
+
* await onInit.trigger();
|
|
89
89
|
* ```
|
|
90
90
|
*
|
|
91
91
|
* @example
|
|
92
92
|
* ```ts
|
|
93
|
-
* // Hook with
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* onTaskComplete.register((taskName, context) => {
|
|
100
|
-
* console.log(`[Audit] Task '${taskName}' completed by user ${context.userId}.`);
|
|
101
|
-
* return true; // Audit successful
|
|
102
|
-
* });
|
|
93
|
+
* // 2. Hook with data, no context
|
|
94
|
+
* const onMessage = asyncHook<string>();
|
|
95
|
+
* onMessage((msg) => console.log('Received:', msg));
|
|
96
|
+
* await onMessage.trigger('Hello');
|
|
97
|
+
* ```
|
|
103
98
|
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```ts
|
|
101
|
+
* // 3. Hook with context
|
|
102
|
+
* type Context = { user: string };
|
|
103
|
+
* const onAction = asyncHook<string, Context>();
|
|
108
104
|
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* { userId: 123, transactionId: 'abc-456' }
|
|
112
|
-
* );
|
|
105
|
+
* // Note: Handlers receive context as the second argument
|
|
106
|
+
* onAction((action, ctx) => console.log(action, ctx.user));
|
|
113
107
|
*
|
|
114
|
-
*
|
|
115
|
-
* }
|
|
108
|
+
* await onAction.trigger('Click', { user: 'Alice' });
|
|
116
109
|
* ```
|
|
117
110
|
*/
|
|
118
|
-
export declare function asyncHook<T, C = never, R = unknown>(): AsyncHook<T, C, R>;
|
|
111
|
+
export declare function asyncHook<T = never, C = never, R = unknown>(): AsyncHook<T, C, R>;
|
|
@@ -2,94 +2,71 @@
|
|
|
2
2
|
* Creates a new asynchronous hook.
|
|
3
3
|
*
|
|
4
4
|
* An async hook is a system that allows you to register multiple "handler" functions
|
|
5
|
-
* that will be executed in
|
|
6
|
-
* for creating extensible, plugin-like architectures.
|
|
7
|
-
* or asynchronous.
|
|
5
|
+
* that will be executed in **sequential order** when a "trigger" event occurs.
|
|
6
|
+
* This is useful for creating extensible, plugin-like architectures.
|
|
7
|
+
* Handlers can be synchronous or asynchronous.
|
|
8
8
|
*
|
|
9
|
-
* @template T The type of the primary value that the hook is triggered with.
|
|
9
|
+
* @template T The type of the primary value that the hook is triggered with. Defaults to `never` (no value).
|
|
10
10
|
* @template C The type of the optional context object passed to the hook's trigger and handlers. Defaults to `never`.
|
|
11
11
|
* @template R The return type of an individual handler. The `trigger` method will resolve with an array of these values (`R[]`). Defaults to `unknown`.
|
|
12
|
-
* @returns {AsyncHook<T, C, R>} An object with `register` and `trigger` methods.
|
|
13
12
|
*
|
|
14
13
|
* @example
|
|
15
14
|
* ```ts
|
|
16
|
-
* // Simple hook
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* onTaskStart.register(taskName => {
|
|
21
|
-
* console.log(`[Logger] Task started: ${taskName}`);
|
|
22
|
-
* });
|
|
23
|
-
*
|
|
24
|
-
* const registration = onTaskStart.register(async taskName => {
|
|
25
|
-
* await new Promise(resolve => setTimeout(resolve, 50));
|
|
26
|
-
* console.log(`[Notifier] Notifying that task started: ${taskName}`);
|
|
27
|
-
* });
|
|
28
|
-
*
|
|
29
|
-
* await onTaskStart.trigger('Process Data');
|
|
30
|
-
* // [Logger] Task started: Process Data
|
|
31
|
-
* // [Notifier] Notifying that task started: Process Data
|
|
32
|
-
*
|
|
33
|
-
* registration.unregister();
|
|
34
|
-
* console.log('Notifier unregistered.');
|
|
35
|
-
*
|
|
36
|
-
* await onTaskStart.trigger('Finalize Report');
|
|
37
|
-
* // [Logger] Task started: Finalize Report
|
|
38
|
-
* }
|
|
15
|
+
* // 1. Simple hook (Signal only, no data)
|
|
16
|
+
* const onInit = asyncHook();
|
|
17
|
+
* onInit(() => console.log('Initialized'));
|
|
18
|
+
* await onInit.trigger();
|
|
39
19
|
* ```
|
|
40
20
|
*
|
|
41
21
|
* @example
|
|
42
22
|
* ```ts
|
|
43
|
-
* // Hook with
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* onTaskComplete.register((taskName, context) => {
|
|
50
|
-
* console.log(`[Audit] Task '${taskName}' completed by user ${context.userId}.`);
|
|
51
|
-
* return true; // Audit successful
|
|
52
|
-
* });
|
|
23
|
+
* // 2. Hook with data, no context
|
|
24
|
+
* const onMessage = asyncHook<string>();
|
|
25
|
+
* onMessage((msg) => console.log('Received:', msg));
|
|
26
|
+
* await onMessage.trigger('Hello');
|
|
27
|
+
* ```
|
|
53
28
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* // 3. Hook with context
|
|
32
|
+
* type Context = { user: string };
|
|
33
|
+
* const onAction = asyncHook<string, Context>();
|
|
58
34
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* { userId: 123, transactionId: 'abc-456' }
|
|
62
|
-
* );
|
|
35
|
+
* // Note: Handlers receive context as the second argument
|
|
36
|
+
* onAction((action, ctx) => console.log(action, ctx.user));
|
|
63
37
|
*
|
|
64
|
-
*
|
|
65
|
-
* }
|
|
38
|
+
* await onAction.trigger('Click', { user: 'Alice' });
|
|
66
39
|
* ```
|
|
67
40
|
*/
|
|
68
41
|
export function asyncHook() {
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
42
|
+
const handles = new Set();
|
|
43
|
+
function register(handler) {
|
|
44
|
+
const handle = {
|
|
45
|
+
handler,
|
|
46
|
+
registration: {
|
|
74
47
|
unregister() {
|
|
75
|
-
|
|
76
|
-
if (index > -1) {
|
|
77
|
-
handlers.splice(index, 1);
|
|
78
|
-
}
|
|
48
|
+
handles.delete(handle);
|
|
79
49
|
},
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
handles.add(handle);
|
|
53
|
+
return handle.registration;
|
|
54
|
+
}
|
|
55
|
+
async function trigger(value, context) {
|
|
56
|
+
const returnValues = [];
|
|
57
|
+
// Create a snapshot of handlers to safely handle unregistrations during execution.
|
|
58
|
+
for (const handle of [...handles]) {
|
|
59
|
+
const returnValue = await handle.handler(value, context);
|
|
60
|
+
returnValues.push(returnValue);
|
|
61
|
+
}
|
|
62
|
+
return returnValues;
|
|
63
|
+
}
|
|
64
|
+
function removeAll() {
|
|
65
|
+
handles.clear();
|
|
66
|
+
}
|
|
67
|
+
const hook = register;
|
|
68
|
+
hook.register = register;
|
|
69
|
+
hook.trigger = trigger;
|
|
70
|
+
hook.removeAll = removeAll;
|
|
71
|
+
return hook;
|
|
95
72
|
}
|