@phreshos/server 0.1.6 → 0.1.7

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,3 @@
1
+ export type Bytes = Uint8Array<ArrayBuffer>;
2
+ export declare const serialize: (value: unknown, attachments?: readonly object[]) => Bytes;
3
+ export declare const deserialize: (bytes: Uint8Array, attachments?: readonly unknown[]) => unknown;
@@ -0,0 +1,130 @@
1
+ import { decode, encode, ExtensionCodec } from "@msgpack/msgpack";
2
+ class Attachment {
3
+ index;
4
+ constructor(index) {
5
+ this.index = index;
6
+ }
7
+ }
8
+ class BigInteger {
9
+ value;
10
+ constructor(value) {
11
+ this.value = value;
12
+ }
13
+ }
14
+ class Undefined {
15
+ }
16
+ const textEncoder = new TextEncoder();
17
+ const textDecoder = new TextDecoder("utf-8", { fatal: true });
18
+ const extensions = new ExtensionCodec();
19
+ const serializePrepared = (value, context) => encode(value, { context, extensionCodec: extensions });
20
+ const deserializePrepared = (bytes, context) => decode(bytes, { context, extensionCodec: extensions });
21
+ extensions.register({
22
+ type: 0,
23
+ encode: value => value instanceof Attachment ? uint32(value.index) : null,
24
+ decode: (bytes, _type, context) => {
25
+ const attachment = context.incoming?.[readUint32(bytes)];
26
+ if (attachment === undefined)
27
+ throw new Error("The MessagePack attachment is absent");
28
+ return attachment;
29
+ }
30
+ });
31
+ extensions.register({
32
+ type: 1,
33
+ encode: (value, context) => value instanceof Map ? serializePrepared([...value], context) : null,
34
+ decode: (bytes, _type, context) => new Map(deserializePrepared(bytes, context))
35
+ });
36
+ extensions.register({
37
+ type: 2,
38
+ encode: (value, context) => value instanceof Set ? serializePrepared([...value], context) : null,
39
+ decode: (bytes, _type, context) => new Set(deserializePrepared(bytes, context))
40
+ });
41
+ extensions.register({
42
+ type: 3,
43
+ encode: (value, context) => value instanceof RegExp ? serializePrepared([value.source, value.flags, value.lastIndex], context) : null,
44
+ decode: (bytes, _type, context) => {
45
+ const [source, flags, lastIndex] = deserializePrepared(bytes, context);
46
+ const expression = new RegExp(source, flags);
47
+ expression.lastIndex = lastIndex;
48
+ return expression;
49
+ }
50
+ });
51
+ extensions.register({
52
+ type: 4,
53
+ encode: value => value instanceof URL ? textEncoder.encode(value.href) : null,
54
+ decode: bytes => new URL(textDecoder.decode(bytes))
55
+ });
56
+ extensions.register({
57
+ type: 5,
58
+ encode: (value, context) => value instanceof Error ? serializePrepared(prepare({
59
+ cause: value.cause,
60
+ message: value.message,
61
+ name: value.name,
62
+ stack: value.stack
63
+ }, context), context) : null,
64
+ decode: (bytes, _type, context) => {
65
+ const value = deserializePrepared(bytes, context);
66
+ const error = new Error(value.message, { cause: value.cause });
67
+ error.name = value.name;
68
+ if (value.stack !== undefined)
69
+ error.stack = value.stack;
70
+ return error;
71
+ }
72
+ });
73
+ extensions.register({
74
+ type: 6,
75
+ encode: value => value instanceof BigInteger ? textEncoder.encode(value.value.toString()) : null,
76
+ decode: bytes => BigInt(textDecoder.decode(bytes))
77
+ });
78
+ extensions.register({
79
+ type: 7,
80
+ encode: value => value instanceof Undefined ? new Uint8Array() : null,
81
+ decode: () => undefined
82
+ });
83
+ export const serialize = (value, attachments = []) => {
84
+ const outgoing = new Map(attachments.map((attachment, index) => [attachment, index]));
85
+ const context = { outgoing };
86
+ return serializePrepared(prepare(value, context), context);
87
+ };
88
+ export const deserialize = (bytes, attachments = []) => {
89
+ return deserializePrepared(bytes, { incoming: attachments });
90
+ };
91
+ const prepare = (value, context) => {
92
+ if (value === undefined)
93
+ return new Undefined();
94
+ if (typeof value === "bigint")
95
+ return new BigInteger(value);
96
+ if (typeof value === "function" || typeof value === "symbol")
97
+ return null;
98
+ if (value === null || typeof value !== "object")
99
+ return value;
100
+ const attachment = context.outgoing?.get(value);
101
+ if (attachment !== undefined)
102
+ return new Attachment(attachment);
103
+ if (value instanceof Date || value instanceof RegExp || value instanceof URL || value instanceof Error)
104
+ return value;
105
+ if (value instanceof ArrayBuffer)
106
+ return new Uint8Array(value);
107
+ if (ArrayBuffer.isView(value))
108
+ return Uint8Array.from(new Uint8Array(value.buffer, value.byteOffset, value.byteLength));
109
+ if (value instanceof Map)
110
+ return new Map([...value].map(([key, entry]) => [prepare(key, context), prepare(entry, context)]));
111
+ if (value instanceof Set)
112
+ return new Set([...value].map(entry => prepare(entry, context)));
113
+ if (Array.isArray(value))
114
+ return value.map(entry => prepare(entry, context));
115
+ if ("toJSON" in value && typeof value.toJSON === "function")
116
+ return prepare(value.toJSON(), context);
117
+ return Object.fromEntries(Object.entries(value)
118
+ .filter(([, entry]) => typeof entry !== "function")
119
+ .map(([key, entry]) => [key, prepare(entry, context)]));
120
+ };
121
+ const uint32 = (value) => {
122
+ const bytes = new Uint8Array(4);
123
+ new DataView(bytes.buffer).setUint32(0, value);
124
+ return bytes;
125
+ };
126
+ const readUint32 = (bytes) => {
127
+ if (bytes.byteLength !== 4)
128
+ throw new Error("The MessagePack attachment reference is invalid");
129
+ return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(0);
130
+ };
package/dist/wire.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import Deadline from "./deadline.js";
3
3
  import { defaultTimeout } from "./events.js";
4
+ import { deserialize, serialize } from "./messagepack.js";
4
5
  /** The server endpoint's sole IPC adapter. */
5
6
  class Wire {
6
7
  pending = new Map();
@@ -12,9 +13,18 @@ class Wire {
12
13
  identityPromise = null;
13
14
  constructor() {
14
15
  process.on("message", message => {
15
- if (!Array.isArray(message))
16
+ if (!(message instanceof Uint8Array))
16
17
  return;
17
- const [route, ...values] = message;
18
+ let decoded;
19
+ try {
20
+ decoded = deserialize(message);
21
+ }
22
+ catch {
23
+ return;
24
+ }
25
+ if (!Array.isArray(decoded) || typeof decoded[0] !== "string")
26
+ return;
27
+ const [route, ...values] = decoded;
18
28
  if (route === "boundary") {
19
29
  const [operation, ...rest] = values;
20
30
  if (operation === "forget" && typeof rest[0] === "string")
@@ -35,10 +45,10 @@ class Wire {
35
45
  }
36
46
  this.deliver(route, values);
37
47
  });
38
- process.send?.(["boundary", "ready"]);
48
+ process.send?.(serialize(["boundary", "ready"]));
39
49
  }
40
50
  send(route, ...values) {
41
- process.send?.([route, ...values]);
51
+ process.send?.(serialize([route, ...values]));
42
52
  }
43
53
  request(values, timeout = defaultTimeout) {
44
54
  return this.requestWithin(values, new Deadline(timeout));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phreshos/server",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "The SDK used by a Program's server endpoint.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
@@ -48,6 +48,9 @@
48
48
  "peerDependencies": {
49
49
  "@phreshos/core": "^0.1.6"
50
50
  },
51
+ "dependencies": {
52
+ "@msgpack/msgpack": "^3.1.3"
53
+ },
51
54
  "devDependencies": {
52
55
  "@phreshos/core": "^0.1.6",
53
56
  "@types/node": "^26.2.0",