contentbase 0.4.0 → 0.4.2

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 (323) hide show
  1. package/dist/adapters/node-fs.d.ts +17 -0
  2. package/dist/adapters/node-fs.d.ts.map +1 -0
  3. package/dist/adapters/node-fs.js +71 -0
  4. package/dist/adapters/node-fs.js.map +1 -0
  5. package/dist/ast-query.d.ts +44 -0
  6. package/dist/ast-query.d.ts.map +1 -0
  7. package/dist/ast-query.js +95 -0
  8. package/dist/ast-query.js.map +1 -0
  9. package/dist/base-model.d.ts +8 -0
  10. package/dist/base-model.d.ts.map +1 -0
  11. package/dist/base-model.js +12 -0
  12. package/dist/base-model.js.map +1 -0
  13. package/dist/cli/commands/action.d.ts +2 -0
  14. package/dist/cli/commands/action.d.ts.map +1 -0
  15. package/dist/cli/commands/action.js +73 -0
  16. package/dist/cli/commands/action.js.map +1 -0
  17. package/dist/cli/commands/api/endpoints/actions.d.ts +13 -0
  18. package/dist/cli/commands/api/endpoints/actions.d.ts.map +1 -0
  19. package/dist/cli/commands/api/endpoints/actions.js +28 -0
  20. package/dist/cli/commands/api/endpoints/actions.js.map +1 -0
  21. package/dist/cli/commands/api/endpoints/doc.d.ts +5 -0
  22. package/dist/cli/commands/api/endpoints/doc.d.ts.map +1 -0
  23. package/dist/cli/commands/api/endpoints/doc.js +186 -0
  24. package/dist/cli/commands/api/endpoints/doc.js.map +1 -0
  25. package/dist/cli/commands/api/endpoints/docs-index.d.ts +8 -0
  26. package/dist/cli/commands/api/endpoints/docs-index.d.ts.map +1 -0
  27. package/dist/cli/commands/api/endpoints/docs-index.js +23 -0
  28. package/dist/cli/commands/api/endpoints/docs-index.js.map +1 -0
  29. package/dist/cli/commands/api/endpoints/document.d.ts +47 -0
  30. package/dist/cli/commands/api/endpoints/document.d.ts.map +1 -0
  31. package/dist/cli/commands/api/endpoints/document.js +146 -0
  32. package/dist/cli/commands/api/endpoints/document.js.map +1 -0
  33. package/dist/cli/commands/api/endpoints/documents.d.ts +35 -0
  34. package/dist/cli/commands/api/endpoints/documents.d.ts.map +1 -0
  35. package/dist/cli/commands/api/endpoints/documents.js +56 -0
  36. package/dist/cli/commands/api/endpoints/documents.js.map +1 -0
  37. package/dist/cli/commands/api/endpoints/index.d.ts +22 -0
  38. package/dist/cli/commands/api/endpoints/index.d.ts.map +1 -0
  39. package/dist/cli/commands/api/endpoints/index.js +38 -0
  40. package/dist/cli/commands/api/endpoints/index.js.map +1 -0
  41. package/dist/cli/commands/api/endpoints/inspect.d.ts +10 -0
  42. package/dist/cli/commands/api/endpoints/inspect.d.ts.map +1 -0
  43. package/dist/cli/commands/api/endpoints/inspect.js +15 -0
  44. package/dist/cli/commands/api/endpoints/inspect.js.map +1 -0
  45. package/dist/cli/commands/api/endpoints/models.d.ts +5 -0
  46. package/dist/cli/commands/api/endpoints/models.d.ts.map +1 -0
  47. package/dist/cli/commands/api/endpoints/models.js +9 -0
  48. package/dist/cli/commands/api/endpoints/models.js.map +1 -0
  49. package/dist/cli/commands/api/endpoints/query.d.ts +40 -0
  50. package/dist/cli/commands/api/endpoints/query.d.ts.map +1 -0
  51. package/dist/cli/commands/api/endpoints/query.js +106 -0
  52. package/dist/cli/commands/api/endpoints/query.js.map +1 -0
  53. package/dist/cli/commands/api/endpoints/root.d.ts +5 -0
  54. package/dist/cli/commands/api/endpoints/root.d.ts.map +1 -0
  55. package/dist/cli/commands/api/endpoints/root.js +7 -0
  56. package/dist/cli/commands/api/endpoints/root.js.map +1 -0
  57. package/dist/cli/commands/api/endpoints/search-reindex.d.ts +10 -0
  58. package/dist/cli/commands/api/endpoints/search-reindex.d.ts.map +1 -0
  59. package/dist/cli/commands/api/endpoints/search-reindex.js +38 -0
  60. package/dist/cli/commands/api/endpoints/search-reindex.js.map +1 -0
  61. package/dist/cli/commands/api/endpoints/search-status.d.ts +7 -0
  62. package/dist/cli/commands/api/endpoints/search-status.d.ts.map +1 -0
  63. package/dist/cli/commands/api/endpoints/search-status.js +33 -0
  64. package/dist/cli/commands/api/endpoints/search-status.js.map +1 -0
  65. package/dist/cli/commands/api/endpoints/search.d.ts +24 -0
  66. package/dist/cli/commands/api/endpoints/search.d.ts.map +1 -0
  67. package/dist/cli/commands/api/endpoints/search.js +61 -0
  68. package/dist/cli/commands/api/endpoints/search.js.map +1 -0
  69. package/dist/cli/commands/api/endpoints/semantic-search.d.ts +28 -0
  70. package/dist/cli/commands/api/endpoints/semantic-search.d.ts.map +1 -0
  71. package/dist/cli/commands/api/endpoints/semantic-search.js +76 -0
  72. package/dist/cli/commands/api/endpoints/semantic-search.js.map +1 -0
  73. package/dist/cli/commands/api/endpoints/text-search.d.ts +17 -0
  74. package/dist/cli/commands/api/endpoints/text-search.d.ts.map +1 -0
  75. package/dist/cli/commands/api/endpoints/text-search.js +55 -0
  76. package/dist/cli/commands/api/endpoints/text-search.js.map +1 -0
  77. package/dist/cli/commands/api/endpoints/validate.d.ts +12 -0
  78. package/dist/cli/commands/api/endpoints/validate.d.ts.map +1 -0
  79. package/dist/cli/commands/api/endpoints/validate.js +27 -0
  80. package/dist/cli/commands/api/endpoints/validate.js.map +1 -0
  81. package/dist/cli/commands/api/helpers.d.ts +8 -0
  82. package/dist/cli/commands/api/helpers.d.ts.map +1 -0
  83. package/dist/cli/commands/api/helpers.js +89 -0
  84. package/dist/cli/commands/api/helpers.js.map +1 -0
  85. package/dist/cli/commands/console.d.ts +2 -0
  86. package/dist/cli/commands/console.d.ts.map +1 -0
  87. package/dist/cli/commands/console.js +117 -0
  88. package/dist/cli/commands/console.js.map +1 -0
  89. package/dist/cli/commands/create.d.ts +2 -0
  90. package/dist/cli/commands/create.d.ts.map +1 -0
  91. package/dist/cli/commands/create.js +185 -0
  92. package/dist/cli/commands/create.js.map +1 -0
  93. package/dist/cli/commands/embed.d.ts +2 -0
  94. package/dist/cli/commands/embed.d.ts.map +1 -0
  95. package/dist/cli/commands/embed.js +231 -0
  96. package/dist/cli/commands/embed.js.map +1 -0
  97. package/dist/cli/commands/export.d.ts +2 -0
  98. package/dist/cli/commands/export.d.ts.map +1 -0
  99. package/dist/cli/commands/export.js +55 -0
  100. package/dist/cli/commands/export.js.map +1 -0
  101. package/dist/cli/commands/extract.d.ts +2 -0
  102. package/dist/cli/commands/extract.d.ts.map +1 -0
  103. package/dist/cli/commands/extract.js +149 -0
  104. package/dist/cli/commands/extract.js.map +1 -0
  105. package/dist/cli/commands/help.d.ts +2 -0
  106. package/dist/cli/commands/help.d.ts.map +1 -0
  107. package/dist/cli/commands/help.js +58 -0
  108. package/dist/cli/commands/help.js.map +1 -0
  109. package/dist/cli/commands/index.d.ts +18 -0
  110. package/dist/cli/commands/index.d.ts.map +1 -0
  111. package/dist/cli/commands/index.js +19 -0
  112. package/dist/cli/commands/index.js.map +1 -0
  113. package/dist/cli/commands/init.d.ts +2 -0
  114. package/dist/cli/commands/init.d.ts.map +1 -0
  115. package/dist/cli/commands/init.js +82 -0
  116. package/dist/cli/commands/init.js.map +1 -0
  117. package/dist/cli/commands/inspect.d.ts +2 -0
  118. package/dist/cli/commands/inspect.d.ts.map +1 -0
  119. package/dist/cli/commands/inspect.js +53 -0
  120. package/dist/cli/commands/inspect.js.map +1 -0
  121. package/dist/cli/commands/mcp.d.ts +2 -0
  122. package/dist/cli/commands/mcp.d.ts.map +1 -0
  123. package/dist/cli/commands/mcp.js +92 -0
  124. package/dist/cli/commands/mcp.js.map +1 -0
  125. package/dist/cli/commands/search.d.ts +2 -0
  126. package/dist/cli/commands/search.d.ts.map +1 -0
  127. package/dist/cli/commands/search.js +212 -0
  128. package/dist/cli/commands/search.js.map +1 -0
  129. package/dist/cli/commands/serve.d.ts +2 -0
  130. package/dist/cli/commands/serve.d.ts.map +1 -0
  131. package/dist/cli/commands/serve.js +298 -0
  132. package/dist/cli/commands/serve.js.map +1 -0
  133. package/dist/cli/commands/summary.d.ts +2 -0
  134. package/dist/cli/commands/summary.d.ts.map +1 -0
  135. package/dist/cli/commands/summary.js +58 -0
  136. package/dist/cli/commands/summary.js.map +1 -0
  137. package/dist/cli/commands/teach.d.ts +2 -0
  138. package/dist/cli/commands/teach.d.ts.map +1 -0
  139. package/dist/cli/commands/teach.js +80 -0
  140. package/dist/cli/commands/teach.js.map +1 -0
  141. package/dist/cli/commands/text-search.d.ts +2 -0
  142. package/dist/cli/commands/text-search.d.ts.map +1 -0
  143. package/dist/cli/commands/text-search.js +125 -0
  144. package/dist/cli/commands/text-search.js.map +1 -0
  145. package/dist/cli/commands/update.d.ts +2 -0
  146. package/dist/cli/commands/update.d.ts.map +1 -0
  147. package/dist/cli/commands/update.js +167 -0
  148. package/dist/cli/commands/update.js.map +1 -0
  149. package/dist/cli/commands/validate.d.ts +2 -0
  150. package/dist/cli/commands/validate.d.ts.map +1 -0
  151. package/dist/cli/commands/validate.js +129 -0
  152. package/dist/cli/commands/validate.js.map +1 -0
  153. package/dist/cli/index.d.ts +3 -0
  154. package/dist/cli/index.d.ts.map +1 -0
  155. package/dist/cli/index.js +84 -0
  156. package/dist/cli/index.js.map +1 -0
  157. package/dist/cli/load-collection.d.ts +7 -0
  158. package/dist/cli/load-collection.d.ts.map +1 -0
  159. package/dist/cli/load-collection.js +201 -0
  160. package/dist/cli/load-collection.js.map +1 -0
  161. package/dist/cli/registry.d.ts +17 -0
  162. package/dist/cli/registry.d.ts.map +1 -0
  163. package/dist/cli/registry.js +16 -0
  164. package/dist/cli/registry.js.map +1 -0
  165. package/dist/cnotes +0 -0
  166. package/dist/collection.d.ts +145 -0
  167. package/dist/collection.d.ts.map +1 -0
  168. package/dist/collection.js +684 -0
  169. package/dist/collection.js.map +1 -0
  170. package/dist/define-model.d.ts +45 -0
  171. package/dist/define-model.d.ts.map +1 -0
  172. package/dist/define-model.js +106 -0
  173. package/dist/define-model.js.map +1 -0
  174. package/dist/document.d.ts +123 -0
  175. package/dist/document.d.ts.map +1 -0
  176. package/dist/document.js +434 -0
  177. package/dist/document.js.map +1 -0
  178. package/dist/extract-sections.d.ts +46 -0
  179. package/dist/extract-sections.d.ts.map +1 -0
  180. package/dist/extract-sections.js +153 -0
  181. package/dist/extract-sections.js.map +1 -0
  182. package/dist/index.d.ts +28 -0
  183. package/dist/index.d.ts.map +1 -0
  184. package/dist/index.js +31 -0
  185. package/dist/index.js.map +1 -0
  186. package/dist/mcp/helpers.d.ts +14 -0
  187. package/dist/mcp/helpers.d.ts.map +1 -0
  188. package/dist/mcp/helpers.js +7 -0
  189. package/dist/mcp/helpers.js.map +1 -0
  190. package/dist/mcp/model-info.d.ts +2 -0
  191. package/dist/mcp/model-info.d.ts.map +1 -0
  192. package/dist/mcp/model-info.js +94 -0
  193. package/dist/mcp/model-info.js.map +1 -0
  194. package/dist/mcp/prompts.d.ts +2 -0
  195. package/dist/mcp/prompts.d.ts.map +1 -0
  196. package/dist/mcp/prompts.js +180 -0
  197. package/dist/mcp/prompts.js.map +1 -0
  198. package/dist/mcp/readme.d.ts +2 -0
  199. package/dist/mcp/readme.d.ts.map +1 -0
  200. package/dist/mcp/readme.js +71 -0
  201. package/dist/mcp/readme.js.map +1 -0
  202. package/dist/mcp/resources.d.ts +2 -0
  203. package/dist/mcp/resources.d.ts.map +1 -0
  204. package/dist/mcp/resources.js +85 -0
  205. package/dist/mcp/resources.js.map +1 -0
  206. package/dist/mcp/server.d.ts +18 -0
  207. package/dist/mcp/server.d.ts.map +1 -0
  208. package/dist/mcp/server.js +83 -0
  209. package/dist/mcp/server.js.map +1 -0
  210. package/dist/mcp/tools/mutation.d.ts +2 -0
  211. package/dist/mcp/tools/mutation.d.ts.map +1 -0
  212. package/dist/mcp/tools/mutation.js +155 -0
  213. package/dist/mcp/tools/mutation.js.map +1 -0
  214. package/dist/mcp/tools/query.d.ts +2 -0
  215. package/dist/mcp/tools/query.d.ts.map +1 -0
  216. package/dist/mcp/tools/query.js +130 -0
  217. package/dist/mcp/tools/query.js.map +1 -0
  218. package/dist/mcp/tools/search.d.ts +2 -0
  219. package/dist/mcp/tools/search.d.ts.map +1 -0
  220. package/dist/mcp/tools/search.js +168 -0
  221. package/dist/mcp/tools/search.js.map +1 -0
  222. package/dist/model-instance.d.ts +11 -0
  223. package/dist/model-instance.d.ts.map +1 -0
  224. package/dist/model-instance.js +208 -0
  225. package/dist/model-instance.js.map +1 -0
  226. package/dist/node-shortcuts.d.ts +25 -0
  227. package/dist/node-shortcuts.d.ts.map +1 -0
  228. package/dist/node-shortcuts.js +67 -0
  229. package/dist/node-shortcuts.js.map +1 -0
  230. package/dist/parse.d.ts +45 -0
  231. package/dist/parse.d.ts.map +1 -0
  232. package/dist/parse.js +98 -0
  233. package/dist/parse.js.map +1 -0
  234. package/dist/query/collection-query.d.ts +39 -0
  235. package/dist/query/collection-query.d.ts.map +1 -0
  236. package/dist/query/collection-query.js +184 -0
  237. package/dist/query/collection-query.js.map +1 -0
  238. package/dist/query/index.d.ts +8 -0
  239. package/dist/query/index.d.ts.map +1 -0
  240. package/dist/query/index.js +5 -0
  241. package/dist/query/index.js.map +1 -0
  242. package/dist/query/operators.d.ts +3 -0
  243. package/dist/query/operators.d.ts.map +1 -0
  244. package/dist/query/operators.js +18 -0
  245. package/dist/query/operators.js.map +1 -0
  246. package/dist/query/query-builder.d.ts +34 -0
  247. package/dist/query/query-builder.d.ts.map +1 -0
  248. package/dist/query/query-builder.js +84 -0
  249. package/dist/query/query-builder.js.map +1 -0
  250. package/dist/query/query-dsl.d.ts +36 -0
  251. package/dist/query/query-dsl.d.ts.map +1 -0
  252. package/dist/query/query-dsl.js +195 -0
  253. package/dist/query/query-dsl.js.map +1 -0
  254. package/dist/relationships/belongs-to.d.ts +9 -0
  255. package/dist/relationships/belongs-to.d.ts.map +1 -0
  256. package/dist/relationships/belongs-to.js +34 -0
  257. package/dist/relationships/belongs-to.js.map +1 -0
  258. package/dist/relationships/has-many.d.ts +23 -0
  259. package/dist/relationships/has-many.d.ts.map +1 -0
  260. package/dist/relationships/has-many.js +136 -0
  261. package/dist/relationships/has-many.js.map +1 -0
  262. package/dist/relationships/index.d.ts +26 -0
  263. package/dist/relationships/index.d.ts.map +1 -0
  264. package/dist/relationships/index.js +32 -0
  265. package/dist/relationships/index.js.map +1 -0
  266. package/dist/relationships/types.d.ts +2 -0
  267. package/dist/relationships/types.d.ts.map +1 -0
  268. package/dist/relationships/types.js +2 -0
  269. package/dist/relationships/types.js.map +1 -0
  270. package/dist/search/document-inputs.d.ts +30 -0
  271. package/dist/search/document-inputs.d.ts.map +1 -0
  272. package/dist/search/document-inputs.js +52 -0
  273. package/dist/search/document-inputs.js.map +1 -0
  274. package/dist/search/luca-semantic-search.d.ts +27 -0
  275. package/dist/search/luca-semantic-search.d.ts.map +1 -0
  276. package/dist/search/luca-semantic-search.js +56 -0
  277. package/dist/search/luca-semantic-search.js.map +1 -0
  278. package/dist/section.d.ts +21 -0
  279. package/dist/section.d.ts.map +1 -0
  280. package/dist/section.js +21 -0
  281. package/dist/section.js.map +1 -0
  282. package/dist/types.d.ts +185 -0
  283. package/dist/types.d.ts.map +1 -0
  284. package/dist/types.js +2 -0
  285. package/dist/types.js.map +1 -0
  286. package/dist/utils/index.d.ts +9 -0
  287. package/dist/utils/index.d.ts.map +1 -0
  288. package/dist/utils/index.js +8 -0
  289. package/dist/utils/index.js.map +1 -0
  290. package/dist/utils/inflect.d.ts +6 -0
  291. package/dist/utils/inflect.d.ts.map +1 -0
  292. package/dist/utils/inflect.js +75 -0
  293. package/dist/utils/inflect.js.map +1 -0
  294. package/dist/utils/match-pattern.d.ts +25 -0
  295. package/dist/utils/match-pattern.d.ts.map +1 -0
  296. package/dist/utils/match-pattern.js +52 -0
  297. package/dist/utils/match-pattern.js.map +1 -0
  298. package/dist/utils/normalize-headings.d.ts +8 -0
  299. package/dist/utils/normalize-headings.d.ts.map +1 -0
  300. package/dist/utils/normalize-headings.js +19 -0
  301. package/dist/utils/normalize-headings.js.map +1 -0
  302. package/dist/utils/parse-table.d.ts +7 -0
  303. package/dist/utils/parse-table.d.ts.map +1 -0
  304. package/dist/utils/parse-table.js +17 -0
  305. package/dist/utils/parse-table.js.map +1 -0
  306. package/dist/utils/read-directory.d.ts +5 -0
  307. package/dist/utils/read-directory.d.ts.map +1 -0
  308. package/dist/utils/read-directory.js +29 -0
  309. package/dist/utils/read-directory.js.map +1 -0
  310. package/dist/utils/stringify-ast.d.ts +6 -0
  311. package/dist/utils/stringify-ast.d.ts.map +1 -0
  312. package/dist/utils/stringify-ast.js +8 -0
  313. package/dist/utils/stringify-ast.js.map +1 -0
  314. package/dist/utils/strip-markdown.d.ts +11 -0
  315. package/dist/utils/strip-markdown.d.ts.map +1 -0
  316. package/dist/utils/strip-markdown.js +48 -0
  317. package/dist/utils/strip-markdown.js.map +1 -0
  318. package/dist/validator.d.ts +10 -0
  319. package/dist/validator.d.ts.map +1 -0
  320. package/dist/validator.js +55 -0
  321. package/dist/validator.js.map +1 -0
  322. package/package.json +3 -3
  323. package/src/document.ts +26 -8
@@ -0,0 +1,684 @@
1
+ import path from "path";
2
+ import matter from "gray-matter";
3
+ import picomatch from "picomatch";
4
+ import { Document } from "./document";
5
+ import { CollectionQuery } from "./query/collection-query";
6
+ import { createModelInstance } from "./model-instance";
7
+ import { pluralize } from "./utils/inflect";
8
+ import { Base } from "./base-model";
9
+ import { NodeStorageAdapter } from "./adapters/node-fs";
10
+ function describeZodType(schema) {
11
+ const def = schema?._zod?.def;
12
+ if (!def)
13
+ return { type: "unknown", optional: false };
14
+ const description = schema.description ?? def.description;
15
+ let optional = schema._zod.optout === "optional";
16
+ let defaultValue = undefined;
17
+ if (def.type === "default") {
18
+ defaultValue = def.defaultValue;
19
+ const inner = describeZodType(def.innerType);
20
+ return { type: inner.type, defaultValue, optional: true, description: description ?? inner.description };
21
+ }
22
+ if (def.type === "optional") {
23
+ const inner = describeZodType(def.innerType);
24
+ return { ...inner, optional: true, description: description ?? inner.description };
25
+ }
26
+ if (def.type === "nullable") {
27
+ const inner = describeZodType(def.innerType);
28
+ return { type: `${inner.type} | null`, optional: inner.optional, defaultValue: inner.defaultValue, description: description ?? inner.description };
29
+ }
30
+ if (def.type === "enum") {
31
+ const values = Object.keys(def.entries);
32
+ return { type: `enum(\`${values.join("`, `")}\`)`, optional, description };
33
+ }
34
+ if (def.type === "array") {
35
+ const element = describeZodType(def.element);
36
+ return { type: `${element.type}[]`, optional, description };
37
+ }
38
+ if (def.type === "record") {
39
+ const valType = describeZodType(def.valueType);
40
+ return { type: `record<string, ${valType.type}>`, optional, description };
41
+ }
42
+ if (def.type === "literal") {
43
+ return { type: `literal(${JSON.stringify(def.value)})`, optional, description };
44
+ }
45
+ if (def.type === "union") {
46
+ const options = def.options.map((o) => describeZodType(o).type);
47
+ return { type: options.join(" | "), optional, description };
48
+ }
49
+ return { type: def.type ?? "unknown", optional, description };
50
+ }
51
+ export function introspectMetaSchema(schema) {
52
+ const shape = schema?._zod?.def?.shape;
53
+ if (!shape)
54
+ return [];
55
+ return Object.entries(shape).map(([name, fieldSchema]) => {
56
+ const info = describeZodType(fieldSchema);
57
+ return {
58
+ name,
59
+ type: info.type,
60
+ required: !info.optional,
61
+ ...(info.defaultValue !== undefined && { defaultValue: info.defaultValue }),
62
+ ...(info.description && { description: info.description }),
63
+ };
64
+ });
65
+ }
66
+ function isModelDefinition(value) {
67
+ return (typeof value === "object" &&
68
+ value !== null &&
69
+ typeof value.name === "string" &&
70
+ typeof value.prefix === "string" &&
71
+ "meta" in value);
72
+ }
73
+ export class Collection {
74
+ rootPath;
75
+ name;
76
+ extensions;
77
+ #adapter;
78
+ #items = new Map();
79
+ #documents = new Map();
80
+ #models = new Map();
81
+ #actions = new Map();
82
+ #loaded = false;
83
+ #autoDiscover;
84
+ #moduleLoader;
85
+ constructor(options) {
86
+ this.#adapter = options.adapter ?? new NodeStorageAdapter();
87
+ // Resolve to an absolute path for local FS (NodeStorageAdapter default).
88
+ // When a custom adapter is provided, rootPath is the backend-specific root
89
+ // (e.g. a bucket prefix for R2) and is stored as-is.
90
+ this.rootPath = options.adapter
91
+ ? options.rootPath
92
+ : path.resolve(options.rootPath);
93
+ this.name = options.name ?? options.rootPath;
94
+ this.extensions = options.extensions ?? ["mdx", "md"];
95
+ this.#autoDiscover = options.autoDiscover ?? true;
96
+ this.#moduleLoader = options.moduleLoader;
97
+ }
98
+ // ─── Model registration ───
99
+ /**
100
+ * Register a model definition with this collection.
101
+ * Accepts the output of defineModel().
102
+ */
103
+ register(definition) {
104
+ this.#models.set(definition.name, definition);
105
+ return this;
106
+ }
107
+ /** Get a model definition by name */
108
+ getModelDefinition(name) {
109
+ return this.#models.get(name);
110
+ }
111
+ get modelDefinitions() {
112
+ return Array.from(this.#models.values());
113
+ }
114
+ // ─── Model auto-discovery ───
115
+ /**
116
+ * Discover and register model definitions from a
117
+ * models.{ts,js,mjs} file in the collection's root path.
118
+ */
119
+ async #discoverModels() {
120
+ for (const ext of ["ts", "js", "mjs"]) {
121
+ const candidate = path.resolve(this.rootPath, `models.${ext}`);
122
+ try {
123
+ const mod = this.#moduleLoader
124
+ ? await this.#moduleLoader(candidate)
125
+ : await import(candidate);
126
+ for (const value of Object.values(mod)) {
127
+ if (isModelDefinition(value) && !this.#models.has(value.name)) {
128
+ this.register(value);
129
+ }
130
+ }
131
+ return;
132
+ }
133
+ catch {
134
+ continue;
135
+ }
136
+ }
137
+ }
138
+ // ─── Loading ───
139
+ get loaded() {
140
+ return this.#loaded;
141
+ }
142
+ get items() {
143
+ return this.#items;
144
+ }
145
+ get documents() {
146
+ return this.#documents;
147
+ }
148
+ get available() {
149
+ return Array.from(this.#items.keys())
150
+ .filter((id) => !this.#isExcludedByModel(id))
151
+ .sort();
152
+ }
153
+ /**
154
+ * Match available pathIds against one or more glob patterns.
155
+ */
156
+ matchPaths(patterns) {
157
+ const isMatch = picomatch(patterns);
158
+ return this.available.filter((id) => isMatch(id));
159
+ }
160
+ /**
161
+ * Check if a pathId is excluded by any model's exclude patterns.
162
+ * Patterns are matched against the full pathId using picomatch (globs) or RegExp.
163
+ */
164
+ #isExcludedByModel(pathId) {
165
+ for (const def of this.modelDefinitions) {
166
+ if (!def.exclude || def.exclude.length === 0)
167
+ continue;
168
+ if (!pathId.startsWith(def.prefix))
169
+ continue;
170
+ for (const pattern of def.exclude) {
171
+ if (pattern instanceof RegExp) {
172
+ if (pattern.test(pathId))
173
+ return true;
174
+ }
175
+ else {
176
+ if (picomatch.isMatch(pathId, pattern))
177
+ return true;
178
+ }
179
+ }
180
+ }
181
+ return false;
182
+ }
183
+ /**
184
+ * Recursively load all markdown files from rootPath.
185
+ * Parses frontmatter with gray-matter, stores content and metadata.
186
+ */
187
+ async load(options = {}) {
188
+ const refresh = options.refresh ?? false;
189
+ if (this.#loaded && !refresh) {
190
+ return this;
191
+ }
192
+ // Auto-discover models if none have been manually registered
193
+ if (this.#models.size === 0 && this.#autoDiscover) {
194
+ await this.#discoverModels();
195
+ }
196
+ // Auto-register Base model as catch-all if not already registered
197
+ if (!this.#models.has("Base")) {
198
+ this.register(Base);
199
+ }
200
+ if (this.#loaded && refresh) {
201
+ this.#items.clear();
202
+ }
203
+ const extensionPattern = new RegExp(`\\.(${this.extensions.join("|")})$`, "i");
204
+ const entries = await this.#adapter.listFiles(this.rootPath, extensionPattern);
205
+ await Promise.all(entries.map(async ({ key, stat }) => {
206
+ const pathId = this.getPathId(key);
207
+ // Globally exclude templates directory — templates are only used
208
+ // for scaffolding and should never appear in queries or listings
209
+ if (pathId.startsWith("templates/") || pathId === "templates")
210
+ return;
211
+ const raw = await this.#adapter.readFile(key);
212
+ const { data, content } = matter(raw);
213
+ this.#items.set(pathId, {
214
+ raw,
215
+ content,
216
+ meta: data,
217
+ path: key,
218
+ createdAt: stat.createdAt,
219
+ updatedAt: stat.updatedAt,
220
+ size: stat.size,
221
+ });
222
+ }));
223
+ // Refresh any already-created documents
224
+ if (this.#loaded && refresh) {
225
+ // Evict documents that no longer exist on disk
226
+ for (const [pathId] of this.#documents) {
227
+ if (!this.#items.has(pathId)) {
228
+ this.#documents.delete(pathId);
229
+ }
230
+ }
231
+ await Promise.all(Array.from(this.#documents.values()).map((doc) => doc.reload()));
232
+ }
233
+ this.#loaded = true;
234
+ return this;
235
+ }
236
+ // ─── Document access (lazy creation) ───
237
+ /**
238
+ * Get or create a Document for the given pathId.
239
+ * Documents are cached: calling document("foo") twice returns the same instance.
240
+ */
241
+ document(pathId) {
242
+ if (!this.#loaded) {
243
+ throw new Error("Collection has not been loaded. Call load() first.");
244
+ }
245
+ // Strip known extensions if provided (e.g. "some-doc.md" -> "some-doc")
246
+ const extensionPattern = new RegExp(`\\.(${this.extensions.join("|")})$`, "i");
247
+ pathId = pathId.replace(extensionPattern, "");
248
+ let doc = this.#documents.get(pathId);
249
+ if (doc)
250
+ return doc;
251
+ const item = this.#items.get(pathId);
252
+ if (!item) {
253
+ throw new Error(`Could not find document "${pathId}"`);
254
+ }
255
+ doc = new Document({
256
+ id: pathId,
257
+ content: item.content,
258
+ meta: item.meta,
259
+ collection: this,
260
+ });
261
+ this.#documents.set(pathId, doc);
262
+ return doc;
263
+ }
264
+ /**
265
+ * Creates a new Document tied to this collection without it
266
+ * existing in the items map. Used for in-memory documents
267
+ * (e.g., extracted from hasMany relationships).
268
+ */
269
+ createDocument(attrs) {
270
+ return new Document({
271
+ id: attrs.id,
272
+ content: attrs.content ?? "",
273
+ meta: attrs.meta ?? {},
274
+ collection: this,
275
+ ast: attrs.ast,
276
+ });
277
+ }
278
+ // ─── Model access ───
279
+ /**
280
+ * Get a typed model instance for a document.
281
+ * The definition parameter carries the type information.
282
+ */
283
+ getModel(pathId, definition) {
284
+ const doc = this.document(pathId);
285
+ return createModelInstance(doc, definition, this);
286
+ }
287
+ /**
288
+ * Determine which model definition matches a document,
289
+ * using match functions or prefix matching.
290
+ */
291
+ findModelDefinition(pathId) {
292
+ const item = this.#items.get(pathId);
293
+ if (!item)
294
+ return undefined;
295
+ // 1. Explicit _model meta key takes priority
296
+ if (item.meta._model && typeof item.meta._model === "string") {
297
+ const explicit = this.#models.get(item.meta._model);
298
+ if (explicit)
299
+ return explicit;
300
+ }
301
+ // 2. Check non-Base models by match/prefix
302
+ let baseModel;
303
+ for (const def of this.#models.values()) {
304
+ if (def.name === "Base") {
305
+ baseModel = def;
306
+ continue;
307
+ }
308
+ if (def.match) {
309
+ if (def.match({ id: pathId, meta: item.meta }))
310
+ return def;
311
+ }
312
+ else {
313
+ if (pathId.startsWith(def.prefix))
314
+ return def;
315
+ }
316
+ }
317
+ // 3. Fall back to Base model only for root-level documents (no subfolder)
318
+ if (baseModel && !pathId.includes("/")) {
319
+ return baseModel;
320
+ }
321
+ return undefined;
322
+ }
323
+ // ─── Querying ───
324
+ /**
325
+ * Create a typed query for model instances.
326
+ */
327
+ query(definition) {
328
+ return new CollectionQuery(this, definition);
329
+ }
330
+ // ─── Persistence ───
331
+ async saveItem(pathId, options) {
332
+ const extension = options.extension ?? ".md";
333
+ if (options.content == null) {
334
+ throw new Error(`saveItem("${pathId}"): content must be a string, got ${typeof options.content}. ` +
335
+ `Use doc.save() or pass the full raw content including frontmatter.`);
336
+ }
337
+ const { data, content } = matter(options.content);
338
+ if (!this.#items.has(pathId)) {
339
+ const filePath = this.resolve(`${pathId}${extension}`);
340
+ this.#items.set(pathId, {
341
+ raw: options.content,
342
+ content,
343
+ meta: data,
344
+ path: filePath,
345
+ createdAt: new Date(),
346
+ updatedAt: new Date(),
347
+ size: Buffer.byteLength(options.content, "utf8"),
348
+ });
349
+ }
350
+ const item = this.#items.get(pathId);
351
+ const filePath = item.path;
352
+ await this.#adapter.writeFile(filePath, options.content);
353
+ // Update the stored item
354
+ item.raw = options.content;
355
+ item.content = content;
356
+ item.meta = data;
357
+ item.updatedAt = new Date();
358
+ item.size = Buffer.byteLength(options.content, "utf8");
359
+ // Invalidate cached Document so the next query rebuilds from fresh data
360
+ this.#documents.delete(pathId);
361
+ return item;
362
+ }
363
+ async deleteItem(pathId) {
364
+ const item = this.#items.get(pathId);
365
+ if (!item)
366
+ return this;
367
+ await this.#adapter.deleteFile(item.path);
368
+ this.#items.delete(pathId);
369
+ this.#documents.delete(pathId);
370
+ return this;
371
+ }
372
+ async readItem(pathId, extension = "md") {
373
+ let filePath;
374
+ if (this.#items.has(pathId)) {
375
+ filePath = this.#items.get(pathId).path;
376
+ }
377
+ else {
378
+ filePath = this.resolve(`${pathId}.${extension}`);
379
+ }
380
+ const raw = await this.#adapter.readFile(filePath);
381
+ const stat = await this.#adapter.stat(filePath);
382
+ const { data, content } = matter(raw);
383
+ const item = {
384
+ raw,
385
+ content,
386
+ meta: data,
387
+ path: filePath,
388
+ createdAt: stat.createdAt,
389
+ updatedAt: stat.updatedAt,
390
+ size: stat.size,
391
+ };
392
+ this.#items.set(pathId, item);
393
+ return item;
394
+ }
395
+ // ─── Actions ───
396
+ action(name, fn) {
397
+ if (typeof fn !== "function") {
398
+ throw new Error("Expected a function for collection action");
399
+ }
400
+ this.#actions.set(name, fn);
401
+ return this;
402
+ }
403
+ get actions() {
404
+ return this.#actions;
405
+ }
406
+ get availableActions() {
407
+ return Array.from(this.#actions.keys());
408
+ }
409
+ async runAction(name, ...args) {
410
+ const fn = this.#actions.get(name);
411
+ if (!fn) {
412
+ throw new Error(`Action "${name}" does not exist on this collection.`);
413
+ }
414
+ return fn(this, ...args);
415
+ }
416
+ // ─── Plugin system ───
417
+ use(plugin, options) {
418
+ plugin(this, options);
419
+ return this;
420
+ }
421
+ // ─── Utilities ───
422
+ resolve(...args) {
423
+ return this.#adapter.join(this.rootPath, ...args);
424
+ }
425
+ getPathId(key) {
426
+ const relativePath = this.#adapter.relative(this.rootPath, key);
427
+ return relativePath.replace(/\.[a-z]+$/i, "");
428
+ }
429
+ // ─── Table of Contents ───
430
+ /**
431
+ * Generate a markdown table of contents with links to each document.
432
+ * Links use relative paths that work for GitHub navigation.
433
+ *
434
+ * If models are registered, items are grouped under model name headings.
435
+ * Items that don't match any model appear under an "Other" group.
436
+ *
437
+ * @example
438
+ * ```ts
439
+ * const toc = collection.tableOfContents({ title: "Project Docs" });
440
+ * // # Project Docs
441
+ * //
442
+ * // ## Epics
443
+ * //
444
+ * // - [Authentication](./epics/authentication.mdx)
445
+ * // - [Searching And Browsing](./epics/searching-and-browsing.mdx)
446
+ * //
447
+ * // ## Stories
448
+ * //
449
+ * // - [A User should be able to register.](./stories/authentication/a-user-should-be-able-to-register.mdx)
450
+ * ```
451
+ */
452
+ tableOfContents(options = {}) {
453
+ if (!this.#loaded) {
454
+ throw new Error("Collection has not been loaded. Call load() first.");
455
+ }
456
+ const basePath = options.basePath ?? ".";
457
+ const lines = [];
458
+ if (options.title) {
459
+ lines.push(`# ${options.title}`, "");
460
+ }
461
+ const sorted = [...this.available].sort();
462
+ if (this.#models.size > 0) {
463
+ const grouped = new Map();
464
+ const ungrouped = [];
465
+ for (const pathId of sorted) {
466
+ const def = this.findModelDefinition(pathId);
467
+ if (def) {
468
+ let group = grouped.get(def.name);
469
+ if (!group) {
470
+ group = [];
471
+ grouped.set(def.name, group);
472
+ }
473
+ group.push(pathId);
474
+ }
475
+ else {
476
+ ungrouped.push(pathId);
477
+ }
478
+ }
479
+ for (const def of this.modelDefinitions) {
480
+ const ids = grouped.get(def.name);
481
+ if (!ids || ids.length === 0)
482
+ continue;
483
+ lines.push(`## ${pluralize(def.name)}`, "");
484
+ for (const id of ids) {
485
+ lines.push(this.#tocEntry(id, basePath));
486
+ }
487
+ lines.push("");
488
+ }
489
+ if (ungrouped.length > 0) {
490
+ lines.push(`## Other`, "");
491
+ for (const id of ungrouped) {
492
+ lines.push(this.#tocEntry(id, basePath));
493
+ }
494
+ lines.push("");
495
+ }
496
+ }
497
+ else {
498
+ for (const id of sorted) {
499
+ lines.push(this.#tocEntry(id, basePath));
500
+ }
501
+ lines.push("");
502
+ }
503
+ return lines.join("\n").trimEnd() + "\n";
504
+ }
505
+ /**
506
+ * Renders an ASCII file tree of all documents in the collection.
507
+ *
508
+ * @example
509
+ * ```
510
+ * const tree = collection.renderFileTree();
511
+ * // epics/
512
+ * // authentication.mdx
513
+ * // searching-and-browsing.mdx
514
+ * // stories/
515
+ * // authentication/
516
+ * // a-user-should-be-able-to-register.mdx
517
+ * ```
518
+ */
519
+ renderFileTree() {
520
+ if (!this.#loaded) {
521
+ throw new Error("Collection has not been loaded. Call load() first.");
522
+ }
523
+ const sorted = [...this.available].sort();
524
+ const root = { children: new Map() };
525
+ for (const pathId of sorted) {
526
+ const item = this.#items.get(pathId);
527
+ const ext = this.#adapter.extname(item.path);
528
+ const fullPath = `${pathId}${ext}`;
529
+ const parts = fullPath.split("/");
530
+ let node = root;
531
+ for (const part of parts) {
532
+ if (!node.children.has(part)) {
533
+ node.children.set(part, { children: new Map() });
534
+ }
535
+ node = node.children.get(part);
536
+ }
537
+ }
538
+ // Render the tree with indentation and connector lines
539
+ const lines = [];
540
+ const render = (node, prefix) => {
541
+ const entries = [...node.children.entries()];
542
+ entries.forEach(([name, child], i) => {
543
+ const isLast = i === entries.length - 1;
544
+ const connector = isLast ? "└── " : "├── ";
545
+ const isDir = child.children.size > 0;
546
+ lines.push(`${prefix}${connector}${name}${isDir ? "/" : ""}`);
547
+ const nextPrefix = prefix + (isLast ? " " : "│ ");
548
+ render(child, nextPrefix);
549
+ });
550
+ };
551
+ render(root, "");
552
+ return lines.join("\n") + "\n";
553
+ }
554
+ #tocEntry(pathId, basePath) {
555
+ const item = this.#items.get(pathId);
556
+ const ext = this.#adapter.extname(item.path);
557
+ const relativePath = `${basePath}/${pathId}${ext}`;
558
+ const doc = this.document(pathId);
559
+ return `- [${doc.title}](${relativePath})`;
560
+ }
561
+ // ─── Model Summary ───
562
+ /**
563
+ * Generate a plain-text summary of the collection and its models.
564
+ * Returns the same output as `cnotes inspect`.
565
+ */
566
+ generateModelSummary(options = {}) {
567
+ if (!this.#loaded) {
568
+ throw new Error("Collection has not been loaded. Call load() first.");
569
+ }
570
+ const lines = [];
571
+ lines.push(`Collection: ${this.name}`);
572
+ lines.push(`Root: ${this.rootPath}`);
573
+ lines.push(`Items: ${this.available.length}`);
574
+ lines.push("");
575
+ for (const def of this.modelDefinitions) {
576
+ lines.push(` Model: ${def.name}`);
577
+ if (def.description) {
578
+ lines.push(` Description: ${def.description}`);
579
+ }
580
+ if (def.pattern) {
581
+ const patterns = Array.isArray(def.pattern) ? def.pattern : [def.pattern];
582
+ lines.push(` Path prefix: ${patterns.join(", ")}`);
583
+ }
584
+ else {
585
+ const rel = path.relative(process.cwd(), path.join(this.rootPath, def.prefix));
586
+ lines.push(` Path prefix: ${rel}/*.md`);
587
+ }
588
+ const fields = introspectMetaSchema(def.meta);
589
+ lines.push(` Meta: ${fields.length > 0 ? fields.map((f) => `${f.name}(${f.type})`).join(", ") : "(none)"}`);
590
+ lines.push(` Sections: ${Object.keys(def.sections).join(", ") || "(none)"}`);
591
+ lines.push(` Relationships: ${Object.keys(def.relationships).join(", ") || "(none)"}`);
592
+ if (options.includeIds) {
593
+ const matchingItems = this.available.filter((id) => this.findModelDefinition(id)?.name === def.name);
594
+ if (matchingItems.length > 0) {
595
+ lines.push(` IDs: ${[...matchingItems].sort().join(", ")}`);
596
+ }
597
+ }
598
+ lines.push("");
599
+ }
600
+ if (this.availableActions.length > 0) {
601
+ lines.push(`Actions: ${this.availableActions.join(", ")}`);
602
+ }
603
+ return lines.join("\n").trimEnd();
604
+ }
605
+ /**
606
+ * Write README.md to the collection root.
607
+ * Preserves the `## Overview` section if it already exists.
608
+ * The generated summary is placed in the `## Summary` section.
609
+ */
610
+ async saveModelSummary(options = {}) {
611
+ const summary = this.generateModelSummary(options);
612
+ const modelsPath = this.#adapter.join(this.rootPath, "README.md");
613
+ // Preserve existing Overview section content
614
+ let overview = "";
615
+ try {
616
+ const existing = await this.#adapter.readFile(modelsPath);
617
+ const overviewStart = existing.indexOf("## Overview");
618
+ if (overviewStart !== -1) {
619
+ const contentStart = existing.indexOf("\n", overviewStart) + 1;
620
+ const nextHeading = existing.indexOf("\n## ", contentStart);
621
+ const contentEnd = nextHeading !== -1 ? nextHeading : existing.length;
622
+ overview = existing.slice(contentStart, contentEnd).trim();
623
+ }
624
+ }
625
+ catch {
626
+ // No existing file
627
+ }
628
+ const lines = [
629
+ "# Models",
630
+ "",
631
+ "## Overview",
632
+ "",
633
+ overview || "",
634
+ "",
635
+ "## Summary",
636
+ "",
637
+ "```",
638
+ summary,
639
+ "```",
640
+ "",
641
+ ];
642
+ const markdown = lines.join("\n");
643
+ await this.#adapter.writeFile(modelsPath, markdown);
644
+ return summary;
645
+ }
646
+ // ─── Serialization ───
647
+ toJSON(options = {}) {
648
+ const models = this.modelDefinitions.map((def) => ({
649
+ name: def.name,
650
+ prefix: def.prefix,
651
+ matchingPaths: this.available.filter((id) => this.findModelDefinition(id)?.name === def.name),
652
+ }));
653
+ const result = {
654
+ models,
655
+ itemIds: this.available,
656
+ };
657
+ if (options.content) {
658
+ const items = {};
659
+ for (const [id, item] of this.#items.entries()) {
660
+ items[id] = item;
661
+ }
662
+ result.items = items;
663
+ }
664
+ return result;
665
+ }
666
+ async export(options = {}) {
667
+ if (!this.#loaded)
668
+ await this.load();
669
+ const json = this.toJSON(options);
670
+ const modelData = {};
671
+ for (const def of this.modelDefinitions) {
672
+ const query = new CollectionQuery(this, def);
673
+ const instances = await query.fetchAll();
674
+ modelData[def.name] = instances.map((inst) => inst.toJSON());
675
+ }
676
+ return {
677
+ ...json,
678
+ modelData,
679
+ rootPath: this.rootPath,
680
+ name: this.name,
681
+ };
682
+ }
683
+ }
684
+ //# sourceMappingURL=collection.js.map