{"_id":"@ao-barbosa/phi-chord","_rev":"2-39f8b2c92df6eecb6c7bed05f874a243","name":"@ao-barbosa/phi-chord","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.0":{"name":"@ao-barbosa/phi-chord","version":"1.0.0","keywords":["application","plugins","rpc","services","state"],"author":{"name":"Earendil Works"},"license":"MIT","_id":"@ao-barbosa/phi-chord@1.0.0","maintainers":[{"name":"ao-barbosa","email":"arthur_o.b@hotmail.com"}],"contributors":[{"url":"https://github.com/ao-Barbosa","name":"ao-Barbosa"}],"homepage":"https://github.com/ao-Barbosa/phi#readme","bugs":{"url":"https://github.com/ao-Barbosa/phi/issues"},"dist":{"shasum":"f8e5f5b88f67e2fb84cce5a594381785cda5677c","tarball":"https://registry.npmjs.org/@ao-barbosa/phi-chord/-/phi-chord-1.0.0.tgz","fileCount":99,"integrity":"sha512-TSb+vJAaY77ftIesRbaLBlvnr67IYnCXCDeTIcCdgW64acc6BgoFxLf+uGOSmSuXdhjIPNJo1Vru/pLchha8dQ==","signatures":[{"sig":"MEUCIQDsb1wBbswlEWQjidzMbGYJVtkqc96YbGTK5eOy1OeZswIgQIGeLuVP7ZYlvGactz6YVijdGaM8wf9DLKWDI4BYICA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":917607},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"bun":">=1.4.2"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","source":"./src/index.ts"},"./node":{"types":"./dist/node.d.ts","import":"./dist/node.js","source":"./src/node.ts"},"./delta":{"types":"./dist/delta/index.d.ts","import":"./dist/delta/index.js","source":"./src/delta/index.ts"},"./bundler":{"types":"./dist/bundler.d.ts","import":"./dist/bundler.js","source":"./src/bundler.ts"},"./context":{"types":"./dist/context/index.d.ts","import":"./dist/context/index.js","source":"./src/context/index.ts"},"./package.json":"./package.json"},"gitHead":"69be3c28c70d249af6bf11fe4d911198b5c70af4","scripts":{"test":"bun test --isolate --preload ../../test-support/setup.ts --timeout 30000","build":"tsgo -p tsconfig.build.json","clean":"rm -rf dist","prepublishOnly":"bun run clean && bun run build"},"_npmUser":{"name":"ao-barbosa","email":"arthur_o.b@hotmail.com"},"repository":{"url":"git+https://github.com/ao-Barbosa/phi.git","type":"git","directory":"packages/chord"},"_npmVersion":"11.17.0","description":"Application composition runtime for services, replicated state, RPC, and plugins","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","dependencies":{"esbuild":"0.28.1"},"_hasShrinkwrap":false,"packageManager":"bun@1.4.2","devDependencies":{"vitest":"4.1.9"},"_npmOperationalInternal":{"tmp":"tmp/phi-chord_1.0.0_1788806482872_0.1165672229542789","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"_id":"@ao-barbosa/phi-chord@1.0.1","bugs":{"url":"https://github.com/ao-Barbosa/phi/issues"},"dist":{"shasum":"652c8311b396b92ffea34fb8721d25db66e455a3","tarball":"https://registry.npmjs.org/@ao-barbosa/phi-chord/-/phi-chord-1.0.1.tgz","fileCount":99,"integrity":"sha512-z+rIQjXHff1iiRcnLv2jwjwM85rG3amU3VnkHD8KorYGb1LA9AWNr92AbbAs8vdICjryNgCph2hbJwXA9707wA==","signatures":[{"sig":"MEYCIQCbcFvz5/Hadzd0Km4sZXi2ylLHwDND+VhlaIhVem1vnAIhAJC+gS7Bw2i3J48AOjx9g+Df1cj5XuQlZ4A9UhiNsj75","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCEFIcft+mMxlS1BySBWsE5fip2UZCaVx5J6z8xr8IlXQIhALzfC2sOr4z39gDLoFuY04TiJ8CKdlckdyOlq4E12jc+"}],"unpackedSize":917606},"main":"./dist/index.js","name":"@ao-barbosa/phi-chord","type":"module","types":"./dist/index.d.ts","author":{"name":"Mario Zechner"},"engines":{"bun":">=1.4.2"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","source":"./src/index.ts"},"./node":{"types":"./dist/node.d.ts","import":"./dist/node.js","source":"./src/node.ts"},"./delta":{"types":"./dist/delta/index.d.ts","import":"./dist/delta/index.js","source":"./src/delta/index.ts"},"./bundler":{"types":"./dist/bundler.d.ts","import":"./dist/bundler.js","source":"./src/bundler.ts"},"./context":{"types":"./dist/context/index.d.ts","import":"./dist/context/index.js","source":"./src/context/index.ts"},"./package.json":"./package.json"},"gitHead":"d4a3bef54ccdc2ab06cff573949ae42c8e28f068","license":"MIT","scripts":{"test":"bun test --isolate --preload ../../test-support/setup.ts --timeout 30000","build":"tsgo -p tsconfig.build.json","clean":"rm -rf dist","prepublishOnly":"bun run clean && bun run build"},"version":"1.0.1","_npmUser":{"name":"ao-barbosa","email":"arthur_o.b@hotmail.com"},"homepage":"https://github.com/ao-Barbosa/phi#readme","keywords":["application","plugins","rpc","services","state"],"repository":{"url":"git+https://github.com/ao-Barbosa/phi.git","type":"git","directory":"packages/chord"},"_npmVersion":"11.17.0","description":"Application composition runtime for services, replicated state, RPC, and plugins","directories":{},"maintainers":[{"name":"ao-barbosa","email":"arthur_o.b@hotmail.com"}],"sideEffects":false,"_nodeVersion":"24.19.0","contributors":[{"url":"https://github.com/ao-Barbosa","name":"ao-Barbosa"}],"dependencies":{"esbuild":"0.28.1"},"_hasShrinkwrap":false,"packageManager":"bun@1.4.2","devDependencies":{"vitest":"4.1.9"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/phi-chord_1.0.1_1789684936618_0.9037397024688181"}}},"time":{"created":"2026-09-07T18:41:22.659Z","modified":"2026-09-17T22:42:16.897Z","1.0.0":"2026-09-07T18:41:22.999Z","1.0.1":"2026-09-17T22:42:16.734Z"},"bugs":{"url":"https://github.com/ao-Barbosa/phi/issues"},"author":{"name":"Mario Zechner"},"license":"MIT","homepage":"https://github.com/ao-Barbosa/phi#readme","keywords":["application","plugins","rpc","services","state"],"repository":{"url":"git+https://github.com/ao-Barbosa/phi.git","type":"git","directory":"packages/chord"},"description":"Application composition runtime for services, replicated state, RPC, and plugins","contributors":[{"url":"https://github.com/ao-Barbosa","name":"ao-Barbosa"}],"maintainers":[{"name":"ao-barbosa","email":"arthur_o.b@hotmail.com"}],"readme":"# @ao-barbosa/phi-chord\n\nChord is an application-composition runtime for systems assembled from\nplugins/extensions. It provides facets, services, replicated state, and a\npluggable remote-service boundary. It is developed as a standalone package in\nthe Phi monorepo, but it is not a Phi package: it does not depend on any other Phi\nworkspace package and can be used by unrelated applications.\n\n## What Chord is for\n\nA single application feature may need to run in several environments: for\nexample, an agent worker, a terminal UI, and a remote WebUI.  Chord provides the\ngeneric machinery to write such extensions in a way that is both delightful for\nhumans as well as agents.\n\nThe design has a few connected pieces:\n\n- **Plugins** are synchronous setup units that declare the services they provide\n  and require. After every plugin has declared its shape, a host validates the\n  complete dependency graph, binds services, activates providers before consumers,\n  and disposes resources in reverse dependency order.  These units are called\n  *facets*.\n\n- **Facets** are parts of a plugin.  Each facet is bundled up separately and runs\n  in the process or environment where it's supposed to run.  You can use facets\n  to split a plugin into separate pieces that need to be loaded into different\n  processes and environments (think backend, browser, TUI etc.)\n\n- **Services** are typed, stable tokens with either one provider (**singleton**)\n  or dynamic keyed instances (**keyed**).  A service can be process-local, with\n  an unrestricted JavaScript contract, or remotely exposable. Consumers retain a\n  stable facade while a provider disconnects or is replaced.\n\n- **Replicated state** exposes authoritative state to local and remote\n  connected consumers. Producers mutate the tracked `state` proxy and call\n  `publish(context)`; consumers receive complete immutable values. Chord flushes\n  one decoded operation batch per publication, while each remote client/state\n  stream owns independent path-codec state. Replicas become unready on disconnect\n  or replacement until they are rehydrated.\n\n- **Delta tracking** derives compact operations from tracked plain JSON at\n  flush time. It preserves string append/front-truncation and array-append\n  behavior without retaining mutation history, supports durable base batches,\n  and validates untrusted operations as they are applied.\n\n- **Remote service sources** advertise services available outside a facet host\n  and open bindings for the services its facets require. Bindings carry logical\n  calls and subscriptions through an application-supplied adapter. Chord\n  requires strict-JSON arguments, results, snapshots, updates, and catalogues,\n  but does not prescribe framing, routing, transport, or an application wire\n  envelope. `JsonRepresentation<T>` derives a wire-safe type for application data\n  with unknown payloads, while `isJsonValue()` validates received values at an\n  adapter boundary. Symmetric RPC peers are planned as one optional\n  implementation of this boundary.\n\n- **Context** Chord provides a Go-like context system for cancellation and\n  invocation-scoped application values. Applications can carry permissions or\n  telemetry through those values without Chord depending on either.\n\nThe current runtime exports service tokens, singleton and keyed providers,\nremote bindings, replicated state, facet hosts, and facet loaders from\n`@ao-barbosa/phi-chord`. Import public types and general runtime APIs from the\npackage root. Context constants and functions live in\n`@ao-barbosa/phi-chord/context` because their generic names should not pollute\nthe root API.\nChord-owned identifiers use the `chord.*` namespace and its reserved service\nprefix is `$chord.*`.\n\n## Remote service adapters\n\nChord owns its transport-independent service wire grammar. Consumer adapters\nuse `createServiceCatalogueCall()`, `createServiceSubscribeCall()`, and\n`createServiceUnsubscribeCall()` for `$chord.service` control calls.\n`createRemoteServiceEndpoint()` handles those calls for one provider consumer,\nincluding subscription activation and cleanup. `parseServiceCall()`,\n`parseServiceCatalogue()`, and the decoded/wire snapshot and update parsers\nvalidate Chord semantics after an adapter has established a strict-JSON\nboundary. `RemoteServiceErrorCode` and `REMOTE_SERVICE_ERROR_CODES` define the\nservice errors that may cross that boundary.\n\nReplicated state operations use one `createServiceStateEncoder()` at the\nprovider side and one `createServiceStateDecoder()` at the consumer side for\neach subscription. Those registries create an independent Delta path dictionary\nfor every instance/member state and reset it on replacement, unavailability,\nclose, or fresh hydration. Applications may place these values inside any\nrouting, request, response, or event envelope; Chord does not prescribe that\nouter protocol.\n\n## Tracking JSON deltas\n\nImport the standalone delta primitive from `@ao-barbosa/phi-chord/delta`:\n\n```ts\nimport { apply, track } from \"@ao-barbosa/phi-chord/delta\";\n\nconst changes = track({ output: \"\", count: 0 });\nchanges.flush(); // opening base batch\nchanges.state.output += \"done\\n\";\nchanges.state.count += 1;\n\nconst ops = changes.flush();\nconst replica = apply({ output: \"\", count: 0 }, ops);\n```\n\nThe first flush is always a complete base batch. Later flushes contain path-based\nchanges. `applyImmutable()` applies those batches while preserving prior replica\nrevisions. `replicatedState(initial)` uses tracking directly:\n\n```ts\nconst status = env.replicatedState({ output: \"\", count: 0 });\nstatus.state.output += \"done\\n\";\nstatus.state.count += 1;\nstatus.publish(context);\n```\n\n`publish()` flushes once; remote connection plumbing encodes that operation batch\nindependently for every client/state pairing. String assignments preserve pure\nappends and rolling-window movement as append and front-truncate operations;\nunrelated rewrites fall back to a set. Values inserted into tracked state become\ntracker-owned and must subsequently be mutated only through `state`. See the\n[Delta guide](src/delta/README.md) for mutation, array, lifecycle, and\nconsumer-ownership rules.\n\n## Bundling and loading facets\n\n`@ao-barbosa/phi-chord/bundler` uses esbuild to turn ESM or TypeScript application\nentries into independent, content-addressed CommonJS files. The package-level API\nreads plugin identity and build configuration from `package.json`, then applies\nfacet path conventions supplied by the host application:\n\n```json\n{\n  \"name\": \"@example/my-plugin\",\n  \"version\": \"1.0.0\",\n  \"type\": \"module\",\n  \"peerDependencies\": {\n    \"@ao-barbosa/phi-chord\": \"^0.84.4\"\n  },\n  \"chord\": {\n    \"facets\": {\n      \"worker\": \"./src/custom-worker.ts\",\n      \"presentation\": false\n    }\n  }\n}\n```\n\n```ts\nimport { bundleFacetPackage } from \"@ao-barbosa/phi-chord/bundler\";\n\nawait bundleFacetPackage({\n\tpackagePath: \"/path/to/my-plugin\",\n\toutdir: \"/application-owned/plugin-builds/my-plugin\",\n\tdefaultFacets: {\n\t\tworker: \"src/worker.ts\",\n\t\tpresentation: \"src/presentation.ts\",\n\t},\n});\n```\n\nExisting conventional files become entries unless `chord.facets` overrides or\ndisables them. Peer dependencies are externalized and resolved against the host\nwhen loading. Chord never installs dependencies or runs package lifecycle\nscripts. `bundleFacets()` remains available as the lower-level API for callers\nthat already have explicit plugin identity and entry mappings.\n\nThe output directory contains one `.cjs` file per entry plus\n`chord-facets.json`. Load one application-selected entry through the Node-only\nloader:\n\n```ts\nimport { createFacetBundleLoader } from \"@ao-barbosa/phi-chord/node\";\n\nconst loader = createFacetBundleLoader({\n\tmanifestPath: \"/application-owned/plugin-builds/my-plugin/chord-facets.json\",\n\tentry: \"worker\",\n\tresolveExternal: (specifier) => import.meta.resolve(specifier),\n});\nconst loaded = await loader.load();\n```\n\nEach `load()` verifies SHA-256 integrity and compiles the CommonJS body directly\nwith `node:vm` instead of putting the plugin into Node's CommonJS or ESM module\ncache. Externals are resolved by the host and loaded through a restricted\n`require`; esbuild lowers dynamic imports so they use the same path. Disposing a\nretired generation releases the loader's facet references, making its compiled\ncode eligible for garbage collection once plugin-owned resources are also gone.\n\nFor transport to another Node host, `readFacetBundleArtifact()` packages one\nverified manifest entry with its source, and `createFacetBundleArtifactLoader()`\nmaterializes fresh temporary generations while resolving externals against the\nreceiving host.\n\nTo reload, load a candidate, pass its facets to `FacetHost.reload()`, dispose the\ncandidate on failure, and dispose the retired `LoadedFacets` only after a\nsuccessful cutover. The host activates and validates the candidate while the old\nproviders remain routed, then replaces each singleton directly without an\nunavailable interval. Stable service handles therefore do not become disconnected\nduring an ordinary reload. Keyed instances\nremain incarnation-specific and replacements receive fresh generations. The\nbundler writes a complete temporary directory before replacing the previous\noutput, so loaders do not observe partially built generations.\n\nSee [PLANNING.md](PLANNING.md) for the broader RPC and generation-loading\narchitecture.\n","readmeFilename":"README.md"}