@actor-system/ask 0.0.1

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.
@@ -0,0 +1,56 @@
1
+ const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
2
+ const TIME_LEN = 10; // 48 bits encoded in 10 Base32 chars
3
+ const RAND_LEN = 16; // 80 bits encoded in 16 Base32 chars
4
+ const ENCODING_LEN = ENCODING.length;
5
+ let lastTime = -1;
6
+ let lastRand = [];
7
+ function encodeTime(num, len) {
8
+ let str = "";
9
+ for (let i = len - 1; i >= 0; i--) {
10
+ const mod = num % ENCODING_LEN;
11
+ str = ENCODING[mod] + str;
12
+ num = Math.floor(num / ENCODING_LEN);
13
+ }
14
+ return str;
15
+ }
16
+ function getRandomBytes(size) {
17
+ if (typeof crypto !== "undefined" &&
18
+ typeof crypto.getRandomValues === "function") {
19
+ const buf = new Uint8Array(size);
20
+ crypto.getRandomValues(buf);
21
+ return buf;
22
+ }
23
+ throw new Error("crypto.getRandomValues is not available in this environment.");
24
+ }
25
+ function incrementRandom(rand) {
26
+ for (let i = rand.length - 1; i >= 0; i--) {
27
+ rand[i]++;
28
+ if (rand[i] < ENCODING_LEN) {
29
+ break;
30
+ }
31
+ rand[i] = 0;
32
+ }
33
+ return rand;
34
+ }
35
+ const generateMonotonicULID = () => {
36
+ const now = Date.now();
37
+ const timePart = encodeTime(now, TIME_LEN);
38
+ let randBytes;
39
+ if (now === lastTime) {
40
+ lastRand = incrementRandom(lastRand);
41
+ randBytes = lastRand;
42
+ }
43
+ else {
44
+ const bytes = getRandomBytes(RAND_LEN);
45
+ randBytes = Array.from(bytes).map((b) => b % ENCODING_LEN);
46
+ lastRand = randBytes.slice();
47
+ lastTime = now;
48
+ }
49
+ let randPart = "";
50
+ for (let i = 0; i < RAND_LEN; i++) {
51
+ randPart += ENCODING[randBytes[i]];
52
+ }
53
+ return timePart + randPart;
54
+ };
55
+
56
+ export { generateMonotonicULID };
@@ -0,0 +1,17 @@
1
+ import { ActorRef } from '@actor-system/core';
2
+ import { CreateRequest } from './ask-with-callback.js';
3
+
4
+ interface AskSyncOptions<out Req, in Res> {
5
+ to: ActorRef<Req>;
6
+ request: CreateRequest<Req, Res>;
7
+ }
8
+ /**
9
+ * If an actor's mailbox uses a synchronous processing strategy,
10
+ * this function can be used to perform an ask operation synchronously.
11
+ *
12
+ * This method will throw an error if the actor does not respond synchronously.
13
+ */
14
+ declare const askSync: <const Req, Res>(options: AskSyncOptions<Req, Res>) => Res;
15
+
16
+ export { askSync };
17
+ export type { AskSyncOptions };
@@ -0,0 +1,17 @@
1
+ import { ActorRef } from '@actor-system/core/internal';
2
+ import { CreateRequest } from './ask-with-callback.internal.js';
3
+
4
+ interface AskSyncOptions<out Req, in Res> {
5
+ to: ActorRef<Req>;
6
+ request: CreateRequest<Req, Res>;
7
+ }
8
+ /**
9
+ * If an actor's mailbox uses a synchronous processing strategy,
10
+ * this function can be used to perform an ask operation synchronously.
11
+ *
12
+ * This method will throw an error if the actor does not respond synchronously.
13
+ */
14
+ declare const askSync: <const Req, Res>(options: AskSyncOptions<Req, Res>) => Res;
15
+
16
+ export { askSync };
17
+ export type { AskSyncOptions };
@@ -0,0 +1,41 @@
1
+ import { PriorityScheduler } from '@actor-system/core';
2
+ import { _askWithCallback } from './ask-with-callback.js';
3
+ import { AskTimeoutError } from './timeout.js';
4
+
5
+ /**
6
+ * If an actor's mailbox uses a synchronous processing strategy,
7
+ * this function can be used to perform an ask operation synchronously.
8
+ *
9
+ * This method will throw an error if the actor does not respond synchronously.
10
+ */
11
+ const askSync = (options) => {
12
+ const { to } = options;
13
+ let msgRef = null;
14
+ let error;
15
+ _askWithCallback({
16
+ ...options,
17
+ timeout: undefined,
18
+ callback: (response) => {
19
+ if (response instanceof AskTimeoutError) {
20
+ error = response;
21
+ }
22
+ else {
23
+ msgRef = { msg: response };
24
+ }
25
+ },
26
+ props: {
27
+ mailboxConfig: {
28
+ scheduler: PriorityScheduler.create("sync"),
29
+ },
30
+ },
31
+ });
32
+ if (error) {
33
+ throw error;
34
+ }
35
+ if (!msgRef) {
36
+ throw new AskTimeoutError(`Ask to '${to.path}' did not respond synchronously. Ensure the actor's mailbox is set to synchronous processing.`, to, NaN, undefined);
37
+ }
38
+ return msgRef.msg;
39
+ };
40
+
41
+ export { askSync };
@@ -0,0 +1,6 @@
1
+ import { ActorRef } from '@actor-system/core';
2
+ import { AskTimeoutError } from './timeout.js';
3
+
4
+ type CreateRequest<out Req, in Res> = (replyTo: ActorRef<Res | AskTimeoutError>) => Req;
5
+
6
+ export type { CreateRequest };
@@ -0,0 +1,6 @@
1
+ import { ActorRef } from '@actor-system/core/internal';
2
+ import { AskTimeoutError } from './timeout.internal.js';
3
+
4
+ type CreateRequest<out Req, in Res> = (replyTo: ActorRef<Res | AskTimeoutError>) => Req;
5
+
6
+ export type { CreateRequest };
@@ -0,0 +1,41 @@
1
+ import { receiveMessage, stopped } from '@actor-system/behaviors';
2
+ import { $system, signal } from '@actor-system/core';
3
+ import { generateMonotonicULID } from './__bundle__/shared/dist/monotonicULID.js';
4
+ import { AskTimeoutError } from './timeout.js';
5
+
6
+ /**
7
+ * @internal
8
+ *
9
+ * Internal function to perform an ask operation with a callback.
10
+ * This function creates a temporary actor to handle the response and
11
+ * invokes the provided callback with the response or an AskTimeoutError.
12
+ */
13
+ const _askWithCallback = (options) => {
14
+ const { to, callback, request: createRequest, timeout, props } = options;
15
+ let done = false;
16
+ let timer;
17
+ if (timeout !== undefined) {
18
+ timer = setTimeout(() => {
19
+ if (done)
20
+ return; // If already done, do not proceed
21
+ replyTo.tell(new AskTimeoutError(`Ask to '${to.path}' timed out after ${timeout}ms`, to, timeout));
22
+ }, timeout);
23
+ }
24
+ const replyTo = to[$system].systemActorOf(receiveMessage((msg) => {
25
+ if (!(msg instanceof AskTimeoutError) && !done) {
26
+ clearTimeout(timer);
27
+ callback(msg);
28
+ done = true; // Mark as done to prevent further processing
29
+ }
30
+ return stopped;
31
+ }).receiveSignal((post) => {
32
+ if (signal.isPostStop(post) && !done) {
33
+ clearTimeout(timer);
34
+ callback(new AskTimeoutError(`Ask to '${to.path}' timed out after ${timeout ?? Number.POSITIVE_INFINITY}ms`, to, timeout ?? Number.POSITIVE_INFINITY));
35
+ done = true; // Mark as done to prevent further processing
36
+ }
37
+ }), `ask-temp-${generateMonotonicULID()}`, props);
38
+ to.tell(createRequest(replyTo));
39
+ };
40
+
41
+ export { _askWithCallback };
package/dist/ask.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { ActorRef } from '@actor-system/core';
2
+ import { CreateRequest } from './ask-with-callback.js';
3
+
4
+ interface AskOptions<Req, Res> {
5
+ timeout?: number;
6
+ to: ActorRef<Req>;
7
+ request: CreateRequest<Req, Res>;
8
+ }
9
+ declare const ask: <const Req, Res>(options: AskOptions<Req, Res>) => Promise<Res>;
10
+
11
+ export { ask };
12
+ export type { AskOptions };
@@ -0,0 +1,12 @@
1
+ import { ActorRef } from '@actor-system/core/internal';
2
+ import { CreateRequest } from './ask-with-callback.internal.js';
3
+
4
+ interface AskOptions<Req, Res> {
5
+ timeout?: number;
6
+ to: ActorRef<Req>;
7
+ request: CreateRequest<Req, Res>;
8
+ }
9
+ declare const ask: <const Req, Res>(options: AskOptions<Req, Res>) => Promise<Res>;
10
+
11
+ export { ask };
12
+ export type { AskOptions };
package/dist/ask.js ADDED
@@ -0,0 +1,17 @@
1
+ import { _askWithCallback } from './ask-with-callback.js';
2
+ import { AskTimeoutError } from './timeout.js';
3
+
4
+ const ask = (options) => new Promise((resolve, reject) => _askWithCallback({
5
+ timeout: 1000,
6
+ ...options,
7
+ callback: (response) => {
8
+ if (response instanceof AskTimeoutError) {
9
+ reject(response);
10
+ }
11
+ else {
12
+ resolve(response);
13
+ }
14
+ },
15
+ }));
16
+
17
+ export { ask };
@@ -0,0 +1,4 @@
1
+ export { AskSyncOptions, askSync } from './ask-sync.js';
2
+ export { AskOptions, ask } from './ask.js';
3
+ export { $timeoutError, AskTimeoutError } from './timeout.js';
4
+ export { Ask, MapResponse, WithAskOptions, withAsk } from './use-ask.js';
@@ -0,0 +1,4 @@
1
+ export { AskSyncOptions, askSync } from './ask-sync.internal.js';
2
+ export { AskOptions, ask } from './ask.internal.js';
3
+ export { $timeoutError, AskTimeoutError } from './timeout.internal.js';
4
+ export { Ask, MapResponse, WithAskOptions, withAsk } from './use-ask.internal.js';
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { askSync } from './ask-sync.js';
2
+ export { ask } from './ask.js';
3
+ export { $timeoutError, AskTimeoutError } from './timeout.js';
4
+ export { withAsk } from './use-ask.js';
@@ -0,0 +1,13 @@
1
+ import { ActorRef } from '@actor-system/core';
2
+ import { Letter } from '@actor-system/mail';
3
+
4
+ declare const $timeoutError = "@@ask.timeout-error";
5
+ declare class AskTimeoutError extends Error implements Letter<typeof $timeoutError> {
6
+ readonly label: typeof $timeoutError;
7
+ readonly from?: ActorRef;
8
+ readonly to: ActorRef;
9
+ readonly timeout: number;
10
+ constructor(message: string, to: ActorRef, timeout: number, from?: ActorRef);
11
+ }
12
+
13
+ export { $timeoutError, AskTimeoutError };
@@ -0,0 +1,13 @@
1
+ import { ActorRef } from '@actor-system/core/internal';
2
+ import { Letter } from '@actor-system/mail/internal';
3
+
4
+ declare const $timeoutError = "@@ask.timeout-error";
5
+ declare class AskTimeoutError extends Error implements Letter<typeof $timeoutError> {
6
+ readonly label: typeof $timeoutError;
7
+ readonly from?: ActorRef;
8
+ readonly to: ActorRef;
9
+ readonly timeout: number;
10
+ constructor(message: string, to: ActorRef, timeout: number, from?: ActorRef);
11
+ }
12
+
13
+ export { $timeoutError, AskTimeoutError };
@@ -0,0 +1,16 @@
1
+ const $timeoutError = "@@ask.timeout-error";
2
+ class AskTimeoutError extends Error {
3
+ label = $timeoutError;
4
+ from;
5
+ to;
6
+ timeout;
7
+ constructor(message, to, timeout, from) {
8
+ super(message);
9
+ this.name = "AskTimeoutError";
10
+ this.from = from;
11
+ this.to = to;
12
+ this.timeout = timeout;
13
+ }
14
+ }
15
+
16
+ export { $timeoutError, AskTimeoutError };
@@ -0,0 +1,25 @@
1
+ import { ActorRef, ActorContext, behavior } from '@actor-system/core';
2
+ import { AskTimeoutError } from './timeout.js';
3
+ import { CreateRequest } from './ask-with-callback.js';
4
+
5
+ type MapResponse<in Res, out T> = (response: Res | AskTimeoutError) => T;
6
+ type WithAskOptions<T, Req, Res> = T extends AskTimeoutError ? WithAskOptions.WithPartialMapResponse<T, Req, Res> : WithAskOptions.WithMapResponse<T, Req, Res>;
7
+ declare namespace WithAskOptions {
8
+ interface WithMapResponse<T, Req, Res> {
9
+ to: ActorRef<Req>;
10
+ request: CreateRequest<Req, Res>;
11
+ timeout?: number;
12
+ mapResponse: MapResponse<Res, T>;
13
+ }
14
+ interface WithPartialMapResponse<T, Req, Res> {
15
+ to: ActorRef<Req>;
16
+ request: CreateRequest<Req, Res>;
17
+ timeout?: number;
18
+ mapResponse?: MapResponse<Res, T>;
19
+ }
20
+ }
21
+ type Ask<T> = <const Req, Res>(options: WithAskOptions<T, Req, Res>) => void;
22
+ declare const withAsk: <T>(factory: (ask: Ask<T>, context: ActorContext<T>) => behavior.Behavior<T>) => behavior.Behavior<T>;
23
+
24
+ export { WithAskOptions, withAsk };
25
+ export type { Ask, MapResponse };
@@ -0,0 +1,25 @@
1
+ import { ActorRef, ActorContext, behavior } from '@actor-system/core/internal';
2
+ import { AskTimeoutError } from './timeout.internal.js';
3
+ import { CreateRequest } from './ask-with-callback.internal.js';
4
+
5
+ type MapResponse<in Res, out T> = (response: Res | AskTimeoutError) => T;
6
+ type WithAskOptions<T, Req, Res> = T extends AskTimeoutError ? WithAskOptions.WithPartialMapResponse<T, Req, Res> : WithAskOptions.WithMapResponse<T, Req, Res>;
7
+ declare namespace WithAskOptions {
8
+ interface WithMapResponse<T, Req, Res> {
9
+ to: ActorRef<Req>;
10
+ request: CreateRequest<Req, Res>;
11
+ timeout?: number;
12
+ mapResponse: MapResponse<Res, T>;
13
+ }
14
+ interface WithPartialMapResponse<T, Req, Res> {
15
+ to: ActorRef<Req>;
16
+ request: CreateRequest<Req, Res>;
17
+ timeout?: number;
18
+ mapResponse?: MapResponse<Res, T>;
19
+ }
20
+ }
21
+ type Ask<T> = <const Req, Res>(options: WithAskOptions<T, Req, Res>) => void;
22
+ declare const withAsk: <T>(factory: (ask: Ask<T>, context: ActorContext<T>) => behavior.Behavior<T>) => behavior.Behavior<T>;
23
+
24
+ export { WithAskOptions, withAsk };
25
+ export type { Ask, MapResponse };
@@ -0,0 +1,23 @@
1
+ import { _askWithCallback } from './ask-with-callback.js';
2
+ import * as behaviors from '@actor-system/behaviors';
3
+
4
+ const $ask = Symbol("ask");
5
+ const withAsk = (factory) => behaviors.setup((context) => factory(useAsk(context), context));
6
+ const useAsk = (context) => {
7
+ if (context[$ask])
8
+ return context[$ask];
9
+ const ask = (options) => {
10
+ const mapResponse = ("mapResponse" in options ? options.mapResponse : undefined) ??
11
+ ((response) => response);
12
+ _askWithCallback({
13
+ timeout: 1000,
14
+ ...options,
15
+ callback: (response) => {
16
+ context.self.tell(mapResponse(response));
17
+ },
18
+ });
19
+ };
20
+ return (context[$ask] = ask);
21
+ };
22
+
23
+ export { withAsk };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@actor-system/ask",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "scripts": {
6
+ "type-check": "tsc -b src",
7
+ "lint": "eslint src",
8
+ "lint:fix": "eslint src --fix",
9
+ "publint": "publint",
10
+ "build:tsc:internal": "tsc -b src/tsconfig.lib.json",
11
+ "build:tsc:trimmed": "tsc -b src/tsconfig.lib.trimmed.json",
12
+ "build:rollup": "rollup -c node:config/rollup-config"
13
+ },
14
+ "module": "./dist/index.js",
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ },
25
+ "./package.json": "./package.json"
26
+ },
27
+ "bundleDependencies": [
28
+ "@actor-system/shared"
29
+ ],
30
+ "dependencies": {
31
+ "@actor-system/behaviors": "0.0.1",
32
+ "@actor-system/core": "0.0.1",
33
+ "@actor-system/mail": "0.0.1"
34
+ },
35
+ "devDependencies": {
36
+ "@actor-system/testing": "0.0.1",
37
+ "@actor-system/shared": "0.0.1",
38
+ "config": "^1.0.0"
39
+ }
40
+ }