{"version":3,"file":"AppGraphNode.mjs","names":[],"sources":["../../src/AppGraphNode.ts"],"sourcesContent":["//\n// Copyright 2023 DXOS.org\n//\n\nimport type * as Context from 'effect/Context';\nimport type * as Effect from 'effect/Effect';\n\nimport { type MakeOptional } from '@dxos/util';\n\n/**\n * Root node type.\n */\nexport const RootType = 'org.dxos.type.graphRoot';\n\n/**\n * Action node type.\n */\nexport const ActionType = 'org.dxos.type.graphAction';\n\n/**\n * Action group node type.\n */\nexport const ActionGroupType = 'org.dxos.type.graphActionGroup';\n\n/**\n * Represents a node in the graph.\n */\n// TODO(wittjosiah): Use Effect Schema.\n// TODO(burdon): Rename GraphNode. Node is already in the global namespace.\nexport type Node<TData = any, TProperties extends Record<string, any> = Record<string, any>> = Readonly<{\n  /**\n   * Globally unique ID.\n   */\n  // TODO(burdon): Allow string array, which is concatenated.\n  id: string;\n\n  /**\n   * Typename of the data the node represents.\n   */\n  type: string;\n\n  /**\n   * Keys in of the properties which should be cached.\n   * If defined, the node will be included in the cache.\n   * If undefined, the node will not be included in the cache.\n   */\n  cacheable?: string[];\n\n  /**\n   * Properties of the node relevant to displaying the node.\n   */\n  properties: Readonly<TProperties>;\n\n  /**\n   * Data the node represents.\n   */\n  // TODO(burdon): Type system (e.g., minimally provide identifier string vs. TypedObject vs. Graph mixin type system)?\n  //  type field would prevent convoluted sniffing of object properties. And allow direct pass-through for ECHO TypedObjects.\n  data: TData;\n}>;\n\nexport type NodeFilter<TData = any, TProperties extends Record<string, any> = Record<string, any>> = (\n  node: Node<unknown, Record<string, any>>,\n  connectedNode: Node,\n) => node is Node<TData, TProperties>;\n\nexport type RelationDirection = 'outbound' | 'inbound';\n\nexport type Relation = Readonly<{\n  kind: string;\n  direction: RelationDirection;\n}>;\n\nexport type RelationInput = Relation | string;\n\nexport const relation = (kind: string, direction: RelationDirection = 'outbound'): Relation => ({ kind, direction });\n\n/** The relation a node's children hang off; the graph's structure. */\nexport const child: Relation = relation('child');\n\n/** The relation a node's actions hang off, which live and die with it. */\nexport const action: Relation = relation('action');\n\nexport const isGraphNode = (data: unknown): data is Node =>\n  data && typeof data === 'object' && 'id' in data && 'properties' in data && data.properties\n    ? typeof data.properties === 'object' && 'data' in data\n    : false;\n\nexport type NodeArg<TData, TProperties extends Record<string, any> = Record<string, any>> = MakeOptional<\n  Node<TData, TProperties>,\n  'data' | 'properties' | 'cacheable'\n> & {\n  /** Will automatically add nodes with an edge from this node to each. */\n  nodes?: NodeArg<unknown>[];\n\n  /** Will automatically add actions with an edge from this node to each. An action child may itself\n   * be an action group (e.g. a toolbar dropdown group), so groups are accepted alongside actions. */\n  actions?: NodeArg<ActionData<any> | typeof actionGroupSymbol>[];\n\n  /** Will automatically add specified edges. */\n  edges?: [string, RelationInput][];\n};\n\n//\n// Actions\n//\n\nexport type InvokeProps = {\n  /** Node the invoked action is connected to. */\n  parent?: Node;\n\n  /** Path from root to the node in the current tree context. */\n  path?: string[];\n\n  caller?: string;\n\n  /** Input modifiers held during the gesture that triggered the action (e.g. shift-clicking a menu item). */\n  modifiers?: { shift?: boolean };\n};\n\n/**\n * Action data is an Effect-returning function.\n * The Effect is provided with captured context at execution time.\n */\nexport type ActionData<R = never> = (params?: InvokeProps) => Effect.Effect<any, Error, R>;\n\n/**\n * Context captured at extension creation time.\n * Automatically provided to action Effects at execution.\n */\nexport type ActionContext = Context.Context<any>;\n\nexport type Action<TProperties extends Record<string, any> = Record<string, any>> = Readonly<\n  Omit<Node<ActionData, TProperties>, 'properties'> & {\n    properties: Readonly<TProperties>;\n    /** Captured context from extension creation. Provided automatically at action execution. */\n    _actionContext?: ActionContext;\n  }\n>;\n\nexport const isAction = (data: unknown): data is Action =>\n  isGraphNode(data) ? typeof data.data === 'function' && data.type === ActionType : false;\n\nexport const actionGroupSymbol = Symbol('ActionGroup');\n\nexport type ActionGroup<TProperties extends Record<string, any> = Record<string, any>> = Readonly<\n  Omit<Node<typeof actionGroupSymbol, TProperties>, 'properties'> & {\n    properties: Readonly<TProperties>;\n  }\n>;\n\nexport const isActionGroup = (data: unknown): data is ActionGroup =>\n  isGraphNode(data) ? data.data === actionGroupSymbol && data.type === ActionGroupType : false;\n\nexport type ActionLike = Action | ActionGroup;\n\nexport const isActionLike = (data: unknown): data is Action | ActionGroup => isAction(data) || isActionGroup(data);\n\n/**\n * Tests whether a node's `disposition` property (a single string or an array, letting one node opt\n * into multiple surfaces at once) includes any of `key`. Every surface that routes nodes/actions by\n * disposition (toolbar, nav-tree list-item, sigil menu, …) should filter through this rather than\n * comparing `properties.disposition` directly, so a node can multi-target surfaces.\n */\nexport const hasDisposition = (node: Pick<Node, 'properties'>, key: string | string[]): boolean => {\n  const disposition = node.properties.disposition;\n  const dispositions = Array.isArray(disposition) ? disposition : disposition !== undefined ? [disposition] : [];\n  const keys = Array.isArray(key) ? key : [key];\n  return dispositions.some((candidate) => keys.includes(candidate));\n};\n\n//\n// Node Factories\n//\n\n/** Typed factory for constructing a NodeArg. Provides auto-complete and type validation. */\nexport const make = <TData = any, TProperties extends Record<string, any> = Record<string, any>>(\n  arg: NodeArg<TData, TProperties>,\n): NodeArg<TData, TProperties> => arg;\n\n/** Create an action node. Automatically sets `type: ActionType`. */\nexport const makeAction = <R = never>(\n  arg: Omit<NodeArg<ActionData<R>>, 'type' | 'nodes' | 'edges'>,\n): NodeArg<ActionData<R>> => ({\n  ...arg,\n  type: ActionType,\n});\n\n/** Create an action group node. Automatically sets `type` and `data`. */\nexport const makeActionGroup = (\n  arg: Omit<NodeArg<typeof actionGroupSymbol>, 'type' | 'data' | 'nodes' | 'edges'>,\n): NodeArg<typeof actionGroupSymbol> => ({\n  ...arg,\n  type: ActionGroupType,\n  data: actionGroupSymbol,\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAYA,IAAa,WAAW;;;;AAKxB,IAAa,aAAa;;;;AAK1B,IAAa,kBAAkB;AAqD/B,IAAa,YAAY,MAAc,YAA+B,gBAA0B;CAAE;CAAM;AAAU;;AAGlH,IAAa,QAAkB,SAAS,OAAO;;AAG/C,IAAa,SAAmB,SAAS,QAAQ;AAEjD,IAAa,eAAe,SAC1B,QAAQ,OAAO,SAAS,YAAY,QAAQ,QAAQ,gBAAgB,QAAQ,KAAK,aAC7E,OAAO,KAAK,eAAe,YAAY,UAAU,OACjD;AAsDN,IAAa,YAAY,SACvB,YAAY,IAAI,IAAI,OAAO,KAAK,SAAS,cAAc,KAAK,SAAA,8BAAsB;AAEpF,IAAa,oBAAoB,OAAO,aAAa;AAQrD,IAAa,iBAAiB,SAC5B,YAAY,IAAI,IAAI,KAAK,SAAS,qBAAqB,KAAK,SAAA,mCAA2B;AAIzF,IAAa,gBAAgB,SAAgD,SAAS,IAAI,KAAK,cAAc,IAAI;;;;;;;AAQjH,IAAa,kBAAkB,MAAgC,QAAoC;CACjG,MAAM,cAAc,KAAK,WAAW;CACpC,MAAM,eAAe,MAAM,QAAQ,WAAW,IAAI,cAAc,gBAAgB,KAAA,IAAY,CAAC,WAAW,IAAI,CAAC;CAC7G,MAAM,OAAO,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,GAAG;CAC5C,OAAO,aAAa,MAAM,cAAc,KAAK,SAAS,SAAS,CAAC;AAClE;;AAOA,IAAa,QACX,QACgC;;AAGlC,IAAa,cACX,SAC4B;CAC5B,GAAG;CACH,MAAM;AACR;;AAGA,IAAa,mBACX,SACuC;CACvC,GAAG;CACH,MAAM;CACN,MAAM;AACR"}