hazo_collect 0.2.2 → 0.2.4

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/CHANGE_LOG.md CHANGED
@@ -1,5 +1,52 @@
1
1
  # hazo_collect — Change Log
2
2
 
3
+ ## 0.2.4 — 2026-06-14
4
+
5
+ ### Fixes — `discover()` read-back of the now-`jsonb` manifest (0.2.3 follow-up)
6
+
7
+ 0.2.3 fixed the **write** side (manifest/payload stored as real `jsonb` objects) but missed the
8
+ matching **read** site. `discover()` still did `JSON.parse(row.manifest)`, and now that the column
9
+ holds real `jsonb` the PostgREST adapter returns `row.manifest` as an already-parsed **object** —
10
+ `JSON.parse(object)` coerces to the string `"[object Object]"` and throws. The worker could not
11
+ boot: `engine.discover_failed` (`"[object Object]" is not valid JSON`) retried forever.
12
+
13
+ Fix: `src/registry/discovery.ts` reads the manifest via `parseJsonbField()` (from
14
+ `hazo_connect/server`), which normalises both shapes — an already-parsed object (PostgREST/pg
15
+ `jsonb`) and a JSON string (text/SQLite read) — instead of blindly `JSON.parse`-ing.
16
+
17
+ Regression test (`src/__tests__/discovery.test.ts`): a PostgREST-shaped adapter wrapper returns
18
+ the registry `manifest` column as an object; `discover()` must boot the worker without re-parsing.
19
+
20
+ **Requires:** `hazo_connect ^3.8.0`.
21
+
22
+ ## 0.2.3 — 2026-06-14
23
+
24
+ ### Fixes — stop double-encoding JSON columns for PostgREST/JSONB adapters
25
+
26
+ Previously, `write-adapter.ts`, `registry/index.ts`, and `manager/index.ts` all called
27
+ `JSON.stringify()` on JSON-valued fields before handing them to the adapter. This was correct for
28
+ SQLite (which stores JSON as TEXT), but caused **double-encoding** for PostgREST/pg adapters whose
29
+ JSONB columns serialize objects natively — making `manifest->>'name'` and `payload->>'seq'`
30
+ return null on ocdata.
31
+
32
+ Fix: pass objects/arrays through directly and declare the JSON columns via the new
33
+ `QueryBuilder.jsonColumns([...])` API (introduced in `hazo_connect@3.8.0`). The SQLite adapter
34
+ reads the declared columns and serializes them itself; the PostgREST adapter passes the object
35
+ through as-is.
36
+
37
+ **Changed write sites:**
38
+ - `src/server/write-adapter.ts` — `payload` no longer pre-stringified; `QueryBuilder` chain adds
39
+ `.jsonColumns(['payload'])`.
40
+ - `src/registry/index.ts` — `manifest` no longer pre-stringified; `RegistryRow.manifest` type
41
+ widened from `string` to `ManifestInput`; both `doUpdate` and `doNothing` QueryBuilders get
42
+ `.jsonColumns(['manifest'])`.
43
+ - `src/manager/index.ts` `insertRunRow` — `errors` changed from `'[]'` string to `[]` array;
44
+ QueryBuilder gets `.jsonColumns(['errors'])`.
45
+ - `src/manager/index.ts` `updateRunRow` — `errors` and `metrics` no longer pre-stringified;
46
+ QueryBuilder gets `.jsonColumns(['errors', 'metrics'])`.
47
+
48
+ **Requires:** `hazo_connect ^3.8.0`.
49
+
3
50
  ## 0.2.2 — 2026-06-14
4
51
 
5
52
  ### Fixes — CJS-host plugin discovery (empty-registry bug)
@@ -22,17 +22,16 @@ function createWriteAdapter(adapter) {
22
22
  let landingWritten = 0;
23
23
  let canonicalWritten = 0;
24
24
  for (const row of landing) {
25
- const payload = typeof row.payload === "string" ? row.payload : JSON.stringify(row.payload);
26
25
  const record = {
27
26
  id: generateRequestId().slice(4),
28
27
  plugin,
29
28
  run_id: row.run_id,
30
29
  idempotency_key: row.idempotency_key,
31
- payload,
30
+ payload: row.payload,
32
31
  ...row.window?.since != null ? { window_since: row.window.since } : {},
33
32
  ...row.window?.until != null ? { window_until: row.window.until } : {}
34
33
  };
35
- const qb = new QueryBuilder().from("hazo_collect_landing").onConflict(["plugin", "idempotency_key"]).doUpdate();
34
+ const qb = new QueryBuilder().from("hazo_collect_landing").onConflict(["plugin", "idempotency_key"]).doUpdate().jsonColumns(["payload"]);
36
35
  await tx.query(qb, "POST", record);
37
36
  landingWritten++;
38
37
  }
@@ -167,22 +166,22 @@ async function insertRunRow(adapter, params) {
167
166
  correlation_id: params.correlation_id,
168
167
  records_fetched: 0,
169
168
  records_written: 0,
170
- errors: "[]",
169
+ errors: [],
171
170
  contract_version: CONTRACT_VERSION
172
171
  };
173
- const qb = new QueryBuilder2().from("hazo_collect_plugin_runs");
172
+ const qb = new QueryBuilder2().from("hazo_collect_plugin_runs").jsonColumns(["errors"]);
174
173
  await adapter.query(qb, "POST", row);
175
174
  }
176
175
  async function updateRunRow(adapter, run_id, result) {
177
- const qb = new QueryBuilder2().from("hazo_collect_plugin_runs").where("run_id", "eq", run_id);
176
+ const qb = new QueryBuilder2().from("hazo_collect_plugin_runs").where("run_id", "eq", run_id).jsonColumns(["errors", "metrics"]);
178
177
  await adapter.query(qb, "PATCH", {
179
178
  status: result.status,
180
179
  completed_at: result.completed_at,
181
180
  records_fetched: result.records_fetched,
182
181
  records_written: result.records_written,
183
182
  watermark: result.watermark ?? null,
184
- errors: JSON.stringify(result.errors),
185
- metrics: result.metrics ? JSON.stringify(result.metrics) : null,
183
+ errors: result.errors,
184
+ metrics: result.metrics ?? null,
186
185
  contract_version: result.contract_version ?? null
187
186
  });
188
187
  }
@@ -373,7 +372,7 @@ import { QueryBuilder as QueryBuilder4, wrapResult as wrapResult3 } from "hazo_c
373
372
  import { readFileSync, readdirSync } from "fs";
374
373
  import { resolve } from "path";
375
374
  import { pathToFileURL } from "url";
376
- import { QueryBuilder as QueryBuilder3 } from "hazo_connect/server";
375
+ import { QueryBuilder as QueryBuilder3, parseJsonbField } from "hazo_connect/server";
377
376
  async function scanFolder(pluginsDir) {
378
377
  const entries = readdirSync(pluginsDir, { withFileTypes: true });
379
378
  const subdirs = entries.filter((e) => e.isDirectory());
@@ -402,7 +401,7 @@ async function discover(opts) {
402
401
  const rows = await opts.adapter.query(qb);
403
402
  const snapshotEntries = [];
404
403
  for (const row of rows) {
405
- const parsedManifest = parseManifest(JSON.parse(row.manifest));
404
+ const parsedManifest = parseManifest(parseJsonbField(row.manifest));
406
405
  const worker = getCollector(parsedManifest.name);
407
406
  if (!worker) {
408
407
  continue;
@@ -460,12 +459,12 @@ async function persistRegistry(adapter, entries, opts) {
460
459
  kind: entry.manifest.kind,
461
460
  version: entry.manifest.version,
462
461
  runtime: entry.manifest.runtime,
463
- manifest: JSON.stringify(entry.manifest),
462
+ manifest: entry.manifest,
464
463
  source: entry.source ?? "folder",
465
464
  valid,
466
465
  quarantine_reason: quarantineReason
467
466
  };
468
- const qb = opts?.reseed ? new QueryBuilder4().from("hazo_collect_plugin_registry").onConflict(["name"]).doUpdate() : new QueryBuilder4().from("hazo_collect_plugin_registry").onConflict(["name"]).doNothing();
467
+ const qb = opts?.reseed ? new QueryBuilder4().from("hazo_collect_plugin_registry").onConflict(["name"]).doUpdate().jsonColumns(["manifest"]) : new QueryBuilder4().from("hazo_collect_plugin_registry").onConflict(["name"]).doNothing().jsonColumns(["manifest"]);
469
468
  await adapter.query(qb, "POST", row);
470
469
  }
471
470
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hazo_collect",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Collector-manager engine for the Ocdata platform",
5
5
  "type": "module",
6
6
  "module": "./dist/index.js",
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "peerDependencies": {
46
46
  "hazo_core": "^1.2.0",
47
- "hazo_connect": "^3.7.0",
47
+ "hazo_connect": "^3.8.0",
48
48
  "hazo_secure": "^1.3.0",
49
49
  "react": "^18.0.0 || ^19.0.0",
50
50
  "react-dom": "^18.0.0 || ^19.0.0",
@@ -75,7 +75,7 @@
75
75
  "@types/react": "^19.0.0",
76
76
  "@types/react-dom": "^19.0.0",
77
77
  "hazo_core": "^1.2.0",
78
- "hazo_connect": "^3.7.0",
78
+ "hazo_connect": "^3.8.0",
79
79
  "hazo_secure": "^1.3.0"
80
80
  },
81
81
  "keywords": [],