import * as Effect from 'effect/Effect'; import * as Option from 'effect/Option'; import * as Atom from 'effect/reactivity/Atom'; import * as Registry from 'effect/reactivity/AtomRegistry'; import { type Type } from '@dxos/echo'; import * as Builder from '@dxos/graph/GraphBuilder'; import { DXN } from '@dxos/keys'; import * as Position from '@dxos/util/Position'; import { type FrameBudget } from '#scheduler'; import * as Graph from './AppGraph.ts'; import * as Node from './AppGraphNode.ts'; /** * Graph builder extension for adding nodes to the graph based on a connection to an existing node. * * @param params.node The existing node the returned nodes will be connected to. */ export type ConnectorExtension = Builder.Connector>; /** * Constrained case of the connector extension for more easily adding actions to the graph. */ export type ActionsExtension = (node: Atom.Atom>) => Atom.Atom>, 'type' | 'nodes' | 'edges'>[]>; /** * Constrained case of the connector extension for more easily adding action groups to the graph. */ export type ActionGroupsExtension = (node: Atom.Atom>) => Atom.Atom, 'type' | 'data' | 'nodes' | 'edges'>[]>; /** * A generic builder extension specialized to the app vocabulary: app nodes, app relations, and a * {@link UrlBinding} as the extension metadata the app layer's node decorator reads. */ export type BuilderExtension = Builder.Extension, Node.RelationInput, UrlBinding>; export type BuilderExtensions = Builder.Extensions; /** * How an extension's nodes map to (and from) the URL pair chain. The `kind` is the resolution tier: * * - `'item'` — Addressed by an id under the workspace (`doc/`). May itself have children. * - `'singleton'` — A single fixed node per workspace, addressed by the key alone (`settings`); its own * segment is the key, below `path`. * * The binding's shape (`workspace`, `path`, `minDepth`) decides which node ids it addresses, and * a node's URL comes from the one binding its id fits. A node therefore has a URL whether or not it is * loaded, and bindings of different keys must not claim the same ids. * * The anchor and linked tiers are not declared per extension: they are fixed keys of the URL grammar, * configured once on the builder as {@link UrlGrammar}. */ export type UrlBinding = { key: string; kind: 'item' | 'singleton'; /** Node-id segments between the workspace and the node's own. */ path: readonly string[]; /** * The fewest segments an item's id spans after `path`, joined by the grammar's tail separator; defaults * to 1. Of two bindings sharing a path, the larger minimum claims the deeper ids. Ignored with `resolve`. */ minDepth?: number; /** Narrows the workspaces the binding applies to; defaults to every workspace. */ workspace?: (workspace: string) => boolean; /** * Forward resolution for a data-dependent shape (nested collections, say): the id is the node's last * segment, at any depth below `path`, and this locates the rest. */ resolve?: PathResolver; }; /** * The URL grammar the builder resolves and stamps against, configured once at construction. * * The two keys are fixed tiers no extension declares (no connector produces their nodes): `anchorKey` * establishes the base that following pairs resolve against and is consumed as a rebase * (`w/`); `linked` addresses a node attached to the preceding item through its relation * (`/`, its segment ``). `tailSeparator` joins the node-id segments * after a key's static `path` into one URL id (`db/+`), so a nested shape needs no resolver. */ export type UrlGrammar = { anchorKey?: string; linked: LinkedGrammar; tailSeparator: string; }; /** How a node attached to the preceding item is addressed: its URL key, its relation, its segment prefix. */ export type LinkedGrammar = { key: string; relation: Node.RelationInput; prefix: string; }; /** {@link UrlGrammar} as supplied at construction; anything omitted falls back to its default. */ export type UrlGrammarProps = Partial> & { linked?: Partial; }; /** Params passed to a {@link PathResolver} for a single `(key, id)` URL pair. */ export type PathResolveParams = { /** The id segment from the `(key, id)` pair. */ id: string; /** The workspace segment from the URL. */ workspace: string; /** Qualified id of the workspace base node (`${GraphNode.RootId}/`). */ workspaceBaseId: string; }; /** * Forward resolution for a {@link UrlBinding} whose ids are data-dependent. Returns the candidate qualified * node id — `path-resolution.ts` then materializes its ancestors and verifies it — or `null` if the id * can't be located. Must be self-contained (the declaring plugin closes over any services it needs), so * `@dxos/app-graph` stays free of service dependencies. */ export type PathResolver = (params: PathResolveParams) => Effect.Effect; /** * The `(key, id?)` a binding gives the node `nodeId`, if its shape addresses that node: the reverse of * forward resolution, minus the workspace. A singleton has no id; a resolver-backed item keeps its last * segment; any other item joins the segments after `path` with the tail separator. */ export declare const urlRepresentation: (nodeId: string, url: UrlBinding, tailSeparator?: string) => Option.Option<{ key: string; id?: string; }>; /** * The node a binding without a resolver addresses by `id` under `workspace`, if its shape admits that id: * the inverse of {@link urlRepresentation}. A singleton takes no id. */ export declare const urlCandidate: (url: UrlBinding, workspace: string, id: string | undefined, tailSeparator?: string) => Option.Option; /** * A node's own URL pair segment — `/[/]`, with no workspace/anchor prefix — or `undefined` when * the binding does not address it. A full URL is composed by prefixing `/w/`. */ export declare const nodeUrlSegment: (nodeId: string, url: UrlBinding, tailSeparator?: string) => string | undefined; export type GraphBuilderTraverseOptions = Builder.TraverseOptions; /** Construction params: the backing graph's props plus the URL grammar's fixed keys. */ export type GraphBuilderProps = Pick & { urlGrammar?: UrlGrammarProps; /** * Applied to each connector-produced node before it enters the graph. Defaults to stamping the * URL segment implied by the producing extension's binding. */ decorateNode?: (node: Node.NodeArg, extension?: BuilderExtension) => Node.NodeArg; }; /** * The generic expansion engine specialized to the app vocabulary: app nodes and relations over an * {@link Graph.ExpandableGraph}, extensions carrying a {@link UrlBinding}, and connector-produced nodes * stamped with their URL segment as they enter the graph. */ export declare class GraphBuilder extends Builder.GraphBuilder, Node.RelationInput, UrlBinding, Graph.ExpandableGraph> { #private; /** The URL grammar (see {@link UrlGrammar}); the keys are absent when URLs are not in play. */ readonly urlGrammar: UrlGrammar; constructor({ registry, urlGrammar, decorateNode, ...graphProps }?: GraphBuilderProps); /** Hand flushes to the scheduler so a large expansion yields to the main thread. */ _schedule(callback: () => void): Promise; _yield(): Promise; _frameBudget(): FrameBudget | undefined; _onReleaseRelation(target: { id: string; relation: string; }): void; _onExpand(id: string, relation: string): void; } /** * Creates a new GraphBuilder instance. */ export declare const make: (params?: GraphBuilderProps) => GraphBuilder; /** * Call from the handler of a user action, before its writes: graph updates those writes cause flush * before the next paint instead of waiting out the frame budget. */ export declare const flushBeforePaint: (builder: GraphBuilder) => void; /** * Creates a GraphBuilder from a serialized pickle string. */ export declare const from: (pickle?: string, registry?: Registry.AtomRegistry, urlGrammar?: UrlGrammarProps) => GraphBuilder; export { addExtension, destroy, explore, flush, release, removeExtension, setRetention, } from '@dxos/graph/GraphBuilder'; /** * Flatten arbitrarily nested extension groups into a single list. Pinned to the app extension type, * which the generic signature cannot infer from a recursively nested argument. */ export declare const flattenExtensions: (extensions: BuilderExtensions, acc?: BuilderExtension[]) => BuilderExtension[]; /** * A graph builder extension is used to add nodes to the graph. * * @param params.id The unique id of the extension. * @param params.relation The relation the graph is being expanded from the existing node. * @param params.position Affects the order the extensions are processed in. * @param params.url URL binding for the nodes this extension produces (key + resolution); see {@link UrlBinding}. * @param params.connector A function to add nodes to the graph based on a connection to an existing node. * @param params.actions A function to add actions to the graph based on a connection to an existing node. * @param params.actionGroups A function to add action groups to the graph based on a connection to an existing node. */ export type CreateExtensionRawOptions = { id: [DXN.Path] extends [never] ? `Invalid id "${Id}": final segment must be camelCase — letters and digits, starting with a letter` : Id; relation?: Node.RelationInput; position?: Position.Position; url?: UrlBinding; connector?: ConnectorExtension; actions?: ActionsExtension; actionGroups?: ActionGroupsExtension; }; /** * Create a graph builder extension (low-level API that works directly with Atoms). */ export declare const createExtensionRaw: (extension: CreateExtensionRawOptions) => BuilderExtension[]; /** * Options for creating a graph builder extension with simplified API. * All callbacks must return Effects for dependency injection. * Effects may defect — defects are caught, logged, and the extension returns empty results. * Use Effect.orDie on any failable effects inside callbacks. */ export type CreateExtensionOptions = { id: [DXN.Path] extends [never] ? `Invalid id "${Id}": final segment must be camelCase — letters and digits, starting with a letter` : Id; match: (node: Node.Node, get: Atom.AtomContext) => Option.Option; actions?: (matched: TMatched, get: Atom.AtomContext) => Effect.Effect, any>, 'type'>[], never, R>; /** Contribute dropdown action groups (each with nested `actions`) to the matched node; the group's * `type`/`data` are set automatically, so returning `Node.makeActionGroup(...)` output is fine. */ actionGroups?: (matched: TMatched, get: Atom.AtomContext) => Effect.Effect, 'type' | 'data'>[], never, R>; connector?: (matched: TMatched, get: Atom.AtomContext) => Effect.Effect[], never, R>; relation?: Node.RelationInput; position?: Position.Position; /** URL binding for the nodes this extension produces (key + resolution); see {@link UrlBinding}. */ url?: UrlBinding; }; /** * Create a graph builder extension with simplified API. * Returns an Effect to allow callbacks to access services via dependency injection. */ export declare const createExtension: (options: CreateExtensionOptions) => Effect.Effect; /** * Create a connector extension from a matcher and factory function. * The factory's data type is inferred from the matcher's return type. */ export declare const createConnector: (matcher: (node: Node.Node, get: Atom.AtomContext) => Option.Option, factory: (data: TData, get: Atom.AtomContext) => Node.NodeArg[]) => ConnectorExtension; /** * Options for creating a type-based extension. * All callbacks must return Effects for dependency injection. * Effects may fail - errors are caught, logged, and the extension returns empty results. */ export type CreateTypeExtensionOptions = { id: [DXN.Path] extends [never] ? `Invalid id "${Id}": final segment must be camelCase — letters and digits, starting with a letter` : Id; type: T; actions?: (object: Type.InstanceType, get: Atom.AtomContext) => Effect.Effect>, 'type'>[], never, R>; actionGroups?: (object: Type.InstanceType, get: Atom.AtomContext) => Effect.Effect, 'type' | 'data'>[], never, R>; connector?: (object: Type.InstanceType, get: Atom.AtomContext) => Effect.Effect[], never, R>; relation?: Node.RelationInput; position?: Position.Position; }; /** * Create an extension that matches nodes by schema type. * The entity type is inferred from the schema type and works for both object and relation schemas. * Returns an Effect to allow callbacks to access services via dependency injection. */ export declare const createTypeExtension: (options: CreateTypeExtensionOptions) => Effect.Effect; //# sourceMappingURL=AppGraphBuilder.d.ts.map