@projectqai/proto 0.0.26 → 0.0.29

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.
Files changed (2) hide show
  1. package/device.ts +85 -4
  2. package/package.json +2 -3
package/device.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createClient, type Client, ConnectError, Code } from "@connectrpc/connect";
2
- import { createGrpcTransport } from "@connectrpc/connect-node";
2
+ import { createTransport } from "@connectrpc/connect/protocol-connect";
3
+ import type { UniversalClientFn } from "@connectrpc/connect/protocol";
3
4
  import { create, clone } from "@bufbuild/protobuf";
4
5
  import {
5
6
  WorldService,
@@ -37,9 +38,72 @@ export {
37
38
  type Entity,
38
39
  } from "./dist/world_pb.js";
39
40
 
41
+ // fetch-based universal HTTP client for the Connect protocol.
42
+ // Works in Node, Bun, browsers, and goja — no http2 dependency.
43
+ const universalFetch: UniversalClientFn = async (req) => {
44
+ const body = req.body ? await collectBytes(req.body) : undefined;
45
+ // Prevent the server from gzip-encoding responses — the Connect
46
+ // transport handles its own compression negotiation.
47
+ req.header.set("Accept-Encoding", "identity");
48
+ const resp = await fetch(req.url, {
49
+ method: req.method,
50
+ headers: req.header,
51
+ body,
52
+ signal: (req as any).signal,
53
+ });
54
+ return {
55
+ status: resp.status,
56
+ header: resp.headers,
57
+ body: responseBodyStream(resp),
58
+ trailer: new Headers(),
59
+ };
60
+ };
61
+
62
+ async function collectBytes(iter: AsyncIterable<Uint8Array>): Promise<Uint8Array> {
63
+ const chunks: Uint8Array[] = [];
64
+ for await (const value of iter) {
65
+ chunks.push(value);
66
+ }
67
+ const len = chunks.reduce((n, c) => n + c.length, 0);
68
+ const out = new Uint8Array(len);
69
+ let off = 0;
70
+ for (const c of chunks) { out.set(c, off); off += c.length; }
71
+ return out;
72
+ }
73
+
74
+ function responseBodyStream(resp: Response): AsyncIterable<Uint8Array> {
75
+ if (!resp.body) return { [Symbol.asyncIterator]() { return { next: () => Promise.resolve({ done: true as const, value: undefined }), throw: () => Promise.resolve({ done: true as const, value: undefined }) }; } };
76
+ const reader = resp.body.getReader();
77
+ return {
78
+ [Symbol.asyncIterator]() {
79
+ return {
80
+ async next() {
81
+ const { done, value } = await reader.read();
82
+ if (done) { reader.releaseLock(); return { done: true as const, value: undefined }; }
83
+ return { done: false as const, value };
84
+ },
85
+ async throw(e: unknown) {
86
+ reader.releaseLock();
87
+ return { done: true as const, value: undefined };
88
+ },
89
+ };
90
+ }
91
+ };
92
+ }
93
+
40
94
  export function connect(serverURL?: string): WorldClient {
41
95
  const base = serverURL ?? process.env.HYDRIS_SERVER ?? "http://localhost:50051";
42
- const transport = createGrpcTransport({ baseUrl: base, httpVersion: "2" });
96
+ const transport = createTransport({
97
+ baseUrl: base,
98
+ httpClient: universalFetch,
99
+ useBinaryFormat: true,
100
+ interceptors: [],
101
+ acceptCompression: [],
102
+ sendCompression: null,
103
+ compressMinBytes: Number.MAX_SAFE_INTEGER,
104
+ readMaxBytes: Number.MAX_SAFE_INTEGER,
105
+ writeMaxBytes: Number.MAX_SAFE_INTEGER,
106
+ });
43
107
  return createClient(WorldService, transport);
44
108
  }
45
109
 
@@ -83,6 +147,7 @@ export interface AttachOptions<S extends SchemaProperties> {
83
147
  device?: { category?: string };
84
148
  icon?: string;
85
149
  schema: S;
150
+ init?: (client: WorldClient, config: InferConfig<S>, signal: AbortSignal) => Promise<void>;
86
151
  run: (client: WorldClient, config: InferConfig<S>, signal: AbortSignal) => Promise<void>;
87
152
  health?: () => HealthResult | Promise<HealthResult>;
88
153
  interval?: number;
@@ -138,10 +203,13 @@ export async function attach<S extends SchemaProperties>(opts: AttachOptions<S>)
138
203
 
139
204
  let heartbeatId: ReturnType<typeof setInterval> | null = null;
140
205
 
206
+ let initialized = false;
207
+
141
208
  const pushHeartbeat = (result?: Record<number, { label: string; value: number | bigint }>) => {
209
+ const state = initialized ? DeviceState.DeviceStateActive : DeviceState.DeviceStatePending;
142
210
  const e = create(EntitySchema, {
143
211
  id: entityID,
144
- device: create(DeviceComponentSchema, { ...entity.device, state: DeviceState.DeviceStateActive }),
212
+ device: create(DeviceComponentSchema, { ...entity.device, state }),
145
213
  lifetime: create(LifetimeSchema, {
146
214
  until: create(TimestampSchema, { seconds: BigInt(Math.floor((Date.now() + interval + 1_000) / 1000)) }),
147
215
  }),
@@ -177,7 +245,7 @@ export async function attach<S extends SchemaProperties>(opts: AttachOptions<S>)
177
245
  : create(ConfigurableComponentSchema);
178
246
  cfg.state = state;
179
247
  cfg.error = error;
180
- if (entity.config && state === ConfigurableState.ConfigurableStateActive) {
248
+ if (entity.config && (state === ConfigurableState.ConfigurableStateStarting || state === ConfigurableState.ConfigurableStateActive)) {
181
249
  cfg.appliedVersion = entity.config.version;
182
250
  }
183
251
  await push(client, create(EntitySchema, { id: entityID, configurable: cfg })).catch(() => { });
@@ -193,6 +261,7 @@ export async function attach<S extends SchemaProperties>(opts: AttachOptions<S>)
193
261
  runningAbort.abort();
194
262
  runningAbort = null;
195
263
  currentEntity = null;
264
+ initialized = false;
196
265
  console.log(`stopped entity=${entityID}`);
197
266
  if (e) pushState(e, ConfigurableState.ConfigurableStateInactive);
198
267
  }
@@ -206,6 +275,18 @@ export async function attach<S extends SchemaProperties>(opts: AttachOptions<S>)
206
275
  (async () => {
207
276
  while (!childSignal.aborted) {
208
277
  await pushState(e, ConfigurableState.ConfigurableStateStarting);
278
+
279
+ try {
280
+ if (opts.init) await opts.init(client, extractConfig(e, opts.schema), childSignal);
281
+ } catch (err) {
282
+ if (childSignal.aborted || isCanceled(err)) return;
283
+ console.error(`init failed entity=${entityID}`, err);
284
+ await pushState(e, ConfigurableState.ConfigurableStateFailed, String(err));
285
+ await sleep(5_000, childSignal);
286
+ continue;
287
+ }
288
+
289
+ initialized = true;
209
290
  await pushState(e, ConfigurableState.ConfigurableStateActive);
210
291
 
211
292
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@projectqai/proto",
3
- "version": "0.0.26",
3
+ "version": "0.0.29",
4
4
  "author": "projectq-release-bot",
5
5
  "type": "module",
6
6
  "exports": {
@@ -23,8 +23,7 @@
23
23
  "@bufbuild/protobuf": "^2.10.1"
24
24
  },
25
25
  "dependencies": {
26
- "@connectrpc/connect": "^2.0.0",
27
- "@connectrpc/connect-node": "^2.0.0"
26
+ "@connectrpc/connect": "^2.0.0"
28
27
  },
29
28
  "devDependencies": {
30
29
  "@bufbuild/protoc-gen-es": "^2.10.1"