@orpc/experimental-durable-iterator 0.0.0-next.01f0b7a
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/LICENSE +21 -0
- package/README.md +81 -0
- package/dist/client/index.d.mts +69 -0
- package/dist/client/index.d.ts +69 -0
- package/dist/client/index.mjs +160 -0
- package/dist/durable-object/index.d.mts +286 -0
- package/dist/durable-object/index.d.ts +286 -0
- package/dist/durable-object/index.mjs +536 -0
- package/dist/index.d.mts +85 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.mjs +105 -0
- package/dist/shared/experimental-durable-iterator.B3M42lLK.mjs +29 -0
- package/dist/shared/experimental-durable-iterator.BRB0hiXN.mjs +15 -0
- package/dist/shared/experimental-durable-iterator.C6YPLbUA.d.ts +28 -0
- package/dist/shared/experimental-durable-iterator.DQjHfIr1.d.mts +42 -0
- package/dist/shared/experimental-durable-iterator.DQjHfIr1.d.ts +42 -0
- package/dist/shared/experimental-durable-iterator.DZOLL3sf.mjs +47 -0
- package/dist/shared/experimental-durable-iterator.DrenXxND.d.mts +28 -0
- package/package.json +55 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import * as _orpc_contract from '@orpc/contract';
|
|
2
|
+
import { AsyncIteratorClass } from '@orpc/shared';
|
|
3
|
+
import * as v from 'valibot';
|
|
4
|
+
import { C as ClientDurableIterator } from './shared/experimental-durable-iterator.C6YPLbUA.js';
|
|
5
|
+
import { D as DurableIteratorObject, I as InferDurableIteratorObjectRPC } from './shared/experimental-durable-iterator.DQjHfIr1.js';
|
|
6
|
+
export { a as DurableIteratorObjectDef, b as DurableIteratorTokenPayload, p as parseDurableIteratorToken, s as signDurableIteratorToken, v as verifyDurableIteratorToken } from './shared/experimental-durable-iterator.DQjHfIr1.js';
|
|
7
|
+
import { Context, Router } from '@orpc/server';
|
|
8
|
+
import { StandardHandlerPlugin, StandardHandlerOptions } from '@orpc/server/standard';
|
|
9
|
+
import '@orpc/client';
|
|
10
|
+
import '@orpc/client/plugins';
|
|
11
|
+
|
|
12
|
+
declare const DURABLE_ITERATOR_TOKEN_PARAM: "token";
|
|
13
|
+
declare const DURABLE_ITERATOR_ID_PARAM: "id";
|
|
14
|
+
declare const DURABLE_ITERATOR_PLUGIN_HEADER_KEY: "x-orpc-dei";
|
|
15
|
+
declare const DURABLE_ITERATOR_PLUGIN_HEADER_VALUE: "1";
|
|
16
|
+
|
|
17
|
+
declare const durableIteratorContract: {
|
|
18
|
+
updateToken: _orpc_contract.ContractProcedureBuilderWithInput<v.ObjectSchema<{
|
|
19
|
+
readonly token: v.StringSchema<undefined>;
|
|
20
|
+
}, undefined>, _orpc_contract.Schema<unknown, unknown>, Record<never, never>, Record<never, never>>;
|
|
21
|
+
subscribe: _orpc_contract.ContractProcedureBuilderWithOutput<_orpc_contract.Schema<unknown, unknown>, _orpc_contract.Schema<AsyncIteratorClass<any, unknown, unknown>, AsyncIteratorClass<any, unknown, unknown>>, Record<never, never>, Record<never, never>>;
|
|
22
|
+
call: _orpc_contract.ContractProcedureBuilderWithInput<v.ObjectSchema<{
|
|
23
|
+
readonly path: v.TupleWithRestSchema<[v.StringSchema<undefined>], v.StringSchema<undefined>, undefined>;
|
|
24
|
+
readonly input: v.UnknownSchema;
|
|
25
|
+
}, undefined>, _orpc_contract.Schema<unknown, unknown>, Record<never, never>, Record<never, never>>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
declare class DurableIteratorError extends Error {
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface DurableIteratorOptions<T extends DurableIteratorObject<any>, RPC extends InferDurableIteratorObjectRPC<T>> {
|
|
32
|
+
/**
|
|
33
|
+
* The signing key used to sign the token
|
|
34
|
+
*/
|
|
35
|
+
signingKey: string;
|
|
36
|
+
/**
|
|
37
|
+
* Time to live for the token in seconds.
|
|
38
|
+
* After expiration, the token will no longer be valid.
|
|
39
|
+
*
|
|
40
|
+
* @default 24 hours (60 * 60 * 24)
|
|
41
|
+
*/
|
|
42
|
+
tokenTTLSeconds?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Tags to attach to the token.
|
|
45
|
+
*/
|
|
46
|
+
tags?: readonly string[];
|
|
47
|
+
/**
|
|
48
|
+
* Token's attachment
|
|
49
|
+
*/
|
|
50
|
+
att?: unknown;
|
|
51
|
+
/**
|
|
52
|
+
* The methods that are allowed to be called remotely.
|
|
53
|
+
*
|
|
54
|
+
* @warning Please use .rpc method to set this field in case ts complains about value you pass
|
|
55
|
+
*/
|
|
56
|
+
rpc?: readonly RPC[];
|
|
57
|
+
}
|
|
58
|
+
declare class DurableIterator<T extends DurableIteratorObject<any>, RPC extends InferDurableIteratorObjectRPC<T> = never> implements PromiseLike<ClientDurableIterator<T, RPC>> {
|
|
59
|
+
private readonly chn;
|
|
60
|
+
private readonly options;
|
|
61
|
+
constructor(chn: string, options: DurableIteratorOptions<T, RPC>);
|
|
62
|
+
/**
|
|
63
|
+
* List of methods that are allowed to be called remotely.
|
|
64
|
+
*/
|
|
65
|
+
rpc<U extends InferDurableIteratorObjectRPC<T>>(...rpc: U[]): Omit<DurableIterator<T, U>, 'rpc'>;
|
|
66
|
+
then<TResult1 = ClientDurableIterator<T, RPC>, TResult2 = never>(onfulfilled?: ((value: ClientDurableIterator<T, RPC>) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined): PromiseLike<TResult1 | TResult2>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface DurableIteratorHandlerPluginContext {
|
|
70
|
+
isClientDurableIteratorOutput?: boolean;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* @see {@link https://orpc.unnoq.com/docs/integrations/durable-iterator Durable Iterator Integration}
|
|
74
|
+
*/
|
|
75
|
+
declare class DurableIteratorHandlerPlugin<T extends Context> implements StandardHandlerPlugin<T> {
|
|
76
|
+
readonly CONTEXT_SYMBOL: symbol;
|
|
77
|
+
/**
|
|
78
|
+
* make sure run after batch plugin
|
|
79
|
+
*/
|
|
80
|
+
order: number;
|
|
81
|
+
init(options: StandardHandlerOptions<T>, _router: Router<any, T>): void;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export { DURABLE_ITERATOR_ID_PARAM, DURABLE_ITERATOR_PLUGIN_HEADER_KEY, DURABLE_ITERATOR_PLUGIN_HEADER_VALUE, DURABLE_ITERATOR_TOKEN_PARAM, DurableIterator, DurableIteratorError, DurableIteratorHandlerPlugin, DurableIteratorObject, InferDurableIteratorObjectRPC, durableIteratorContract };
|
|
85
|
+
export type { DurableIteratorHandlerPluginContext, DurableIteratorOptions };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { s as signDurableIteratorToken, D as DurableIteratorError, a as DURABLE_ITERATOR_PLUGIN_HEADER_VALUE, b as DURABLE_ITERATOR_PLUGIN_HEADER_KEY } from './shared/experimental-durable-iterator.DZOLL3sf.mjs';
|
|
2
|
+
export { d as DURABLE_ITERATOR_ID_PARAM, c as DURABLE_ITERATOR_TOKEN_PARAM, p as parseDurableIteratorToken, v as verifyDurableIteratorToken } from './shared/experimental-durable-iterator.DZOLL3sf.mjs';
|
|
3
|
+
export { d as durableIteratorContract } from './shared/experimental-durable-iterator.BRB0hiXN.mjs';
|
|
4
|
+
import { AsyncIteratorClass } from '@orpc/shared';
|
|
5
|
+
import { c as createClientDurableIterator, g as getClientDurableIteratorToken } from './shared/experimental-durable-iterator.B3M42lLK.mjs';
|
|
6
|
+
import '@orpc/client';
|
|
7
|
+
import '@orpc/client/plugins';
|
|
8
|
+
import '@orpc/client/websocket';
|
|
9
|
+
import 'partysocket';
|
|
10
|
+
import '@orpc/server/helpers';
|
|
11
|
+
import 'valibot';
|
|
12
|
+
import '@orpc/contract';
|
|
13
|
+
|
|
14
|
+
class DurableIterator {
|
|
15
|
+
constructor(chn, options) {
|
|
16
|
+
this.chn = chn;
|
|
17
|
+
this.options = options;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* List of methods that are allowed to be called remotely.
|
|
21
|
+
*/
|
|
22
|
+
rpc(...rpc) {
|
|
23
|
+
return new DurableIterator(this.chn, {
|
|
24
|
+
...this.options,
|
|
25
|
+
rpc
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
then(onfulfilled, onrejected) {
|
|
29
|
+
return (async () => {
|
|
30
|
+
const tokenTTLSeconds = this.options.tokenTTLSeconds ?? 60 * 60 * 24;
|
|
31
|
+
const nowInSeconds = Math.floor(Date.now() / 1e3);
|
|
32
|
+
const token = await signDurableIteratorToken(this.options.signingKey, {
|
|
33
|
+
chn: this.chn,
|
|
34
|
+
tags: this.options.tags,
|
|
35
|
+
att: this.options.att,
|
|
36
|
+
rpc: this.options.rpc,
|
|
37
|
+
iat: nowInSeconds,
|
|
38
|
+
exp: nowInSeconds + tokenTTLSeconds
|
|
39
|
+
});
|
|
40
|
+
const iterator = new AsyncIteratorClass(
|
|
41
|
+
() => Promise.reject(new DurableIteratorError("Cannot be iterated directly.")),
|
|
42
|
+
() => Promise.reject(new DurableIteratorError("Cannot be cleaned up directly."))
|
|
43
|
+
);
|
|
44
|
+
const link = {
|
|
45
|
+
call() {
|
|
46
|
+
throw new DurableIteratorError("Cannot call methods directly.");
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const durableIterator = createClientDurableIterator(iterator, link, {
|
|
50
|
+
getToken: () => token
|
|
51
|
+
});
|
|
52
|
+
return durableIterator;
|
|
53
|
+
})().then(onfulfilled, onrejected);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
class DurableIteratorHandlerPlugin {
|
|
58
|
+
CONTEXT_SYMBOL = Symbol("ORPC_DURABLE_ITERATOR_HANDLER_PLUGIN_CONTEXT");
|
|
59
|
+
/**
|
|
60
|
+
* make sure run after batch plugin
|
|
61
|
+
*/
|
|
62
|
+
order = 15e5;
|
|
63
|
+
init(options, _router) {
|
|
64
|
+
options.interceptors ??= [];
|
|
65
|
+
options.clientInterceptors ??= [];
|
|
66
|
+
options.interceptors.unshift(async (options2) => {
|
|
67
|
+
const pluginContext = {};
|
|
68
|
+
const result = await options2.next({
|
|
69
|
+
...options2,
|
|
70
|
+
context: {
|
|
71
|
+
[this.CONTEXT_SYMBOL]: pluginContext,
|
|
72
|
+
...options2.context
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
if (!result.matched) {
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
...result,
|
|
80
|
+
response: {
|
|
81
|
+
...result.response,
|
|
82
|
+
headers: {
|
|
83
|
+
...result.response.headers,
|
|
84
|
+
[DURABLE_ITERATOR_PLUGIN_HEADER_KEY]: pluginContext.isClientDurableIteratorOutput ? DURABLE_ITERATOR_PLUGIN_HEADER_VALUE : void 0
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
options.clientInterceptors.unshift(async (options2) => {
|
|
90
|
+
const pluginContext = options2.context[this.CONTEXT_SYMBOL];
|
|
91
|
+
if (!pluginContext) {
|
|
92
|
+
throw new DurableIteratorError("Plugin context has been corrupted or modified by another plugin or interceptor");
|
|
93
|
+
}
|
|
94
|
+
const output = await options2.next();
|
|
95
|
+
const token = getClientDurableIteratorToken(output);
|
|
96
|
+
if (typeof token === "string") {
|
|
97
|
+
pluginContext.isClientDurableIteratorOutput = true;
|
|
98
|
+
return token;
|
|
99
|
+
}
|
|
100
|
+
return output;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export { DURABLE_ITERATOR_PLUGIN_HEADER_KEY, DURABLE_ITERATOR_PLUGIN_HEADER_VALUE, DurableIterator, DurableIteratorError, DurableIteratorHandlerPlugin, signDurableIteratorToken };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createORPCClient } from '@orpc/client';
|
|
2
|
+
import { isAsyncIteratorObject } from '@orpc/shared';
|
|
3
|
+
import { p as parseDurableIteratorToken } from './experimental-durable-iterator.DZOLL3sf.mjs';
|
|
4
|
+
|
|
5
|
+
const CLIENT_DURABLE_ITERATOR_TOKEN_SYMBOL = Symbol("ORPC_CLIENT_DURABLE_ITERATOR_TOKEN");
|
|
6
|
+
function createClientDurableIterator(iterator, link, options) {
|
|
7
|
+
const proxy = new Proxy(iterator, {
|
|
8
|
+
get(target, prop) {
|
|
9
|
+
const token = options.getToken();
|
|
10
|
+
const { rpc: allowMethods } = parseDurableIteratorToken(token);
|
|
11
|
+
if (prop === CLIENT_DURABLE_ITERATOR_TOKEN_SYMBOL) {
|
|
12
|
+
return token;
|
|
13
|
+
}
|
|
14
|
+
if (typeof prop === "string" && allowMethods?.includes(prop)) {
|
|
15
|
+
return createORPCClient(link, { path: [prop] });
|
|
16
|
+
}
|
|
17
|
+
const v = Reflect.get(target, prop);
|
|
18
|
+
return typeof v === "function" ? v.bind(target) : v;
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
return proxy;
|
|
22
|
+
}
|
|
23
|
+
function getClientDurableIteratorToken(client) {
|
|
24
|
+
if (isAsyncIteratorObject(client)) {
|
|
25
|
+
return Reflect.get(client, CLIENT_DURABLE_ITERATOR_TOKEN_SYMBOL);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { createClientDurableIterator as c, getClientDurableIteratorToken as g };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { oc, type } from '@orpc/contract';
|
|
2
|
+
import * as v from 'valibot';
|
|
3
|
+
|
|
4
|
+
const durableIteratorContract = {
|
|
5
|
+
updateToken: oc.input(v.object({ token: v.string() })).route({ summary: "Update old token with a new one, usually before it expires" }),
|
|
6
|
+
subscribe: oc.route({ summary: "Listen to durable iterator events" }).output(type()),
|
|
7
|
+
call: oc.route({ summary: "Call a remote method" }).input(
|
|
8
|
+
v.object({
|
|
9
|
+
path: v.tupleWithRest([v.string()], v.string()),
|
|
10
|
+
input: v.unknown()
|
|
11
|
+
})
|
|
12
|
+
)
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export { durableIteratorContract as d };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { NestedClient, Client, ThrowableError, ClientLink } from '@orpc/client';
|
|
2
|
+
import { ClientRetryPluginContext } from '@orpc/client/plugins';
|
|
3
|
+
import { AsyncIteratorClass } from '@orpc/shared';
|
|
4
|
+
import { D as DurableIteratorObject, I as InferDurableIteratorObjectRPC } from './experimental-durable-iterator.DQjHfIr1.js';
|
|
5
|
+
|
|
6
|
+
interface ClientDurableIteratorRpcContext extends ClientRetryPluginContext {
|
|
7
|
+
}
|
|
8
|
+
type ClientDurableIteratorRpc<T extends NestedClient<object>> = T extends Client<any, infer UInput, infer UOutput, any> ? Client<ClientDurableIteratorRpcContext, UInput, UOutput, ThrowableError> : {
|
|
9
|
+
[K in keyof T]: T[K] extends NestedClient<object> ? ClientDurableIteratorRpc<T[K]> : never;
|
|
10
|
+
};
|
|
11
|
+
type ClientDurableIterator<T extends DurableIteratorObject<any>, RPC extends InferDurableIteratorObjectRPC<T>> = AsyncIteratorClass<T extends DurableIteratorObject<infer TPayload> ? TPayload : never> & {
|
|
12
|
+
[K in RPC]: T[K] extends (...args: any[]) => (infer R extends NestedClient<object>) ? ClientDurableIteratorRpc<R> : never;
|
|
13
|
+
};
|
|
14
|
+
interface CreateClientDurableIteratorOptions {
|
|
15
|
+
/**
|
|
16
|
+
* The token used to authenticate the client.
|
|
17
|
+
* this is a function because the token is lazy, and dynamic-able
|
|
18
|
+
*/
|
|
19
|
+
getToken: () => string;
|
|
20
|
+
}
|
|
21
|
+
declare function createClientDurableIterator<T extends DurableIteratorObject<any>, RPC extends InferDurableIteratorObjectRPC<T>>(iterator: AsyncIteratorClass<T>, link: ClientLink<object>, options: CreateClientDurableIteratorOptions): ClientDurableIterator<T, RPC>;
|
|
22
|
+
/**
|
|
23
|
+
* If return a token if the client is a Client Durable Iterator.
|
|
24
|
+
*/
|
|
25
|
+
declare function getClientDurableIteratorToken(client: unknown): string | undefined;
|
|
26
|
+
|
|
27
|
+
export { createClientDurableIterator as d, getClientDurableIteratorToken as g };
|
|
28
|
+
export type { ClientDurableIterator as C, ClientDurableIteratorRpcContext as a, ClientDurableIteratorRpc as b, CreateClientDurableIteratorOptions as c };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { NestedClient } from '@orpc/client';
|
|
2
|
+
import { AsyncIteratorClass } from '@orpc/shared';
|
|
3
|
+
import * as v from 'valibot';
|
|
4
|
+
|
|
5
|
+
interface DurableIteratorObjectDef<T extends object> {
|
|
6
|
+
'~eventPayloadType'?: {
|
|
7
|
+
type: T;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
interface DurableIteratorObject<T extends object> {
|
|
11
|
+
'~orpc'?: DurableIteratorObjectDef<T>;
|
|
12
|
+
}
|
|
13
|
+
type InferDurableIteratorObjectRPC<T extends DurableIteratorObject<any>> = Exclude<{
|
|
14
|
+
[K in keyof T]: T[K] extends ((...args: any[]) => NestedClient<object>) ? K : never;
|
|
15
|
+
}[keyof T], keyof AsyncIteratorClass<any>> & string;
|
|
16
|
+
|
|
17
|
+
type DurableIteratorTokenPayload = v.InferOutput<typeof DurableIteratorTokenPayloadSchema>;
|
|
18
|
+
declare const DurableIteratorTokenPayloadSchema: v.ObjectSchema<{
|
|
19
|
+
readonly chn: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.DescriptionAction<string, "Channel name">]>;
|
|
20
|
+
readonly tags: v.SchemaWithPipe<readonly [v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>, v.ReadonlyAction<string[] | undefined>, v.DescriptionAction<readonly string[] | undefined, "Tags">]>;
|
|
21
|
+
readonly att: v.SchemaWithPipe<readonly [v.OptionalSchema<v.UnknownSchema, undefined>, v.DescriptionAction<unknown, "Attachment">]>;
|
|
22
|
+
readonly rpc: v.SchemaWithPipe<readonly [v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>, v.ReadonlyAction<string[] | undefined>, v.DescriptionAction<readonly string[] | undefined, "Allowed remote methods">]>;
|
|
23
|
+
readonly iat: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.DescriptionAction<number, "Issued at time in seconds">]>;
|
|
24
|
+
readonly exp: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.DescriptionAction<number, "Expiration time in seconds">]>;
|
|
25
|
+
}, undefined>;
|
|
26
|
+
/**
|
|
27
|
+
* Signs and encodes a token payload.
|
|
28
|
+
*/
|
|
29
|
+
declare function signDurableIteratorToken(secret: string, payload: DurableIteratorTokenPayload): Promise<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Verifies a token and returns the payload if valid.
|
|
32
|
+
*/
|
|
33
|
+
declare function verifyDurableIteratorToken(secret: string, token: string): Promise<DurableIteratorTokenPayload | undefined>;
|
|
34
|
+
/**
|
|
35
|
+
* Extracts the payload from a token without verifying its signature.
|
|
36
|
+
*
|
|
37
|
+
* @throws if invalid format
|
|
38
|
+
*/
|
|
39
|
+
declare function parseDurableIteratorToken(token: string | null | undefined): DurableIteratorTokenPayload;
|
|
40
|
+
|
|
41
|
+
export { parseDurableIteratorToken as p, signDurableIteratorToken as s, verifyDurableIteratorToken as v };
|
|
42
|
+
export type { DurableIteratorObject as D, InferDurableIteratorObjectRPC as I, DurableIteratorObjectDef as a, DurableIteratorTokenPayload as b };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { NestedClient } from '@orpc/client';
|
|
2
|
+
import { AsyncIteratorClass } from '@orpc/shared';
|
|
3
|
+
import * as v from 'valibot';
|
|
4
|
+
|
|
5
|
+
interface DurableIteratorObjectDef<T extends object> {
|
|
6
|
+
'~eventPayloadType'?: {
|
|
7
|
+
type: T;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
interface DurableIteratorObject<T extends object> {
|
|
11
|
+
'~orpc'?: DurableIteratorObjectDef<T>;
|
|
12
|
+
}
|
|
13
|
+
type InferDurableIteratorObjectRPC<T extends DurableIteratorObject<any>> = Exclude<{
|
|
14
|
+
[K in keyof T]: T[K] extends ((...args: any[]) => NestedClient<object>) ? K : never;
|
|
15
|
+
}[keyof T], keyof AsyncIteratorClass<any>> & string;
|
|
16
|
+
|
|
17
|
+
type DurableIteratorTokenPayload = v.InferOutput<typeof DurableIteratorTokenPayloadSchema>;
|
|
18
|
+
declare const DurableIteratorTokenPayloadSchema: v.ObjectSchema<{
|
|
19
|
+
readonly chn: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.DescriptionAction<string, "Channel name">]>;
|
|
20
|
+
readonly tags: v.SchemaWithPipe<readonly [v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>, v.ReadonlyAction<string[] | undefined>, v.DescriptionAction<readonly string[] | undefined, "Tags">]>;
|
|
21
|
+
readonly att: v.SchemaWithPipe<readonly [v.OptionalSchema<v.UnknownSchema, undefined>, v.DescriptionAction<unknown, "Attachment">]>;
|
|
22
|
+
readonly rpc: v.SchemaWithPipe<readonly [v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>, v.ReadonlyAction<string[] | undefined>, v.DescriptionAction<readonly string[] | undefined, "Allowed remote methods">]>;
|
|
23
|
+
readonly iat: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.DescriptionAction<number, "Issued at time in seconds">]>;
|
|
24
|
+
readonly exp: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.DescriptionAction<number, "Expiration time in seconds">]>;
|
|
25
|
+
}, undefined>;
|
|
26
|
+
/**
|
|
27
|
+
* Signs and encodes a token payload.
|
|
28
|
+
*/
|
|
29
|
+
declare function signDurableIteratorToken(secret: string, payload: DurableIteratorTokenPayload): Promise<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Verifies a token and returns the payload if valid.
|
|
32
|
+
*/
|
|
33
|
+
declare function verifyDurableIteratorToken(secret: string, token: string): Promise<DurableIteratorTokenPayload | undefined>;
|
|
34
|
+
/**
|
|
35
|
+
* Extracts the payload from a token without verifying its signature.
|
|
36
|
+
*
|
|
37
|
+
* @throws if invalid format
|
|
38
|
+
*/
|
|
39
|
+
declare function parseDurableIteratorToken(token: string | null | undefined): DurableIteratorTokenPayload;
|
|
40
|
+
|
|
41
|
+
export { parseDurableIteratorToken as p, signDurableIteratorToken as s, verifyDurableIteratorToken as v };
|
|
42
|
+
export type { DurableIteratorObject as D, InferDurableIteratorObjectRPC as I, DurableIteratorObjectDef as a, DurableIteratorTokenPayload as b };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { unsign, sign, getSignedValue } from '@orpc/server/helpers';
|
|
2
|
+
import { parseEmptyableJSON, stringifyJSON } from '@orpc/shared';
|
|
3
|
+
import * as v from 'valibot';
|
|
4
|
+
|
|
5
|
+
const DURABLE_ITERATOR_TOKEN_PARAM = "token";
|
|
6
|
+
const DURABLE_ITERATOR_ID_PARAM = "id";
|
|
7
|
+
const DURABLE_ITERATOR_PLUGIN_HEADER_KEY = "x-orpc-dei";
|
|
8
|
+
const DURABLE_ITERATOR_PLUGIN_HEADER_VALUE = "1";
|
|
9
|
+
|
|
10
|
+
class DurableIteratorError extends Error {
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const DurableIteratorTokenPayloadSchema = v.object({
|
|
14
|
+
chn: v.pipe(v.string(), v.description("Channel name")),
|
|
15
|
+
tags: v.pipe(v.optional(v.array(v.string())), v.readonly(), v.description("Tags")),
|
|
16
|
+
att: v.pipe(v.optional(v.unknown()), v.description("Attachment")),
|
|
17
|
+
rpc: v.pipe(v.optional(v.array(v.string())), v.readonly(), v.description("Allowed remote methods")),
|
|
18
|
+
iat: v.pipe(v.number(), v.description("Issued at time in seconds")),
|
|
19
|
+
exp: v.pipe(v.number(), v.description("Expiration time in seconds"))
|
|
20
|
+
});
|
|
21
|
+
function signDurableIteratorToken(secret, payload) {
|
|
22
|
+
return sign(stringifyJSON(payload), secret);
|
|
23
|
+
}
|
|
24
|
+
async function verifyDurableIteratorToken(secret, token) {
|
|
25
|
+
try {
|
|
26
|
+
const payload = parseEmptyableJSON(await unsign(token, secret));
|
|
27
|
+
if (!v.is(DurableIteratorTokenPayloadSchema, payload)) {
|
|
28
|
+
return void 0;
|
|
29
|
+
}
|
|
30
|
+
if (payload.exp < Date.now() / 1e3) {
|
|
31
|
+
return void 0;
|
|
32
|
+
}
|
|
33
|
+
return payload;
|
|
34
|
+
} catch {
|
|
35
|
+
return void 0;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function parseDurableIteratorToken(token) {
|
|
39
|
+
try {
|
|
40
|
+
const payload = parseEmptyableJSON(getSignedValue(token));
|
|
41
|
+
return v.parse(DurableIteratorTokenPayloadSchema, payload);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
throw new DurableIteratorError("Invalid token payload", { cause: error });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { DurableIteratorError as D, DURABLE_ITERATOR_PLUGIN_HEADER_VALUE as a, DURABLE_ITERATOR_PLUGIN_HEADER_KEY as b, DURABLE_ITERATOR_TOKEN_PARAM as c, DURABLE_ITERATOR_ID_PARAM as d, parseDurableIteratorToken as p, signDurableIteratorToken as s, verifyDurableIteratorToken as v };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { NestedClient, Client, ThrowableError, ClientLink } from '@orpc/client';
|
|
2
|
+
import { ClientRetryPluginContext } from '@orpc/client/plugins';
|
|
3
|
+
import { AsyncIteratorClass } from '@orpc/shared';
|
|
4
|
+
import { D as DurableIteratorObject, I as InferDurableIteratorObjectRPC } from './experimental-durable-iterator.DQjHfIr1.mjs';
|
|
5
|
+
|
|
6
|
+
interface ClientDurableIteratorRpcContext extends ClientRetryPluginContext {
|
|
7
|
+
}
|
|
8
|
+
type ClientDurableIteratorRpc<T extends NestedClient<object>> = T extends Client<any, infer UInput, infer UOutput, any> ? Client<ClientDurableIteratorRpcContext, UInput, UOutput, ThrowableError> : {
|
|
9
|
+
[K in keyof T]: T[K] extends NestedClient<object> ? ClientDurableIteratorRpc<T[K]> : never;
|
|
10
|
+
};
|
|
11
|
+
type ClientDurableIterator<T extends DurableIteratorObject<any>, RPC extends InferDurableIteratorObjectRPC<T>> = AsyncIteratorClass<T extends DurableIteratorObject<infer TPayload> ? TPayload : never> & {
|
|
12
|
+
[K in RPC]: T[K] extends (...args: any[]) => (infer R extends NestedClient<object>) ? ClientDurableIteratorRpc<R> : never;
|
|
13
|
+
};
|
|
14
|
+
interface CreateClientDurableIteratorOptions {
|
|
15
|
+
/**
|
|
16
|
+
* The token used to authenticate the client.
|
|
17
|
+
* this is a function because the token is lazy, and dynamic-able
|
|
18
|
+
*/
|
|
19
|
+
getToken: () => string;
|
|
20
|
+
}
|
|
21
|
+
declare function createClientDurableIterator<T extends DurableIteratorObject<any>, RPC extends InferDurableIteratorObjectRPC<T>>(iterator: AsyncIteratorClass<T>, link: ClientLink<object>, options: CreateClientDurableIteratorOptions): ClientDurableIterator<T, RPC>;
|
|
22
|
+
/**
|
|
23
|
+
* If return a token if the client is a Client Durable Iterator.
|
|
24
|
+
*/
|
|
25
|
+
declare function getClientDurableIteratorToken(client: unknown): string | undefined;
|
|
26
|
+
|
|
27
|
+
export { createClientDurableIterator as d, getClientDurableIteratorToken as g };
|
|
28
|
+
export type { ClientDurableIterator as C, ClientDurableIteratorRpcContext as a, ClientDurableIteratorRpc as b, CreateClientDurableIteratorOptions as c };
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@orpc/experimental-durable-iterator",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.0-next.01f0b7a",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://orpc.unnoq.com",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/unnoq/orpc.git",
|
|
10
|
+
"directory": "packages/durable-iterator"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"unnoq",
|
|
14
|
+
"orpc"
|
|
15
|
+
],
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.mts",
|
|
19
|
+
"import": "./dist/index.mjs",
|
|
20
|
+
"default": "./dist/index.mjs"
|
|
21
|
+
},
|
|
22
|
+
"./client": {
|
|
23
|
+
"types": "./dist/client/index.d.mts",
|
|
24
|
+
"import": "./dist/client/index.mjs",
|
|
25
|
+
"default": "./dist/client/index.mjs"
|
|
26
|
+
},
|
|
27
|
+
"./durable-object": {
|
|
28
|
+
"types": "./dist/durable-object/index.d.mts",
|
|
29
|
+
"import": "./dist/durable-object/index.mjs",
|
|
30
|
+
"default": "./dist/durable-object/index.mjs"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"partysocket": "^1.1.5",
|
|
38
|
+
"valibot": "^1.1.0",
|
|
39
|
+
"@orpc/client": "0.0.0-next.01f0b7a",
|
|
40
|
+
"@orpc/contract": "0.0.0-next.01f0b7a",
|
|
41
|
+
"@orpc/server": "0.0.0-next.01f0b7a",
|
|
42
|
+
"@orpc/shared": "0.0.0-next.01f0b7a"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@cloudflare/workers-types": "^4.20250924.0",
|
|
46
|
+
"@types/node": "^22.15.30",
|
|
47
|
+
"@orpc/standard-server-peer": "0.0.0-next.01f0b7a"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "unbuild",
|
|
51
|
+
"build:watch": "pnpm run build --watch",
|
|
52
|
+
"type:check": "tsc -b",
|
|
53
|
+
"type:check:test": "tsc -p tsconfig.test.json --noEmit"
|
|
54
|
+
}
|
|
55
|
+
}
|