@agent-native/core 0.39.2 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/README.md +1 -1
  2. package/dist/action.js +12 -0
  3. package/dist/action.js.map +1 -1
  4. package/dist/cli/create.d.ts.map +1 -1
  5. package/dist/cli/create.js +5 -1
  6. package/dist/cli/create.js.map +1 -1
  7. package/dist/cli/skills.d.ts +4 -3
  8. package/dist/cli/skills.d.ts.map +1 -1
  9. package/dist/cli/skills.js +756 -694
  10. package/dist/cli/skills.js.map +1 -1
  11. package/dist/client/blocks/AiEditableField.d.ts +8 -0
  12. package/dist/client/blocks/AiEditableField.d.ts.map +1 -0
  13. package/dist/client/blocks/AiEditableField.js +10 -0
  14. package/dist/client/blocks/AiEditableField.js.map +1 -0
  15. package/dist/client/blocks/BlockView.d.ts +3 -3
  16. package/dist/client/blocks/BlockView.d.ts.map +1 -1
  17. package/dist/client/blocks/BlockView.js +15 -3
  18. package/dist/client/blocks/BlockView.js.map +1 -1
  19. package/dist/client/blocks/SchemaBlockEditor.js +2 -2
  20. package/dist/client/blocks/SchemaBlockEditor.js.map +1 -1
  21. package/dist/client/blocks/index.d.ts +5 -2
  22. package/dist/client/blocks/index.d.ts.map +1 -1
  23. package/dist/client/blocks/index.js +6 -3
  24. package/dist/client/blocks/index.js.map +1 -1
  25. package/dist/client/blocks/library/ApiEndpointBlock.d.ts.map +1 -1
  26. package/dist/client/blocks/library/ApiEndpointBlock.js +20 -6
  27. package/dist/client/blocks/library/ApiEndpointBlock.js.map +1 -1
  28. package/dist/client/blocks/library/DiffBlock.d.ts +29 -0
  29. package/dist/client/blocks/library/DiffBlock.d.ts.map +1 -1
  30. package/dist/client/blocks/library/DiffBlock.js +190 -30
  31. package/dist/client/blocks/library/DiffBlock.js.map +1 -1
  32. package/dist/client/blocks/library/FileTreeBlock.d.ts.map +1 -1
  33. package/dist/client/blocks/library/FileTreeBlock.js +46 -7
  34. package/dist/client/blocks/library/FileTreeBlock.js.map +1 -1
  35. package/dist/client/blocks/library/HighlightedCode.d.ts +10 -0
  36. package/dist/client/blocks/library/HighlightedCode.d.ts.map +1 -0
  37. package/dist/client/blocks/library/HighlightedCode.js +92 -0
  38. package/dist/client/blocks/library/HighlightedCode.js.map +1 -0
  39. package/dist/client/blocks/library/JsonExplorerBlock.d.ts +9 -4
  40. package/dist/client/blocks/library/JsonExplorerBlock.d.ts.map +1 -1
  41. package/dist/client/blocks/library/JsonExplorerBlock.js +66 -30
  42. package/dist/client/blocks/library/JsonExplorerBlock.js.map +1 -1
  43. package/dist/client/blocks/library/MermaidBlock.d.ts.map +1 -1
  44. package/dist/client/blocks/library/MermaidBlock.js +73 -44
  45. package/dist/client/blocks/library/MermaidBlock.js.map +1 -1
  46. package/dist/client/blocks/library/OpenApiSpecBlock.d.ts.map +1 -1
  47. package/dist/client/blocks/library/OpenApiSpecBlock.js +3 -2
  48. package/dist/client/blocks/library/OpenApiSpecBlock.js.map +1 -1
  49. package/dist/client/blocks/library/checklist.d.ts.map +1 -1
  50. package/dist/client/blocks/library/checklist.js +1 -0
  51. package/dist/client/blocks/library/checklist.js.map +1 -1
  52. package/dist/client/blocks/library/code-tabs.d.ts.map +1 -1
  53. package/dist/client/blocks/library/code-tabs.js +183 -102
  54. package/dist/client/blocks/library/code-tabs.js.map +1 -1
  55. package/dist/client/blocks/library/columns.config.d.ts +60 -0
  56. package/dist/client/blocks/library/columns.config.d.ts.map +1 -0
  57. package/dist/client/blocks/library/columns.config.js +37 -0
  58. package/dist/client/blocks/library/columns.config.js.map +1 -0
  59. package/dist/client/blocks/library/columns.d.ts +25 -0
  60. package/dist/client/blocks/library/columns.d.ts.map +1 -0
  61. package/dist/client/blocks/library/columns.js +199 -0
  62. package/dist/client/blocks/library/columns.js.map +1 -0
  63. package/dist/client/blocks/library/dev-doc-ui.d.ts +2 -1
  64. package/dist/client/blocks/library/dev-doc-ui.d.ts.map +1 -1
  65. package/dist/client/blocks/library/dev-doc-ui.js +2 -1
  66. package/dist/client/blocks/library/dev-doc-ui.js.map +1 -1
  67. package/dist/client/blocks/library/html.d.ts +1 -1
  68. package/dist/client/blocks/library/html.d.ts.map +1 -1
  69. package/dist/client/blocks/library/html.js +34 -4
  70. package/dist/client/blocks/library/html.js.map +1 -1
  71. package/dist/client/blocks/library/json-explorer.config.d.ts +3 -1
  72. package/dist/client/blocks/library/json-explorer.config.d.ts.map +1 -1
  73. package/dist/client/blocks/library/json-explorer.config.js +30 -1
  74. package/dist/client/blocks/library/json-explorer.config.js.map +1 -1
  75. package/dist/client/blocks/library/server-specs.d.ts.map +1 -1
  76. package/dist/client/blocks/library/server-specs.js +13 -3
  77. package/dist/client/blocks/library/server-specs.js.map +1 -1
  78. package/dist/client/blocks/library/specs.d.ts +4 -4
  79. package/dist/client/blocks/library/specs.d.ts.map +1 -1
  80. package/dist/client/blocks/library/specs.js +21 -16
  81. package/dist/client/blocks/library/specs.js.map +1 -1
  82. package/dist/client/blocks/library/table.config.d.ts +3 -0
  83. package/dist/client/blocks/library/table.config.d.ts.map +1 -1
  84. package/dist/client/blocks/library/table.config.js +13 -1
  85. package/dist/client/blocks/library/table.config.js.map +1 -1
  86. package/dist/client/blocks/library/table.d.ts.map +1 -1
  87. package/dist/client/blocks/library/table.js +90 -9
  88. package/dist/client/blocks/library/table.js.map +1 -1
  89. package/dist/client/blocks/library/tabs.config.d.ts +16 -8
  90. package/dist/client/blocks/library/tabs.config.d.ts.map +1 -1
  91. package/dist/client/blocks/library/tabs.config.js +10 -4
  92. package/dist/client/blocks/library/tabs.config.js.map +1 -1
  93. package/dist/client/blocks/library/tabs.d.ts.map +1 -1
  94. package/dist/client/blocks/library/tabs.js +146 -21
  95. package/dist/client/blocks/library/tabs.js.map +1 -1
  96. package/dist/client/blocks/server.d.ts +2 -1
  97. package/dist/client/blocks/server.d.ts.map +1 -1
  98. package/dist/client/blocks/server.js +1 -0
  99. package/dist/client/blocks/server.js.map +1 -1
  100. package/dist/client/blocks/types.d.ts +99 -9
  101. package/dist/client/blocks/types.d.ts.map +1 -1
  102. package/dist/client/blocks/types.js.map +1 -1
  103. package/dist/client/index.d.ts +1 -1
  104. package/dist/client/index.d.ts.map +1 -1
  105. package/dist/client/index.js +2 -2
  106. package/dist/client/index.js.map +1 -1
  107. package/dist/client/rich-markdown-editor/BubbleToolbar.d.ts.map +1 -1
  108. package/dist/client/rich-markdown-editor/BubbleToolbar.js +13 -3
  109. package/dist/client/rich-markdown-editor/BubbleToolbar.js.map +1 -1
  110. package/dist/client/rich-markdown-editor/DragHandle.d.ts +49 -4
  111. package/dist/client/rich-markdown-editor/DragHandle.d.ts.map +1 -1
  112. package/dist/client/rich-markdown-editor/DragHandle.js +656 -88
  113. package/dist/client/rich-markdown-editor/DragHandle.js.map +1 -1
  114. package/dist/client/rich-markdown-editor/RegistryBlockNode.d.ts +10 -1
  115. package/dist/client/rich-markdown-editor/RegistryBlockNode.d.ts.map +1 -1
  116. package/dist/client/rich-markdown-editor/RegistryBlockNode.js +180 -15
  117. package/dist/client/rich-markdown-editor/RegistryBlockNode.js.map +1 -1
  118. package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts +2 -1
  119. package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts.map +1 -1
  120. package/dist/client/rich-markdown-editor/SharedRichEditor.js +3 -1
  121. package/dist/client/rich-markdown-editor/SharedRichEditor.js.map +1 -1
  122. package/dist/client/rich-markdown-editor/SlashCommandMenu.d.ts +5 -0
  123. package/dist/client/rich-markdown-editor/SlashCommandMenu.d.ts.map +1 -1
  124. package/dist/client/rich-markdown-editor/SlashCommandMenu.js +33 -5
  125. package/dist/client/rich-markdown-editor/SlashCommandMenu.js.map +1 -1
  126. package/dist/client/rich-markdown-editor/index.d.ts +3 -3
  127. package/dist/client/rich-markdown-editor/index.d.ts.map +1 -1
  128. package/dist/client/rich-markdown-editor/index.js +2 -2
  129. package/dist/client/rich-markdown-editor/index.js.map +1 -1
  130. package/dist/client/rich-markdown-editor/registrySlashCommands.d.ts +14 -0
  131. package/dist/client/rich-markdown-editor/registrySlashCommands.d.ts.map +1 -1
  132. package/dist/client/rich-markdown-editor/registrySlashCommands.js +38 -0
  133. package/dist/client/rich-markdown-editor/registrySlashCommands.js.map +1 -1
  134. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts +1 -0
  135. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  136. package/dist/client/rich-markdown-editor/useCollabReconcile.js +4 -0
  137. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  138. package/dist/db/client.d.ts.map +1 -1
  139. package/dist/db/client.js +17 -1
  140. package/dist/db/client.js.map +1 -1
  141. package/dist/sharing/access.d.ts +4 -2
  142. package/dist/sharing/access.d.ts.map +1 -1
  143. package/dist/sharing/access.js +8 -3
  144. package/dist/sharing/access.js.map +1 -1
  145. package/dist/sharing/actions/set-resource-visibility.d.ts.map +1 -1
  146. package/dist/sharing/actions/set-resource-visibility.js +2 -3
  147. package/dist/sharing/actions/set-resource-visibility.js.map +1 -1
  148. package/dist/sharing/registry.d.ts +13 -0
  149. package/dist/sharing/registry.d.ts.map +1 -1
  150. package/dist/sharing/registry.js.map +1 -1
  151. package/dist/styles/rich-markdown-editor.css +15 -0
  152. package/package.json +16 -1
@@ -1,3 +1,40 @@
1
+ const COMPACT_REGISTRY_BLOCK_DESCRIPTIONS = {
2
+ callout: "Emphasized note",
3
+ diagram: "Inline diagram",
4
+ wireframe: "Screen mockup",
5
+ "question-form": "Interactive questions",
6
+ checklist: "Checklist items",
7
+ table: "Editable grid",
8
+ "table-block": "Editable grid",
9
+ "code-tabs": "Tabbed code snippets",
10
+ "custom-html": "Sandboxed HTML",
11
+ tabs: "Tabbed block group",
12
+ columns: "Side-by-side columns",
13
+ mermaid: "Mermaid diagram",
14
+ "api-endpoint": "API reference",
15
+ "openapi-spec": "OpenAPI document",
16
+ "data-model": "ERD schema",
17
+ diff: "Code diff",
18
+ "file-tree": "File/change tree",
19
+ "json-explorer": "JSON tree",
20
+ "annotated-code": "Code walkthrough",
21
+ };
22
+ /**
23
+ * Compact, user-facing slash-menu copy for structured registry blocks. The full
24
+ * registry description remains available through search text, but the visible
25
+ * row should scan like a command palette, not a block reference page.
26
+ */
27
+ export function getRegistryBlockSlashDescription(spec) {
28
+ return (COMPACT_REGISTRY_BLOCK_DESCRIPTIONS[spec.type] ??
29
+ spec.description.trim().replace(/\s+/g, " "));
30
+ }
31
+ /** Searchable text for registry block slash items, including raw type keywords. */
32
+ export function getRegistryBlockSlashSearchText(spec) {
33
+ return [spec.label, spec.description, spec.type]
34
+ .filter(Boolean)
35
+ .join(" ")
36
+ .toLowerCase();
37
+ }
1
38
  /**
2
39
  * Build the registry-derived block slash items, shared by plan and content. Each
3
40
  * app prepends its own prose/base commands and wraps the result in its own item
@@ -7,6 +44,7 @@ export function buildRegistryBlockSlashItems(registry, options) {
7
44
  const isCompatible = options.isNotionCompatible ?? ((spec) => Boolean(spec.notionCompatible));
8
45
  return registry
9
46
  .list("block")
47
+ .filter((spec) => options.includeSpec?.(spec) ?? true)
10
48
  .filter((spec) => !options.notionCompatibleOnly || isCompatible(spec))
11
49
  .map((spec) => options.toItem(spec, (editor) => options.insertBlock(editor, spec)));
12
50
  }
@@ -1 +1 @@
1
- {"version":3,"file":"registrySlashCommands.js","sourceRoot":"","sources":["../../../src/client/rich-markdown-editor/registrySlashCommands.ts"],"names":[],"mappings":"AAyCA;;;;GAIG;AACH,MAAM,UAAU,4BAA4B,CAC1C,QAAuB,EACvB,OAA4D;IAE5D,MAAM,YAAY,GAChB,OAAO,CAAC,kBAAkB,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAC3E,OAAO,QAAQ;SACZ,IAAI,CAAC,OAAO,CAAC;SACb,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,oBAAoB,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;SACrE,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CACZ,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CACpE,CAAC;AACN,CAAC","sourcesContent":["import type { BlockRegistry, BlockSpec } from \"../blocks/index.js\";\n\n/**\n * Shared builder for the registry-derived block slash commands both the plan and\n * content editors offer. Both apps take every `BlockSpec` whose `placement`\n * includes `\"block\"`, gate it by Notion-compatibility when the open document is\n * linked to a Notion page, and emit one slash item per surviving spec that\n * inserts that block's atom node. The only legitimate per-app differences are:\n *\n * - the ITEM SHAPE (plan uses a text-glyph `icon`, content a React component),\n * - the Notion-compat PREDICATE (plan unions in prose-only NFM analogs, content\n * reads the registry `notionCompatible` flag directly), and\n * - the INSERT behavior (plan inserts a `planBlock` node, content a\n * `registryBlock` node seeded with inline `__raw`).\n *\n * Those three are injected; everything else (the `list(\"block\")` source, the\n * Notion filter wiring, the one-item-per-spec mapping) lives here so adding a\n * new library block only touches the registry, never the slash builders.\n */\nexport interface BuildRegistryBlockSlashItemsOptions<TItem, TEditor> {\n /**\n * When `true`, only specs the predicate accepts are offered (the open document\n * is linked to a Notion page, so blocks that can't round-trip to NFM are\n * hidden). When unset/false, every block-placed spec is offered.\n */\n notionCompatibleOnly?: boolean;\n /**\n * Decide whether a spec round-trips to Notion. Defaults to the spec's own\n * `notionCompatible` flag (content's rule). Plan passes a predicate that unions\n * in prose-only NFM analogs not carried as registry flags.\n */\n isNotionCompatible?: (spec: BlockSpec) => boolean;\n /** Build one app-shaped slash item from a surviving block spec. */\n toItem: (spec: BlockSpec, insert: (editor: TEditor) => void) => TItem;\n /**\n * Insert this spec's block atom into the editor. Plan inserts a `planBlock`\n * node; content inserts a `registryBlock` node seeded with inline `__raw`.\n */\n insertBlock: (editor: TEditor, spec: BlockSpec) => void;\n}\n\n/**\n * Build the registry-derived block slash items, shared by plan and content. Each\n * app prepends its own prose/base commands and wraps the result in its own item\n * type via {@link BuildRegistryBlockSlashItemsOptions.toItem}.\n */\nexport function buildRegistryBlockSlashItems<TItem, TEditor>(\n registry: BlockRegistry,\n options: BuildRegistryBlockSlashItemsOptions<TItem, TEditor>,\n): TItem[] {\n const isCompatible =\n options.isNotionCompatible ?? ((spec) => Boolean(spec.notionCompatible));\n return registry\n .list(\"block\")\n .filter((spec) => !options.notionCompatibleOnly || isCompatible(spec))\n .map((spec) =>\n options.toItem(spec, (editor) => options.insertBlock(editor, spec)),\n );\n}\n"]}
1
+ {"version":3,"file":"registrySlashCommands.js","sourceRoot":"","sources":["../../../src/client/rich-markdown-editor/registrySlashCommands.ts"],"names":[],"mappings":"AAEA,MAAM,mCAAmC,GAA2B;IAClE,OAAO,EAAE,iBAAiB;IAC1B,OAAO,EAAE,gBAAgB;IACzB,SAAS,EAAE,eAAe;IAC1B,eAAe,EAAE,uBAAuB;IACxC,SAAS,EAAE,iBAAiB;IAC5B,KAAK,EAAE,eAAe;IACtB,aAAa,EAAE,eAAe;IAC9B,WAAW,EAAE,sBAAsB;IACnC,aAAa,EAAE,gBAAgB;IAC/B,IAAI,EAAE,oBAAoB;IAC1B,OAAO,EAAE,sBAAsB;IAC/B,OAAO,EAAE,iBAAiB;IAC1B,cAAc,EAAE,eAAe;IAC/B,cAAc,EAAE,kBAAkB;IAClC,YAAY,EAAE,YAAY;IAC1B,IAAI,EAAE,WAAW;IACjB,WAAW,EAAE,kBAAkB;IAC/B,eAAe,EAAE,WAAW;IAC5B,gBAAgB,EAAE,kBAAkB;CACrC,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,gCAAgC,CAC9C,IAA6C;IAE7C,OAAO,CACL,mCAAmC,CAAC,IAAI,CAAC,IAAI,CAAC;QAC9C,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAC7C,CAAC;AACJ,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,+BAA+B,CAC7C,IAAuD;IAEvD,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC;SAC7C,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,GAAG,CAAC;SACT,WAAW,EAAE,CAAC;AACnB,CAAC;AA+CD;;;;GAIG;AACH,MAAM,UAAU,4BAA4B,CAC1C,QAAuB,EACvB,OAA4D;IAE5D,MAAM,YAAY,GAChB,OAAO,CAAC,kBAAkB,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAC3E,OAAO,QAAQ;SACZ,IAAI,CAAC,OAAO,CAAC;SACb,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;SACrD,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,oBAAoB,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;SACrE,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CACZ,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CACpE,CAAC;AACN,CAAC","sourcesContent":["import type { BlockRegistry, BlockSpec } from \"../blocks/index.js\";\n\nconst COMPACT_REGISTRY_BLOCK_DESCRIPTIONS: Record<string, string> = {\n callout: \"Emphasized note\",\n diagram: \"Inline diagram\",\n wireframe: \"Screen mockup\",\n \"question-form\": \"Interactive questions\",\n checklist: \"Checklist items\",\n table: \"Editable grid\",\n \"table-block\": \"Editable grid\",\n \"code-tabs\": \"Tabbed code snippets\",\n \"custom-html\": \"Sandboxed HTML\",\n tabs: \"Tabbed block group\",\n columns: \"Side-by-side columns\",\n mermaid: \"Mermaid diagram\",\n \"api-endpoint\": \"API reference\",\n \"openapi-spec\": \"OpenAPI document\",\n \"data-model\": \"ERD schema\",\n diff: \"Code diff\",\n \"file-tree\": \"File/change tree\",\n \"json-explorer\": \"JSON tree\",\n \"annotated-code\": \"Code walkthrough\",\n};\n\n/**\n * Compact, user-facing slash-menu copy for structured registry blocks. The full\n * registry description remains available through search text, but the visible\n * row should scan like a command palette, not a block reference page.\n */\nexport function getRegistryBlockSlashDescription(\n spec: Pick<BlockSpec, \"type\" | \"description\">,\n): string {\n return (\n COMPACT_REGISTRY_BLOCK_DESCRIPTIONS[spec.type] ??\n spec.description.trim().replace(/\\s+/g, \" \")\n );\n}\n\n/** Searchable text for registry block slash items, including raw type keywords. */\nexport function getRegistryBlockSlashSearchText(\n spec: Pick<BlockSpec, \"type\" | \"label\" | \"description\">,\n): string {\n return [spec.label, spec.description, spec.type]\n .filter(Boolean)\n .join(\" \")\n .toLowerCase();\n}\n\n/**\n * Shared builder for the registry-derived block slash commands both the plan and\n * content editors offer. Both apps take every `BlockSpec` whose `placement`\n * includes `\"block\"`, gate it by Notion-compatibility when the open document is\n * linked to a Notion page, and emit one slash item per surviving spec that\n * inserts that block's atom node. The only legitimate per-app differences are:\n *\n * - the ITEM SHAPE (plan uses a text-glyph `icon`, content a React component),\n * - the Notion-compat PREDICATE (plan unions in prose-only NFM analogs, content\n * reads the registry `notionCompatible` flag directly), and\n * - the INSERT behavior (plan inserts a `planBlock` node, content a\n * `registryBlock` node seeded with inline `__raw`).\n *\n * Those three are injected; everything else (the `list(\"block\")` source, the\n * Notion filter wiring, the one-item-per-spec mapping) lives here so adding a\n * new library block only touches the registry, never the slash builders.\n */\nexport interface BuildRegistryBlockSlashItemsOptions<TItem, TEditor> {\n /**\n * When `true`, only specs the predicate accepts are offered (the open document\n * is linked to a Notion page, so blocks that can't round-trip to NFM are\n * hidden). When unset/false, every block-placed spec is offered.\n */\n notionCompatibleOnly?: boolean;\n /**\n * Decide whether a spec round-trips to Notion. Defaults to the spec's own\n * `notionCompatible` flag (content's rule). Plan passes a predicate that unions\n * in prose-only NFM analogs not carried as registry flags.\n */\n isNotionCompatible?: (spec: BlockSpec) => boolean;\n /** Build one app-shaped slash item from a surviving block spec. */\n toItem: (spec: BlockSpec, insert: (editor: TEditor) => void) => TItem;\n /**\n * Optional app-level capability gate. Use this for blocks whose schema is\n * registered for parse/render compatibility but whose authoring experience is\n * not available in this editor yet.\n */\n includeSpec?: (spec: BlockSpec) => boolean;\n /**\n * Insert this spec's block atom into the editor. Plan inserts a `planBlock`\n * node; content inserts a `registryBlock` node seeded with inline `__raw`.\n */\n insertBlock: (editor: TEditor, spec: BlockSpec) => void;\n}\n\n/**\n * Build the registry-derived block slash items, shared by plan and content. Each\n * app prepends its own prose/base commands and wraps the result in its own item\n * type via {@link BuildRegistryBlockSlashItemsOptions.toItem}.\n */\nexport function buildRegistryBlockSlashItems<TItem, TEditor>(\n registry: BlockRegistry,\n options: BuildRegistryBlockSlashItemsOptions<TItem, TEditor>,\n): TItem[] {\n const isCompatible =\n options.isNotionCompatible ?? ((spec) => Boolean(spec.notionCompatible));\n return registry\n .list(\"block\")\n .filter((spec) => options.includeSpec?.(spec) ?? true)\n .filter((spec) => !options.notionCompatibleOnly || isCompatible(spec))\n .map((spec) =>\n options.toItem(spec, (editor) => options.insertBlock(editor, spec)),\n );\n}\n"]}
@@ -3,6 +3,7 @@ import type { Editor } from "@tiptap/react";
3
3
  import type { Transaction } from "@tiptap/pm/state";
4
4
  import type { Doc as YDoc } from "yjs";
5
5
  import type { Awareness } from "y-protocols/awareness";
6
+ export declare const RICH_MARKDOWN_PROGRAMMATIC_TRANSACTION = "an-rich-md-programmatic-transaction";
6
7
  /** Reads the current markdown out of the tiptap-markdown storage. */
7
8
  export declare function getEditorMarkdown(editor: Editor): string;
8
9
  export interface UseCollabReconcileOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"useCollabReconcile.d.ts","sourceRoot":"","sources":["../../../src/client/rich-markdown-editor/useCollabReconcile.ts"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAC3E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,KAAK,EAAE,GAAG,IAAI,IAAI,EAAE,MAAM,KAAK,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAIvD,qEAAqE;AACrE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKxD;AAqBD,MAAM,WAAW,yBAAyB;IACxC,gDAAgD;IAChD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,uEAAuE;IACvE,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAC7B,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,qFAAqF;IACrF,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IACzC;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,CACX,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,KACtD,IAAI,CAAC;IACV;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3C;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,eAAe,EAAE,MAAM,CAAC;QACxB,cAAc,EAAE,MAAM,CAAC;KACxB,KAAK,OAAO,CAAC;IACd;;;;;;;OAOG;IACH,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzC;AAED,MAAM,WAAW,wBAAwB;IACvC,iEAAiE;IACjE,MAAM,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,mBAAmB,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC/C;;;;OAIG;IACH,kBAAkB,EAAE,CAAC,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC;IAC1D;;;;OAIG;IACH,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;CAChD;AAoED,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,IAAW,EACX,SAAgB,EAChB,KAAK,EACL,gBAAgB,EAChB,QAAQ,EACR,WAA+B,EAC/B,UAA8B,EAC9B,cAAyB,EACzB,UAA8B,EAC9B,uBAAuB,GACxB,EAAE,yBAAyB,GAAG,wBAAwB,CA4StD"}
1
+ {"version":3,"file":"useCollabReconcile.d.ts","sourceRoot":"","sources":["../../../src/client/rich-markdown-editor/useCollabReconcile.ts"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAC3E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,KAAK,EAAE,GAAG,IAAI,IAAI,EAAE,MAAM,KAAK,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAIvD,eAAO,MAAM,sCAAsC,wCACZ,CAAC;AAExC,qEAAqE;AACrE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKxD;AAqBD,MAAM,WAAW,yBAAyB;IACxC,gDAAgD;IAChD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,uEAAuE;IACvE,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAC7B,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,qFAAqF;IACrF,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IACzC;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,CACX,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,KACtD,IAAI,CAAC;IACV;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3C;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,eAAe,EAAE,MAAM,CAAC;QACxB,cAAc,EAAE,MAAM,CAAC;KACxB,KAAK,OAAO,CAAC;IACd;;;;;;;OAOG;IACH,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzC;AAED,MAAM,WAAW,wBAAwB;IACvC,iEAAiE;IACjE,MAAM,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,mBAAmB,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC/C;;;;OAIG;IACH,kBAAkB,EAAE,CAAC,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC;IAC1D;;;;OAIG;IACH,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;CAChD;AAoED,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,IAAW,EACX,SAAgB,EAChB,KAAK,EACL,gBAAgB,EAChB,QAAQ,EACR,WAA+B,EAC/B,UAA8B,EAC9B,cAAyB,EACzB,UAA8B,EAC9B,uBAAuB,GACxB,EAAE,yBAAyB,GAAG,wBAAwB,CA+StD"}
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react";
2
2
  import { isChangeOrigin } from "@tiptap/extension-collaboration";
3
3
  import { isReconcileLeadClient } from "../../collab/client.js";
4
4
  import { AGENT_CLIENT_ID } from "../../collab/agent-identity.js";
5
+ export const RICH_MARKDOWN_PROGRAMMATIC_TRANSACTION = "an-rich-md-programmatic-transaction";
5
6
  /** Reads the current markdown out of the tiptap-markdown storage. */
6
7
  export function getEditorMarkdown(editor) {
7
8
  const markdownStorage = editor.storage;
@@ -347,6 +348,9 @@ export function useCollabReconcile({ editor, ydoc = null, awareness = null, valu
347
348
  const shouldIgnoreUpdate = (transaction) => {
348
349
  if (!editable || isSettingContentRef.current)
349
350
  return true;
351
+ if (transaction.getMeta(RICH_MARKDOWN_PROGRAMMATIC_TRANSACTION)) {
352
+ return true;
353
+ }
350
354
  // In collab mode, never persist remote-originated changes (the initial Yjs
351
355
  // state load or a peer's edit arriving via sync). Each client saves only its
352
356
  // OWN local edits; a peer's edit is saved by that peer. Without this, a
@@ -1 +1 @@
1
- {"version":3,"file":"useCollabReconcile.js","sourceRoot":"","sources":["../../../src/client/rich-markdown-editor/useCollabReconcile.ts"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useRef, useState, type MutableRefObject } from \"react\";\nimport type { Editor } from \"@tiptap/react\";\nimport type { Transaction } from \"@tiptap/pm/state\";\nimport { isChangeOrigin } from \"@tiptap/extension-collaboration\";\nimport type { Doc as YDoc } from \"yjs\";\nimport type { Awareness } from \"y-protocols/awareness\";\nimport { isReconcileLeadClient } from \"../../collab/client.js\";\nimport { AGENT_CLIENT_ID } from \"../../collab/agent-identity.js\";\n\n/** Reads the current markdown out of the tiptap-markdown storage. */\nexport function getEditorMarkdown(editor: Editor): string {\n const markdownStorage = editor.storage as unknown as {\n markdown?: { getMarkdown?: () => string };\n };\n return markdownStorage.markdown?.getMarkdown?.() ?? \"\";\n}\n\n/**\n * Push a value onto the bounded ring of recently-emitted markdown (most recent\n * last, deduped, capped). The reconcile uses this to recognize a stale-but-recent\n * echo of OUR OWN edits: a debounced autosave can persist a PARTIAL burst, and\n * the next poll re-supplies that partial value with a newer timestamp — applying\n * it would clobber the freshly-typed tail. An external change (agent/peer) never\n * byte-matches one of our own recent emissions, and if it somehow did the content\n * is identical, so skipping it is safe by construction.\n */\nconst EMITTED_RING_MAX = 16;\nfunction pushEmittedRing(ring: string[], value: string): void {\n if (!value) return;\n if (ring[ring.length - 1] === value) return;\n const dupe = ring.indexOf(value);\n if (dupe !== -1) ring.splice(dupe, 1);\n ring.push(value);\n if (ring.length > EMITTED_RING_MAX) ring.shift();\n}\n\nexport interface UseCollabReconcileOptions {\n /** The live editor, or null until it mounts. */\n editor: Editor | null;\n /** Shared Y.Doc when collaborating; null disables all collab paths. */\n ydoc?: YDoc | null;\n /** Shared awareness; null keeps the sole-client lead path. */\n awareness?: Awareness | null;\n /** Authoritative markdown value (SQL source of truth). */\n value: string;\n /** Timestamp of the authoritative value; gates newer-than reconcile. */\n contentUpdatedAt?: string | null;\n /** Whether the editor accepts edits. Reconcile/seed only run for the live editor. */\n editable: boolean;\n /**\n * Reads the current markdown from the editor. Injected so a dialect could\n * swap serializers; defaults to the tiptap-markdown storage reader. For an app\n * with a custom serializer (e.g. Content's `docToNfm(editor.getJSON())`), pass\n * it here so the seed/reconcile equality checks compare like-for-like.\n */\n getMarkdown?: (editor: Editor) => string;\n /**\n * Applies the authoritative `value` into the editor. Defaults to passing the\n * raw markdown string to `editor.commands.setContent`. Apps whose serializer\n * is NOT tiptap-markdown (Content parses `nfmToDoc(value)` into a PM doc)\n * override this so seed + reconcile write the correct content shape. The\n * supplied `options` carry the history/whitespace flags the default path uses;\n * a custom implementation should forward them when relevant.\n */\n setContent?: (\n editor: Editor,\n value: string,\n options: { emitUpdate?: boolean; addToHistory?: boolean },\n ) => void;\n /**\n * Normalizes the authoritative `value` to the canonical markdown the editor\n * would emit, so the \"already in sync / our own echo\" equality checks match a\n * serializer that re-canonicalizes (Content's `canonicalizeNfm`). Defaults to\n * identity (GFM already round-trips byte-stably).\n */\n normalizeValue?: (value: string) => string;\n /**\n * Decides whether the empty-doc seed should run for the current shared\n * fragment. Defaults to \"fragment has no nodes, or the editor holds no\n * semantic markdown\". Apps with sentinel-empty content (Content's\n * `<empty-block/>` filler) override this. Receives the live fragment length\n * and the editor's current markdown.\n */\n shouldSeed?: (info: {\n value: string;\n currentMarkdown: string;\n fragmentLength: number;\n }) => boolean;\n /**\n * The initial \"applied\" watermark. Default mirrors `contentUpdatedAt`, so a\n * fresh mount whose Y.Doc already matches SQL doesn't re-apply. Pass `null`\n * to force the first reconcile pass to adopt authoritative SQL even at the\n * same timestamp — Content does this so a stale persisted Y.Doc (an agent that\n * edited the CLOSED doc) is corrected on open. The editor is keyed per\n * document upstream, so this only affects the first mount of each doc.\n */\n initialAppliedUpdatedAt?: string | null;\n}\n\nexport interface UseCollabReconcileResult {\n /** True when a Y.Doc is bound (collaborative editing active). */\n collab: boolean;\n /**\n * Set true around any programmatic `setContent` so the editor's `onUpdate`\n * can ignore the resulting transaction (it isn't a user edit).\n */\n isSettingContentRef: MutableRefObject<boolean>;\n /**\n * Call from `onUpdate` BEFORE serializing. Returns true when the update must\n * be ignored: editor not editable, mid-programmatic-setContent, or (in collab\n * mode) a remote-origin transaction. Also records the local typing time.\n */\n shouldIgnoreUpdate: (transaction: Transaction) => boolean;\n /**\n * Call from `onUpdate` AFTER computing the markdown to emit. Returns false\n * when the value must NOT be persisted yet (an empty collab doc before the\n * seed has run); records it as the last-emitted value otherwise.\n */\n registerEmitted: (markdown: string) => boolean;\n}\n\n/**\n * The subtle seed / reconcile / lead-client logic for the shared markdown\n * editor, extracted once so it can never be duplicated across embedders.\n *\n * Responsibilities (reproducing the Plan editor's behavior exactly):\n * - Track whether THIS client is the reconcile lead (sole client always leads;\n * otherwise elected via {@link isReconcileLeadClient}) and how many other\n * visible human peers are present.\n * - Seed an empty shared Y.Doc once from `value` — lead client only — so two\n * clients opening a brand-new block don't both insert the content.\n * - Reconcile authoritative external markdown (agent edit, source patch, peer\n * edit mirrored to SQL) into the editor: in collab mode only the lead client\n * applies it through `setContent` and Yjs propagates; in non-collab mode this\n * is the original controlled-value reconcile.\n * - Provide the `onUpdate` guards (`shouldIgnoreUpdate`, `registerEmitted`) so\n * the component never persists remote-origin or pre-seed empty content.\n */\n/** Default seed predicate: seed only when the shared doc is genuinely empty. */\nfunction defaultShouldSeed({\n currentMarkdown,\n fragmentLength,\n}: {\n value: string;\n currentMarkdown: string;\n fragmentLength: number;\n}): boolean {\n return fragmentLength === 0 || !currentMarkdown.trim();\n}\n\n/**\n * Default content writer: hand the raw markdown string to `setContent`, which\n * tiptap-markdown overrides to parse the markdown into a ProseMirror doc.\n *\n * IMPORTANT: do NOT pass `parseOptions: { preserveWhitespace: \"full\" }` here.\n * In tiptap v3 the core `setContent` command routes `preserveWhitespace: \"full\"`\n * through `insertContentAt`, which tiptap-markdown ALSO overrides to re-run its\n * markdown parser. That double-parse stringifies the already-parsed PM doc and\n * re-parses it as HTML, so a clean heading/list/code block comes back as the\n * escaped, non-idempotent `&lt;h1&gt;…` — which then escalates every reconcile\n * cycle (`<p>` → `&lt;p&gt;` → `&amp;lt;p&amp;gt;` …). Letting the markdown\n * override parse the string directly (no `parseOptions`) round-trips byte-stably\n * for the GFM corpus, including code-block and empty-line whitespace. Content's\n * NFM path supplies its own `setContent` (it passes a pre-parsed PM doc) and is\n * unaffected by this default.\n */\nfunction defaultSetContent(\n editor: Editor,\n value: string,\n options: { emitUpdate?: boolean; addToHistory?: boolean },\n): void {\n if (options.addToHistory === false) {\n editor\n .chain()\n .command(({ tr }) => {\n // addToHistory:false so cmd+z (or Yjs undo) doesn't erase\n // externally-loaded content.\n tr.setMeta(\"addToHistory\", false);\n return true;\n })\n .setContent(value, { emitUpdate: options.emitUpdate })\n .run();\n return;\n }\n editor.commands.setContent(value);\n}\n\nexport function useCollabReconcile({\n editor,\n ydoc = null,\n awareness = null,\n value,\n contentUpdatedAt,\n editable,\n getMarkdown = getEditorMarkdown,\n setContent = defaultSetContent,\n normalizeValue = (v) => v,\n shouldSeed = defaultShouldSeed,\n initialAppliedUpdatedAt,\n}: UseCollabReconcileOptions): UseCollabReconcileResult {\n const collab = !!ydoc;\n const isSettingContentRef = useRef(false);\n const lastEmittedRef = useRef(\"\");\n // Ring of recent local emissions (see pushEmittedRing). Lets the reconcile\n // recognize a stale-but-recent echo of our OWN (possibly partial, debounced)\n // save so a lagging poll never clobbers freshly-typed text.\n const recentEmittedRef = useRef<string[]>([]);\n const lastTypedAtRef = useRef(0);\n // The raw authoritative `value` string the reconcile last applied. When the\n // SAME raw string is re-fetched (a lagging poll, or a source-sync that keeps\n // re-supplying the same stored markdown), applying it again would only\n // reproduce the doc we already hold — and if `value` is NON-idempotent\n // (serialize(parse(value)) !== value) re-applying compounds the divergence\n // every cycle (`<p>` → `&lt;p&gt;` → `&amp;lt;p&amp;gt;` …). Tracked so the\n // identical re-fetch is recognized and skipped.\n const lastAppliedValueRef = useRef<string | null>(null);\n // The editor's SERIALIZED output captured right AFTER the last reconcile/seed\n // apply (`getMarkdown(editor)` once the content settled). For non-idempotent\n // input this is what autosave actually persists, so the NEXT poll hands it\n // back as the new `value`. Comparing the incoming value against this lets the\n // reconcile recognize its own echo even when the raw string changed once, so\n // it never re-parses content the editor already represents. This is the\n // doc-equivalence guard that breaks the escalation loop.\n const lastAppliedSerializedRef = useRef<string | null>(null);\n const lastAppliedUpdatedAtRef = useRef<string | null>(\n initialAppliedUpdatedAt !== undefined\n ? initialAppliedUpdatedAt\n : (contentUpdatedAt ?? null),\n );\n\n // Whether THIS client is the one that seeds the empty shared doc / applies an\n // authoritative external snapshot into it. Exactly one client does, so the\n // content isn't inserted once per open editor. A sole client always leads.\n const [isLeadClient, setIsLeadClient] = useState(true);\n // Count of OTHER visible human collaborators. When >0, a peer's edit also\n // arrives via Yjs, so external markdown reconcile must defer (avoid applying\n // the same change through both Yjs and setContent).\n const peerCountRef = useRef(0);\n useEffect(() => {\n if (!collab || !awareness || !ydoc) {\n setIsLeadClient(true);\n peerCountRef.current = 0;\n return;\n }\n const update = () => {\n setIsLeadClient(isReconcileLeadClient(awareness, ydoc.clientID));\n let peers = 0;\n awareness.getStates().forEach((state, clientId) => {\n if (clientId === ydoc.clientID) return; // self\n if (clientId === AGENT_CLIENT_ID) return; // agent isn't a Yjs editor\n const s = state as { user?: unknown; visible?: boolean };\n if (s && s.user && s.visible !== false) peers += 1;\n });\n peerCountRef.current = peers;\n };\n update();\n awareness.on(\"change\", update);\n document.addEventListener(\"visibilitychange\", update);\n return () => {\n awareness.off(\"change\", update);\n document.removeEventListener(\"visibilitychange\", update);\n };\n }, [collab, awareness, ydoc]);\n\n // Collab seed: populate an empty shared Y.Doc from the markdown `value` once.\n // The Collaboration extension does NOT auto-seed; only the lead client does,\n // so two clients opening a brand-new block at once don't both seed (which\n // would duplicate the content via concurrent inserts at the same position).\n const seededRef = useRef(false);\n useEffect(() => {\n if (!collab || !editor || editor.isDestroyed || !ydoc) return;\n if (seededRef.current) return;\n if (!isLeadClient) return;\n if (!value.trim()) return;\n const fragment = ydoc.getXmlFragment(\"default\");\n const currentMarkdown = getMarkdown(editor);\n // Seed only when the shared doc is genuinely empty — either the fragment has\n // no nodes yet, or it holds no semantic markdown (an empty paragraph, or an\n // app's sentinel-empty filler via a custom `shouldSeed`).\n if (\n shouldSeed({ value, currentMarkdown, fragmentLength: fragment.length })\n ) {\n isSettingContentRef.current = true;\n setContent(editor, value, {});\n isSettingContentRef.current = false;\n const serialized = getMarkdown(editor);\n lastEmittedRef.current = serialized;\n pushEmittedRing(recentEmittedRef.current, serialized);\n lastAppliedValueRef.current = value;\n lastAppliedSerializedRef.current = serialized;\n if (contentUpdatedAt) lastAppliedUpdatedAtRef.current = contentUpdatedAt;\n }\n seededRef.current = true;\n }, [\n collab,\n editor,\n ydoc,\n value,\n isLeadClient,\n contentUpdatedAt,\n getMarkdown,\n setContent,\n shouldSeed,\n ]);\n\n // Reconcile authoritative external markdown (agent edit, source patch, or a\n // peer edit mirrored to SQL) into the live editor. In collab mode only the\n // lead client applies it through setContent; Yjs propagates the result to\n // every other client. In non-collab mode this is the original controlled-value\n // reconcile, unchanged.\n useEffect(() => {\n if (!editor || editor.isDestroyed) return;\n\n let cancelled = false;\n let retry: ReturnType<typeof setTimeout> | null = null;\n // With peers present, a peer's edit also arrives via Yjs. Defer one poll\n // cycle (+margin) and re-check before applying via setContent so the same\n // change isn't inserted twice (Yjs + setContent → duplicated region).\n const PEER_SETTLE_MS = 2500;\n\n const apply = (deferred = false) => {\n if (cancelled || editor.isDestroyed) return;\n // In collab mode, defer all reconcile until the shared doc is seeded so we\n // never setContent over an unseeded fragment.\n if (collab && !seededRef.current) {\n retry = setTimeout(() => apply(deferred), 300);\n return;\n }\n const currentMarkdown = getMarkdown(editor);\n // Compare against the canonical form the editor would emit so a serializer\n // that re-normalizes (Content's NFM) still recognizes \"already in sync\".\n const normalizedValue = normalizeValue(value);\n // Whether the editor still holds exactly what THIS hook last applied (the\n // user hasn't edited since). Only then are the round-trip echo guards\n // below safe: if the user has since edited away from the applied content,\n // an external snapshot equal to a previously-applied value is a real\n // revert and must NOT be swallowed as an echo.\n const editorUnchangedSinceApply =\n lastAppliedSerializedRef.current !== null &&\n currentMarkdown === lastAppliedSerializedRef.current;\n\n // Doc-equivalence skip. Never re-apply content the editor already\n // represents — comparing by DOC EQUIVALENCE, not raw strings/timestamps:\n // 1. `currentMarkdown === normalizedValue` — the editor's CURRENT\n // serialized doc already equals the (normalized) incoming value.\n // 2. `value === lastEmittedRef.current` — the incoming value is our own\n // just-emitted markdown echoing back.\n // 3. `value === lastAppliedValueRef.current` — the SAME raw value we\n // already applied is being re-supplied (a lagging poll or a\n // source-sync re-handing the same stored markdown). Applying it again\n // would only reproduce the doc we hold; for NON-idempotent input it\n // would compound divergence. Guarded by `editorUnchangedSinceApply`\n // so a deliberate revert-to-previous after a local edit still lands.\n // 4. `normalizedValue === lastAppliedSerializedRef.current` — the\n // incoming value round-trips to the serialized output we last\n // produced (our own autosaved echo coming back from SQL). For\n // non-idempotent input the raw string differs from what we were\n // handed, but it is doc-equivalent to what the editor already shows,\n // so re-parsing it must be skipped. This is the guard that stops the\n // `<p>` → `&lt;p&gt;` → `&amp;lt;p&amp;gt;` escalation.\n if (\n currentMarkdown === normalizedValue ||\n value === lastEmittedRef.current ||\n // A stale-but-recent echo of our own (possibly partial) save — applying\n // it would clobber the freshly-typed tail. External edits never match.\n recentEmittedRef.current.includes(value) ||\n recentEmittedRef.current.includes(normalizedValue) ||\n (editorUnchangedSinceApply &&\n (value === lastAppliedValueRef.current ||\n normalizedValue === lastAppliedSerializedRef.current))\n ) {\n if (contentUpdatedAt) {\n lastAppliedUpdatedAtRef.current = contentUpdatedAt;\n }\n return;\n }\n\n const externalNewer =\n !lastAppliedUpdatedAtRef.current ||\n !contentUpdatedAt ||\n contentUpdatedAt > lastAppliedUpdatedAtRef.current;\n\n // Only the lead client applies an authoritative snapshot into the shared\n // Y.Doc; peers receive it through Yjs sync.\n if (collab && !isLeadClient) {\n if (contentUpdatedAt && !externalNewer) {\n lastAppliedUpdatedAtRef.current = contentUpdatedAt;\n }\n return;\n }\n\n // Never clobber an in-progress edit. While the user is actively typing\n // (focused and a keystroke landed within the window) defer and re-check —\n // applying external content now would yank text out from under them and,\n // for non-idempotent input, fight every keystroke. Newer external content\n // retries so it still lands once they pause; older-or-equal content is a\n // stale poll and is dropped outright while focused.\n const typingRecently =\n editor.isFocused && Date.now() - lastTypedAtRef.current < 1500;\n if (typingRecently) {\n if (externalNewer) {\n retry = setTimeout(() => apply(deferred), 700);\n }\n return;\n }\n if (!externalNewer && editor.isFocused) return;\n\n // Race guard: with peers present, let Yjs deliver a peer's edit first.\n // Defer once and re-check — a peer edit makes the equality check above\n // no-op next pass; an agent/source edit still differs and applies.\n if (collab && externalNewer && !deferred && peerCountRef.current > 0) {\n retry = setTimeout(() => apply(true), PEER_SETTLE_MS);\n return;\n }\n\n queueMicrotask(() => {\n if (cancelled || editor.isDestroyed) return;\n // Re-check doc-equivalence at apply time. Between the decision above and\n // this microtask a peer/Yjs edit (or our own prior apply) may have made\n // the editor already represent this value — re-applying would be a\n // wasted setContent that, for non-idempotent input, re-triggers the\n // loop. Skip when the editor's current serialization already matches the\n // normalized value, or the value round-trips to what we last produced.\n const beforeMarkdown = getMarkdown(editor);\n const normalized = normalizeValue(value);\n const unchangedSinceApply =\n lastAppliedSerializedRef.current !== null &&\n beforeMarkdown === lastAppliedSerializedRef.current;\n if (\n beforeMarkdown === normalized ||\n (unchangedSinceApply &&\n normalized === lastAppliedSerializedRef.current)\n ) {\n lastAppliedValueRef.current = value;\n if (contentUpdatedAt) {\n lastAppliedUpdatedAtRef.current = contentUpdatedAt;\n }\n return;\n }\n isSettingContentRef.current = true;\n setContent(editor, value, { emitUpdate: false, addToHistory: false });\n isSettingContentRef.current = false;\n // Capture the SERIALIZED result, not the raw value. For non-idempotent\n // input these differ; recording the serialized output is what lets the\n // next poll (which returns this serialized form) be recognized as our\n // own echo and skipped — stabilizing the doc after exactly one apply.\n const serialized = getMarkdown(editor);\n lastEmittedRef.current = serialized;\n pushEmittedRing(recentEmittedRef.current, serialized);\n lastAppliedValueRef.current = value;\n lastAppliedSerializedRef.current = serialized;\n if (contentUpdatedAt) {\n lastAppliedUpdatedAtRef.current = contentUpdatedAt;\n }\n });\n };\n\n apply();\n return () => {\n cancelled = true;\n if (retry) clearTimeout(retry);\n };\n }, [\n contentUpdatedAt,\n editor,\n value,\n collab,\n isLeadClient,\n getMarkdown,\n setContent,\n normalizeValue,\n ]);\n\n const shouldIgnoreUpdate = (transaction: Transaction): boolean => {\n if (!editable || isSettingContentRef.current) return true;\n // In collab mode, never persist remote-originated changes (the initial Yjs\n // state load or a peer's edit arriving via sync). Each client saves only its\n // OWN local edits; a peer's edit is saved by that peer. Without this, a\n // lagging Y.Doc load would write stale markdown over newer SQL.\n if (collab && transaction && isChangeOrigin(transaction)) return true;\n lastTypedAtRef.current = Date.now();\n return false;\n };\n\n const registerEmitted = (markdown: string): boolean => {\n // Don't persist an empty doc before Collaboration has seeded — that would\n // clobber the saved block content with an empty string.\n if (collab && !markdown.trim()) return false;\n lastEmittedRef.current = markdown;\n pushEmittedRing(recentEmittedRef.current, markdown);\n return true;\n };\n\n return {\n collab,\n isSettingContentRef,\n shouldIgnoreUpdate,\n registerEmitted,\n };\n}\n"]}
1
+ {"version":3,"file":"useCollabReconcile.js","sourceRoot":"","sources":["../../../src/client/rich-markdown-editor/useCollabReconcile.ts"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useRef, useState, type MutableRefObject } from \"react\";\nimport type { Editor } from \"@tiptap/react\";\nimport type { Transaction } from \"@tiptap/pm/state\";\nimport { isChangeOrigin } from \"@tiptap/extension-collaboration\";\nimport type { Doc as YDoc } from \"yjs\";\nimport type { Awareness } from \"y-protocols/awareness\";\nimport { isReconcileLeadClient } from \"../../collab/client.js\";\nimport { AGENT_CLIENT_ID } from \"../../collab/agent-identity.js\";\n\nexport const RICH_MARKDOWN_PROGRAMMATIC_TRANSACTION =\n \"an-rich-md-programmatic-transaction\";\n\n/** Reads the current markdown out of the tiptap-markdown storage. */\nexport function getEditorMarkdown(editor: Editor): string {\n const markdownStorage = editor.storage as unknown as {\n markdown?: { getMarkdown?: () => string };\n };\n return markdownStorage.markdown?.getMarkdown?.() ?? \"\";\n}\n\n/**\n * Push a value onto the bounded ring of recently-emitted markdown (most recent\n * last, deduped, capped). The reconcile uses this to recognize a stale-but-recent\n * echo of OUR OWN edits: a debounced autosave can persist a PARTIAL burst, and\n * the next poll re-supplies that partial value with a newer timestamp — applying\n * it would clobber the freshly-typed tail. An external change (agent/peer) never\n * byte-matches one of our own recent emissions, and if it somehow did the content\n * is identical, so skipping it is safe by construction.\n */\nconst EMITTED_RING_MAX = 16;\nfunction pushEmittedRing(ring: string[], value: string): void {\n if (!value) return;\n if (ring[ring.length - 1] === value) return;\n const dupe = ring.indexOf(value);\n if (dupe !== -1) ring.splice(dupe, 1);\n ring.push(value);\n if (ring.length > EMITTED_RING_MAX) ring.shift();\n}\n\nexport interface UseCollabReconcileOptions {\n /** The live editor, or null until it mounts. */\n editor: Editor | null;\n /** Shared Y.Doc when collaborating; null disables all collab paths. */\n ydoc?: YDoc | null;\n /** Shared awareness; null keeps the sole-client lead path. */\n awareness?: Awareness | null;\n /** Authoritative markdown value (SQL source of truth). */\n value: string;\n /** Timestamp of the authoritative value; gates newer-than reconcile. */\n contentUpdatedAt?: string | null;\n /** Whether the editor accepts edits. Reconcile/seed only run for the live editor. */\n editable: boolean;\n /**\n * Reads the current markdown from the editor. Injected so a dialect could\n * swap serializers; defaults to the tiptap-markdown storage reader. For an app\n * with a custom serializer (e.g. Content's `docToNfm(editor.getJSON())`), pass\n * it here so the seed/reconcile equality checks compare like-for-like.\n */\n getMarkdown?: (editor: Editor) => string;\n /**\n * Applies the authoritative `value` into the editor. Defaults to passing the\n * raw markdown string to `editor.commands.setContent`. Apps whose serializer\n * is NOT tiptap-markdown (Content parses `nfmToDoc(value)` into a PM doc)\n * override this so seed + reconcile write the correct content shape. The\n * supplied `options` carry the history/whitespace flags the default path uses;\n * a custom implementation should forward them when relevant.\n */\n setContent?: (\n editor: Editor,\n value: string,\n options: { emitUpdate?: boolean; addToHistory?: boolean },\n ) => void;\n /**\n * Normalizes the authoritative `value` to the canonical markdown the editor\n * would emit, so the \"already in sync / our own echo\" equality checks match a\n * serializer that re-canonicalizes (Content's `canonicalizeNfm`). Defaults to\n * identity (GFM already round-trips byte-stably).\n */\n normalizeValue?: (value: string) => string;\n /**\n * Decides whether the empty-doc seed should run for the current shared\n * fragment. Defaults to \"fragment has no nodes, or the editor holds no\n * semantic markdown\". Apps with sentinel-empty content (Content's\n * `<empty-block/>` filler) override this. Receives the live fragment length\n * and the editor's current markdown.\n */\n shouldSeed?: (info: {\n value: string;\n currentMarkdown: string;\n fragmentLength: number;\n }) => boolean;\n /**\n * The initial \"applied\" watermark. Default mirrors `contentUpdatedAt`, so a\n * fresh mount whose Y.Doc already matches SQL doesn't re-apply. Pass `null`\n * to force the first reconcile pass to adopt authoritative SQL even at the\n * same timestamp — Content does this so a stale persisted Y.Doc (an agent that\n * edited the CLOSED doc) is corrected on open. The editor is keyed per\n * document upstream, so this only affects the first mount of each doc.\n */\n initialAppliedUpdatedAt?: string | null;\n}\n\nexport interface UseCollabReconcileResult {\n /** True when a Y.Doc is bound (collaborative editing active). */\n collab: boolean;\n /**\n * Set true around any programmatic `setContent` so the editor's `onUpdate`\n * can ignore the resulting transaction (it isn't a user edit).\n */\n isSettingContentRef: MutableRefObject<boolean>;\n /**\n * Call from `onUpdate` BEFORE serializing. Returns true when the update must\n * be ignored: editor not editable, mid-programmatic-setContent, or (in collab\n * mode) a remote-origin transaction. Also records the local typing time.\n */\n shouldIgnoreUpdate: (transaction: Transaction) => boolean;\n /**\n * Call from `onUpdate` AFTER computing the markdown to emit. Returns false\n * when the value must NOT be persisted yet (an empty collab doc before the\n * seed has run); records it as the last-emitted value otherwise.\n */\n registerEmitted: (markdown: string) => boolean;\n}\n\n/**\n * The subtle seed / reconcile / lead-client logic for the shared markdown\n * editor, extracted once so it can never be duplicated across embedders.\n *\n * Responsibilities (reproducing the Plan editor's behavior exactly):\n * - Track whether THIS client is the reconcile lead (sole client always leads;\n * otherwise elected via {@link isReconcileLeadClient}) and how many other\n * visible human peers are present.\n * - Seed an empty shared Y.Doc once from `value` — lead client only — so two\n * clients opening a brand-new block don't both insert the content.\n * - Reconcile authoritative external markdown (agent edit, source patch, peer\n * edit mirrored to SQL) into the editor: in collab mode only the lead client\n * applies it through `setContent` and Yjs propagates; in non-collab mode this\n * is the original controlled-value reconcile.\n * - Provide the `onUpdate` guards (`shouldIgnoreUpdate`, `registerEmitted`) so\n * the component never persists remote-origin or pre-seed empty content.\n */\n/** Default seed predicate: seed only when the shared doc is genuinely empty. */\nfunction defaultShouldSeed({\n currentMarkdown,\n fragmentLength,\n}: {\n value: string;\n currentMarkdown: string;\n fragmentLength: number;\n}): boolean {\n return fragmentLength === 0 || !currentMarkdown.trim();\n}\n\n/**\n * Default content writer: hand the raw markdown string to `setContent`, which\n * tiptap-markdown overrides to parse the markdown into a ProseMirror doc.\n *\n * IMPORTANT: do NOT pass `parseOptions: { preserveWhitespace: \"full\" }` here.\n * In tiptap v3 the core `setContent` command routes `preserveWhitespace: \"full\"`\n * through `insertContentAt`, which tiptap-markdown ALSO overrides to re-run its\n * markdown parser. That double-parse stringifies the already-parsed PM doc and\n * re-parses it as HTML, so a clean heading/list/code block comes back as the\n * escaped, non-idempotent `&lt;h1&gt;…` — which then escalates every reconcile\n * cycle (`<p>` → `&lt;p&gt;` → `&amp;lt;p&amp;gt;` …). Letting the markdown\n * override parse the string directly (no `parseOptions`) round-trips byte-stably\n * for the GFM corpus, including code-block and empty-line whitespace. Content's\n * NFM path supplies its own `setContent` (it passes a pre-parsed PM doc) and is\n * unaffected by this default.\n */\nfunction defaultSetContent(\n editor: Editor,\n value: string,\n options: { emitUpdate?: boolean; addToHistory?: boolean },\n): void {\n if (options.addToHistory === false) {\n editor\n .chain()\n .command(({ tr }) => {\n // addToHistory:false so cmd+z (or Yjs undo) doesn't erase\n // externally-loaded content.\n tr.setMeta(\"addToHistory\", false);\n return true;\n })\n .setContent(value, { emitUpdate: options.emitUpdate })\n .run();\n return;\n }\n editor.commands.setContent(value);\n}\n\nexport function useCollabReconcile({\n editor,\n ydoc = null,\n awareness = null,\n value,\n contentUpdatedAt,\n editable,\n getMarkdown = getEditorMarkdown,\n setContent = defaultSetContent,\n normalizeValue = (v) => v,\n shouldSeed = defaultShouldSeed,\n initialAppliedUpdatedAt,\n}: UseCollabReconcileOptions): UseCollabReconcileResult {\n const collab = !!ydoc;\n const isSettingContentRef = useRef(false);\n const lastEmittedRef = useRef(\"\");\n // Ring of recent local emissions (see pushEmittedRing). Lets the reconcile\n // recognize a stale-but-recent echo of our OWN (possibly partial, debounced)\n // save so a lagging poll never clobbers freshly-typed text.\n const recentEmittedRef = useRef<string[]>([]);\n const lastTypedAtRef = useRef(0);\n // The raw authoritative `value` string the reconcile last applied. When the\n // SAME raw string is re-fetched (a lagging poll, or a source-sync that keeps\n // re-supplying the same stored markdown), applying it again would only\n // reproduce the doc we already hold — and if `value` is NON-idempotent\n // (serialize(parse(value)) !== value) re-applying compounds the divergence\n // every cycle (`<p>` → `&lt;p&gt;` → `&amp;lt;p&amp;gt;` …). Tracked so the\n // identical re-fetch is recognized and skipped.\n const lastAppliedValueRef = useRef<string | null>(null);\n // The editor's SERIALIZED output captured right AFTER the last reconcile/seed\n // apply (`getMarkdown(editor)` once the content settled). For non-idempotent\n // input this is what autosave actually persists, so the NEXT poll hands it\n // back as the new `value`. Comparing the incoming value against this lets the\n // reconcile recognize its own echo even when the raw string changed once, so\n // it never re-parses content the editor already represents. This is the\n // doc-equivalence guard that breaks the escalation loop.\n const lastAppliedSerializedRef = useRef<string | null>(null);\n const lastAppliedUpdatedAtRef = useRef<string | null>(\n initialAppliedUpdatedAt !== undefined\n ? initialAppliedUpdatedAt\n : (contentUpdatedAt ?? null),\n );\n\n // Whether THIS client is the one that seeds the empty shared doc / applies an\n // authoritative external snapshot into it. Exactly one client does, so the\n // content isn't inserted once per open editor. A sole client always leads.\n const [isLeadClient, setIsLeadClient] = useState(true);\n // Count of OTHER visible human collaborators. When >0, a peer's edit also\n // arrives via Yjs, so external markdown reconcile must defer (avoid applying\n // the same change through both Yjs and setContent).\n const peerCountRef = useRef(0);\n useEffect(() => {\n if (!collab || !awareness || !ydoc) {\n setIsLeadClient(true);\n peerCountRef.current = 0;\n return;\n }\n const update = () => {\n setIsLeadClient(isReconcileLeadClient(awareness, ydoc.clientID));\n let peers = 0;\n awareness.getStates().forEach((state, clientId) => {\n if (clientId === ydoc.clientID) return; // self\n if (clientId === AGENT_CLIENT_ID) return; // agent isn't a Yjs editor\n const s = state as { user?: unknown; visible?: boolean };\n if (s && s.user && s.visible !== false) peers += 1;\n });\n peerCountRef.current = peers;\n };\n update();\n awareness.on(\"change\", update);\n document.addEventListener(\"visibilitychange\", update);\n return () => {\n awareness.off(\"change\", update);\n document.removeEventListener(\"visibilitychange\", update);\n };\n }, [collab, awareness, ydoc]);\n\n // Collab seed: populate an empty shared Y.Doc from the markdown `value` once.\n // The Collaboration extension does NOT auto-seed; only the lead client does,\n // so two clients opening a brand-new block at once don't both seed (which\n // would duplicate the content via concurrent inserts at the same position).\n const seededRef = useRef(false);\n useEffect(() => {\n if (!collab || !editor || editor.isDestroyed || !ydoc) return;\n if (seededRef.current) return;\n if (!isLeadClient) return;\n if (!value.trim()) return;\n const fragment = ydoc.getXmlFragment(\"default\");\n const currentMarkdown = getMarkdown(editor);\n // Seed only when the shared doc is genuinely empty — either the fragment has\n // no nodes yet, or it holds no semantic markdown (an empty paragraph, or an\n // app's sentinel-empty filler via a custom `shouldSeed`).\n if (\n shouldSeed({ value, currentMarkdown, fragmentLength: fragment.length })\n ) {\n isSettingContentRef.current = true;\n setContent(editor, value, {});\n isSettingContentRef.current = false;\n const serialized = getMarkdown(editor);\n lastEmittedRef.current = serialized;\n pushEmittedRing(recentEmittedRef.current, serialized);\n lastAppliedValueRef.current = value;\n lastAppliedSerializedRef.current = serialized;\n if (contentUpdatedAt) lastAppliedUpdatedAtRef.current = contentUpdatedAt;\n }\n seededRef.current = true;\n }, [\n collab,\n editor,\n ydoc,\n value,\n isLeadClient,\n contentUpdatedAt,\n getMarkdown,\n setContent,\n shouldSeed,\n ]);\n\n // Reconcile authoritative external markdown (agent edit, source patch, or a\n // peer edit mirrored to SQL) into the live editor. In collab mode only the\n // lead client applies it through setContent; Yjs propagates the result to\n // every other client. In non-collab mode this is the original controlled-value\n // reconcile, unchanged.\n useEffect(() => {\n if (!editor || editor.isDestroyed) return;\n\n let cancelled = false;\n let retry: ReturnType<typeof setTimeout> | null = null;\n // With peers present, a peer's edit also arrives via Yjs. Defer one poll\n // cycle (+margin) and re-check before applying via setContent so the same\n // change isn't inserted twice (Yjs + setContent → duplicated region).\n const PEER_SETTLE_MS = 2500;\n\n const apply = (deferred = false) => {\n if (cancelled || editor.isDestroyed) return;\n // In collab mode, defer all reconcile until the shared doc is seeded so we\n // never setContent over an unseeded fragment.\n if (collab && !seededRef.current) {\n retry = setTimeout(() => apply(deferred), 300);\n return;\n }\n const currentMarkdown = getMarkdown(editor);\n // Compare against the canonical form the editor would emit so a serializer\n // that re-normalizes (Content's NFM) still recognizes \"already in sync\".\n const normalizedValue = normalizeValue(value);\n // Whether the editor still holds exactly what THIS hook last applied (the\n // user hasn't edited since). Only then are the round-trip echo guards\n // below safe: if the user has since edited away from the applied content,\n // an external snapshot equal to a previously-applied value is a real\n // revert and must NOT be swallowed as an echo.\n const editorUnchangedSinceApply =\n lastAppliedSerializedRef.current !== null &&\n currentMarkdown === lastAppliedSerializedRef.current;\n\n // Doc-equivalence skip. Never re-apply content the editor already\n // represents — comparing by DOC EQUIVALENCE, not raw strings/timestamps:\n // 1. `currentMarkdown === normalizedValue` — the editor's CURRENT\n // serialized doc already equals the (normalized) incoming value.\n // 2. `value === lastEmittedRef.current` — the incoming value is our own\n // just-emitted markdown echoing back.\n // 3. `value === lastAppliedValueRef.current` — the SAME raw value we\n // already applied is being re-supplied (a lagging poll or a\n // source-sync re-handing the same stored markdown). Applying it again\n // would only reproduce the doc we hold; for NON-idempotent input it\n // would compound divergence. Guarded by `editorUnchangedSinceApply`\n // so a deliberate revert-to-previous after a local edit still lands.\n // 4. `normalizedValue === lastAppliedSerializedRef.current` — the\n // incoming value round-trips to the serialized output we last\n // produced (our own autosaved echo coming back from SQL). For\n // non-idempotent input the raw string differs from what we were\n // handed, but it is doc-equivalent to what the editor already shows,\n // so re-parsing it must be skipped. This is the guard that stops the\n // `<p>` → `&lt;p&gt;` → `&amp;lt;p&amp;gt;` escalation.\n if (\n currentMarkdown === normalizedValue ||\n value === lastEmittedRef.current ||\n // A stale-but-recent echo of our own (possibly partial) save — applying\n // it would clobber the freshly-typed tail. External edits never match.\n recentEmittedRef.current.includes(value) ||\n recentEmittedRef.current.includes(normalizedValue) ||\n (editorUnchangedSinceApply &&\n (value === lastAppliedValueRef.current ||\n normalizedValue === lastAppliedSerializedRef.current))\n ) {\n if (contentUpdatedAt) {\n lastAppliedUpdatedAtRef.current = contentUpdatedAt;\n }\n return;\n }\n\n const externalNewer =\n !lastAppliedUpdatedAtRef.current ||\n !contentUpdatedAt ||\n contentUpdatedAt > lastAppliedUpdatedAtRef.current;\n\n // Only the lead client applies an authoritative snapshot into the shared\n // Y.Doc; peers receive it through Yjs sync.\n if (collab && !isLeadClient) {\n if (contentUpdatedAt && !externalNewer) {\n lastAppliedUpdatedAtRef.current = contentUpdatedAt;\n }\n return;\n }\n\n // Never clobber an in-progress edit. While the user is actively typing\n // (focused and a keystroke landed within the window) defer and re-check —\n // applying external content now would yank text out from under them and,\n // for non-idempotent input, fight every keystroke. Newer external content\n // retries so it still lands once they pause; older-or-equal content is a\n // stale poll and is dropped outright while focused.\n const typingRecently =\n editor.isFocused && Date.now() - lastTypedAtRef.current < 1500;\n if (typingRecently) {\n if (externalNewer) {\n retry = setTimeout(() => apply(deferred), 700);\n }\n return;\n }\n if (!externalNewer && editor.isFocused) return;\n\n // Race guard: with peers present, let Yjs deliver a peer's edit first.\n // Defer once and re-check — a peer edit makes the equality check above\n // no-op next pass; an agent/source edit still differs and applies.\n if (collab && externalNewer && !deferred && peerCountRef.current > 0) {\n retry = setTimeout(() => apply(true), PEER_SETTLE_MS);\n return;\n }\n\n queueMicrotask(() => {\n if (cancelled || editor.isDestroyed) return;\n // Re-check doc-equivalence at apply time. Between the decision above and\n // this microtask a peer/Yjs edit (or our own prior apply) may have made\n // the editor already represent this value — re-applying would be a\n // wasted setContent that, for non-idempotent input, re-triggers the\n // loop. Skip when the editor's current serialization already matches the\n // normalized value, or the value round-trips to what we last produced.\n const beforeMarkdown = getMarkdown(editor);\n const normalized = normalizeValue(value);\n const unchangedSinceApply =\n lastAppliedSerializedRef.current !== null &&\n beforeMarkdown === lastAppliedSerializedRef.current;\n if (\n beforeMarkdown === normalized ||\n (unchangedSinceApply &&\n normalized === lastAppliedSerializedRef.current)\n ) {\n lastAppliedValueRef.current = value;\n if (contentUpdatedAt) {\n lastAppliedUpdatedAtRef.current = contentUpdatedAt;\n }\n return;\n }\n isSettingContentRef.current = true;\n setContent(editor, value, { emitUpdate: false, addToHistory: false });\n isSettingContentRef.current = false;\n // Capture the SERIALIZED result, not the raw value. For non-idempotent\n // input these differ; recording the serialized output is what lets the\n // next poll (which returns this serialized form) be recognized as our\n // own echo and skipped — stabilizing the doc after exactly one apply.\n const serialized = getMarkdown(editor);\n lastEmittedRef.current = serialized;\n pushEmittedRing(recentEmittedRef.current, serialized);\n lastAppliedValueRef.current = value;\n lastAppliedSerializedRef.current = serialized;\n if (contentUpdatedAt) {\n lastAppliedUpdatedAtRef.current = contentUpdatedAt;\n }\n });\n };\n\n apply();\n return () => {\n cancelled = true;\n if (retry) clearTimeout(retry);\n };\n }, [\n contentUpdatedAt,\n editor,\n value,\n collab,\n isLeadClient,\n getMarkdown,\n setContent,\n normalizeValue,\n ]);\n\n const shouldIgnoreUpdate = (transaction: Transaction): boolean => {\n if (!editable || isSettingContentRef.current) return true;\n if (transaction.getMeta(RICH_MARKDOWN_PROGRAMMATIC_TRANSACTION)) {\n return true;\n }\n // In collab mode, never persist remote-originated changes (the initial Yjs\n // state load or a peer's edit arriving via sync). Each client saves only its\n // OWN local edits; a peer's edit is saved by that peer. Without this, a\n // lagging Y.Doc load would write stale markdown over newer SQL.\n if (collab && transaction && isChangeOrigin(transaction)) return true;\n lastTypedAtRef.current = Date.now();\n return false;\n };\n\n const registerEmitted = (markdown: string): boolean => {\n // Don't persist an empty doc before Collaboration has seeded — that would\n // clobber the saved block content with an empty string.\n if (collab && !markdown.trim()) return false;\n lastEmittedRef.current = markdown;\n pushEmittedRing(recentEmittedRef.current, markdown);\n return true;\n };\n\n return {\n collab,\n isSettingContentRef,\n shouldIgnoreUpdate,\n registerEmitted,\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/db/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/db/client.ts"],"names":[],"mappings":"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"}
package/dist/db/client.js CHANGED
@@ -538,7 +538,23 @@ async function createDbExecInternal(config = {}, trackSingletonResources = false
538
538
  const args = typeof sql === "string" ? [] : sql.args || [];
539
539
  const pgSql = sqliteToPostgresParams(rawSql);
540
540
  const result = await retryOnConnectionError(async () => {
541
- const client = await pool.connect();
541
+ // Bound the pooled-connection ACQUIRE, not just the query below.
542
+ // Neon's pooler can stall on `connect()` when cold or exhausted,
543
+ // and that happens BEFORE `client.query`, so the query-level
544
+ // timeout never fires — the request hangs until the platform kills
545
+ // the function (~"the site won't load" for authenticated users,
546
+ // whose every request runs a session/org lookup). Time the acquire
547
+ // out into a retryable CONNECT_TIMEOUT that retryOnConnectionError
548
+ // already handles, and release the connection if it resolves after
549
+ // we've given up so the scarce pool slot isn't leaked.
550
+ let acquireTimedOut = false;
551
+ const client = await withDbTimeout("connect", () => pool.connect().then((c) => {
552
+ if (acquireTimedOut)
553
+ c.release();
554
+ return c;
555
+ }), dbOpTimeoutMs(), () => {
556
+ acquireTimedOut = true;
557
+ });
542
558
  let released = false;
543
559
  const releaseClient = (err) => {
544
560
  if (released)
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/db/client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Central database client abstraction.\n *\n * Detects the database backend from the environment (D1, Postgres, or SQLite/libsql)\n * and returns a unified `DbExec` interface that all core stores use.\n *\n * Imports for postgres, better-sqlite3, and @libsql/client/web are lazy\n * (dynamic import) so this module can be loaded in any runtime (Node.js,\n * Cloudflare Workers, edge) without failing on missing native deps.\n */\nimport path from \"path\";\n\nconst recyclingPostgresPools = new WeakSet<object>();\nconst loggedNeonPools = new WeakSet<object>();\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type Dialect = \"sqlite\" | \"postgres\" | \"d1\";\n\nexport interface DbExec {\n execute(\n sql: string | { sql: string; args: any[] },\n ): Promise<{ rows: any[]; rowsAffected: number }>;\n}\n\nexport interface DbExecConfig {\n url?: string;\n authToken?: string;\n d1Binding?: any;\n}\n\n// ---------------------------------------------------------------------------\n// Per-app DATABASE_URL resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the database URL for the current app.\n *\n * Checks for `<APP_NAME>_DATABASE_URL` first (e.g. `MAIL_DATABASE_URL`),\n * then falls back to `DATABASE_URL`. This allows multiple apps to run in the\n * same process group (e.g. eager repo dev or builder.io) with separate databases.\n *\n * Set `APP_NAME=mail` in the child process env and\n * `MAIL_DATABASE_URL=postgres://...` in the shared env.\n */\nexport function getDatabaseUrl(fallback = \"\"): string {\n const appName = process.env.APP_NAME?.toUpperCase().replace(/-/g, \"_\");\n if (appName) {\n const prefixed = process.env[`${appName}_DATABASE_URL`];\n if (prefixed) return prefixed;\n }\n return process.env.DATABASE_URL || fallback;\n}\n\n/** Same per-app resolution for DATABASE_AUTH_TOKEN (used by Turso/libsql). */\nexport function getDatabaseAuthToken(): string | undefined {\n const appName = process.env.APP_NAME?.toUpperCase().replace(/-/g, \"_\");\n if (appName) {\n const prefixed = process.env[`${appName}_DATABASE_AUTH_TOKEN`];\n if (prefixed) return prefixed;\n }\n return process.env.DATABASE_AUTH_TOKEN;\n}\n\nexport function isLocalSqliteUrl(url: string): boolean {\n return url === \"\" || url.startsWith(\"file:\") || !url.includes(\"://\");\n}\n\nexport async function prepareLocalSqliteUrl(url: string): Promise<string> {\n if (!url.startsWith(\"file:\")) return url;\n\n // On serverless runtimes (Netlify / Vercel / AWS Lambda / CF Pages) the\n // working directory is read-only. Detect this and redirect local SQLite to\n // /tmp which IS writable (ephemeral per invocation, but the server stays\n // alive for the request). Shares the canonical isServerlessRuntime() check.\n const isServerless = isServerlessRuntime();\n try {\n const fs = await import(\"fs\");\n if (isServerless && url === \"file:./data/app.db\") {\n fs.mkdirSync(\"/tmp/data\", { recursive: true });\n return \"file:///tmp/data/app.db\";\n }\n fs.mkdirSync(path.join(process.cwd(), \"data\"), { recursive: true });\n } catch {\n // Edge runtime — no filesystem.\n }\n return url;\n}\n\nexport function sqliteFilenameFromUrl(url: string): string {\n if (url.startsWith(\"file://\")) {\n return decodeURIComponent(new URL(url).pathname);\n }\n if (url.startsWith(\"file:\")) {\n return url.slice(\"file:\".length) || \":memory:\";\n }\n return url || \"./data/app.db\";\n}\n\n// ---------------------------------------------------------------------------\n// Safe JSON column parsing\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a JSON-serialized column value defensively. A malformed row — from a\n * hand-edit, dirty migration, or a misbehaving agent that wrote raw SQL —\n * must not break an entire list endpoint. Callers supply a fallback for the\n * malformed path; null/undefined values also fall back.\n */\nexport function safeJsonParse<T>(value: unknown, fallback: T): T {\n if (value == null) return fallback;\n try {\n return JSON.parse(String(value)) as T;\n } catch {\n return fallback;\n }\n}\n\n// ---------------------------------------------------------------------------\n// SQLite retry helper\n// ---------------------------------------------------------------------------\n\n/**\n * Retry an async operation when it fails with SQLITE_BUSY.\n * Used during WAL initialization and migrations where a stale WAL from a\n * previous crash or HMR restart can briefly lock the database.\n */\nexport async function retrySqliteBusy<T>(\n fn: () => Promise<T>,\n opts: { maxAttempts?: number; baseDelayMs?: number; rethrow?: boolean } = {},\n): Promise<T> {\n const { maxAttempts = 5, baseDelayMs = 500, rethrow = false } = opts;\n let last: unknown;\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n return await fn();\n } catch (e: any) {\n last = e;\n const msg = String(e?.message || e);\n if (msg.includes(\"SQLITE_BUSY\") && attempt < maxAttempts - 1) {\n await new Promise((r) => setTimeout(r, baseDelayMs * (attempt + 1)));\n } else {\n break;\n }\n }\n }\n if (rethrow) throw last;\n return undefined as unknown as T; // caller handles undefined (e.g. PRAGMA setup)\n}\n\n/**\n * Retry a DDL statement (CREATE TABLE, CREATE INDEX) once when it fails due\n * to a Postgres pg_catalog race.\n *\n * Postgres's `IF NOT EXISTS` check is NOT atomic with the `pg_type` /\n * `pg_class` catalog insert. When multiple processes boot concurrently and\n * issue the same CREATE, both can pass the existence check and one fails\n * with code 23505 on `pg_type_typname_nsp_index` or similar. The table does\n * end up created by the winner, so rerunning the same `IF NOT EXISTS`\n * statement is a safe no-op.\n */\nexport async function retryOnDdlRace<T>(fn: () => Promise<T>): Promise<T> {\n try {\n return await fn();\n } catch (e: any) {\n if (!isPgCatalogRace(e)) throw e;\n return await fn();\n }\n}\n\nfunction isPgCatalogRace(e: any): boolean {\n if (e?.code === \"42P07\") return true;\n if (e?.code !== \"23505\") return false;\n const constraint = String(e?.constraint_name ?? e?.constraint ?? \"\");\n const detail = String(e?.detail ?? \"\");\n const msg = String(e?.message ?? \"\");\n return (\n constraint.startsWith(\"pg_type\") ||\n constraint.startsWith(\"pg_class\") ||\n detail.includes(\"pg_type\") ||\n detail.includes(\"pg_class\") ||\n /relation .* already exists/i.test(msg)\n );\n}\n\n/**\n * True when `e` is a UNIQUE / PRIMARY KEY constraint violation from any\n * supported driver (Postgres 23505, SQLite SQLITE_CONSTRAINT_PRIMARYKEY /\n * _UNIQUE, D1). Used by stores that accept caller-provided ids and want to\n * surface a clean \"already exists\" error instead of the raw SQL text.\n */\nexport function isUniqueViolation(e: any): boolean {\n if (e?.code === \"23505\") return true;\n const code = String(e?.code ?? \"\");\n if (\n code === \"SQLITE_CONSTRAINT_PRIMARYKEY\" ||\n code === \"SQLITE_CONSTRAINT_UNIQUE\"\n ) {\n return true;\n }\n const msg = String(e?.message ?? \"\").toLowerCase();\n return (\n msg.includes(\"unique constraint\") ||\n msg.includes(\"primary key constraint\") ||\n msg.includes(\"duplicate key\")\n );\n}\n\n// ---------------------------------------------------------------------------\n// Dialect detection\n// ---------------------------------------------------------------------------\n\nlet _dialect: Dialect | undefined;\n\nexport function getDialect(): Dialect {\n if (_dialect !== undefined) return _dialect;\n\n // DATABASE_URL takes priority over D1 when set.\n const url = getDatabaseUrl();\n if (url.startsWith(\"postgres://\") || url.startsWith(\"postgresql://\")) {\n _dialect = \"postgres\";\n return _dialect;\n }\n if (url && !url.startsWith(\"file:\")) {\n // Remote libsql (e.g. Turso)\n _dialect = \"sqlite\";\n return _dialect;\n }\n\n const d1 = globalThis.__cf_env?.DB;\n if (d1) {\n _dialect = \"d1\";\n return _dialect;\n }\n\n // Don't cache the fallthrough — on CF Workers, env bindings (__cf_env) aren't\n // available at import time. If we cache \"sqlite\" here, D1 will never be\n // detected once the bindings are set in the fetch handler.\n return \"sqlite\";\n}\n\nexport function isPostgres(): boolean {\n return getDialect() === \"postgres\";\n}\n\nfunction dialectForConfig(config: DbExecConfig): Dialect {\n const url = config.url ?? \"\";\n if (url.startsWith(\"postgres://\") || url.startsWith(\"postgresql://\")) {\n return \"postgres\";\n }\n if (url && !url.startsWith(\"file:\")) {\n return \"sqlite\";\n }\n if (config.d1Binding) {\n return \"d1\";\n }\n return \"sqlite\";\n}\n\n/**\n * Returns true when the database is a local-only SQLite file (or unset, which\n * defaults to a local SQLite file). Returns false for Postgres, remote libsql\n * (Turso), and D1 — any backend that could be shared across developers.\n *\n * Used to gate local@localhost mode: that mode uses a single shared virtual\n * user with no per-machine scoping, so on any shared database two developers\n * would read and write each other's settings, oauth tokens, and app state.\n */\nexport function isLocalDatabase(): boolean {\n if (getDialect() !== \"sqlite\") return false;\n const url = getDatabaseUrl();\n return url === \"\" || url.startsWith(\"file:\");\n}\n\n/** Returns BIGINT for Postgres (64-bit), INTEGER for SQLite (already 64-bit). */\nexport function intType(): string {\n return isPostgres() ? \"BIGINT\" : \"INTEGER\";\n}\n\n// ---------------------------------------------------------------------------\n// Parameter conversion: ? -> $1, $2, $3\n// ---------------------------------------------------------------------------\n\nfunction sqliteToPostgresParams(sql: string): string {\n let i = 0;\n return sql.replace(/\\?/g, () => `$${++i}`);\n}\n\n// ---------------------------------------------------------------------------\n// Connection error retry (ECONNRESET, etc.)\n// ---------------------------------------------------------------------------\n\n/** Error codes that indicate a dead/stale connection we can safely retry. */\nconst CONNECTION_ERROR_CODES = new Set([\n \"ECONNRESET\",\n \"ETIMEDOUT\",\n \"EPIPE\",\n \"ENOTFOUND\",\n \"CONNECT_TIMEOUT\",\n \"CONNECTION_ENDED\",\n \"CONNECTION_DESTROYED\",\n \"CONNECTION_CLOSED\",\n]);\n\nexport function isConnectionError(err: any): boolean {\n if (!err) return false;\n const code = err.code || err.cause?.code;\n if (code && CONNECTION_ERROR_CODES.has(code)) return true;\n // Neon serverless WS driver: errors from the underlying undici WebSocket\n // closing mid-query come through as TypeError or ErrorEvent without a code.\n const name = err.name || err.cause?.name || \"\";\n if (name === \"ErrorEvent\") return true;\n const stack = String(err.stack || err.cause?.stack || \"\");\n if (\n /WebSocket\\.#onSocketClose|failWebsocketConnection|onSocketClose/.test(\n stack,\n )\n ) {\n return true;\n }\n const msg = String(err.message || err.cause?.message || \"\");\n return /ECONNRESET|ETIMEDOUT|EPIPE|connection.*(closed|ended|terminated)|socket hang up|websocket/i.test(\n msg,\n );\n}\n\nexport async function retryOnConnectionError<T>(\n fn: () => Promise<T>,\n maxAttempts = 3,\n): Promise<T> {\n let last: unknown;\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n return await fn();\n } catch (e) {\n last = e;\n if (!isConnectionError(e) || attempt === maxAttempts - 1) throw e;\n await new Promise((r) => setTimeout(r, 100 * (attempt + 1)));\n }\n }\n throw last;\n}\n\n// ---------------------------------------------------------------------------\n// Per-op timeout — converts a silent serverless hang into a retryable error\n// ---------------------------------------------------------------------------\n\n/**\n * Max wall time for a single DB op (init or query) before we treat it as a\n * dead connection. A frozen→thawed serverless instance can leave the Neon\n * WebSocket (or a postgres.js socket) hung mid-flight: the promise neither\n * settles nor errors, so retryOnConnectionError() — which only retries thrown\n * errors — can't help and the request hangs until the platform kills the\n * function (~30s on Netlify). For authenticated requests that run a session\n * lookup on every navigation this surfaces as \"the site won't load\". Bounding\n * each op well under the platform function limit turns the silent hang into a\n * CONNECT_TIMEOUT that the existing retry and reject-reset paths already\n * handle. Override with DB_OP_TIMEOUT_MS.\n */\nexport function dbOpTimeoutMs(): number {\n const raw = Number(process.env.DB_OP_TIMEOUT_MS);\n if (Number.isFinite(raw) && raw > 0) return raw;\n return isServerlessRuntime() ? 8_000 : 30_000;\n}\n\n/**\n * Timeout error tagged with a recognized connection-error code so\n * isConnectionError() / retryOnConnectionError() treat a hung op as a\n * retryable dead connection, and upstream reject-reset guards (e.g. the\n * cached session-table init promise) clear their poisoned state.\n */\nclass DbTimeoutError extends Error {\n code = \"CONNECT_TIMEOUT\";\n constructor(op: string, ms: number) {\n super(`DB ${op} timed out after ${ms}ms (connection terminated)`);\n this.name = \"DbTimeoutError\";\n }\n}\n\n/**\n * Race a DB op against {@link dbOpTimeoutMs}. Callers that own a cancellable\n * query or pooled client should pass onTimeout so the losing operation does\n * not keep occupying a scarce connection slot after the request has recovered.\n */\nexport async function withDbTimeout<T>(\n op: string,\n run: () => Promise<T>,\n ms = dbOpTimeoutMs(),\n onTimeout?: () => void | Promise<void>,\n): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n let settled = false;\n\n const runCleanup = async () => {\n if (!onTimeout) return;\n try {\n await onTimeout();\n } catch (err) {\n console.warn(\n `[db] timeout cleanup for ${op} failed:`,\n err instanceof Error ? err.message : err,\n );\n }\n };\n\n return await new Promise<T>((resolve, reject) => {\n const finish = (\n complete: (value: T | PromiseLike<T>) => void,\n value: T | PromiseLike<T>,\n ) => {\n if (settled) return;\n settled = true;\n if (timer) clearTimeout(timer);\n complete(value);\n };\n const fail = (err: unknown) => {\n if (settled) return;\n settled = true;\n if (timer) clearTimeout(timer);\n reject(err);\n };\n\n timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n void (async () => {\n await runCleanup();\n reject(new DbTimeoutError(op, ms));\n })();\n }, ms);\n\n let promise: Promise<T>;\n try {\n promise = run();\n } catch (err) {\n fail(err);\n return;\n }\n promise.then((value) => finish(resolve, value), fail);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Serverless-aware Postgres pool options\n// ---------------------------------------------------------------------------\n\n/**\n * True on serverless function runtimes (Netlify / Vercel / AWS Lambda /\n * Cloudflare Pages Functions) where every concurrent request can spin up its\n * own frozen process. Connections cannot be shared across instances, so each\n * instance must keep its pool tiny — otherwise dozens of warm instances each\n * holding postgres.js's default 10-connection pool blow past Neon/Postgres'\n * connection cap and every `/_agent-native/*` route 500s with \"Max client\n * connections reached\".\n */\nexport function isServerlessRuntime(): boolean {\n return (\n !!process.env.NETLIFY ||\n !!process.env.VERCEL ||\n !!process.env.AWS_LAMBDA_FUNCTION_NAME ||\n !!process.env.LAMBDA_TASK_ROOT ||\n !!process.env.CF_PAGES\n );\n}\n\n/**\n * postgres.js pool options tuned per runtime. A serverless instance handles\n * one request at a time, so a tiny pool is enough — but we cap at 2 (not 1)\n * so a single slow query or open transaction can't serialize every other\n * query in the same request. Total connections stay bounded to ≈ 2×\n * concurrent-instance count instead of 10×. idle_timeout is shortened on\n * serverless so a thawed-but-idle instance releases its connections quickly.\n * Long-lived Node servers keep the normal pool for throughput.\n */\nexport function pgPoolOptions(url: string): Record<string, unknown> {\n const serverless = isServerlessRuntime();\n return {\n onnotice: () => {},\n max: serverless ? 2 : 10,\n idle_timeout: serverless ? 20 : 240,\n max_lifetime: 60 * 30,\n connect_timeout: 10,\n // Supabase's connection pooler (Transaction mode) requires prepare:false.\n // Only disable for Supabase URLs to avoid degrading other deployments.\n ...(url.includes(\"supabase\") ? { prepare: false } : {}),\n };\n}\n\n/**\n * Connection cap for the @neondatabase/serverless `Pool`. Same instance\n * accumulation risk as postgres.js — a small pool (2) is enough on serverless\n * and keeps total connections bounded while still letting a second query\n * proceed when one connection is busy.\n */\nexport function neonPoolMax(): number {\n return isServerlessRuntime() ? 2 : 10;\n}\n\nexport function attachNeonPoolErrorLogger(\n pool: unknown,\n label = \"db/neon\",\n): void {\n if (!pool || typeof pool !== \"object\") return;\n if (loggedNeonPools.has(pool)) return;\n const withEvents = pool as {\n on?: (event: string, listener: (...args: unknown[]) => void) => unknown;\n };\n if (typeof withEvents.on !== \"function\") return;\n\n loggedNeonPools.add(pool);\n withEvents.on(\"error\", (err: unknown) => {\n console.warn(\n `[${label}] pool error (will reconnect on next query):`,\n err instanceof Error ? err.message : err,\n );\n });\n\n // Attach a persistent 'error' listener to EVERY client for its whole lifetime.\n //\n // @neondatabase/serverless mirrors pg-pool, which only keeps its own idle\n // error listener on a client while that client is idle — it REMOVES the\n // listener the moment the client is checked out. So when a checked-out\n // client's WebSocket drops mid-flight (Lambda freeze/thaw, Neon \"terminating\n // connection due to administrator command\", an idle socket the pooler closed),\n // the client emits 'error' with no listener. Node turns an unhandled 'error'\n // EventEmitter event into an uncaught exception, which crashes the whole\n // serverless function. This was by far the single highest-volume production\n // crash (Sentry \"Unhandled error. ()\", mechanism auto.node.onuncaughtexception,\n // culprit neondatabase__serverless). pg routes the failure to the in-flight\n // query independently, so this listener only needs to keep the emit from going\n // unhandled — the dropped client is discarded and the next query reconnects.\n withEvents.on(\"connect\", (client: unknown) => {\n if (!client || typeof client !== \"object\") return;\n const clientEvents = client as {\n on?: (event: string, listener: (...args: unknown[]) => void) => unknown;\n };\n if (typeof clientEvents.on !== \"function\") return;\n clientEvents.on(\"error\", (err: unknown) => {\n console.warn(\n `[${label}] client connection error (connection discarded, next query reconnects):`,\n err instanceof Error ? err.message : err,\n );\n });\n });\n}\n\nfunction disposePostgresPoolEventually(\n pool: { end: () => Promise<unknown> },\n label: string,\n): void {\n if (!pool || typeof pool !== \"object\") return;\n if (recyclingPostgresPools.has(pool)) return;\n recyclingPostgresPools.add(pool);\n void pool.end().catch((err: unknown) => {\n console.warn(\n `[db/postgres] ${label} cleanup failed:`,\n err instanceof Error ? err.message : err,\n );\n });\n}\n\n// ---------------------------------------------------------------------------\n// Singleton client — lazy-initialized on first execute() call\n// ---------------------------------------------------------------------------\n\nlet _exec: DbExec | undefined;\nlet _pgPool: any;\nlet _neonPool: any;\nlet _sqlite: any;\nlet _initPromise: Promise<void> | undefined;\n\nasync function createDbExecInternal(\n config: DbExecConfig = {},\n trackSingletonResources = false,\n): Promise<DbExec> {\n const dialect = dialectForConfig(config);\n\n // Cloudflare D1\n if (dialect === \"d1\") {\n const d1 = config.d1Binding;\n return {\n async execute(sql) {\n if (typeof sql === \"string\") {\n const r = await d1.prepare(sql).all();\n return {\n rows: r.results || [],\n rowsAffected: r.meta?.changes ?? 0,\n };\n }\n const r = await d1\n .prepare(sql.sql)\n .bind(...sql.args)\n .all();\n return { rows: r.results || [], rowsAffected: r.meta?.changes ?? 0 };\n },\n };\n }\n\n let url = config.url || \"file:./data/app.db\";\n\n // Postgres — uses postgres.js. Works on Node.js natively and on Cloudflare\n // Workers with the nodejs_compat compatibility flag (provides net/tls polyfills).\n // On Workers, connections can't be shared across requests, so we create a\n // fresh connection per query (max:1) to avoid the \"I/O on behalf of a\n // different request\" error.\n if (dialect === \"postgres\") {\n const { isNeonUrl } = await import(\"./create-get-db.js\");\n\n // Neon over @neondatabase/serverless (WebSocket upgrade on port 443).\n // postgres-js uses a raw TCP socket on 5432 that frequently fails on\n // serverless runtimes (Netlify Functions, Vercel, CF Workers) when\n // Neon's pooler is cold — every request after an idle period times out\n // with CONNECT_TIMEOUT. The serverless Pool handles wake-up transparently\n // and keeps the same `pg`-compatible query(...) interface we need here.\n if (isNeonUrl(url)) {\n const { Pool } = await import(\"@neondatabase/serverless\");\n const pool = new Pool({ connectionString: url, max: neonPoolMax() });\n attachNeonPoolErrorLogger(pool);\n if (trackSingletonResources) _neonPool = pool;\n return {\n async execute(sql) {\n const rawSql = typeof sql === \"string\" ? sql : sql.sql;\n const args = typeof sql === \"string\" ? [] : sql.args || [];\n const pgSql = sqliteToPostgresParams(rawSql);\n const result = await retryOnConnectionError<{\n rows: unknown[];\n rowCount?: number;\n }>(async () => {\n const client = await pool.connect();\n let released = false;\n const releaseClient = (err?: Error | boolean) => {\n if (released) return;\n released = true;\n client.release(err);\n };\n\n try {\n const result = await withDbTimeout(\n \"query\",\n () =>\n client.query(pgSql, args as any[]) as Promise<{\n rows: unknown[];\n rowCount?: number;\n }>,\n dbOpTimeoutMs(),\n () => releaseClient(true),\n );\n releaseClient();\n return result;\n } catch (err) {\n releaseClient(isConnectionError(err) ? true : undefined);\n throw err;\n }\n });\n return {\n rows: result.rows,\n rowsAffected: result.rowCount ?? 0,\n };\n },\n };\n }\n\n const { default: postgres } = await import(\"postgres\");\n const isWorkers =\n \"__cf_env\" in globalThis ||\n (typeof navigator !== \"undefined\" &&\n navigator.userAgent === \"Cloudflare-Workers\");\n\n if (isWorkers) {\n // Workers: fresh connection per query — I/O can't be shared across requests\n return {\n async execute(sql) {\n const conn = postgres(url, {\n max: 1,\n idle_timeout: 0,\n onnotice: () => {},\n });\n let timedOut = false;\n try {\n const rawSql = typeof sql === \"string\" ? sql : sql.sql;\n const args = typeof sql === \"string\" ? [] : sql.args || [];\n const pgSql = sqliteToPostgresParams(rawSql);\n const result = await withDbTimeout<\n ArrayLike<unknown> & { count?: number }\n >(\n \"query\",\n () =>\n conn.unsafe(pgSql, args as any[]) as Promise<\n ArrayLike<unknown> & { count?: number }\n >,\n dbOpTimeoutMs(),\n () => {\n timedOut = true;\n disposePostgresPoolEventually(conn, \"timed-out worker query\");\n },\n );\n return {\n rows: Array.from(result),\n rowsAffected: result.count ?? 0,\n };\n } finally {\n if (!timedOut) {\n await conn.end().catch((err: unknown) => {\n console.warn(\n \"[db/postgres] worker query cleanup failed:\",\n err instanceof Error ? err.message : err,\n );\n });\n }\n }\n },\n };\n } else {\n // Node.js: reuse connection pool. pgPoolOptions caps the pool to a\n // small size on serverless (Netlify/Vercel/Lambda/CF) so concurrent\n // frozen instances don't exhaust Neon/Postgres' connection limit;\n // idle_timeout also closes idle connections before Neon's ~5min\n // server-side timeout, avoiding ECONNRESET when the server hangs up.\n const createPool = () => postgres(url, pgPoolOptions(url));\n type PostgresPool = ReturnType<typeof createPool>;\n let pool = createPool();\n if (trackSingletonResources) _pgPool = pool;\n const recyclePool = (timedOutPool: PostgresPool) => {\n if (pool === timedOutPool) {\n pool = createPool();\n if (trackSingletonResources) _pgPool = pool;\n }\n disposePostgresPoolEventually(timedOutPool, \"timed-out pooled query\");\n };\n\n return {\n async execute(sql) {\n const rawSql = typeof sql === \"string\" ? sql : sql.sql;\n const args = typeof sql === \"string\" ? [] : sql.args || [];\n const pgSql = sqliteToPostgresParams(rawSql);\n const result = await retryOnConnectionError<\n ArrayLike<unknown> & { count?: number }\n >(() => {\n const queryPool = pool;\n const query = queryPool.unsafe(pgSql, args as any[]);\n return withDbTimeout(\n \"query\",\n () => query,\n dbOpTimeoutMs(),\n () => recyclePool(queryPool),\n );\n });\n return {\n rows: Array.from(result),\n rowsAffected: result.count ?? 0,\n };\n },\n };\n }\n }\n\n // SQLite / libsql (default). Local file databases use better-sqlite3 so\n // serverless bundles do not need libsql's platform-specific native package.\n if (isLocalSqliteUrl(url)) {\n url = await prepareLocalSqliteUrl(\n url.startsWith(\"file:\") ? url : `file:${url}`,\n );\n const { default: Database } = await import(\"better-sqlite3\");\n const sqlite = new Database(sqliteFilenameFromUrl(url));\n sqlite.pragma(\"busy_timeout = 10000\");\n sqlite.pragma(\"journal_mode = WAL\");\n if (trackSingletonResources) _sqlite = sqlite;\n\n return {\n async execute(sql) {\n const rawSql = typeof sql === \"string\" ? sql : sql.sql;\n const args = typeof sql === \"string\" ? [] : sql.args || [];\n const stmt = sqlite.prepare(rawSql);\n if (stmt.reader) {\n return {\n rows: stmt.all(...args),\n rowsAffected: 0,\n };\n }\n const result = stmt.run(...args);\n return {\n rows: [],\n rowsAffected: result.changes ?? 0,\n };\n },\n };\n }\n\n const { createClient } = await import(\"@libsql/client/web\");\n const client = createClient({\n url,\n authToken: config.authToken,\n });\n\n return {\n async execute(sql) {\n if (typeof sql === \"string\") {\n const r = await client.execute(sql);\n return {\n rows: r.rows as any[],\n rowsAffected: r.rowsAffected,\n };\n }\n const r = await client.execute({\n sql: sql.sql,\n args: sql.args as any[],\n });\n return {\n rows: r.rows as any[],\n rowsAffected: r.rowsAffected,\n };\n },\n };\n}\n\nexport async function createDbExec(config: DbExecConfig = {}): Promise<DbExec> {\n return createDbExecInternal(config, false);\n}\n\nasync function initClient(): Promise<void> {\n if (_exec) return;\n\n const dialect = getDialect();\n const url = getDatabaseUrl(\"file:./data/app.db\");\n _exec = await createDbExecInternal(\n {\n url,\n authToken: getDatabaseAuthToken(),\n d1Binding: dialect === \"d1\" ? globalThis.__cf_env?.DB : undefined,\n },\n true,\n );\n}\n\n/**\n * Get the singleton database client. Returns a `DbExec` whose first\n * `execute()` call lazily initializes the underlying driver.\n */\nexport function getDbExec(): DbExec {\n if (_exec) return _exec;\n\n // Sanitize args: replace undefined with null (libsql rejects undefined)\n function sanitize(\n sql: string | { sql: string; args: any[] },\n ): string | { sql: string; args: any[] } {\n if (typeof sql === \"object\" && sql.args) {\n return { ...sql, args: sql.args.map((a: any) => a ?? null) };\n }\n return sql;\n }\n\n // Return a proxy that lazy-inits on first call\n const proxy: DbExec = {\n async execute(sql) {\n if (!_initPromise) _initPromise = initClient();\n try {\n await _initPromise;\n } catch (err) {\n // A failed/hung init must not poison the singleton for the life of\n // the process — drop it so the next call retries a fresh connection\n // instead of re-awaiting a permanently rejected/pending promise.\n _initPromise = undefined;\n _exec = undefined;\n throw err;\n }\n // After init, swap to a sanitizing wrapper around the real client\n const wrapper: DbExec = {\n execute: (s) => _exec!.execute(sanitize(s)),\n };\n Object.assign(proxy, wrapper);\n return _exec!.execute(sanitize(sql));\n },\n };\n return proxy;\n}\n\n/** Close the database connection (for scripts that need cleanup). */\nexport async function closeDbExec(): Promise<void> {\n if (_pgPool) {\n await _pgPool.end();\n _pgPool = undefined;\n }\n if (_neonPool) {\n await _neonPool.end();\n _neonPool = undefined;\n }\n if (_sqlite) {\n _sqlite.close();\n _sqlite = undefined;\n }\n _exec = undefined;\n _initPromise = undefined;\n}\n"]}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/db/client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Central database client abstraction.\n *\n * Detects the database backend from the environment (D1, Postgres, or SQLite/libsql)\n * and returns a unified `DbExec` interface that all core stores use.\n *\n * Imports for postgres, better-sqlite3, and @libsql/client/web are lazy\n * (dynamic import) so this module can be loaded in any runtime (Node.js,\n * Cloudflare Workers, edge) without failing on missing native deps.\n */\nimport path from \"path\";\n\nconst recyclingPostgresPools = new WeakSet<object>();\nconst loggedNeonPools = new WeakSet<object>();\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type Dialect = \"sqlite\" | \"postgres\" | \"d1\";\n\nexport interface DbExec {\n execute(\n sql: string | { sql: string; args: any[] },\n ): Promise<{ rows: any[]; rowsAffected: number }>;\n}\n\nexport interface DbExecConfig {\n url?: string;\n authToken?: string;\n d1Binding?: any;\n}\n\n// ---------------------------------------------------------------------------\n// Per-app DATABASE_URL resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the database URL for the current app.\n *\n * Checks for `<APP_NAME>_DATABASE_URL` first (e.g. `MAIL_DATABASE_URL`),\n * then falls back to `DATABASE_URL`. This allows multiple apps to run in the\n * same process group (e.g. eager repo dev or builder.io) with separate databases.\n *\n * Set `APP_NAME=mail` in the child process env and\n * `MAIL_DATABASE_URL=postgres://...` in the shared env.\n */\nexport function getDatabaseUrl(fallback = \"\"): string {\n const appName = process.env.APP_NAME?.toUpperCase().replace(/-/g, \"_\");\n if (appName) {\n const prefixed = process.env[`${appName}_DATABASE_URL`];\n if (prefixed) return prefixed;\n }\n return process.env.DATABASE_URL || fallback;\n}\n\n/** Same per-app resolution for DATABASE_AUTH_TOKEN (used by Turso/libsql). */\nexport function getDatabaseAuthToken(): string | undefined {\n const appName = process.env.APP_NAME?.toUpperCase().replace(/-/g, \"_\");\n if (appName) {\n const prefixed = process.env[`${appName}_DATABASE_AUTH_TOKEN`];\n if (prefixed) return prefixed;\n }\n return process.env.DATABASE_AUTH_TOKEN;\n}\n\nexport function isLocalSqliteUrl(url: string): boolean {\n return url === \"\" || url.startsWith(\"file:\") || !url.includes(\"://\");\n}\n\nexport async function prepareLocalSqliteUrl(url: string): Promise<string> {\n if (!url.startsWith(\"file:\")) return url;\n\n // On serverless runtimes (Netlify / Vercel / AWS Lambda / CF Pages) the\n // working directory is read-only. Detect this and redirect local SQLite to\n // /tmp which IS writable (ephemeral per invocation, but the server stays\n // alive for the request). Shares the canonical isServerlessRuntime() check.\n const isServerless = isServerlessRuntime();\n try {\n const fs = await import(\"fs\");\n if (isServerless && url === \"file:./data/app.db\") {\n fs.mkdirSync(\"/tmp/data\", { recursive: true });\n return \"file:///tmp/data/app.db\";\n }\n fs.mkdirSync(path.join(process.cwd(), \"data\"), { recursive: true });\n } catch {\n // Edge runtime — no filesystem.\n }\n return url;\n}\n\nexport function sqliteFilenameFromUrl(url: string): string {\n if (url.startsWith(\"file://\")) {\n return decodeURIComponent(new URL(url).pathname);\n }\n if (url.startsWith(\"file:\")) {\n return url.slice(\"file:\".length) || \":memory:\";\n }\n return url || \"./data/app.db\";\n}\n\n// ---------------------------------------------------------------------------\n// Safe JSON column parsing\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a JSON-serialized column value defensively. A malformed row — from a\n * hand-edit, dirty migration, or a misbehaving agent that wrote raw SQL —\n * must not break an entire list endpoint. Callers supply a fallback for the\n * malformed path; null/undefined values also fall back.\n */\nexport function safeJsonParse<T>(value: unknown, fallback: T): T {\n if (value == null) return fallback;\n try {\n return JSON.parse(String(value)) as T;\n } catch {\n return fallback;\n }\n}\n\n// ---------------------------------------------------------------------------\n// SQLite retry helper\n// ---------------------------------------------------------------------------\n\n/**\n * Retry an async operation when it fails with SQLITE_BUSY.\n * Used during WAL initialization and migrations where a stale WAL from a\n * previous crash or HMR restart can briefly lock the database.\n */\nexport async function retrySqliteBusy<T>(\n fn: () => Promise<T>,\n opts: { maxAttempts?: number; baseDelayMs?: number; rethrow?: boolean } = {},\n): Promise<T> {\n const { maxAttempts = 5, baseDelayMs = 500, rethrow = false } = opts;\n let last: unknown;\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n return await fn();\n } catch (e: any) {\n last = e;\n const msg = String(e?.message || e);\n if (msg.includes(\"SQLITE_BUSY\") && attempt < maxAttempts - 1) {\n await new Promise((r) => setTimeout(r, baseDelayMs * (attempt + 1)));\n } else {\n break;\n }\n }\n }\n if (rethrow) throw last;\n return undefined as unknown as T; // caller handles undefined (e.g. PRAGMA setup)\n}\n\n/**\n * Retry a DDL statement (CREATE TABLE, CREATE INDEX) once when it fails due\n * to a Postgres pg_catalog race.\n *\n * Postgres's `IF NOT EXISTS` check is NOT atomic with the `pg_type` /\n * `pg_class` catalog insert. When multiple processes boot concurrently and\n * issue the same CREATE, both can pass the existence check and one fails\n * with code 23505 on `pg_type_typname_nsp_index` or similar. The table does\n * end up created by the winner, so rerunning the same `IF NOT EXISTS`\n * statement is a safe no-op.\n */\nexport async function retryOnDdlRace<T>(fn: () => Promise<T>): Promise<T> {\n try {\n return await fn();\n } catch (e: any) {\n if (!isPgCatalogRace(e)) throw e;\n return await fn();\n }\n}\n\nfunction isPgCatalogRace(e: any): boolean {\n if (e?.code === \"42P07\") return true;\n if (e?.code !== \"23505\") return false;\n const constraint = String(e?.constraint_name ?? e?.constraint ?? \"\");\n const detail = String(e?.detail ?? \"\");\n const msg = String(e?.message ?? \"\");\n return (\n constraint.startsWith(\"pg_type\") ||\n constraint.startsWith(\"pg_class\") ||\n detail.includes(\"pg_type\") ||\n detail.includes(\"pg_class\") ||\n /relation .* already exists/i.test(msg)\n );\n}\n\n/**\n * True when `e` is a UNIQUE / PRIMARY KEY constraint violation from any\n * supported driver (Postgres 23505, SQLite SQLITE_CONSTRAINT_PRIMARYKEY /\n * _UNIQUE, D1). Used by stores that accept caller-provided ids and want to\n * surface a clean \"already exists\" error instead of the raw SQL text.\n */\nexport function isUniqueViolation(e: any): boolean {\n if (e?.code === \"23505\") return true;\n const code = String(e?.code ?? \"\");\n if (\n code === \"SQLITE_CONSTRAINT_PRIMARYKEY\" ||\n code === \"SQLITE_CONSTRAINT_UNIQUE\"\n ) {\n return true;\n }\n const msg = String(e?.message ?? \"\").toLowerCase();\n return (\n msg.includes(\"unique constraint\") ||\n msg.includes(\"primary key constraint\") ||\n msg.includes(\"duplicate key\")\n );\n}\n\n// ---------------------------------------------------------------------------\n// Dialect detection\n// ---------------------------------------------------------------------------\n\nlet _dialect: Dialect | undefined;\n\nexport function getDialect(): Dialect {\n if (_dialect !== undefined) return _dialect;\n\n // DATABASE_URL takes priority over D1 when set.\n const url = getDatabaseUrl();\n if (url.startsWith(\"postgres://\") || url.startsWith(\"postgresql://\")) {\n _dialect = \"postgres\";\n return _dialect;\n }\n if (url && !url.startsWith(\"file:\")) {\n // Remote libsql (e.g. Turso)\n _dialect = \"sqlite\";\n return _dialect;\n }\n\n const d1 = globalThis.__cf_env?.DB;\n if (d1) {\n _dialect = \"d1\";\n return _dialect;\n }\n\n // Don't cache the fallthrough — on CF Workers, env bindings (__cf_env) aren't\n // available at import time. If we cache \"sqlite\" here, D1 will never be\n // detected once the bindings are set in the fetch handler.\n return \"sqlite\";\n}\n\nexport function isPostgres(): boolean {\n return getDialect() === \"postgres\";\n}\n\nfunction dialectForConfig(config: DbExecConfig): Dialect {\n const url = config.url ?? \"\";\n if (url.startsWith(\"postgres://\") || url.startsWith(\"postgresql://\")) {\n return \"postgres\";\n }\n if (url && !url.startsWith(\"file:\")) {\n return \"sqlite\";\n }\n if (config.d1Binding) {\n return \"d1\";\n }\n return \"sqlite\";\n}\n\n/**\n * Returns true when the database is a local-only SQLite file (or unset, which\n * defaults to a local SQLite file). Returns false for Postgres, remote libsql\n * (Turso), and D1 — any backend that could be shared across developers.\n *\n * Used to gate local@localhost mode: that mode uses a single shared virtual\n * user with no per-machine scoping, so on any shared database two developers\n * would read and write each other's settings, oauth tokens, and app state.\n */\nexport function isLocalDatabase(): boolean {\n if (getDialect() !== \"sqlite\") return false;\n const url = getDatabaseUrl();\n return url === \"\" || url.startsWith(\"file:\");\n}\n\n/** Returns BIGINT for Postgres (64-bit), INTEGER for SQLite (already 64-bit). */\nexport function intType(): string {\n return isPostgres() ? \"BIGINT\" : \"INTEGER\";\n}\n\n// ---------------------------------------------------------------------------\n// Parameter conversion: ? -> $1, $2, $3\n// ---------------------------------------------------------------------------\n\nfunction sqliteToPostgresParams(sql: string): string {\n let i = 0;\n return sql.replace(/\\?/g, () => `$${++i}`);\n}\n\n// ---------------------------------------------------------------------------\n// Connection error retry (ECONNRESET, etc.)\n// ---------------------------------------------------------------------------\n\n/** Error codes that indicate a dead/stale connection we can safely retry. */\nconst CONNECTION_ERROR_CODES = new Set([\n \"ECONNRESET\",\n \"ETIMEDOUT\",\n \"EPIPE\",\n \"ENOTFOUND\",\n \"CONNECT_TIMEOUT\",\n \"CONNECTION_ENDED\",\n \"CONNECTION_DESTROYED\",\n \"CONNECTION_CLOSED\",\n]);\n\nexport function isConnectionError(err: any): boolean {\n if (!err) return false;\n const code = err.code || err.cause?.code;\n if (code && CONNECTION_ERROR_CODES.has(code)) return true;\n // Neon serverless WS driver: errors from the underlying undici WebSocket\n // closing mid-query come through as TypeError or ErrorEvent without a code.\n const name = err.name || err.cause?.name || \"\";\n if (name === \"ErrorEvent\") return true;\n const stack = String(err.stack || err.cause?.stack || \"\");\n if (\n /WebSocket\\.#onSocketClose|failWebsocketConnection|onSocketClose/.test(\n stack,\n )\n ) {\n return true;\n }\n const msg = String(err.message || err.cause?.message || \"\");\n return /ECONNRESET|ETIMEDOUT|EPIPE|connection.*(closed|ended|terminated)|socket hang up|websocket/i.test(\n msg,\n );\n}\n\nexport async function retryOnConnectionError<T>(\n fn: () => Promise<T>,\n maxAttempts = 3,\n): Promise<T> {\n let last: unknown;\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n return await fn();\n } catch (e) {\n last = e;\n if (!isConnectionError(e) || attempt === maxAttempts - 1) throw e;\n await new Promise((r) => setTimeout(r, 100 * (attempt + 1)));\n }\n }\n throw last;\n}\n\n// ---------------------------------------------------------------------------\n// Per-op timeout — converts a silent serverless hang into a retryable error\n// ---------------------------------------------------------------------------\n\n/**\n * Max wall time for a single DB op (init or query) before we treat it as a\n * dead connection. A frozen→thawed serverless instance can leave the Neon\n * WebSocket (or a postgres.js socket) hung mid-flight: the promise neither\n * settles nor errors, so retryOnConnectionError() — which only retries thrown\n * errors — can't help and the request hangs until the platform kills the\n * function (~30s on Netlify). For authenticated requests that run a session\n * lookup on every navigation this surfaces as \"the site won't load\". Bounding\n * each op well under the platform function limit turns the silent hang into a\n * CONNECT_TIMEOUT that the existing retry and reject-reset paths already\n * handle. Override with DB_OP_TIMEOUT_MS.\n */\nexport function dbOpTimeoutMs(): number {\n const raw = Number(process.env.DB_OP_TIMEOUT_MS);\n if (Number.isFinite(raw) && raw > 0) return raw;\n return isServerlessRuntime() ? 8_000 : 30_000;\n}\n\n/**\n * Timeout error tagged with a recognized connection-error code so\n * isConnectionError() / retryOnConnectionError() treat a hung op as a\n * retryable dead connection, and upstream reject-reset guards (e.g. the\n * cached session-table init promise) clear their poisoned state.\n */\nclass DbTimeoutError extends Error {\n code = \"CONNECT_TIMEOUT\";\n constructor(op: string, ms: number) {\n super(`DB ${op} timed out after ${ms}ms (connection terminated)`);\n this.name = \"DbTimeoutError\";\n }\n}\n\n/**\n * Race a DB op against {@link dbOpTimeoutMs}. Callers that own a cancellable\n * query or pooled client should pass onTimeout so the losing operation does\n * not keep occupying a scarce connection slot after the request has recovered.\n */\nexport async function withDbTimeout<T>(\n op: string,\n run: () => Promise<T>,\n ms = dbOpTimeoutMs(),\n onTimeout?: () => void | Promise<void>,\n): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n let settled = false;\n\n const runCleanup = async () => {\n if (!onTimeout) return;\n try {\n await onTimeout();\n } catch (err) {\n console.warn(\n `[db] timeout cleanup for ${op} failed:`,\n err instanceof Error ? err.message : err,\n );\n }\n };\n\n return await new Promise<T>((resolve, reject) => {\n const finish = (\n complete: (value: T | PromiseLike<T>) => void,\n value: T | PromiseLike<T>,\n ) => {\n if (settled) return;\n settled = true;\n if (timer) clearTimeout(timer);\n complete(value);\n };\n const fail = (err: unknown) => {\n if (settled) return;\n settled = true;\n if (timer) clearTimeout(timer);\n reject(err);\n };\n\n timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n void (async () => {\n await runCleanup();\n reject(new DbTimeoutError(op, ms));\n })();\n }, ms);\n\n let promise: Promise<T>;\n try {\n promise = run();\n } catch (err) {\n fail(err);\n return;\n }\n promise.then((value) => finish(resolve, value), fail);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Serverless-aware Postgres pool options\n// ---------------------------------------------------------------------------\n\n/**\n * True on serverless function runtimes (Netlify / Vercel / AWS Lambda /\n * Cloudflare Pages Functions) where every concurrent request can spin up its\n * own frozen process. Connections cannot be shared across instances, so each\n * instance must keep its pool tiny — otherwise dozens of warm instances each\n * holding postgres.js's default 10-connection pool blow past Neon/Postgres'\n * connection cap and every `/_agent-native/*` route 500s with \"Max client\n * connections reached\".\n */\nexport function isServerlessRuntime(): boolean {\n return (\n !!process.env.NETLIFY ||\n !!process.env.VERCEL ||\n !!process.env.AWS_LAMBDA_FUNCTION_NAME ||\n !!process.env.LAMBDA_TASK_ROOT ||\n !!process.env.CF_PAGES\n );\n}\n\n/**\n * postgres.js pool options tuned per runtime. A serverless instance handles\n * one request at a time, so a tiny pool is enough — but we cap at 2 (not 1)\n * so a single slow query or open transaction can't serialize every other\n * query in the same request. Total connections stay bounded to ≈ 2×\n * concurrent-instance count instead of 10×. idle_timeout is shortened on\n * serverless so a thawed-but-idle instance releases its connections quickly.\n * Long-lived Node servers keep the normal pool for throughput.\n */\nexport function pgPoolOptions(url: string): Record<string, unknown> {\n const serverless = isServerlessRuntime();\n return {\n onnotice: () => {},\n max: serverless ? 2 : 10,\n idle_timeout: serverless ? 20 : 240,\n max_lifetime: 60 * 30,\n connect_timeout: 10,\n // Supabase's connection pooler (Transaction mode) requires prepare:false.\n // Only disable for Supabase URLs to avoid degrading other deployments.\n ...(url.includes(\"supabase\") ? { prepare: false } : {}),\n };\n}\n\n/**\n * Connection cap for the @neondatabase/serverless `Pool`. Same instance\n * accumulation risk as postgres.js — a small pool (2) is enough on serverless\n * and keeps total connections bounded while still letting a second query\n * proceed when one connection is busy.\n */\nexport function neonPoolMax(): number {\n return isServerlessRuntime() ? 2 : 10;\n}\n\nexport function attachNeonPoolErrorLogger(\n pool: unknown,\n label = \"db/neon\",\n): void {\n if (!pool || typeof pool !== \"object\") return;\n if (loggedNeonPools.has(pool)) return;\n const withEvents = pool as {\n on?: (event: string, listener: (...args: unknown[]) => void) => unknown;\n };\n if (typeof withEvents.on !== \"function\") return;\n\n loggedNeonPools.add(pool);\n withEvents.on(\"error\", (err: unknown) => {\n console.warn(\n `[${label}] pool error (will reconnect on next query):`,\n err instanceof Error ? err.message : err,\n );\n });\n\n // Attach a persistent 'error' listener to EVERY client for its whole lifetime.\n //\n // @neondatabase/serverless mirrors pg-pool, which only keeps its own idle\n // error listener on a client while that client is idle — it REMOVES the\n // listener the moment the client is checked out. So when a checked-out\n // client's WebSocket drops mid-flight (Lambda freeze/thaw, Neon \"terminating\n // connection due to administrator command\", an idle socket the pooler closed),\n // the client emits 'error' with no listener. Node turns an unhandled 'error'\n // EventEmitter event into an uncaught exception, which crashes the whole\n // serverless function. This was by far the single highest-volume production\n // crash (Sentry \"Unhandled error. ()\", mechanism auto.node.onuncaughtexception,\n // culprit neondatabase__serverless). pg routes the failure to the in-flight\n // query independently, so this listener only needs to keep the emit from going\n // unhandled — the dropped client is discarded and the next query reconnects.\n withEvents.on(\"connect\", (client: unknown) => {\n if (!client || typeof client !== \"object\") return;\n const clientEvents = client as {\n on?: (event: string, listener: (...args: unknown[]) => void) => unknown;\n };\n if (typeof clientEvents.on !== \"function\") return;\n clientEvents.on(\"error\", (err: unknown) => {\n console.warn(\n `[${label}] client connection error (connection discarded, next query reconnects):`,\n err instanceof Error ? err.message : err,\n );\n });\n });\n}\n\nfunction disposePostgresPoolEventually(\n pool: { end: () => Promise<unknown> },\n label: string,\n): void {\n if (!pool || typeof pool !== \"object\") return;\n if (recyclingPostgresPools.has(pool)) return;\n recyclingPostgresPools.add(pool);\n void pool.end().catch((err: unknown) => {\n console.warn(\n `[db/postgres] ${label} cleanup failed:`,\n err instanceof Error ? err.message : err,\n );\n });\n}\n\n// ---------------------------------------------------------------------------\n// Singleton client — lazy-initialized on first execute() call\n// ---------------------------------------------------------------------------\n\nlet _exec: DbExec | undefined;\nlet _pgPool: any;\nlet _neonPool: any;\nlet _sqlite: any;\nlet _initPromise: Promise<void> | undefined;\n\nasync function createDbExecInternal(\n config: DbExecConfig = {},\n trackSingletonResources = false,\n): Promise<DbExec> {\n const dialect = dialectForConfig(config);\n\n // Cloudflare D1\n if (dialect === \"d1\") {\n const d1 = config.d1Binding;\n return {\n async execute(sql) {\n if (typeof sql === \"string\") {\n const r = await d1.prepare(sql).all();\n return {\n rows: r.results || [],\n rowsAffected: r.meta?.changes ?? 0,\n };\n }\n const r = await d1\n .prepare(sql.sql)\n .bind(...sql.args)\n .all();\n return { rows: r.results || [], rowsAffected: r.meta?.changes ?? 0 };\n },\n };\n }\n\n let url = config.url || \"file:./data/app.db\";\n\n // Postgres — uses postgres.js. Works on Node.js natively and on Cloudflare\n // Workers with the nodejs_compat compatibility flag (provides net/tls polyfills).\n // On Workers, connections can't be shared across requests, so we create a\n // fresh connection per query (max:1) to avoid the \"I/O on behalf of a\n // different request\" error.\n if (dialect === \"postgres\") {\n const { isNeonUrl } = await import(\"./create-get-db.js\");\n\n // Neon over @neondatabase/serverless (WebSocket upgrade on port 443).\n // postgres-js uses a raw TCP socket on 5432 that frequently fails on\n // serverless runtimes (Netlify Functions, Vercel, CF Workers) when\n // Neon's pooler is cold — every request after an idle period times out\n // with CONNECT_TIMEOUT. The serverless Pool handles wake-up transparently\n // and keeps the same `pg`-compatible query(...) interface we need here.\n if (isNeonUrl(url)) {\n const { Pool } = await import(\"@neondatabase/serverless\");\n const pool = new Pool({ connectionString: url, max: neonPoolMax() });\n attachNeonPoolErrorLogger(pool);\n if (trackSingletonResources) _neonPool = pool;\n return {\n async execute(sql) {\n const rawSql = typeof sql === \"string\" ? sql : sql.sql;\n const args = typeof sql === \"string\" ? [] : sql.args || [];\n const pgSql = sqliteToPostgresParams(rawSql);\n const result = await retryOnConnectionError<{\n rows: unknown[];\n rowCount?: number;\n }>(async () => {\n // Bound the pooled-connection ACQUIRE, not just the query below.\n // Neon's pooler can stall on `connect()` when cold or exhausted,\n // and that happens BEFORE `client.query`, so the query-level\n // timeout never fires — the request hangs until the platform kills\n // the function (~\"the site won't load\" for authenticated users,\n // whose every request runs a session/org lookup). Time the acquire\n // out into a retryable CONNECT_TIMEOUT that retryOnConnectionError\n // already handles, and release the connection if it resolves after\n // we've given up so the scarce pool slot isn't leaked.\n let acquireTimedOut = false;\n const client = await withDbTimeout(\n \"connect\",\n () =>\n pool.connect().then((c) => {\n if (acquireTimedOut) c.release();\n return c;\n }),\n dbOpTimeoutMs(),\n () => {\n acquireTimedOut = true;\n },\n );\n let released = false;\n const releaseClient = (err?: Error | boolean) => {\n if (released) return;\n released = true;\n client.release(err);\n };\n\n try {\n const result = await withDbTimeout(\n \"query\",\n () =>\n client.query(pgSql, args as any[]) as Promise<{\n rows: unknown[];\n rowCount?: number;\n }>,\n dbOpTimeoutMs(),\n () => releaseClient(true),\n );\n releaseClient();\n return result;\n } catch (err) {\n releaseClient(isConnectionError(err) ? true : undefined);\n throw err;\n }\n });\n return {\n rows: result.rows,\n rowsAffected: result.rowCount ?? 0,\n };\n },\n };\n }\n\n const { default: postgres } = await import(\"postgres\");\n const isWorkers =\n \"__cf_env\" in globalThis ||\n (typeof navigator !== \"undefined\" &&\n navigator.userAgent === \"Cloudflare-Workers\");\n\n if (isWorkers) {\n // Workers: fresh connection per query — I/O can't be shared across requests\n return {\n async execute(sql) {\n const conn = postgres(url, {\n max: 1,\n idle_timeout: 0,\n onnotice: () => {},\n });\n let timedOut = false;\n try {\n const rawSql = typeof sql === \"string\" ? sql : sql.sql;\n const args = typeof sql === \"string\" ? [] : sql.args || [];\n const pgSql = sqliteToPostgresParams(rawSql);\n const result = await withDbTimeout<\n ArrayLike<unknown> & { count?: number }\n >(\n \"query\",\n () =>\n conn.unsafe(pgSql, args as any[]) as Promise<\n ArrayLike<unknown> & { count?: number }\n >,\n dbOpTimeoutMs(),\n () => {\n timedOut = true;\n disposePostgresPoolEventually(conn, \"timed-out worker query\");\n },\n );\n return {\n rows: Array.from(result),\n rowsAffected: result.count ?? 0,\n };\n } finally {\n if (!timedOut) {\n await conn.end().catch((err: unknown) => {\n console.warn(\n \"[db/postgres] worker query cleanup failed:\",\n err instanceof Error ? err.message : err,\n );\n });\n }\n }\n },\n };\n } else {\n // Node.js: reuse connection pool. pgPoolOptions caps the pool to a\n // small size on serverless (Netlify/Vercel/Lambda/CF) so concurrent\n // frozen instances don't exhaust Neon/Postgres' connection limit;\n // idle_timeout also closes idle connections before Neon's ~5min\n // server-side timeout, avoiding ECONNRESET when the server hangs up.\n const createPool = () => postgres(url, pgPoolOptions(url));\n type PostgresPool = ReturnType<typeof createPool>;\n let pool = createPool();\n if (trackSingletonResources) _pgPool = pool;\n const recyclePool = (timedOutPool: PostgresPool) => {\n if (pool === timedOutPool) {\n pool = createPool();\n if (trackSingletonResources) _pgPool = pool;\n }\n disposePostgresPoolEventually(timedOutPool, \"timed-out pooled query\");\n };\n\n return {\n async execute(sql) {\n const rawSql = typeof sql === \"string\" ? sql : sql.sql;\n const args = typeof sql === \"string\" ? [] : sql.args || [];\n const pgSql = sqliteToPostgresParams(rawSql);\n const result = await retryOnConnectionError<\n ArrayLike<unknown> & { count?: number }\n >(() => {\n const queryPool = pool;\n const query = queryPool.unsafe(pgSql, args as any[]);\n return withDbTimeout(\n \"query\",\n () => query,\n dbOpTimeoutMs(),\n () => recyclePool(queryPool),\n );\n });\n return {\n rows: Array.from(result),\n rowsAffected: result.count ?? 0,\n };\n },\n };\n }\n }\n\n // SQLite / libsql (default). Local file databases use better-sqlite3 so\n // serverless bundles do not need libsql's platform-specific native package.\n if (isLocalSqliteUrl(url)) {\n url = await prepareLocalSqliteUrl(\n url.startsWith(\"file:\") ? url : `file:${url}`,\n );\n const { default: Database } = await import(\"better-sqlite3\");\n const sqlite = new Database(sqliteFilenameFromUrl(url));\n sqlite.pragma(\"busy_timeout = 10000\");\n sqlite.pragma(\"journal_mode = WAL\");\n if (trackSingletonResources) _sqlite = sqlite;\n\n return {\n async execute(sql) {\n const rawSql = typeof sql === \"string\" ? sql : sql.sql;\n const args = typeof sql === \"string\" ? [] : sql.args || [];\n const stmt = sqlite.prepare(rawSql);\n if (stmt.reader) {\n return {\n rows: stmt.all(...args),\n rowsAffected: 0,\n };\n }\n const result = stmt.run(...args);\n return {\n rows: [],\n rowsAffected: result.changes ?? 0,\n };\n },\n };\n }\n\n const { createClient } = await import(\"@libsql/client/web\");\n const client = createClient({\n url,\n authToken: config.authToken,\n });\n\n return {\n async execute(sql) {\n if (typeof sql === \"string\") {\n const r = await client.execute(sql);\n return {\n rows: r.rows as any[],\n rowsAffected: r.rowsAffected,\n };\n }\n const r = await client.execute({\n sql: sql.sql,\n args: sql.args as any[],\n });\n return {\n rows: r.rows as any[],\n rowsAffected: r.rowsAffected,\n };\n },\n };\n}\n\nexport async function createDbExec(config: DbExecConfig = {}): Promise<DbExec> {\n return createDbExecInternal(config, false);\n}\n\nasync function initClient(): Promise<void> {\n if (_exec) return;\n\n const dialect = getDialect();\n const url = getDatabaseUrl(\"file:./data/app.db\");\n _exec = await createDbExecInternal(\n {\n url,\n authToken: getDatabaseAuthToken(),\n d1Binding: dialect === \"d1\" ? globalThis.__cf_env?.DB : undefined,\n },\n true,\n );\n}\n\n/**\n * Get the singleton database client. Returns a `DbExec` whose first\n * `execute()` call lazily initializes the underlying driver.\n */\nexport function getDbExec(): DbExec {\n if (_exec) return _exec;\n\n // Sanitize args: replace undefined with null (libsql rejects undefined)\n function sanitize(\n sql: string | { sql: string; args: any[] },\n ): string | { sql: string; args: any[] } {\n if (typeof sql === \"object\" && sql.args) {\n return { ...sql, args: sql.args.map((a: any) => a ?? null) };\n }\n return sql;\n }\n\n // Return a proxy that lazy-inits on first call\n const proxy: DbExec = {\n async execute(sql) {\n if (!_initPromise) _initPromise = initClient();\n try {\n await _initPromise;\n } catch (err) {\n // A failed/hung init must not poison the singleton for the life of\n // the process — drop it so the next call retries a fresh connection\n // instead of re-awaiting a permanently rejected/pending promise.\n _initPromise = undefined;\n _exec = undefined;\n throw err;\n }\n // After init, swap to a sanitizing wrapper around the real client\n const wrapper: DbExec = {\n execute: (s) => _exec!.execute(sanitize(s)),\n };\n Object.assign(proxy, wrapper);\n return _exec!.execute(sanitize(sql));\n },\n };\n return proxy;\n}\n\n/** Close the database connection (for scripts that need cleanup). */\nexport async function closeDbExec(): Promise<void> {\n if (_pgPool) {\n await _pgPool.end();\n _pgPool = undefined;\n }\n if (_neonPool) {\n await _neonPool.end();\n _neonPool = undefined;\n }\n if (_sqlite) {\n _sqlite.close();\n _sqlite = undefined;\n }\n _exec = undefined;\n _initPromise = undefined;\n}\n"]}