diff --git a/apps/website/content/docs/chat/api/parse-tree-store.mdx b/apps/website/content/docs/chat/api/parse-tree-store.mdx
index 419c282a1..a631724f3 100644
--- a/apps/website/content/docs/chat/api/parse-tree-store.mdx
+++ b/apps/website/content/docs/chat/api/parse-tree-store.mdx
@@ -1,6 +1,6 @@
# createParseTreeStore()
-Factory function that creates a `ParseTreeStore` — a bridge between the `@ngaf/partial-json` parser and Angular's signal-based `Spec` rendering. It materializes the parse tree into a `Spec` signal with structural sharing on each push.
+Factory function that creates a `ParseTreeStore` — a bridge between the `@cacheplane/partial-json` parser and Angular's signal-based `Spec` rendering. It materializes the parse tree into a `Spec` signal with structural sharing on each push.
**Import:**
@@ -17,7 +17,7 @@ function createParseTreeStore(parser: PartialJsonParser): ParseTreeStore
| Parameter | Type | Description |
|-----------|------|-------------|
-| `parser` | `PartialJsonParser` | A parser instance from `createPartialJsonParser()` in `@ngaf/partial-json` |
+| `parser` | `PartialJsonParser` | A parser instance from `createPartialJsonParser()` in `@cacheplane/partial-json` |
**Returns:** `ParseTreeStore` — must be called within an Angular injection context.
@@ -50,7 +50,7 @@ interface ElementAccumulationState {
## Usage
```typescript
-import { createPartialJsonParser } from '@ngaf/partial-json';
+import { createPartialJsonParser } from '@cacheplane/partial-json';
import { createParseTreeStore } from '@ngaf/chat';
const parser = createPartialJsonParser();
diff --git a/apps/website/content/docs/chat/getting-started/installation.mdx b/apps/website/content/docs/chat/getting-started/installation.mdx
index 6221c3839..5aece0734 100644
--- a/apps/website/content/docs/chat/getting-started/installation.mdx
+++ b/apps/website/content/docs/chat/getting-started/installation.mdx
@@ -32,11 +32,12 @@ npm install @ngaf/chat marked
| `@angular/forms` | `^20.0.0 \|\| ^21.0.0` | Yes |
| `@ngaf/render` | `^0.0.1` | Yes |
| `@ngaf/a2ui` | `^0.0.1` | Yes |
-| `@ngaf/partial-json` | `^0.0.1` | Yes |
| `@json-render/core` | `^0.16.0` | Yes |
| `@langchain/core` | `^1.1.33` | Yes |
| `marked` | `^15.0.0 \|\| ^16.0.0` | Yes |
+`@cacheplane/partial-json` is installed by `@ngaf/chat` for streaming JSON parsing.
+
`marked` parses AI message content into HTML (headings, code blocks, tables, lists). It is a required peer; the library ships a defensive plain-text fallback for resilience, but the rendered output is unusable without `marked` installed.
diff --git a/apps/website/content/docs/chat/guides/generative-ui.mdx b/apps/website/content/docs/chat/guides/generative-ui.mdx
index 3fe8936de..f1443e010 100644
--- a/apps/website/content/docs/chat/guides/generative-ui.mdx
+++ b/apps/website/content/docs/chat/guides/generative-ui.mdx
@@ -16,7 +16,7 @@ AI message content (token by token)
→ JSON specs via RenderSpecComponent + your view registry
```
-The JSON path uses `@ngaf/partial-json` to parse incomplete JSON character-by-character, producing a live `Spec` signal with structural sharing — unchanged elements keep the same object reference so Angular skips re-rendering them.
+The JSON path uses `@cacheplane/partial-json` to parse incomplete JSON character-by-character, producing a live `Spec` signal with structural sharing — unchanged elements keep the same object reference so Angular skips re-rendering them.
## Setup
diff --git a/apps/website/content/docs/chat/guides/streaming.mdx b/apps/website/content/docs/chat/guides/streaming.mdx
index c0ee2336d..4693b10c8 100644
--- a/apps/website/content/docs/chat/guides/streaming.mdx
+++ b/apps/website/content/docs/chat/guides/streaming.mdx
@@ -8,7 +8,7 @@ Each AI message is processed by a `ContentClassifier` that examines the content
| Trigger | Content Type | What Happens |
|---------|-------------|--------------|
-| First non-whitespace is `{` | `json-render` | Parsed as a JSON spec via `@ngaf/partial-json` |
+| First non-whitespace is `{` | `json-render` | Parsed as a JSON spec via `@cacheplane/partial-json` |
| Any other text | `markdown` | Rendered as markdown prose |
@@ -74,7 +74,7 @@ type ContentType = 'undetermined' | 'markdown' | 'json-render' | 'a2ui' | 'mixed
For lower-level control over JSON-to-Spec materialization:
```typescript
-import { createPartialJsonParser } from '@ngaf/partial-json';
+import { createPartialJsonParser } from '@cacheplane/partial-json';
import { createParseTreeStore } from '@ngaf/chat';
const parser = createPartialJsonParser();
diff --git a/apps/website/src/app/llms.txt/route.ts b/apps/website/src/app/llms.txt/route.ts
index a204fa58f..82f6f0fd6 100644
--- a/apps/website/src/app/llms.txt/route.ts
+++ b/apps/website/src/app/llms.txt/route.ts
@@ -26,7 +26,6 @@ function buildLlmsTxt(): string {
'- @ngaf/ag-ui — adapter for any AG-UI-compatible backend (CrewAI, Mastra, Microsoft AF, AG2, Pydantic AI, AWS Strands, CopilotKit runtime)',
'- @ngaf/render — generative UI runtime (Vercel json-render + Google A2UI)',
'- @ngaf/a2ui — A2UI catalog components',
- '- @ngaf/partial-json — streaming JSON parser',
'- @ngaf/licensing — license verification client',
'',
'## Install',
diff --git a/cockpit/render/shared/streaming-simulator.ts b/cockpit/render/shared/streaming-simulator.ts
index 1452ef0fc..45ed77908 100644
--- a/cockpit/render/shared/streaming-simulator.ts
+++ b/cockpit/render/shared/streaming-simulator.ts
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
import { signal, computed } from '@angular/core';
-import { createPartialJsonParser, materialize } from '@ngaf/partial-json';
-import type { PartialJsonParser, ParseEvent } from '@ngaf/partial-json';
+import { createPartialJsonParser, materialize } from '@cacheplane/partial-json';
+import type { PartialJsonParser, ParseEvent } from '@cacheplane/partial-json';
import type { Spec } from '@json-render/core';
export class StreamingSimulator {
diff --git a/docs/RELEASE.md b/docs/RELEASE.md
index 32be579de..f4a2d981a 100644
--- a/docs/RELEASE.md
+++ b/docs/RELEASE.md
@@ -1,6 +1,6 @@
# Release Process
-The seven publishable libraries (`@ngaf/chat`, `@ngaf/langgraph`, `@ngaf/ag-ui`, `@ngaf/render`, `@ngaf/a2ui`, `@ngaf/partial-json`, `@ngaf/licensing`) ship together at a synchronized version via Nx Release. During the `0.0.x` exploratory phase, only patch bumps are used.
+The seven publishable libraries (`@ngaf/chat`, `@ngaf/langgraph`, `@ngaf/ag-ui`, `@ngaf/render`, `@ngaf/a2ui`, `@ngaf/licensing`, `@ngaf/telemetry`) ship together at a synchronized version via Nx Release. During the `0.0.x` exploratory phase, only patch bumps are used.
## One-shot release (recommended; second release onward)
@@ -51,7 +51,7 @@ The very first publish ships the version currently on disk (`0.0.1`) — no vers
```bash
# 1. Build everything
-npx nx run-many -t build --projects=chat,langgraph,ag-ui,render,a2ui,partial-json,licensing
+npx nx run-many -t build --projects=chat,langgraph,ag-ui,render,a2ui,licensing,telemetry
# 2. Generate the initial CHANGELOG, commit, and tag v0.0.1
npx nx release changelog 0.0.1 --first-release
diff --git a/libs/partial-json/package.json b/libs/partial-json/package.json
deleted file mode 100644
index 79a4e3468..000000000
--- a/libs/partial-json/package.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "name": "@ngaf/partial-json",
- "version": "0.0.29",
- "deprecated": "Replaced by @cacheplane/partial-json. No further versions will be published from this package.",
- "license": "MIT",
- "repository": {
- "type": "git",
- "url": "https://github.com/cacheplane/angular-agent-framework.git",
- "directory": "libs/partial-json"
- },
- "homepage": "https://github.com/cacheplane/angular-agent-framework#readme",
- "bugs": {
- "url": "https://github.com/cacheplane/angular-agent-framework/issues"
- },
- "sideEffects": false
-}
diff --git a/libs/partial-json/project.json b/libs/partial-json/project.json
deleted file mode 100644
index 841eb2710..000000000
--- a/libs/partial-json/project.json
+++ /dev/null
@@ -1,37 +0,0 @@
-{
- "name": "partial-json",
- "$schema": "../../node_modules/nx/schemas/project-schema.json",
- "sourceRoot": "libs/partial-json/src",
- "projectType": "library",
- "tags": [
- "scope:shared",
- "type:lib"
- ],
- "targets": {
- "build": {
- "executor": "@nx/js:tsc",
- "outputs": [
- "{workspaceRoot}/dist/libs/partial-json"
- ],
- "options": {
- "outputPath": "dist/libs/partial-json",
- "main": "libs/partial-json/src/index.ts",
- "tsConfig": "libs/partial-json/tsconfig.lib.json"
- }
- },
- "lint": {
- "executor": "@nx/eslint:lint"
- },
- "test": {
- "executor": "@nx/vitest:test",
- "options": {
- "configFile": "libs/partial-json/vite.config.mts"
- }
- },
- "nx-release-publish": {
- "options": {
- "packageRoot": "dist/{projectRoot}"
- }
- }
- }
-}
diff --git a/libs/partial-json/src/index.ts b/libs/partial-json/src/index.ts
deleted file mode 100644
index 119d2fa75..000000000
--- a/libs/partial-json/src/index.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-// SPDX-License-Identifier: MIT
-export type {
- JsonNodeType, JsonNodeStatus, JsonNodeBase,
- JsonObjectNode, JsonArrayNode, JsonStringNode,
- JsonNumberNode, JsonBooleanNode, JsonNullNode,
- JsonNode, ParseEvent, PartialJsonParser,
-} from './lib/types';
-export { createPartialJsonParser } from './lib/parser';
-export { materialize } from './lib/materialize';
diff --git a/libs/partial-json/src/lib/materialize.spec.ts b/libs/partial-json/src/lib/materialize.spec.ts
deleted file mode 100644
index e0bb4e718..000000000
--- a/libs/partial-json/src/lib/materialize.spec.ts
+++ /dev/null
@@ -1,144 +0,0 @@
-// SPDX-License-Identifier: MIT
-import { describe, it, expect } from 'vitest';
-import { createPartialJsonParser } from './parser';
-import { materialize } from './materialize';
-import type { JsonArrayNode, JsonObjectNode, JsonStringNode } from './types';
-
-describe('materialize', () => {
- describe('basic materialization', () => {
- it('should materialize a string node', () => {
- const parser = createPartialJsonParser();
- parser.push('"hello"');
- expect(materialize(parser.root!)).toBe('hello');
- });
-
- it('should materialize a number node inside an array', () => {
- const parser = createPartialJsonParser();
- parser.push('[42]');
- const result = materialize(parser.root!) as unknown[];
- expect(result).toEqual([42]);
- });
-
- it('should materialize a boolean true', () => {
- const parser = createPartialJsonParser();
- parser.push('[true]');
- const result = materialize(parser.root!) as unknown[];
- expect(result).toEqual([true]);
- });
-
- it('should materialize a boolean false', () => {
- const parser = createPartialJsonParser();
- parser.push('[false]');
- const result = materialize(parser.root!) as unknown[];
- expect(result).toEqual([false]);
- });
-
- it('should materialize null', () => {
- const parser = createPartialJsonParser();
- parser.push('[null]');
- const result = materialize(parser.root!) as unknown[];
- expect(result).toEqual([null]);
- });
-
- it('should materialize a simple object', () => {
- const parser = createPartialJsonParser();
- parser.push('{"a": 1, "b": "two"}');
- const result = materialize(parser.root!) as Record;
- expect(result).toEqual({ a: 1, b: 'two' });
- });
-
- it('should materialize an array', () => {
- const parser = createPartialJsonParser();
- parser.push('[1, 2, 3]');
- const result = materialize(parser.root!) as unknown[];
- expect(result).toEqual([1, 2, 3]);
- });
-
- it('should materialize nested structures', () => {
- const parser = createPartialJsonParser();
- parser.push('{"items": [{"name": "a"}, {"name": "b"}]}');
- const result = materialize(parser.root!) as Record;
- expect(result).toEqual({ items: [{ name: 'a' }, { name: 'b' }] });
- });
-
- it('should materialize a partial streaming string', () => {
- const parser = createPartialJsonParser();
- parser.push('{"msg": "hel');
- const result = materialize(parser.root!) as Record;
- expect(result).toEqual({ msg: 'hel' });
- });
-
- it('should materialize a partial streaming number with best-effort', () => {
- const parser = createPartialJsonParser();
- parser.push('[12');
- // Number is still streaming (not terminated), so best-effort parse
- const result = materialize(parser.root!) as unknown[];
- expect(result).toEqual([12]);
- });
- });
-
- describe('structural sharing', () => {
- it('should return the same reference for unchanged subtrees', () => {
- const parser = createPartialJsonParser();
- parser.push('{"a": {"x": 1}, "b": "hel');
-
- const result1 = materialize(parser.root!) as Record;
- const aRef1 = result1['a'];
-
- // Stream more into b
- parser.push('lo"');
- const result2 = materialize(parser.root!) as Record;
- const aRef2 = result2['a'];
-
- // a subtree didn't change, should be same reference
- expect(aRef2).toBe(aRef1);
- // b did change
- expect(result2['b']).toBe('hello');
- });
-
- it('should preserve sibling references when one property changes', () => {
- const parser = createPartialJsonParser();
- parser.push('{"x": [1, 2], "y": [3, 4], "z": "stream');
-
- const result1 = materialize(parser.root!) as Record;
- const xRef1 = result1['x'];
- const yRef1 = result1['y'];
-
- // Only z changes
- parser.push('ing"');
- const result2 = materialize(parser.root!) as Record;
-
- expect(result2['x']).toBe(xRef1);
- expect(result2['y']).toBe(yRef1);
- expect(result2['z']).toBe('streaming');
- });
-
- it('should return the same reference when nothing changed between calls', () => {
- const parser = createPartialJsonParser();
- parser.push('{"a": 1, "b": 2}');
-
- const result1 = materialize(parser.root!);
- const result2 = materialize(parser.root!);
-
- expect(result2).toBe(result1);
- });
-
- it('should detect changes in nested arrays', () => {
- const parser = createPartialJsonParser();
- parser.push('{"items": [1');
-
- const result1 = materialize(parser.root!) as Record;
- const items1 = result1['items'] as unknown[];
- expect(items1).toEqual([1]);
-
- // Add another item
- parser.push(', 2]');
- const result2 = materialize(parser.root!) as Record;
- const items2 = result2['items'] as unknown[];
- expect(items2).toEqual([1, 2]);
-
- // items array changed (new child), so different reference
- expect(items2).not.toBe(items1);
- });
- });
-});
diff --git a/libs/partial-json/src/lib/materialize.ts b/libs/partial-json/src/lib/materialize.ts
deleted file mode 100644
index d42d73798..000000000
--- a/libs/partial-json/src/lib/materialize.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-// SPDX-License-Identifier: MIT
-import type { JsonNode, JsonObjectNode, JsonArrayNode } from './types';
-
-/**
- * Cache entry storing the last materialized value and a version fingerprint
- * used to detect whether a subtree has changed.
- */
-interface CacheEntry {
- version: string;
- value: unknown;
-}
-
-const cache = new WeakMap();
-
-/**
- * Compute a lightweight version fingerprint for a node.
- * The fingerprint captures enough state to detect any change in the
- * subtree rooted at this node.
- */
-function computeVersion(node: JsonNode): string {
- switch (node.type) {
- case 'string':
- return `s:${node.status}:${node.value}`;
- case 'number':
- return `n:${node.status}:${node.raw}`;
- case 'boolean':
- return `b:${node.value}`;
- case 'null':
- return 'null';
- case 'object': {
- const obj = node as JsonObjectNode;
- const parts: string[] = [`o:${obj.status}:${obj.children.size}`];
- for (const [key, child] of obj.children) {
- parts.push(`${key}=${computeVersion(child)}`);
- }
- return parts.join('|');
- }
- case 'array': {
- const arr = node as JsonArrayNode;
- const parts: string[] = [`a:${arr.status}:${arr.children.length}`];
- for (const child of arr.children) {
- parts.push(computeVersion(child));
- }
- return parts.join('|');
- }
- }
-}
-
-/**
- * Convert a parse-tree node to a plain JS value.
- *
- * Structural sharing: unchanged subtrees return the exact same object
- * reference across consecutive calls, enabling cheap `===` checks
- * in downstream consumers (e.g. Angular signals, React memos).
- */
-export function materialize(node: JsonNode): unknown {
- const version = computeVersion(node);
- const cached = cache.get(node);
- if (cached && cached.version === version) {
- return cached.value;
- }
-
- let value: unknown;
-
- switch (node.type) {
- case 'string':
- value = node.value;
- break;
- case 'number':
- // Complete numbers have a parsed value; streaming ones get best-effort
- value = node.value !== null ? node.value : Number(node.raw);
- break;
- case 'boolean':
- value = node.value;
- break;
- case 'null':
- value = null;
- break;
- case 'object': {
- const obj = node as JsonObjectNode;
- const result: Record = {};
- for (const [key, child] of obj.children) {
- result[key] = materialize(child);
- }
- value = result;
- break;
- }
- case 'array': {
- const arr = node as JsonArrayNode;
- value = arr.children.map((child) => materialize(child));
- break;
- }
- }
-
- cache.set(node, { version, value });
- return value;
-}
diff --git a/libs/partial-json/src/lib/parser.spec.ts b/libs/partial-json/src/lib/parser.spec.ts
deleted file mode 100644
index 7fb0afe02..000000000
--- a/libs/partial-json/src/lib/parser.spec.ts
+++ /dev/null
@@ -1,404 +0,0 @@
-// SPDX-License-Identifier: MIT
-import { describe, it, expect } from 'vitest';
-import { createPartialJsonParser } from './parser';
-import type {
- JsonStringNode,
- JsonNumberNode,
- JsonBooleanNode,
- JsonNullNode,
- JsonObjectNode,
- JsonArrayNode,
- ParseEvent,
-} from './types';
-
-describe('createPartialJsonParser', () => {
- describe('strings', () => {
- it('should parse a complete string', () => {
- const parser = createPartialJsonParser();
- const events = parser.push('"hello"');
- const root = parser.root as JsonStringNode;
- expect(root).not.toBeNull();
- expect(root.type).toBe('string');
- expect(root.value).toBe('hello');
- expect(root.status).toBe('complete');
- });
-
- it('should stream a string character-by-character', () => {
- const parser = createPartialJsonParser();
- parser.push('"');
- parser.push('h');
- parser.push('e');
- parser.push('l');
- parser.push('l');
- parser.push('o');
- parser.push('"');
- const root = parser.root as JsonStringNode;
- expect(root.type).toBe('string');
- expect(root.value).toBe('hello');
- expect(root.status).toBe('complete');
- });
-
- it('should emit value-updated events with delta for strings', () => {
- const parser = createPartialJsonParser();
- parser.push('"');
- const events1 = parser.push('he');
- const valueUpdates = events1.filter((e) => e.type === 'value-updated');
- expect(valueUpdates.length).toBeGreaterThan(0);
- for (const ev of valueUpdates) {
- expect(ev.delta).toBeDefined();
- }
- parser.push('llo"');
- const root = parser.root as JsonStringNode;
- expect(root.value).toBe('hello');
- });
-
- it('should handle escaped characters (\\n, \\", \\\\)', () => {
- const parser = createPartialJsonParser();
- parser.push('"line1\\nline2"');
- const root = parser.root as JsonStringNode;
- expect(root.value).toBe('line1\nline2');
- });
-
- it('should handle escaped double quote', () => {
- const parser = createPartialJsonParser();
- parser.push('"say \\"hi\\""');
- const root = parser.root as JsonStringNode;
- expect(root.value).toBe('say "hi"');
- });
-
- it('should handle escaped backslash', () => {
- const parser = createPartialJsonParser();
- parser.push('"a\\\\b"');
- const root = parser.root as JsonStringNode;
- expect(root.value).toBe('a\\b');
- });
-
- it('should handle unicode escapes (\\u0041 = A)', () => {
- const parser = createPartialJsonParser();
- parser.push('"\\u0041"');
- const root = parser.root as JsonStringNode;
- expect(root.value).toBe('A');
- });
- });
-
- describe('numbers', () => {
- it('should parse a complete integer in an array', () => {
- const parser = createPartialJsonParser();
- parser.push('[42]');
- const root = parser.root as JsonArrayNode;
- const num = root.children[0] as JsonNumberNode;
- expect(num.type).toBe('number');
- expect(num.value).toBe(42);
- expect(num.status).toBe('complete');
- });
-
- it('should complete a number when followed by }', () => {
- const parser = createPartialJsonParser();
- parser.push('{"a":123}');
- const root = parser.root as JsonObjectNode;
- const num = root.children.get('a') as JsonNumberNode;
- expect(num.type).toBe('number');
- expect(num.value).toBe(123);
- expect(num.status).toBe('complete');
- });
-
- it('should handle negative and decimal numbers', () => {
- const parser = createPartialJsonParser();
- parser.push('[-3.14]');
- const root = parser.root as JsonArrayNode;
- const num = root.children[0] as JsonNumberNode;
- expect(num.value).toBe(-3.14);
- expect(num.status).toBe('complete');
- });
-
- it('should stream numbers at end of input', () => {
- const parser = createPartialJsonParser();
- parser.push('[12');
- const root = parser.root as JsonArrayNode;
- const num = root.children[0] as JsonNumberNode;
- expect(num.type).toBe('number');
- expect(num.raw).toBe('12');
- expect(num.status).toBe('streaming');
- });
- });
-
- describe('booleans and null', () => {
- it('should parse true', () => {
- const parser = createPartialJsonParser();
- parser.push('[true]');
- const root = parser.root as JsonArrayNode;
- const node = root.children[0] as JsonBooleanNode;
- expect(node.type).toBe('boolean');
- expect(node.value).toBe(true);
- expect(node.status).toBe('complete');
- });
-
- it('should parse false', () => {
- const parser = createPartialJsonParser();
- parser.push('[false]');
- const root = parser.root as JsonArrayNode;
- const node = root.children[0] as JsonBooleanNode;
- expect(node.type).toBe('boolean');
- expect(node.value).toBe(false);
- expect(node.status).toBe('complete');
- });
-
- it('should parse null', () => {
- const parser = createPartialJsonParser();
- parser.push('[null]');
- const root = parser.root as JsonArrayNode;
- const node = root.children[0] as JsonNullNode;
- expect(node.type).toBe('null');
- expect(node.status).toBe('complete');
- });
-
- it('should handle partial keywords gracefully', () => {
- const parser = createPartialJsonParser();
- parser.push('[tru');
- const root = parser.root as JsonArrayNode;
- // Partial keyword should create a pending node
- expect(root.children.length).toBe(1);
- expect(root.children[0].status).toBe('pending');
- });
- });
-
- describe('objects', () => {
- it('should parse a simple object', () => {
- const parser = createPartialJsonParser();
- parser.push('{"a":"b"}');
- const root = parser.root as JsonObjectNode;
- expect(root.type).toBe('object');
- expect(root.status).toBe('complete');
- const child = root.children.get('a') as JsonStringNode;
- expect(child.value).toBe('b');
- expect(child.status).toBe('complete');
- });
-
- it('should stream property values', () => {
- const parser = createPartialJsonParser();
- parser.push('{"name":"Al');
- const root = parser.root as JsonObjectNode;
- expect(root.type).toBe('object');
- expect(root.status).toBe('streaming');
- const child = root.children.get('name') as JsonStringNode;
- expect(child.type).toBe('string');
- expect(child.value).toBe('Al');
- expect(child.status).toBe('streaming');
- });
-
- it('should handle multiple properties', () => {
- const parser = createPartialJsonParser();
- parser.push('{"a":"1","b":"2"}');
- const root = parser.root as JsonObjectNode;
- expect(root.children.size).toBe(2);
- expect((root.children.get('a') as JsonStringNode).value).toBe('1');
- expect((root.children.get('b') as JsonStringNode).value).toBe('2');
- });
-
- it('should handle nested objects', () => {
- const parser = createPartialJsonParser();
- parser.push('{"outer":{"inner":"value"}}');
- const root = parser.root as JsonObjectNode;
- const outer = root.children.get('outer') as JsonObjectNode;
- expect(outer.type).toBe('object');
- const inner = outer.children.get('inner') as JsonStringNode;
- expect(inner.value).toBe('value');
- });
- });
-
- describe('arrays', () => {
- it('should parse a simple array of numbers', () => {
- const parser = createPartialJsonParser();
- parser.push('[1,2,3]');
- const root = parser.root as JsonArrayNode;
- expect(root.type).toBe('array');
- expect(root.status).toBe('complete');
- expect(root.children.length).toBe(3);
- expect((root.children[0] as JsonNumberNode).value).toBe(1);
- expect((root.children[1] as JsonNumberNode).value).toBe(2);
- expect((root.children[2] as JsonNumberNode).value).toBe(3);
- });
-
- it('should parse an array of strings', () => {
- const parser = createPartialJsonParser();
- parser.push('["a","b","c"]');
- const root = parser.root as JsonArrayNode;
- expect(root.children.length).toBe(3);
- expect((root.children[0] as JsonStringNode).value).toBe('a');
- expect((root.children[1] as JsonStringNode).value).toBe('b');
- expect((root.children[2] as JsonStringNode).value).toBe('c');
- });
-
- it('should parse nested arrays', () => {
- const parser = createPartialJsonParser();
- parser.push('[[1,2],[3]]');
- const root = parser.root as JsonArrayNode;
- expect(root.children.length).toBe(2);
- const first = root.children[0] as JsonArrayNode;
- expect(first.type).toBe('array');
- expect(first.children.length).toBe(2);
- });
- });
-
- describe('streaming complex structures', () => {
- it('should build a spec-like structure token-by-token', () => {
- const parser = createPartialJsonParser();
- const json = '{"type":"div","props":{"class":"main"},"children":[{"type":"span"}]}';
- // Feed one character at a time
- for (const ch of json) {
- parser.push(ch);
- }
- const root = parser.root as JsonObjectNode;
- expect(root.type).toBe('object');
- expect(root.status).toBe('complete');
- const typeNode = root.children.get('type') as JsonStringNode;
- expect(typeNode.value).toBe('div');
- const propsNode = root.children.get('props') as JsonObjectNode;
- expect(propsNode.children.get('class')).toBeDefined();
- const childrenNode = root.children.get('children') as JsonArrayNode;
- expect(childrenNode.children.length).toBe(1);
- });
-
- it('should maintain stable node identities across pushes', () => {
- const parser = createPartialJsonParser();
- parser.push('{"name":"');
- const root1 = parser.root as JsonObjectNode;
- const nameNode1 = root1.children.get('name') as JsonStringNode;
- const id1 = nameNode1.id;
-
- parser.push('Al');
- const nameNode2 = (parser.root as JsonObjectNode).children.get(
- 'name'
- ) as JsonStringNode;
- expect(nameNode2.id).toBe(id1);
- expect(nameNode2.value).toBe('Al');
-
- parser.push('ice"');
- const nameNode3 = (parser.root as JsonObjectNode).children.get(
- 'name'
- ) as JsonStringNode;
- expect(nameNode3.id).toBe(id1);
- expect(nameNode3.value).toBe('Alice');
- });
- });
-
- describe('getByPath', () => {
- it('should return root for empty path', () => {
- const parser = createPartialJsonParser();
- parser.push('{"a":1}');
- expect(parser.getByPath('')).toBe(parser.root);
- });
-
- it('should look up object properties', () => {
- const parser = createPartialJsonParser();
- parser.push('{"a":{"b":"c"}}');
- const node = parser.getByPath('/a/b') as JsonStringNode;
- expect(node).not.toBeNull();
- expect(node.type).toBe('string');
- expect(node.value).toBe('c');
- });
-
- it('should look up array indices', () => {
- const parser = createPartialJsonParser();
- parser.push('{"items":["x","y","z"]}');
- const node = parser.getByPath('/items/1') as JsonStringNode;
- expect(node).not.toBeNull();
- expect(node.value).toBe('y');
- });
-
- it('should return null for non-existent paths', () => {
- const parser = createPartialJsonParser();
- parser.push('{"a":1}');
- expect(parser.getByPath('/b')).toBeNull();
- expect(parser.getByPath('/a/b')).toBeNull();
- });
- });
-
- describe('error recovery', () => {
- it('handles empty input', () => {
- const parser = createPartialJsonParser();
- const events = parser.push('');
- expect(events).toEqual([]);
- expect(parser.root).toBeNull();
- });
-
- it('handles input with only whitespace', () => {
- const parser = createPartialJsonParser();
- const events = parser.push(' \n\t');
- expect(events).toEqual([]);
- expect(parser.root).toBeNull();
- });
-
- it('leaves root as null for invalid characters in EXPECT_VALUE state', () => {
- const parser = createPartialJsonParser();
- // 'x' does not match any case in EXPECT_VALUE, so it falls through the switch.
- // The parser never creates a root node for unrecognized characters.
- const events = parser.push('xxx{"a":1}');
- // Because 'x' is not whitespace and not a recognized token start,
- // the parser stays in EXPECT_VALUE but does nothing — root remains null.
- // Only when '{' is encountered does parsing begin, but by then 'xxx' has already
- // been consumed with no effect.
- // Actually, 'x' falls through the switch with no match, so processing continues
- // to the next char. '{' will be reached and parsed normally.
- expect(parser.root).not.toBeNull();
- const root = parser.root as JsonObjectNode;
- expect(root.type).toBe('object');
- expect((root.children.get('a') as JsonNumberNode).value).toBe(1);
- });
-
- it('handles trailing text after valid JSON', () => {
- const parser = createPartialJsonParser();
- parser.push('{"a":1}some trailing text');
- const root = parser.root as JsonObjectNode;
- expect(root.type).toBe('object');
- expect(root.status).toBe('complete');
- expect((root.children.get('a') as JsonNumberNode).value).toBe(1);
- });
-
- it('handles very long strings without crashing', () => {
- const parser = createPartialJsonParser();
- const longStr = 'a'.repeat(100000);
- parser.push('"' + longStr + '"');
- const root = parser.root as JsonStringNode;
- expect(root.type).toBe('string');
- expect(root.value.length).toBe(100000);
- expect(root.status).toBe('complete');
- });
-
- it('handles deeply nested objects without stack overflow', () => {
- const parser = createPartialJsonParser();
- const depth = 100;
- const open = '{"a":'.repeat(depth);
- const close = '}'.repeat(depth);
- parser.push(open + '"leaf"' + close);
- let current = parser.root as JsonObjectNode;
- for (let i = 0; i < depth - 1; i++) {
- expect(current.type).toBe('object');
- current = current.children.get('a') as JsonObjectNode;
- }
- // The innermost value is a string
- const leaf = current.children.get('a') as JsonStringNode;
- expect(leaf.type).toBe('string');
- expect(leaf.value).toBe('leaf');
- });
- });
-
- describe('whitespace', () => {
- it('should handle whitespace between tokens', () => {
- const parser = createPartialJsonParser();
- parser.push('{ "a" : "b" , "c" : "d" }');
- const root = parser.root as JsonObjectNode;
- expect(root.type).toBe('object');
- expect(root.status).toBe('complete');
- expect((root.children.get('a') as JsonStringNode).value).toBe('b');
- expect((root.children.get('c') as JsonStringNode).value).toBe('d');
- });
-
- it('should handle leading whitespace', () => {
- const parser = createPartialJsonParser();
- parser.push(' "hello"');
- const root = parser.root as JsonStringNode;
- expect(root.value).toBe('hello');
- });
- });
-});
diff --git a/libs/partial-json/src/lib/parser.ts b/libs/partial-json/src/lib/parser.ts
deleted file mode 100644
index 1f1c99f33..000000000
--- a/libs/partial-json/src/lib/parser.ts
+++ /dev/null
@@ -1,472 +0,0 @@
-// SPDX-License-Identifier: MIT
-import type {
- JsonNode,
- JsonObjectNode,
- JsonArrayNode,
- JsonStringNode,
- JsonNumberNode,
- JsonBooleanNode,
- JsonNullNode,
- ParseEvent,
- PartialJsonParser,
-} from './types';
-
-type State =
- | 'EXPECT_VALUE'
- | 'IN_STRING'
- | 'IN_STRING_ESCAPE'
- | 'IN_STRING_UNICODE'
- | 'IN_NUMBER'
- | 'IN_KEYWORD'
- | 'EXPECT_KEY'
- | 'IN_KEY_STRING'
- | 'IN_KEY_STRING_ESCAPE'
- | 'IN_KEY_STRING_UNICODE'
- | 'EXPECT_COLON'
- | 'AFTER_VALUE';
-
-const ESCAPE_MAP: Record = {
- '"': '"',
- '\\': '\\',
- '/': '/',
- b: '\b',
- f: '\f',
- n: '\n',
- r: '\r',
- t: '\t',
-};
-
-const KEYWORDS: Record = {
- true: { type: 'boolean', value: true },
- false: { type: 'boolean', value: false },
- null: { type: 'null', value: null },
-};
-
-export function createPartialJsonParser(): PartialJsonParser {
- let nextId = 0;
- let root: JsonNode | null = null;
- let state: State = 'EXPECT_VALUE';
- let currentNode: JsonNode | null = null;
-
- // For string values
- let stringNode: JsonStringNode | null = null;
-
- // For unicode escapes
- let unicodeBuffer = '';
- let unicodeCount = 0;
-
- // For key strings in objects
- let keyBuffer = '';
- let keyUnicodeBuffer = '';
- let keyUnicodeCount = 0;
-
- // For keywords (true, false, null)
- let keywordBuffer = '';
- let keywordNode: JsonNode | null = null;
-
- // Stack of container nodes for nested structures
- const containerStack: (JsonObjectNode | JsonArrayNode)[] = [];
-
- function makeId(): number {
- return nextId++;
- }
-
- function createStringNode(parent: JsonNode | null, key: string | number | null): JsonStringNode {
- return {
- id: makeId(),
- type: 'string',
- status: 'streaming',
- parent,
- key,
- value: '',
- };
- }
-
- function createNumberNode(parent: JsonNode | null, key: string | number | null, firstChar: string): JsonNumberNode {
- return {
- id: makeId(),
- type: 'number',
- status: 'streaming',
- parent,
- key,
- raw: firstChar,
- value: null,
- };
- }
-
- function createObjectNode(parent: JsonNode | null, key: string | number | null): JsonObjectNode {
- return {
- id: makeId(),
- type: 'object',
- status: 'streaming',
- parent,
- key,
- children: new Map(),
- pendingKey: null,
- };
- }
-
- function createArrayNode(parent: JsonNode | null, key: string | number | null): JsonArrayNode {
- return {
- id: makeId(),
- type: 'array',
- status: 'streaming',
- parent,
- key,
- children: [],
- };
- }
-
- function currentContainer(): JsonObjectNode | JsonArrayNode | null {
- return containerStack.length > 0 ? containerStack[containerStack.length - 1] : null;
- }
-
- function getKeyForNewChild(): string | number | null {
- const container = currentContainer();
- if (!container) return null;
- if (container.type === 'object') {
- return container.pendingKey;
- }
- return container.children.length;
- }
-
- function attachChild(node: JsonNode): void {
- const container = currentContainer();
- if (!container) {
- root = node;
- return;
- }
- if (container.type === 'object') {
- const objContainer = container;
- const key = objContainer.pendingKey!;
- node.key = key;
- node.parent = objContainer;
- objContainer.children.set(key, node);
- objContainer.pendingKey = null;
- } else {
- node.key = container.children.length;
- node.parent = container;
- container.children.push(node);
- }
- }
-
- function completeNumber(numNode: JsonNumberNode, events: ParseEvent[]): void {
- numNode.value = Number(numNode.raw);
- numNode.status = 'complete';
- events.push({ type: 'node-completed', node: numNode });
- }
-
- function completeKeyword(events: ParseEvent[]): void {
- const kw = keywordBuffer;
- const kwDef = KEYWORDS[kw];
- if (!kwDef) return;
-
- const node = keywordNode!;
- if (kwDef.type === 'boolean') {
- (node as JsonBooleanNode).value = kwDef.value as boolean;
- }
- node.status = 'complete';
- events.push({ type: 'node-completed', node });
- keywordBuffer = '';
- keywordNode = null;
- }
-
- function push(chunk: string): ParseEvent[] {
- const events: ParseEvent[] = [];
-
- for (let i = 0; i < chunk.length; i++) {
- const ch = chunk[i];
-
- switch (state) {
- case 'EXPECT_VALUE': {
- if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') continue;
- if (ch === '"') {
- const key = getKeyForNewChild();
- const node = createStringNode(currentContainer(), key);
- attachChild(node);
- if (!root) root = node;
- stringNode = node;
- events.push({ type: 'node-created', node });
- state = 'IN_STRING';
- } else if (ch === '{') {
- const key = getKeyForNewChild();
- const node = createObjectNode(currentContainer(), key);
- attachChild(node);
- if (!root) root = node;
- events.push({ type: 'node-created', node });
- containerStack.push(node);
- state = 'EXPECT_KEY';
- } else if (ch === '[') {
- const key = getKeyForNewChild();
- const node = createArrayNode(currentContainer(), key);
- attachChild(node);
- if (!root) root = node;
- events.push({ type: 'node-created', node });
- containerStack.push(node);
- state = 'EXPECT_VALUE';
- } else if (ch === ']') {
- // Empty array close
- const container = currentContainer();
- if (container && container.type === 'array') {
- container.status = 'complete';
- containerStack.pop();
- events.push({ type: 'node-completed', node: container });
- state = containerStack.length > 0 ? 'AFTER_VALUE' : 'AFTER_VALUE';
- }
- } else if (ch === '-' || (ch >= '0' && ch <= '9')) {
- const key = getKeyForNewChild();
- const node = createNumberNode(currentContainer(), key, ch);
- attachChild(node);
- if (!root) root = node;
- events.push({ type: 'node-created', node });
- currentNode = node;
- state = 'IN_NUMBER';
- } else if (ch === 't' || ch === 'f' || ch === 'n') {
- const key = getKeyForNewChild();
- let node: JsonNode;
- if (ch === 'n') {
- node = {
- id: makeId(),
- type: 'null',
- status: 'pending',
- parent: currentContainer(),
- key,
- } as JsonNullNode;
- } else {
- node = {
- id: makeId(),
- type: 'boolean',
- status: 'pending',
- parent: currentContainer(),
- key,
- value: ch === 't',
- } as JsonBooleanNode;
- }
- attachChild(node);
- if (!root) root = node;
- events.push({ type: 'node-created', node });
- keywordBuffer = ch;
- keywordNode = node;
- state = 'IN_KEYWORD';
- }
- break;
- }
-
- case 'IN_STRING': {
- if (ch === '\\') {
- state = 'IN_STRING_ESCAPE';
- } else if (ch === '"') {
- stringNode!.status = 'complete';
- events.push({ type: 'node-completed', node: stringNode! });
- stringNode = null;
- state = containerStack.length > 0 ? 'AFTER_VALUE' : 'AFTER_VALUE';
- } else {
- stringNode!.value += ch;
- events.push({ type: 'value-updated', node: stringNode!, delta: ch });
- }
- break;
- }
-
- case 'IN_STRING_ESCAPE': {
- if (ch === 'u') {
- unicodeBuffer = '';
- unicodeCount = 0;
- state = 'IN_STRING_UNICODE';
- } else {
- const mapped = ESCAPE_MAP[ch] ?? ch;
- stringNode!.value += mapped;
- events.push({ type: 'value-updated', node: stringNode!, delta: mapped });
- state = 'IN_STRING';
- }
- break;
- }
-
- case 'IN_STRING_UNICODE': {
- unicodeBuffer += ch;
- unicodeCount++;
- if (unicodeCount === 4) {
- const codePoint = parseInt(unicodeBuffer, 16);
- const char = String.fromCharCode(codePoint);
- stringNode!.value += char;
- events.push({ type: 'value-updated', node: stringNode!, delta: char });
- unicodeBuffer = '';
- unicodeCount = 0;
- state = 'IN_STRING';
- }
- break;
- }
-
- case 'IN_NUMBER': {
- const numNode = currentNode as JsonNumberNode;
- if ((ch >= '0' && ch <= '9') || ch === '.' || ch === 'e' || ch === 'E' || ch === '+' || ch === '-') {
- numNode.raw += ch;
- } else {
- // Number terminated by this character
- completeNumber(numNode, events);
- currentNode = null;
- // Re-process this character
- if (ch === ',' || ch === ']' || ch === '}') {
- state = 'AFTER_VALUE';
- i--; // reprocess
- } else {
- state = 'AFTER_VALUE';
- i--; // reprocess
- }
- }
- break;
- }
-
- case 'IN_KEYWORD': {
- keywordBuffer += ch;
- const possibleKeywords = Object.keys(KEYWORDS).filter((k) => k.startsWith(keywordBuffer));
- if (possibleKeywords.length === 0) {
- // Not a valid keyword continuation - treat as terminator
- state = 'AFTER_VALUE';
- i--; // reprocess
- } else {
- const exact = KEYWORDS[keywordBuffer];
- if (exact) {
- completeKeyword(events);
- state = containerStack.length > 0 ? 'AFTER_VALUE' : 'AFTER_VALUE';
- }
- // Otherwise still accumulating
- }
- break;
- }
-
- case 'EXPECT_KEY': {
- if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') continue;
- if (ch === '"') {
- keyBuffer = '';
- state = 'IN_KEY_STRING';
- } else if (ch === '}') {
- // Empty object
- const container = currentContainer();
- if (container && container.type === 'object') {
- container.status = 'complete';
- containerStack.pop();
- events.push({ type: 'node-completed', node: container });
- state = containerStack.length > 0 ? 'AFTER_VALUE' : 'AFTER_VALUE';
- }
- }
- break;
- }
-
- case 'IN_KEY_STRING': {
- if (ch === '\\') {
- state = 'IN_KEY_STRING_ESCAPE';
- } else if (ch === '"') {
- const container = currentContainer() as JsonObjectNode;
- container.pendingKey = keyBuffer;
- state = 'EXPECT_COLON';
- } else {
- keyBuffer += ch;
- }
- break;
- }
-
- case 'IN_KEY_STRING_ESCAPE': {
- if (ch === 'u') {
- keyUnicodeBuffer = '';
- keyUnicodeCount = 0;
- state = 'IN_KEY_STRING_UNICODE';
- } else {
- const mapped = ESCAPE_MAP[ch] ?? ch;
- keyBuffer += mapped;
- state = 'IN_KEY_STRING';
- }
- break;
- }
-
- case 'IN_KEY_STRING_UNICODE': {
- keyUnicodeBuffer += ch;
- keyUnicodeCount++;
- if (keyUnicodeCount === 4) {
- const codePoint = parseInt(keyUnicodeBuffer, 16);
- keyBuffer += String.fromCharCode(codePoint);
- keyUnicodeBuffer = '';
- keyUnicodeCount = 0;
- state = 'IN_KEY_STRING';
- }
- break;
- }
-
- case 'EXPECT_COLON': {
- if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') continue;
- if (ch === ':') {
- state = 'EXPECT_VALUE';
- }
- break;
- }
-
- case 'AFTER_VALUE': {
- if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') continue;
- if (ch === ',') {
- const container = currentContainer();
- if (container) {
- if (container.type === 'object') {
- state = 'EXPECT_KEY';
- } else {
- state = 'EXPECT_VALUE';
- }
- }
- } else if (ch === '}') {
- const container = currentContainer();
- if (container && container.type === 'object') {
- container.status = 'complete';
- containerStack.pop();
- events.push({ type: 'node-completed', node: container });
- state = containerStack.length > 0 ? 'AFTER_VALUE' : 'AFTER_VALUE';
- }
- } else if (ch === ']') {
- const container = currentContainer();
- if (container && container.type === 'array') {
- container.status = 'complete';
- containerStack.pop();
- events.push({ type: 'node-completed', node: container });
- state = containerStack.length > 0 ? 'AFTER_VALUE' : 'AFTER_VALUE';
- }
- }
- break;
- }
- }
- }
-
- return events;
- }
-
- function getByPath(path: string): JsonNode | null {
- if (!root) return null;
- if (path === '' || path === '/') return root;
-
- // Handle paths that don't start with /
- const normalizedPath = path.startsWith('/') ? path : '/' + path;
- const segments = normalizedPath.split('/').slice(1); // remove leading empty string
-
- let current: JsonNode = root;
- for (const segment of segments) {
- if (current.type === 'object') {
- const child = (current as JsonObjectNode).children.get(segment);
- if (!child) return null;
- current = child;
- } else if (current.type === 'array') {
- const index = parseInt(segment, 10);
- if (isNaN(index)) return null;
- const child = (current as JsonArrayNode).children[index];
- if (!child) return null;
- current = child;
- } else {
- return null;
- }
- }
- return current;
- }
-
- return {
- push,
- get root() {
- return root;
- },
- getByPath,
- };
-}
diff --git a/libs/partial-json/src/lib/types.ts b/libs/partial-json/src/lib/types.ts
deleted file mode 100644
index 8269d7794..000000000
--- a/libs/partial-json/src/lib/types.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-// SPDX-License-Identifier: MIT
-
-/** Kinds of JSON values a node can represent. */
-export type JsonNodeType = 'object' | 'array' | 'string' | 'number' | 'boolean' | 'null';
-
-/** Parsing state of a node. */
-export type JsonNodeStatus = 'pending' | 'streaming' | 'complete';
-
-/** Base shape shared by all nodes. */
-export interface JsonNodeBase {
- /** Stable identity — assigned on creation, never changes. */
- readonly id: number;
- /** What kind of JSON value this node represents. */
- readonly type: JsonNodeType;
- /** Parsing state. */
- status: JsonNodeStatus;
- /** Parent node (null for root). */
- parent: JsonNode | null;
- /** Key in parent — string for object properties, number for array indices. */
- key: string | number | null;
-}
-
-export interface JsonObjectNode extends JsonNodeBase {
- readonly type: 'object';
- children: Map;
- /** Key currently being built (between quote open and colon). */
- pendingKey: string | null;
-}
-
-export interface JsonArrayNode extends JsonNodeBase {
- readonly type: 'array';
- children: JsonNode[];
-}
-
-export interface JsonStringNode extends JsonNodeBase {
- readonly type: 'string';
- /** Grows character-by-character as tokens arrive. */
- value: string;
-}
-
-export interface JsonNumberNode extends JsonNodeBase {
- readonly type: 'number';
- /** Raw characters accumulated so far. */
- raw: string;
- /** Parsed value — set when node completes. */
- value: number | null;
-}
-
-export interface JsonBooleanNode extends JsonNodeBase {
- readonly type: 'boolean';
- value: boolean;
-}
-
-export interface JsonNullNode extends JsonNodeBase {
- readonly type: 'null';
-}
-
-export type JsonNode =
- | JsonObjectNode
- | JsonArrayNode
- | JsonStringNode
- | JsonNumberNode
- | JsonBooleanNode
- | JsonNullNode;
-
-/** Events emitted by the parser as the tree changes. */
-export interface ParseEvent {
- type: 'node-created' | 'value-updated' | 'node-completed';
- node: JsonNode;
- /** For value-updated on strings: the characters appended this push. */
- delta?: string;
-}
-
-/** Push-based streaming JSON parser. */
-export interface PartialJsonParser {
- /** Feed characters. Returns events for what changed. */
- push(chunk: string): ParseEvent[];
- /** Root node of the parse tree. */
- readonly root: JsonNode | null;
- /** Look up a node by JSON Pointer path (e.g., "/elements/el-1/props"). */
- getByPath(path: string): JsonNode | null;
-}
diff --git a/libs/partial-json/tsconfig.json b/libs/partial-json/tsconfig.json
deleted file mode 100644
index 1645f314f..000000000
--- a/libs/partial-json/tsconfig.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "extends": "../../tsconfig.base.json",
- "files": [],
- "references": [
- { "path": "./tsconfig.lib.json" }
- ]
-}
diff --git a/libs/partial-json/tsconfig.lib.json b/libs/partial-json/tsconfig.lib.json
deleted file mode 100644
index 6ad422f39..000000000
--- a/libs/partial-json/tsconfig.lib.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "../../dist/out-tsc",
- "declaration": true,
- "emitDeclarationOnly": false
- },
- "include": [
- "src/**/*.ts"
- ],
- "exclude": [
- "src/**/*.spec.ts"
- ]
-}
diff --git a/libs/partial-json/vite.config.mts b/libs/partial-json/vite.config.mts
deleted file mode 100644
index 971c722be..000000000
--- a/libs/partial-json/vite.config.mts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { defineConfig } from 'vite';
-import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
-
-export default defineConfig({
- plugins: [nxViteTsPaths()],
- test: {
- environment: 'node',
- globals: true,
- include: ['src/**/*.spec.ts'],
- passWithNoTests: true,
- },
-});
diff --git a/libs/render/README.md b/libs/render/README.md
index e3954e5cf..40d8d803a 100644
--- a/libs/render/README.md
+++ b/libs/render/README.md
@@ -16,7 +16,7 @@ npm install @ngaf/render
- **Two protocols supported** — Vercel `json-render` and Google A2UI v1
- **Per-component fallback API** — when a spec node has no registered component, you control what renders (and surface it to your observability layer)
- **Readiness gate** — holds renders until the surface is real, so users never see mystery partial UI
-- **Streaming partial renders** — works with `@ngaf/partial-json` to render progressive JSON as it streams
+- **Streaming partial renders** — works with `@cacheplane/partial-json` to render progressive JSON as it streams
## Documentation
diff --git a/nx.json b/nx.json
index 89d3a428e..c3ae6fe34 100644
--- a/nx.json
+++ b/nx.json
@@ -51,7 +51,6 @@
"ag-ui",
"render",
"a2ui",
- "partial-json",
"licensing",
"telemetry"
],
@@ -59,7 +58,7 @@
}
},
"version": {
- "preVersionCommand": "npx nx run-many -t build --projects=chat,langgraph,ag-ui,render,a2ui,partial-json,licensing,telemetry",
+ "preVersionCommand": "npx nx run-many -t build --projects=chat,langgraph,ag-ui,render,a2ui,licensing,telemetry",
"updateDependents": "auto",
"preserveLocalDependencyProtocols": true
},
diff --git a/package-lock.json b/package-lock.json
index 73dad3e68..5d76e8436 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -244,7 +244,7 @@
},
"libs/design-tokens": {
"name": "@ngaf/design-tokens",
- "version": "0.0.33",
+ "version": "0.0.34",
"license": "MIT"
},
"libs/example-layouts": {
@@ -278,12 +278,6 @@
"@noble/ed25519": "^2.2.3"
}
},
- "libs/partial-json": {
- "name": "@ngaf/partial-json",
- "version": "0.0.29",
- "deprecated": "Replaced by @cacheplane/partial-json. No further versions will be published from this package.",
- "license": "MIT"
- },
"libs/render": {
"name": "@ngaf/render",
"version": "0.0.29",
@@ -12021,10 +12015,6 @@
"resolved": "apps/minting-service",
"link": true
},
- "node_modules/@ngaf/partial-json": {
- "resolved": "libs/partial-json",
- "link": true
- },
"node_modules/@ngaf/render": {
"resolved": "libs/render",
"link": true
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 8a4bc04c7..79183d553 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -33,7 +33,6 @@
"@ngaf/langgraph": ["libs/langgraph/src/public-api.ts"],
"@ngaf/licensing": ["libs/licensing/src/index.ts"],
"@ngaf/licensing/testing": ["libs/licensing/src/testing.ts"],
- "@ngaf/partial-json": ["libs/partial-json/src/index.ts"],
"@ngaf/render": ["libs/render/src/public-api.ts"]
},
"skipLibCheck": true,