@tstdl/base 0.93.74 → 0.93.77
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ai/prompts/instructions-formatter.d.ts +68 -5
- package/ai/prompts/instructions-formatter.js +11 -3
- package/document-management/server/services/document-management-observation.service.js +4 -4
- package/orm/server/transaction.d.ts +7 -7
- package/orm/server/transaction.js +51 -25
- package/package.json +4 -4
- package/utils/async-hook/async-hook.d.ts +61 -68
- package/utils/async-hook/async-hook.js +49 -72
|
@@ -1,25 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defines the visual formatting style for a list of instructions.
|
|
3
|
+
* - `sections`: Renders keys as Markdown headers (e.g., `# Header`).
|
|
4
|
+
* - `ordered`: Renders items as a numbered list (e.g., `1. Item`).
|
|
5
|
+
* - `unordered`: Renders items as a bulleted list (e.g., `- Item`).
|
|
6
|
+
*/
|
|
1
7
|
export type ListStyle = 'sections' | 'ordered' | 'unordered';
|
|
2
|
-
|
|
8
|
+
/**
|
|
9
|
+
* The content of an instructions list, which can be either:
|
|
10
|
+
* - An array of strings (simple list items).
|
|
11
|
+
* - A nested `Instructions` object (key-value pairs or further lists).
|
|
12
|
+
*/
|
|
13
|
+
export type InstructionsListContent = string[] | Instructions;
|
|
14
|
+
/**
|
|
15
|
+
* A container representing a specific grouping of instructions with a defined style.
|
|
16
|
+
* This is usually created via factory functions like `sections()`, `orderedList()`, or `unorderedList()`.
|
|
17
|
+
*/
|
|
3
18
|
export type InstructionsList = {
|
|
19
|
+
/** The rendering style to apply to this group. */
|
|
4
20
|
style: ListStyle;
|
|
21
|
+
/** An optional high-level instruction or description associated with this group. */
|
|
5
22
|
instruction?: string;
|
|
23
|
+
/** The content of the list, either an array of strings or a nested key-value map. */
|
|
6
24
|
items: InstructionsListContent;
|
|
7
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
* A recursive dictionary structure for defining structured instructions.
|
|
28
|
+
* Keys typically represent labels or headers, while values represent the content.
|
|
29
|
+
*/
|
|
8
30
|
export type Instructions = {
|
|
9
31
|
[key: string]: string | string[] | InstructionsList | Instructions;
|
|
10
32
|
};
|
|
33
|
+
/**
|
|
34
|
+
* Creates a container where content is rendered as Markdown sections.
|
|
35
|
+
* Keys in the object map become headers (e.g., `# Key`), and nested items reset indentation.
|
|
36
|
+
*
|
|
37
|
+
* @param items - The content of the section.
|
|
38
|
+
*/
|
|
11
39
|
export declare function sections(items: InstructionsListContent): InstructionsList;
|
|
40
|
+
/**
|
|
41
|
+
* Creates a container where content is rendered as Markdown sections with a preamble.
|
|
42
|
+
*
|
|
43
|
+
* @param instruction - A general instruction describing this section.
|
|
44
|
+
* @param items - The content of the section.
|
|
45
|
+
*/
|
|
12
46
|
export declare function sections(instruction: string, items: InstructionsListContent): InstructionsList;
|
|
47
|
+
/**
|
|
48
|
+
* Creates a container rendered as a numbered list.
|
|
49
|
+
*
|
|
50
|
+
* @param items - The list items or map.
|
|
51
|
+
*/
|
|
13
52
|
export declare function orderedList(items: InstructionsListContent): InstructionsList;
|
|
53
|
+
/**
|
|
54
|
+
* Creates a container rendered as a numbered list with a preamble.
|
|
55
|
+
*
|
|
56
|
+
* @param instruction - An instruction describing the list.
|
|
57
|
+
* @param items - The list items or map.
|
|
58
|
+
*/
|
|
14
59
|
export declare function orderedList(instruction: string, items: InstructionsListContent): InstructionsList;
|
|
60
|
+
/**
|
|
61
|
+
* Creates a container rendered as a bulleted list.
|
|
62
|
+
*
|
|
63
|
+
* @param items - The list items or map.
|
|
64
|
+
*/
|
|
15
65
|
export declare function unorderedList(items: InstructionsListContent): InstructionsList;
|
|
66
|
+
/**
|
|
67
|
+
* Creates a container rendered as a bulleted list with a preamble.
|
|
68
|
+
*
|
|
69
|
+
* @param instruction - An instruction describing the list.
|
|
70
|
+
* @param items - The list items or map.
|
|
71
|
+
*/
|
|
16
72
|
export declare function unorderedList(instruction: string, items: InstructionsListContent): InstructionsList;
|
|
17
73
|
/**
|
|
18
|
-
* Formats instructions into a string representation suitable for AI prompts.
|
|
19
|
-
*
|
|
20
|
-
*
|
|
74
|
+
* Formats a structured instructions object into a string representation suitable for AI prompts (Markdown).
|
|
75
|
+
*
|
|
76
|
+
* It recursively handles:
|
|
77
|
+
* - `sections`: Creates headers (H1, H2...).
|
|
78
|
+
* - `ordered` / `unordered`: Creates indented lists.
|
|
79
|
+
* - `Instructions`: Objects are formatted as key-value pairs (e.g., `- **Key:** Value`).
|
|
80
|
+
*
|
|
81
|
+
* @param node - The root instructions object, array, or instructions list wrapper.
|
|
82
|
+
* @param options - Formatting options.
|
|
83
|
+
* @param options.initialDepth - The starting indentation level (default: 0).
|
|
84
|
+
* @returns A formatted string.
|
|
21
85
|
*/
|
|
22
86
|
export declare function formatInstructions(node: Instructions | InstructionsList | string[], options?: {
|
|
23
87
|
initialDepth?: number;
|
|
24
88
|
}): string;
|
|
25
|
-
export {};
|
|
@@ -146,9 +146,17 @@ function processNode(node, context) {
|
|
|
146
146
|
}).join(separator);
|
|
147
147
|
}
|
|
148
148
|
/**
|
|
149
|
-
* Formats instructions into a string representation suitable for AI prompts.
|
|
150
|
-
*
|
|
151
|
-
*
|
|
149
|
+
* Formats a structured instructions object into a string representation suitable for AI prompts (Markdown).
|
|
150
|
+
*
|
|
151
|
+
* It recursively handles:
|
|
152
|
+
* - `sections`: Creates headers (H1, H2...).
|
|
153
|
+
* - `ordered` / `unordered`: Creates indented lists.
|
|
154
|
+
* - `Instructions`: Objects are formatted as key-value pairs (e.g., `- **Key:** Value`).
|
|
155
|
+
*
|
|
156
|
+
* @param node - The root instructions object, array, or instructions list wrapper.
|
|
157
|
+
* @param options - Formatting options.
|
|
158
|
+
* @param options.initialDepth - The starting indentation level (default: 0).
|
|
159
|
+
* @returns A formatted string.
|
|
152
160
|
*/
|
|
153
161
|
export function formatInstructions(node, options = {}) {
|
|
154
162
|
// Heuristic: If passing a raw object, assume it's a Root Section unless specified otherwise.
|
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tstdl/base",
|
|
3
|
-
"version": "0.93.
|
|
3
|
+
"version": "0.93.77",
|
|
4
4
|
"author": "Patrick Hein",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -150,7 +150,7 @@
|
|
|
150
150
|
"@zxcvbn-ts/language-de": "^3.0",
|
|
151
151
|
"@zxcvbn-ts/language-en": "^3.0",
|
|
152
152
|
"drizzle-orm": "^0.45",
|
|
153
|
-
"file-type": "^21.
|
|
153
|
+
"file-type": "^21.3",
|
|
154
154
|
"genkit": "^1.27",
|
|
155
155
|
"handlebars": "^4.7",
|
|
156
156
|
"minio": "^8.0",
|
|
@@ -181,13 +181,13 @@
|
|
|
181
181
|
"concurrently": "9.2",
|
|
182
182
|
"drizzle-kit": "0.31",
|
|
183
183
|
"eslint": "9.39",
|
|
184
|
-
"globals": "
|
|
184
|
+
"globals": "17.0",
|
|
185
185
|
"tsc-alias": "1.8",
|
|
186
186
|
"typedoc-github-wiki-theme": "2.1",
|
|
187
187
|
"typedoc-plugin-markdown": "4.9",
|
|
188
188
|
"typedoc-plugin-missing-exports": "4.1",
|
|
189
189
|
"typescript": "5.9",
|
|
190
|
-
"typescript-eslint": "8.
|
|
190
|
+
"typescript-eslint": "8.51"
|
|
191
191
|
},
|
|
192
192
|
"overrides": {
|
|
193
193
|
"drizzle-kit": {
|
|
@@ -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
|
}
|