@pinet/transport-core 0.2.2 → 0.2.6

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/README.md CHANGED
@@ -8,6 +8,11 @@ Tiny transport-neutral contracts package for the `extensions` repo.
8
8
  - canonical `OutboundMessage` contract
9
9
  - normalized outbound `content` shape for transport-aware rendering with plain-text fallback
10
10
  - canonical `MessageAdapter` transport interface
11
+ - shared zero-dependency async primitives via the `@pinet/transport-core/async`
12
+ subpath: `sleep` (abortable delay), `withTimeout`, `computeBackoffDelay`
13
+ (capped + jittered exponential backoff), and `AbortError`/`TimeoutError`
14
+ helpers. Retry _drivers_ stay bespoke at call sites; this module only owns
15
+ the primitives they compose.
11
16
 
12
17
  ## What stays out of scope
13
18
 
@@ -0,0 +1,44 @@
1
+ /** Create an Error whose name is "AbortError", matching DOM abort semantics. */
2
+ export declare function createAbortError(message?: string): Error;
3
+ /** True when the value is an Error whose name is "AbortError". */
4
+ export declare function isAbortError<T>(error: T): boolean;
5
+ export interface SleepOptions {
6
+ /** Optional signal that aborts the sleep early; the promise rejects with an AbortError. */
7
+ signal?: AbortSignal;
8
+ }
9
+ /**
10
+ * Resolve after `ms` milliseconds. When `options.signal` is provided and
11
+ * aborts first, the timer is cleared and the promise rejects with an
12
+ * AbortError (also when the signal is already aborted on entry).
13
+ */
14
+ export declare function sleep(ms: number, options?: SleepOptions): Promise<void>;
15
+ /** Create an Error whose name is "TimeoutError". */
16
+ export declare function createTimeoutError(timeoutMs: number, label?: string): Error;
17
+ /** True when the value is an Error whose name is "TimeoutError". */
18
+ export declare function isTimeoutError<T>(error: T): boolean;
19
+ /**
20
+ * Reject with a TimeoutError when the promise does not settle within
21
+ * `timeoutMs`. The guard timer is unref'd so it never keeps the process
22
+ * alive. Non-Error rejections from the wrapped promise are normalized to
23
+ * Error instances.
24
+ */
25
+ export declare function withTimeout<T>(promise: Promise<T>, timeoutMs: number, label?: string): Promise<T>;
26
+ export interface BackoffOptions {
27
+ /** Delay for attempt 0, before capping and jitter. */
28
+ initialMs: number;
29
+ /** Upper bound applied before jitter. */
30
+ maxMs: number;
31
+ /** Exponential growth factor per attempt. Default 2. */
32
+ factor?: number;
33
+ /** Jitter as a ratio of the capped delay (±ratio). Default 0.25; 0 disables jitter. */
34
+ jitterRatio?: number;
35
+ /** Random sample in [0, 1). Injectable for deterministic tests; defaults to Math.random(). */
36
+ random?: number;
37
+ }
38
+ /**
39
+ * Compute a capped, jittered exponential-backoff delay for a retry attempt.
40
+ * Attempt numbers below zero are treated as zero. With the defaults the
41
+ * result is `min(initialMs * 2^attempt, maxMs)` scaled by a random factor
42
+ * in [0.75, 1.25), rounded to the nearest millisecond.
43
+ */
44
+ export declare function computeBackoffDelay(attempt: number, options: BackoffOptions): number;
package/dist/async.js ADDED
@@ -0,0 +1,89 @@
1
+ // Shared async primitives for pi transports and bridges.
2
+ //
3
+ // These are the zero-dependency building blocks that were previously
4
+ // hand-rolled per package (abortable delays, promise timeouts, jittered
5
+ // exponential backoff). Retry *drivers* stay bespoke at call sites — this
6
+ // module only owns the primitives they compose.
7
+ /** Create an Error whose name is "AbortError", matching DOM abort semantics. */
8
+ export function createAbortError(message = "Operation aborted") {
9
+ const error = new Error(message);
10
+ error.name = "AbortError";
11
+ return error;
12
+ }
13
+ /** True when the value is an Error whose name is "AbortError". */
14
+ export function isAbortError(error) {
15
+ return error instanceof Error && error.name === "AbortError";
16
+ }
17
+ /**
18
+ * Resolve after `ms` milliseconds. When `options.signal` is provided and
19
+ * aborts first, the timer is cleared and the promise rejects with an
20
+ * AbortError (also when the signal is already aborted on entry).
21
+ */
22
+ export function sleep(ms, options = {}) {
23
+ const { signal } = options;
24
+ if (!signal) {
25
+ return new Promise((resolve) => {
26
+ setTimeout(resolve, ms);
27
+ });
28
+ }
29
+ if (signal.aborted) {
30
+ return Promise.reject(createAbortError());
31
+ }
32
+ const abortSignal = signal;
33
+ return new Promise((resolve, reject) => {
34
+ const timer = setTimeout(() => {
35
+ abortSignal.removeEventListener("abort", onAbort);
36
+ resolve();
37
+ }, ms);
38
+ function onAbort() {
39
+ clearTimeout(timer);
40
+ abortSignal.removeEventListener("abort", onAbort);
41
+ reject(createAbortError());
42
+ }
43
+ abortSignal.addEventListener("abort", onAbort, { once: true });
44
+ });
45
+ }
46
+ /** Create an Error whose name is "TimeoutError". */
47
+ export function createTimeoutError(timeoutMs, label) {
48
+ const error = new Error(label ? `${label} timed out after ${timeoutMs}ms` : `Timed out after ${timeoutMs}ms`);
49
+ error.name = "TimeoutError";
50
+ return error;
51
+ }
52
+ /** True when the value is an Error whose name is "TimeoutError". */
53
+ export function isTimeoutError(error) {
54
+ return error instanceof Error && error.name === "TimeoutError";
55
+ }
56
+ /**
57
+ * Reject with a TimeoutError when the promise does not settle within
58
+ * `timeoutMs`. The guard timer is unref'd so it never keeps the process
59
+ * alive. Non-Error rejections from the wrapped promise are normalized to
60
+ * Error instances.
61
+ */
62
+ export function withTimeout(promise, timeoutMs, label) {
63
+ return new Promise((resolve, reject) => {
64
+ const timer = setTimeout(() => reject(createTimeoutError(timeoutMs, label)), timeoutMs);
65
+ timer.unref?.();
66
+ promise.then((value) => {
67
+ clearTimeout(timer);
68
+ resolve(value);
69
+ }, (error) => {
70
+ clearTimeout(timer);
71
+ reject(error instanceof Error ? error : new Error(String(error)));
72
+ });
73
+ });
74
+ }
75
+ /**
76
+ * Compute a capped, jittered exponential-backoff delay for a retry attempt.
77
+ * Attempt numbers below zero are treated as zero. With the defaults the
78
+ * result is `min(initialMs * 2^attempt, maxMs)` scaled by a random factor
79
+ * in [0.75, 1.25), rounded to the nearest millisecond.
80
+ */
81
+ export function computeBackoffDelay(attempt, options) {
82
+ const factor = options.factor ?? 2;
83
+ const jitterRatio = options.jitterRatio ?? 0.25;
84
+ const random = options.random ?? Math.random();
85
+ const base = options.initialMs * Math.pow(factor, Math.max(0, attempt));
86
+ const capped = Math.min(base, options.maxMs);
87
+ const jittered = capped * (1 - jitterRatio + random * 2 * jitterRatio);
88
+ return Math.round(jittered);
89
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,9 @@
1
1
  export type RuntimeScopeSource = "explicit" | "compatibility";
2
+ export type TransportJsonValue = unknown;
3
+ export type TransportJsonObject = Record<string, TransportJsonValue>;
4
+ export type TransportRichBlock = TransportJsonObject;
5
+ export type AdapterCapabilityParams = TransportJsonObject;
6
+ export type AdapterCapabilityPayload = TransportJsonObject;
2
7
  export declare const DEFAULT_COMPATIBILITY_SCOPE_KEY = "default";
3
8
  export interface WorkspaceInstallScopeCarrier {
4
9
  provider: string;
@@ -43,7 +48,7 @@ export interface InboundMessage {
43
48
  text: string;
44
49
  timestamp: string;
45
50
  isChannelMention?: boolean;
46
- metadata?: Record<string, unknown>;
51
+ metadata?: TransportJsonObject;
47
52
  scope?: RuntimeScopeCarrier;
48
53
  }
49
54
  export interface NormalizedMessageContent {
@@ -54,7 +59,7 @@ export interface NormalizedMessageContent {
54
59
  * This remains intentionally Slack-shaped for the current publish-readiness
55
60
  * track while a future transport-neutral rich-content model is considered.
56
61
  */
57
- slackBlocks?: ReadonlyArray<Record<string, unknown>>;
62
+ slackBlocks?: ReadonlyArray<TransportRichBlock>;
58
63
  }
59
64
  export interface OutboundAttachmentFile {
60
65
  path: string;
@@ -67,12 +72,12 @@ export interface OutboundMessage {
67
72
  channel: string;
68
73
  text: string;
69
74
  content?: NormalizedMessageContent;
70
- blocks?: ReadonlyArray<Record<string, unknown>>;
75
+ blocks?: ReadonlyArray<TransportRichBlock>;
71
76
  files?: ReadonlyArray<OutboundAttachmentFile>;
72
77
  agentName?: string;
73
78
  agentEmoji?: string;
74
79
  agentOwnerToken?: string;
75
- metadata?: Record<string, unknown>;
80
+ metadata?: TransportJsonObject;
76
81
  scope?: RuntimeScopeCarrier;
77
82
  }
78
83
  export interface AdapterThreadClaimEffect {
@@ -84,10 +89,10 @@ export interface AdapterCapabilityEffects {
84
89
  }
85
90
  export interface AdapterCapabilityRequest {
86
91
  capability: string;
87
- params: Record<string, unknown>;
92
+ params: AdapterCapabilityParams;
88
93
  }
89
94
  export interface AdapterCapabilityResult {
90
- result: Record<string, unknown>;
95
+ result: AdapterCapabilityPayload;
91
96
  effects?: AdapterCapabilityEffects;
92
97
  }
93
98
  export interface MessageAdapter {
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@pinet/transport-core",
3
- "version": "0.2.2",
3
+ "version": "0.2.6",
4
4
  "type": "module",
5
5
  "description": "Transport-neutral message contracts for pi transports",
6
6
  "author": "Will Porcellini <5994936+gugu91@users.noreply.github.com>",
7
7
  "license": "MIT",
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "git+https://github.com/gugu91/extensions.git",
10
+ "url": "git+https://github.com/gugu91/pinet.git",
11
11
  "directory": "transport-core"
12
12
  },
13
13
  "publishConfig": {
@@ -16,6 +16,7 @@
16
16
  "main": "./dist/index.js",
17
17
  "exports": {
18
18
  ".": "./dist/index.js",
19
+ "./async": "./dist/async.js",
19
20
  "./package.json": "./package.json"
20
21
  },
21
22
  "files": [
@@ -27,7 +28,7 @@
27
28
  "scripts": {
28
29
  "build": "node ../scripts/build-package.mjs",
29
30
  "prepack": "pnpm run build",
30
- "lint": "eslint . --ext .ts",
31
+ "lint": "oxlint .",
31
32
  "typecheck": "tsc --noEmit",
32
33
  "test": "node --experimental-strip-types --test *.test.ts"
33
34
  },