@plexspaces/sdk 0.1.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.
- package/README.md +225 -0
- package/dist/actor.d.ts +81 -0
- package/dist/actor.js +484 -0
- package/dist/actor.test.d.ts +1 -0
- package/dist/actor.test.js +171 -0
- package/dist/actor_id.d.ts +46 -0
- package/dist/actor_id.js +85 -0
- package/dist/decorators.d.ts +40 -0
- package/dist/decorators.js +97 -0
- package/dist/host.d.ts +312 -0
- package/dist/host.js +828 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +14 -0
- package/dist/leader_worker.d.ts +36 -0
- package/dist/leader_worker.js +119 -0
- package/dist/process_groups.d.ts +4 -0
- package/dist/process_groups.js +14 -0
- package/dist/proto.d.ts +33 -0
- package/dist/proto.js +10 -0
- package/dist/router.d.ts +37 -0
- package/dist/router.js +78 -0
- package/dist/wire/http-fetch-proto-wire.d.ts +7 -0
- package/dist/wire/http-fetch-proto-wire.js +101 -0
- package/dist/wire/proto-wire-common.d.ts +18 -0
- package/dist/wire/proto-wire-common.js +91 -0
- package/dist/wire/registry-proto-wire.d.ts +41 -0
- package/dist/wire/registry-proto-wire.js +280 -0
- package/dist/wire/shard-group-proto-wire.d.ts +7 -0
- package/dist/wire/shard-group-proto-wire.js +723 -0
- package/dist/wire/tuplespace-proto-wire.d.ts +8 -0
- package/dist/wire/tuplespace-proto-wire.js +183 -0
- package/dist/wit-payload.d.ts +23 -0
- package/dist/wit-payload.js +42 -0
- package/dist/workflow.d.ts +14 -0
- package/dist/workflow.js +47 -0
- package/package.json +36 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Build WriteRequest protobuf bytes (field 1: tuple with repeated TupleField). */
|
|
2
|
+
export declare function encodeWriteRequest(tuple: unknown[]): Uint8Array;
|
|
3
|
+
/** Build ReadRequest protobuf bytes. */
|
|
4
|
+
export declare function encodeReadRequest(pattern: unknown[], take: boolean, maxResults: number): Uint8Array;
|
|
5
|
+
/** Parse ReadResponse bytes into list of tuple field arrays. */
|
|
6
|
+
export declare function parseReadResponseTuples(data: Uint8Array): unknown[][];
|
|
7
|
+
export declare function decodeReadResponseFirstTuple(raw: Uint8Array): unknown[] | null;
|
|
8
|
+
export declare function decodeReadResponseAllTuples(raw: Uint8Array): unknown[][];
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright (C) 2025 PlexSpaces Contributors
|
|
3
|
+
//
|
|
4
|
+
// Protobuf wire for plexspaces.tuplespace.v1 WriteRequest / ReadRequest / ReadResponse
|
|
5
|
+
// (subset for WASM host imports). Byte-compatible with:
|
|
6
|
+
// - sdks/go/plexspaces/tuplespace_proto_wire.go
|
|
7
|
+
// - crates/wasm-runtime simple_component_host (prost)
|
|
8
|
+
import { appendLengthDelimited, appendVarint, concatBytes, readLengthDelimited, readVarint, skipField, } from './proto-wire-common.js';
|
|
9
|
+
const MIN_INT64 = -9223372036854775808n;
|
|
10
|
+
const MAX_INT64 = 9223372036854775807n;
|
|
11
|
+
function encodeTupleField(v, allowWildcardStar) {
|
|
12
|
+
if (v === null || v === undefined) {
|
|
13
|
+
return appendVarint(new Uint8Array([0x38]), 1);
|
|
14
|
+
}
|
|
15
|
+
if (typeof v === 'string') {
|
|
16
|
+
if (allowWildcardStar && v === '*') {
|
|
17
|
+
return appendVarint(new Uint8Array([0x38]), 1);
|
|
18
|
+
}
|
|
19
|
+
const enc = new TextEncoder();
|
|
20
|
+
const bytes = new Uint8Array(enc.encode(v));
|
|
21
|
+
let inner = new Uint8Array([0x1a]);
|
|
22
|
+
inner = appendVarint(inner, bytes.length);
|
|
23
|
+
inner = concatBytes(inner, bytes);
|
|
24
|
+
return inner;
|
|
25
|
+
}
|
|
26
|
+
if (typeof v === 'boolean') {
|
|
27
|
+
const inner = new Uint8Array([0x20]);
|
|
28
|
+
return appendVarint(inner, v ? 1 : 0);
|
|
29
|
+
}
|
|
30
|
+
if (typeof v === 'number' && Number.isFinite(v)) {
|
|
31
|
+
const t = Math.trunc(v);
|
|
32
|
+
if (t === v && t >= Number(MIN_INT64) && t <= Number(MAX_INT64)) {
|
|
33
|
+
let inner = new Uint8Array([0x08]);
|
|
34
|
+
inner = appendVarintSigned(inner, t);
|
|
35
|
+
return inner;
|
|
36
|
+
}
|
|
37
|
+
let inner = new Uint8Array([0x11]);
|
|
38
|
+
const tmp = new Uint8Array(8);
|
|
39
|
+
new DataView(tmp.buffer).setFloat64(0, v, true);
|
|
40
|
+
inner = concatBytes(inner, tmp);
|
|
41
|
+
return inner;
|
|
42
|
+
}
|
|
43
|
+
throw new Error(`unsupported tuple field type ${typeof v}`);
|
|
44
|
+
}
|
|
45
|
+
function appendVarintSigned(buf, xIn) {
|
|
46
|
+
let x = BigInt(xIn);
|
|
47
|
+
if (x < 0n)
|
|
48
|
+
x = BigInt.asUintN(64, x);
|
|
49
|
+
const parts = [];
|
|
50
|
+
let n = x;
|
|
51
|
+
while (n >= 0x80n) {
|
|
52
|
+
parts.push(Number(n & 0xffn) | 0x80);
|
|
53
|
+
n >>= 7n;
|
|
54
|
+
}
|
|
55
|
+
parts.push(Number(n));
|
|
56
|
+
return concatBytes(buf, new Uint8Array(parts));
|
|
57
|
+
}
|
|
58
|
+
function encodeTupleFields(tuple, allowWildcardStar) {
|
|
59
|
+
let out = new Uint8Array(0);
|
|
60
|
+
for (const el of tuple) {
|
|
61
|
+
const tf = encodeTupleField(el, allowWildcardStar);
|
|
62
|
+
out = appendLengthDelimited(out, 2, tf);
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
/** Build WriteRequest protobuf bytes (field 1: tuple with repeated TupleField). */
|
|
67
|
+
export function encodeWriteRequest(tuple) {
|
|
68
|
+
const tupleBody = encodeTupleFields(tuple, false);
|
|
69
|
+
return appendLengthDelimited(new Uint8Array(0), 1, tupleBody);
|
|
70
|
+
}
|
|
71
|
+
/** Build ReadRequest protobuf bytes. */
|
|
72
|
+
export function encodeReadRequest(pattern, take, maxResults) {
|
|
73
|
+
const templateBody = encodeTupleFields(pattern, true);
|
|
74
|
+
let out = appendLengthDelimited(new Uint8Array(0), 1, templateBody);
|
|
75
|
+
if (take) {
|
|
76
|
+
out = concatBytes(out, new Uint8Array([0x20, 0x01]));
|
|
77
|
+
}
|
|
78
|
+
out = concatBytes(out, new Uint8Array([0x28]));
|
|
79
|
+
out = appendVarint(out, maxResults >>> 0);
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
function parseTupleFieldMsg(msg) {
|
|
83
|
+
let pos = 0;
|
|
84
|
+
let last = undefined;
|
|
85
|
+
while (pos < msg.length) {
|
|
86
|
+
const { value: tag, n: tn } = readVarint(msg, pos);
|
|
87
|
+
pos += tn;
|
|
88
|
+
const fn = Number(tag >> 3n);
|
|
89
|
+
const wt = Number(tag & 7n);
|
|
90
|
+
if (wt === 0) {
|
|
91
|
+
const { value: v, n: m } = readVarint(msg, pos);
|
|
92
|
+
pos += m;
|
|
93
|
+
if (fn === 1)
|
|
94
|
+
last = Number(v);
|
|
95
|
+
else if (fn === 4)
|
|
96
|
+
last = v !== 0n;
|
|
97
|
+
else if (fn === 6 || fn === 7)
|
|
98
|
+
last = null;
|
|
99
|
+
}
|
|
100
|
+
else if (wt === 1) {
|
|
101
|
+
if (pos + 8 > msg.length)
|
|
102
|
+
throw new Error('double underflow');
|
|
103
|
+
const view = new DataView(msg.buffer, msg.byteOffset + pos, 8);
|
|
104
|
+
const d = view.getFloat64(0, true);
|
|
105
|
+
pos += 8;
|
|
106
|
+
if (fn === 2)
|
|
107
|
+
last = d;
|
|
108
|
+
}
|
|
109
|
+
else if (wt === 2) {
|
|
110
|
+
const { slice: chunk, nextPos } = readLengthDelimited(msg, pos);
|
|
111
|
+
pos = nextPos;
|
|
112
|
+
if (fn === 3 || fn === 5) {
|
|
113
|
+
last = new TextDecoder('utf-8', { fatal: false }).decode(chunk);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
pos = skipField(msg, pos, wt);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return last;
|
|
121
|
+
}
|
|
122
|
+
function parseTupleMsg(msg) {
|
|
123
|
+
const fields = [];
|
|
124
|
+
let pos = 0;
|
|
125
|
+
while (pos < msg.length) {
|
|
126
|
+
const { value: tag, n: tn } = readVarint(msg, pos);
|
|
127
|
+
pos += tn;
|
|
128
|
+
const fn = Number(tag >> 3n);
|
|
129
|
+
const wt = Number(tag & 7n);
|
|
130
|
+
if (fn === 2 && wt === 2) {
|
|
131
|
+
const { slice: sub, nextPos } = readLengthDelimited(msg, pos);
|
|
132
|
+
pos = nextPos;
|
|
133
|
+
fields.push(parseTupleFieldMsg(sub));
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
pos = skipField(msg, pos, wt);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return fields;
|
|
140
|
+
}
|
|
141
|
+
/** Parse ReadResponse bytes into list of tuple field arrays. */
|
|
142
|
+
export function parseReadResponseTuples(data) {
|
|
143
|
+
const tuples = [];
|
|
144
|
+
let pos = 0;
|
|
145
|
+
while (pos < data.length) {
|
|
146
|
+
const { value: tag, n: tn } = readVarint(data, pos);
|
|
147
|
+
pos += tn;
|
|
148
|
+
const fn = Number(tag >> 3n);
|
|
149
|
+
const wt = Number(tag & 7n);
|
|
150
|
+
if (fn === 1 && wt === 2) {
|
|
151
|
+
const { slice, nextPos } = readLengthDelimited(data, pos);
|
|
152
|
+
pos = nextPos;
|
|
153
|
+
tuples.push(parseTupleMsg(slice));
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
pos = skipField(data, pos, wt);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return tuples;
|
|
160
|
+
}
|
|
161
|
+
export function decodeReadResponseFirstTuple(raw) {
|
|
162
|
+
if (raw.length === 0)
|
|
163
|
+
return null;
|
|
164
|
+
try {
|
|
165
|
+
const tuples = parseReadResponseTuples(raw);
|
|
166
|
+
if (tuples.length === 0)
|
|
167
|
+
return null;
|
|
168
|
+
return tuples[0] ?? null;
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
export function decodeReadResponseAllTuples(raw) {
|
|
175
|
+
if (raw.length === 0)
|
|
176
|
+
return [];
|
|
177
|
+
try {
|
|
178
|
+
return parseReadResponseTuples(raw);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decode WIT `payload` (`list<u8>`) or a UTF-8 string for JSON/text handling in the guest.
|
|
3
|
+
*
|
|
4
|
+
* jco/wasmtime passes `list<u8>` as `Uint8Array`. Using `payload.trim` is wrong (undefined on
|
|
5
|
+
* TypedArray), which skips `JSON.parse` and drops `op` / `message_type` from the body when the
|
|
6
|
+
* host falls back to envelope `msgType` "call".
|
|
7
|
+
*
|
|
8
|
+
* Some embeddings surface other `ArrayBufferView` types; treat any view like `Uint8Array`.
|
|
9
|
+
*
|
|
10
|
+
* @param input - Raw config, message body, or state bytes from the component boundary
|
|
11
|
+
* @returns UTF-8 string (may be empty)
|
|
12
|
+
*/
|
|
13
|
+
export declare function decodeWitPayloadUtf8(input: string | Uint8Array | ArrayBuffer | ArrayBufferView): string;
|
|
14
|
+
/**
|
|
15
|
+
* Encode a UTF-8 string as WIT `payload` (`list<u8>`) for guest exports.
|
|
16
|
+
*
|
|
17
|
+
* jco maps `list<u8>` to `Uint8Array` on the JavaScript side — not `string`. Returning a plain
|
|
18
|
+
* string from `handle` / `get-state` can lift as an empty success payload at the component ABI.
|
|
19
|
+
*
|
|
20
|
+
* @param text - JSON or other UTF-8 text to pass to the host
|
|
21
|
+
* @returns Bytes for the canonical ABI
|
|
22
|
+
*/
|
|
23
|
+
export declare function encodeWitPayloadUtf8(text: string): Uint8Array;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright (C) 2025 PlexSpaces Contributors
|
|
3
|
+
//
|
|
4
|
+
// Pure UTF-8 helpers for WIT `payload` (`list<u8>`) at the component boundary.
|
|
5
|
+
// Kept separate from actor.ts so Node tests can import without `plexspaces:` virtual imports.
|
|
6
|
+
/**
|
|
7
|
+
* Decode WIT `payload` (`list<u8>`) or a UTF-8 string for JSON/text handling in the guest.
|
|
8
|
+
*
|
|
9
|
+
* jco/wasmtime passes `list<u8>` as `Uint8Array`. Using `payload.trim` is wrong (undefined on
|
|
10
|
+
* TypedArray), which skips `JSON.parse` and drops `op` / `message_type` from the body when the
|
|
11
|
+
* host falls back to envelope `msgType` "call".
|
|
12
|
+
*
|
|
13
|
+
* Some embeddings surface other `ArrayBufferView` types; treat any view like `Uint8Array`.
|
|
14
|
+
*
|
|
15
|
+
* @param input - Raw config, message body, or state bytes from the component boundary
|
|
16
|
+
* @returns UTF-8 string (may be empty)
|
|
17
|
+
*/
|
|
18
|
+
export function decodeWitPayloadUtf8(input) {
|
|
19
|
+
if (typeof input === 'string') {
|
|
20
|
+
return input;
|
|
21
|
+
}
|
|
22
|
+
if (input instanceof ArrayBuffer) {
|
|
23
|
+
return new TextDecoder('utf-8', { fatal: false }).decode(new Uint8Array(input));
|
|
24
|
+
}
|
|
25
|
+
if (ArrayBuffer.isView(input)) {
|
|
26
|
+
const v = input;
|
|
27
|
+
return new TextDecoder('utf-8', { fatal: false }).decode(new Uint8Array(v.buffer, v.byteOffset, v.byteLength));
|
|
28
|
+
}
|
|
29
|
+
return '';
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Encode a UTF-8 string as WIT `payload` (`list<u8>`) for guest exports.
|
|
33
|
+
*
|
|
34
|
+
* jco maps `list<u8>` to `Uint8Array` on the JavaScript side — not `string`. Returning a plain
|
|
35
|
+
* string from `handle` / `get-state` can lift as an empty success payload at the component ABI.
|
|
36
|
+
*
|
|
37
|
+
* @param text - JSON or other UTF-8 text to pass to the host
|
|
38
|
+
* @returns Bytes for the canonical ABI
|
|
39
|
+
*/
|
|
40
|
+
export function encodeWitPayloadUtf8(text) {
|
|
41
|
+
return new TextEncoder().encode(text);
|
|
42
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { RetryConfig } from './proto.js';
|
|
2
|
+
export type { RetryConfig } from './proto.js';
|
|
3
|
+
/** Default retry config: single attempt (no retries). Aligns with proto when fields unset. */
|
|
4
|
+
export declare function defaultRetryConfig(): RetryConfig;
|
|
5
|
+
/**
|
|
6
|
+
* Execute a step with retries. Uses RetryConfig. When config is omitted or max_attempts unset, 3 attempts.
|
|
7
|
+
* When config.max_attempts is 0 or 1, single attempt. Aligns with Rust run(name, retry, op) and proto RetryConfig.
|
|
8
|
+
*
|
|
9
|
+
* @param fn - Function to execute. Must return a value or throw.
|
|
10
|
+
* @param config - Optional RetryConfig (omitted => 3 attempts; max_attempts: 1 => one attempt).
|
|
11
|
+
* @returns The result of fn() after a successful attempt.
|
|
12
|
+
* @throws The last error if all attempts fail.
|
|
13
|
+
*/
|
|
14
|
+
export declare function withRetry<T>(fn: () => T, config?: RetryConfig): T;
|
package/dist/workflow.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright (C) 2025 PlexSpaces Contributors
|
|
3
|
+
//
|
|
4
|
+
// Workflow retry helpers aligned with proto RetryConfig (plexspaces.workflow.v1.RetryConfig).
|
|
5
|
+
// Single run-with-config semantics: use defaultRetryConfig() for one attempt; pass config with
|
|
6
|
+
// max_attempts > 1 for retries.
|
|
7
|
+
/** Default retry config: single attempt (no retries). Aligns with proto when fields unset. */
|
|
8
|
+
export function defaultRetryConfig() {
|
|
9
|
+
return {
|
|
10
|
+
max_attempts: 1,
|
|
11
|
+
initial_interval_ms: 100,
|
|
12
|
+
backoff_rate: 2,
|
|
13
|
+
max_interval_ms: 30000,
|
|
14
|
+
retryable_errors: [],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Effective max attempts: 0 or unset means 1. */
|
|
18
|
+
function effectiveMaxAttempts(c) {
|
|
19
|
+
const n = c.max_attempts ?? 0;
|
|
20
|
+
return n === 0 ? 1 : n;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Execute a step with retries. Uses RetryConfig. When config is omitted or max_attempts unset, 3 attempts.
|
|
24
|
+
* When config.max_attempts is 0 or 1, single attempt. Aligns with Rust run(name, retry, op) and proto RetryConfig.
|
|
25
|
+
*
|
|
26
|
+
* @param fn - Function to execute. Must return a value or throw.
|
|
27
|
+
* @param config - Optional RetryConfig (omitted => 3 attempts; max_attempts: 1 => one attempt).
|
|
28
|
+
* @returns The result of fn() after a successful attempt.
|
|
29
|
+
* @throws The last error if all attempts fail.
|
|
30
|
+
*/
|
|
31
|
+
export function withRetry(fn, config = {}) {
|
|
32
|
+
const fromConfig = config.max_attempts !== undefined ? effectiveMaxAttempts(config) : 0;
|
|
33
|
+
const maxAttempts = fromConfig > 0 ? fromConfig : 3;
|
|
34
|
+
let lastError;
|
|
35
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
36
|
+
try {
|
|
37
|
+
return fn();
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
lastError = e;
|
|
41
|
+
if (attempt === maxAttempts) {
|
|
42
|
+
throw e;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
throw lastError;
|
|
47
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@plexspaces/sdk",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "PlexSpaces TypeScript SDK - build actors with minimal boilerplate (inheritance-based)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "npm run generate:types && tsc -p .",
|
|
19
|
+
"generate:types": "node scripts/generate-types.mjs",
|
|
20
|
+
"test": "npm run build && node --test test/*.test.mjs",
|
|
21
|
+
"prepublishOnly": "npm run build"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@bytecodealliance/jco": "^1.17.0",
|
|
25
|
+
"typescript": "^5.0.0"
|
|
26
|
+
},
|
|
27
|
+
"license": "AGPL-3.0-or-later",
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/plexobject/plexspaces.git",
|
|
34
|
+
"directory": "sdks/typescript"
|
|
35
|
+
}
|
|
36
|
+
}
|