@open-knowledge/knowledge-bundle-api
v0.0.2
Published
TypeScript contracts for exposing read-only and editable knowledge bundles over RPC
Maintainers
Readme
Knowledge Bundle API
Type-only contracts for exposing a knowledge bundle through an RPC system such as
Cap'n Web. KnowledgeBundle is the root read capability;
use ReadonlyKnowledgeBundle or EditableKnowledgeBundle when the granted access is known.
Install
npm install @open-knowledge/knowledge-bundle-apiImport the contracts with import type so they are erased from emitted JavaScript:
import type { ReadonlyKnowledgeBundle } from '@open-knowledge/knowledge-bundle-api'
export function listRoot(bundle: ReadonlyKnowledgeBundle) {
return bundle.list({ path: '/', depth: 1 })
}Bundle metadata is generic and must be pass-by-value data supported by the chosen RPC transport.
Entries, inputs, and mutation results are plain, acyclic RPC data. The change listener and returned
subscription from the optional SubscribableKnowledgeBundle extension are capabilities instead.
Methods return promises so implementations may be asynchronous; calls through a Cap'n Web stub
return RpcPromise values and support promise
pipelining. A Cap'n Web server implementation must extend RpcTarget, while a client uses these
interfaces with Cap'n Web's RpcStub<T> and session generics. Do not copy the interfaces merely to
replace Promise with RpcPromise; RpcStub<T> performs that type transformation.
KnowledgeBundle<Metadata, Access> is the common read surface. ReadonlyKnowledgeBundle and
EditableKnowledgeBundle specialize its Access parameter, preserving the literal returned by
getInfo(). A readonly bundle is not the supertype of an editable bundle; use KnowledgeBundle
when either grant is accepted. Native TypeScript utilities such as Parameters, ReturnType,
Awaited, Pick, and Omit can derive adapters from these contracts without repeating method
signatures. Such utilities only change static types and never attenuate a runtime capability.
Remote Cap'n Web Endpoint
A remote bundle locator has this shape:
okb://hostname/path/to/bundle
okb://hostname/path/to/bundle?insecure=trueThe authority and path identify one Cap'n Web endpoint. That single endpoint exposes the
KnowledgeBundle target itself as the Cap'n Web main object; it does not wrap the bundle in a
second root API or require a separate discovery request.
Clients translate okb: to https:. They use http: only when the URI contains the exact query
parameter insecure=true. insecure is a transport control parameter and is removed before the
HTTP request; all other query parameters are retained. Fragments are not valid endpoint locators.
Clients should reject embedded username/password credentials rather than persist or display them;
authentication material can use an intentional endpoint query parameter or an application-specific
out-of-band mechanism.
For example:
function capnWebEndpoint(locator: string): URL {
const candidate = locator.trim()
if (candidate.includes('#')) {
throw new TypeError('Fragments are not allowed.')
}
const parsed = new URL(candidate)
if (parsed.protocol !== 'okb:' || !parsed.hostname || parsed.username || parsed.password) {
throw new TypeError('Expected an okb:// endpoint without a fragment.')
}
const insecure = parsed.searchParams.getAll('insecure')
if (insecure.length > 1) {
throw new TypeError('Only one insecure parameter is allowed.')
}
const protocol = insecure[0] === 'true' ? 'http:' : 'https:'
const endpoint = new URL(parsed.href.replace(/^okb:/, protocol))
endpoint.searchParams.delete('insecure')
return endpoint
}HTTP batch RPC is the interoperability baseline and is sufficient for every bounded operation:
import { newHttpBatchRpcSession, type RpcStub } from 'capnweb'
import type {
EditableKnowledgeBundle,
KnowledgeBundle,
ReadonlyKnowledgeBundle
} from '@open-knowledge/knowledge-bundle-api'
type RemoteBundle = RpcStub<KnowledgeBundle>
type RemoteReadonlyBundle = RpcStub<ReadonlyKnowledgeBundle>
type RemoteEditableBundle = RpcStub<EditableKnowledgeBundle>
const endpoint = capnWebEndpoint('okb://knowledge.example.test/bundles/main')
const request = new Request(endpoint, {
redirect: 'error',
signal: AbortSignal.timeout(15_000)
})
using bundle: RemoteBundle = newHttpBatchRpcSession<KnowledgeBundle>(request)
const info = await bundle.getInfo()Use newHttpBatchRpcSession<ReadonlyKnowledgeBundle>() when the grant is known to be readonly and
newHttpBatchRpcSession<EditableKnowledgeBundle>() when it is known to be editable. A client that
discovers access through getInfo() starts a correctly typed session for subsequent calls rather
than casting the common stub.
Servers may omit subscription support by implementing only KnowledgeBundle,
ReadonlyKnowledgeBundle, or EditableKnowledgeBundle. A server which supports it additionally
implements SubscribableKnowledgeBundle and clients use that extension as their typed session
contract. Do not probe an ordinary Cap'n Web stub for an optional method: proxies accept every
property name at runtime. A retained listener requires a persistent bidirectional transport such as
WebSocket, and the server must duplicate a callback stub before retaining it and dispose that
duplicate when the returned subscription closes. Short-lived HTTP batch clients refresh explicitly.
Paths are canonical, bundle-scoped absolute paths. They begin with /; / is the root; empty,
. and .. segments are invalid. The TypeScript template type only checks the leading slash, so
implementations must validate every untrusted path before resolving it. IDs and revisions are
opaque and scoped to one bundle.
Every listing and mutation includes a path and may include the stable ID learned from an earlier result. When an ID is supplied, it is authoritative. The backend follows a relocated entry and returns its canonical current path; it never falls back to a different entry which has since occupied the stale path. An unresolved supplied ID fails even if the stale path now exists.
The read capability covers metadata, bounded recursive listing, bounded file reads, and optional change subscriptions. The editable capability adds file and directory creation, full file replacement, entry moves (including renames), and entry deletion. Revisions and content hashes are optional mutation preconditions; every supplied precondition must match.
Readonly and editable interfaces describe separate grants, but TypeScript types are not an
authorization boundary. A readonly Cap'n Web grant must expose a target without public mutation
methods, or the target must enforce authorization on every mutation call. Merely typing an
editable target as ReadonlyKnowledgeBundle does not hide its methods from an untyped peer.
This package contains declarations only and has no runtime or transport dependency.
