cursedbelt-core 1.0.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 (307) hide show
  1. package/LICENSE +15 -0
  2. package/dist/client/wsWire.d.ts +49 -0
  3. package/dist/client/wsWire.js +57 -0
  4. package/dist/config/appConfig.d.ts +361 -0
  5. package/dist/config/appConfig.js +308 -0
  6. package/dist/core/activity/index.d.ts +8 -0
  7. package/dist/core/activity/index.js +7 -0
  8. package/dist/core/activity/model.d.ts +115 -0
  9. package/dist/core/activity/model.js +63 -0
  10. package/dist/core/analytics/index.d.ts +18 -0
  11. package/dist/core/analytics/index.js +18 -0
  12. package/dist/core/analytics/range.d.ts +186 -0
  13. package/dist/core/analytics/range.js +271 -0
  14. package/dist/core/analytics/schedule.d.ts +301 -0
  15. package/dist/core/analytics/schedule.js +372 -0
  16. package/dist/core/category/index.d.ts +3 -0
  17. package/dist/core/category/index.js +6 -0
  18. package/dist/core/category/memoryStore.d.ts +21 -0
  19. package/dist/core/category/memoryStore.js +97 -0
  20. package/dist/core/category/tree.d.ts +26 -0
  21. package/dist/core/category/tree.js +113 -0
  22. package/dist/core/category/types.d.ts +48 -0
  23. package/dist/core/category/types.js +8 -0
  24. package/dist/core/color.d.ts +14 -0
  25. package/dist/core/color.js +32 -0
  26. package/dist/core/colorScheme/colorSchemeStore.d.ts +70 -0
  27. package/dist/core/colorScheme/colorSchemeStore.js +123 -0
  28. package/dist/core/colorScheme/index.d.ts +7 -0
  29. package/dist/core/colorScheme/index.js +7 -0
  30. package/dist/core/domainOwners.d.ts +19 -0
  31. package/dist/core/domainOwners.js +15 -0
  32. package/dist/core/download.d.ts +14 -0
  33. package/dist/core/download.js +37 -0
  34. package/dist/core/events/ccEventBus.d.ts +44 -0
  35. package/dist/core/events/ccEventBus.js +70 -0
  36. package/dist/core/file-tree/fileTreeModel.d.ts +276 -0
  37. package/dist/core/file-tree/fileTreeModel.js +312 -0
  38. package/dist/core/folder-tree/actions.d.ts +84 -0
  39. package/dist/core/folder-tree/actions.js +119 -0
  40. package/dist/core/folder-tree/index.d.ts +3 -0
  41. package/dist/core/folder-tree/index.js +20 -0
  42. package/dist/core/folder-tree/path.d.ts +76 -0
  43. package/dist/core/folder-tree/path.js +146 -0
  44. package/dist/core/folder-tree/tree.d.ts +86 -0
  45. package/dist/core/folder-tree/tree.js +184 -0
  46. package/dist/core/layout/index.d.ts +1 -0
  47. package/dist/core/layout/index.js +7 -0
  48. package/dist/core/master-lock/index.d.ts +14 -0
  49. package/dist/core/master-lock/index.js +14 -0
  50. package/dist/core/master-lock/kdf.d.ts +63 -0
  51. package/dist/core/master-lock/kdf.js +117 -0
  52. package/dist/core/master-lock/policy.d.ts +32 -0
  53. package/dist/core/master-lock/policy.js +52 -0
  54. package/dist/core/master-lock/presence.d.ts +52 -0
  55. package/dist/core/master-lock/presence.js +61 -0
  56. package/dist/core/master-lock/wire.d.ts +139 -0
  57. package/dist/core/master-lock/wire.js +81 -0
  58. package/dist/core/media/bgRemoval.d.ts +27 -0
  59. package/dist/core/media/bgRemoval.js +101 -0
  60. package/dist/core/media/ffmpeg.d.ts +57 -0
  61. package/dist/core/media/ffmpeg.js +126 -0
  62. package/dist/core/media/format.d.ts +20 -0
  63. package/dist/core/media/format.js +54 -0
  64. package/dist/core/media/index.d.ts +12 -0
  65. package/dist/core/media/index.js +18 -0
  66. package/dist/core/media/mediaItem.d.ts +15 -0
  67. package/dist/core/media/mediaItem.js +34 -0
  68. package/dist/core/media/renderPlan.d.ts +63 -0
  69. package/dist/core/media/renderPlan.js +232 -0
  70. package/dist/core/media/segment.d.ts +79 -0
  71. package/dist/core/media/segment.js +247 -0
  72. package/dist/core/media/size.d.ts +1 -0
  73. package/dist/core/media/size.js +3 -0
  74. package/dist/core/media/trim.d.ts +21 -0
  75. package/dist/core/media/trim.js +40 -0
  76. package/dist/core/media/types.d.ts +148 -0
  77. package/dist/core/media/types.js +11 -0
  78. package/dist/core/media/uploadLimits.d.ts +7 -0
  79. package/dist/core/media/uploadLimits.js +16 -0
  80. package/dist/core/media/validate.d.ts +9 -0
  81. package/dist/core/media/validate.js +121 -0
  82. package/dist/core/media/videoCuts.d.ts +152 -0
  83. package/dist/core/media/videoCuts.js +284 -0
  84. package/dist/core/navigation/breadcrumbCompat.d.ts +24 -0
  85. package/dist/core/navigation/breadcrumbCompat.js +21 -0
  86. package/dist/core/navigation/coverageTest.d.ts +24 -0
  87. package/dist/core/navigation/coverageTest.js +48 -0
  88. package/dist/core/navigation/index.d.ts +6 -0
  89. package/dist/core/navigation/index.js +10 -0
  90. package/dist/core/navigation/migrateNavPreferences.d.ts +55 -0
  91. package/dist/core/navigation/migrateNavPreferences.js +63 -0
  92. package/dist/core/navigation/navigationModel.d.ts +165 -0
  93. package/dist/core/navigation/navigationModel.js +14 -0
  94. package/dist/core/navigation/navigationSelectors.d.ts +63 -0
  95. package/dist/core/navigation/navigationSelectors.js +167 -0
  96. package/dist/core/navigation/searchCatalogue.d.ts +23 -0
  97. package/dist/core/navigation/searchCatalogue.js +76 -0
  98. package/dist/core/net/reconnectSchedule.d.ts +46 -0
  99. package/dist/core/net/reconnectSchedule.js +38 -0
  100. package/dist/core/palette/ShortcutRegistry.d.ts +33 -0
  101. package/dist/core/palette/ShortcutRegistry.js +52 -0
  102. package/dist/core/palette/fuzzyRank.d.ts +18 -0
  103. package/dist/core/palette/fuzzyRank.js +73 -0
  104. package/dist/core/palette/hotkeys.d.ts +28 -0
  105. package/dist/core/palette/hotkeys.js +118 -0
  106. package/dist/core/palette/index.d.ts +3 -0
  107. package/dist/core/palette/index.js +8 -0
  108. package/dist/core/schema/projection.d.ts +84 -0
  109. package/dist/core/schema/projection.js +184 -0
  110. package/dist/core/session/claims.d.ts +145 -0
  111. package/dist/core/session/claims.js +157 -0
  112. package/dist/core/sharing/index.d.ts +5 -0
  113. package/dist/core/sharing/index.js +4 -0
  114. package/dist/core/sharing/model.d.ts +195 -0
  115. package/dist/core/sharing/model.js +150 -0
  116. package/dist/core/site/siteCopy.d.ts +92 -0
  117. package/dist/core/site/siteCopy.js +91 -0
  118. package/dist/core/slots.d.ts +22 -0
  119. package/dist/core/slots.js +31 -0
  120. package/dist/core/styles/constructableSheet.d.ts +51 -0
  121. package/dist/core/styles/constructableSheet.js +80 -0
  122. package/dist/core/tokens/registry.d.ts +119 -0
  123. package/dist/core/tokens/registry.js +447 -0
  124. package/dist/core/uiScale/index.d.ts +8 -0
  125. package/dist/core/uiScale/index.js +8 -0
  126. package/dist/core/uiScale/scale.d.ts +36 -0
  127. package/dist/core/uiScale/scale.js +47 -0
  128. package/dist/core/uiScale/uiScaleStore.d.ts +47 -0
  129. package/dist/core/uiScale/uiScaleStore.js +63 -0
  130. package/dist/core/wire/__fixtures__/rowsMarshalWorker.d.ts +1 -0
  131. package/dist/core/wire/__fixtures__/rowsMarshalWorker.js +23 -0
  132. package/dist/core/wire/index.d.ts +17 -0
  133. package/dist/core/wire/index.js +17 -0
  134. package/dist/core/wire/ingress.d.ts +44 -0
  135. package/dist/core/wire/ingress.js +134 -0
  136. package/dist/core/wire/marshalWorker.d.ts +70 -0
  137. package/dist/core/wire/marshalWorker.js +273 -0
  138. package/dist/core/wire/marshalling.d.ts +122 -0
  139. package/dist/core/wire/marshalling.js +126 -0
  140. package/dist/core/wire/wireHub.d.ts +80 -0
  141. package/dist/core/wire/wireHub.js +116 -0
  142. package/dist/ctgr/base64url.d.ts +8 -0
  143. package/dist/ctgr/base64url.js +39 -0
  144. package/dist/ctgr/chunkAccumulator.d.ts +214 -0
  145. package/dist/ctgr/chunkAccumulator.js +307 -0
  146. package/dist/ctgr/chunkStore.d.ts +24 -0
  147. package/dist/ctgr/chunkStore.js +125 -0
  148. package/dist/ctgr/codec.d.ts +44 -0
  149. package/dist/ctgr/codec.js +153 -0
  150. package/dist/ctgr/compress.d.ts +2 -0
  151. package/dist/ctgr/compress.js +20 -0
  152. package/dist/ctgr/crc32.d.ts +2 -0
  153. package/dist/ctgr/crc32.js +23 -0
  154. package/dist/ctgr/digest.d.ts +1 -0
  155. package/dist/ctgr/digest.js +15 -0
  156. package/dist/ctgr/index.d.ts +9 -0
  157. package/dist/ctgr/index.js +17 -0
  158. package/dist/ctgr/types.d.ts +64 -0
  159. package/dist/ctgr/types.js +21 -0
  160. package/dist/ctgr/v0compat.d.ts +27 -0
  161. package/dist/ctgr/v0compat.js +128 -0
  162. package/dist/index.d.ts +15 -0
  163. package/dist/index.js +17 -0
  164. package/dist/shared/errors.d.ts +101 -0
  165. package/dist/shared/errors.js +99 -0
  166. package/dist/testing/bytes.d.ts +32 -0
  167. package/dist/testing/bytes.js +33 -0
  168. package/dist/testing/domInspect.d.ts +59 -0
  169. package/dist/testing/domInspect.js +115 -0
  170. package/dist/testing/fakeFetch.d.ts +23 -0
  171. package/dist/testing/fakeFetch.js +21 -0
  172. package/dist/testing/index.d.ts +3 -0
  173. package/dist/testing/index.js +11 -0
  174. package/package.json +373 -0
  175. package/src/client/wsWire.ts +83 -0
  176. package/src/config/appConfig.spec.ts +158 -0
  177. package/src/config/appConfig.ts +372 -0
  178. package/src/core/activity/index.ts +21 -0
  179. package/src/core/activity/model.ts +160 -0
  180. package/src/core/analytics/index.ts +18 -0
  181. package/src/core/analytics/range.spec.ts +280 -0
  182. package/src/core/analytics/range.ts +383 -0
  183. package/src/core/analytics/schedule.spec.ts +311 -0
  184. package/src/core/analytics/schedule.ts +624 -0
  185. package/src/core/category/category.spec.ts +144 -0
  186. package/src/core/category/index.ts +19 -0
  187. package/src/core/category/memoryStore.ts +104 -0
  188. package/src/core/category/tree.ts +118 -0
  189. package/src/core/category/types.ts +59 -0
  190. package/src/core/color.spec.ts +35 -0
  191. package/src/core/color.ts +35 -0
  192. package/src/core/colorScheme/colorScheme.spec.ts +127 -0
  193. package/src/core/colorScheme/colorSchemeStore.ts +184 -0
  194. package/src/core/colorScheme/index.ts +23 -0
  195. package/src/core/domainOwners.ts +32 -0
  196. package/src/core/download.spec.ts +38 -0
  197. package/src/core/download.ts +35 -0
  198. package/src/core/events/ccEventBus.spec.ts +90 -0
  199. package/src/core/events/ccEventBus.ts +120 -0
  200. package/src/core/file-tree/fileTreeModel.spec.ts +462 -0
  201. package/src/core/file-tree/fileTreeModel.ts +511 -0
  202. package/src/core/folder-tree/actions.spec.ts +103 -0
  203. package/src/core/folder-tree/actions.ts +153 -0
  204. package/src/core/folder-tree/index.ts +46 -0
  205. package/src/core/folder-tree/path.spec.ts +113 -0
  206. package/src/core/folder-tree/path.ts +148 -0
  207. package/src/core/folder-tree/tree.spec.ts +157 -0
  208. package/src/core/folder-tree/tree.ts +244 -0
  209. package/src/core/layout/index.ts +7 -0
  210. package/src/core/master-lock/index.ts +50 -0
  211. package/src/core/master-lock/kdf.spec.ts +95 -0
  212. package/src/core/master-lock/kdf.ts +145 -0
  213. package/src/core/master-lock/policy.ts +55 -0
  214. package/src/core/master-lock/presence.spec.ts +73 -0
  215. package/src/core/master-lock/presence.ts +71 -0
  216. package/src/core/master-lock/wire.ts +155 -0
  217. package/src/core/media/bgRemoval.spec.ts +120 -0
  218. package/src/core/media/bgRemoval.ts +123 -0
  219. package/src/core/media/ffmpeg.spec.ts +135 -0
  220. package/src/core/media/ffmpeg.ts +162 -0
  221. package/src/core/media/format.spec.ts +41 -0
  222. package/src/core/media/format.ts +58 -0
  223. package/src/core/media/index.ts +105 -0
  224. package/src/core/media/mediaItem.spec.ts +46 -0
  225. package/src/core/media/mediaItem.ts +40 -0
  226. package/src/core/media/renderPlan.spec.ts +140 -0
  227. package/src/core/media/renderPlan.ts +318 -0
  228. package/src/core/media/segment.spec.ts +159 -0
  229. package/src/core/media/segment.ts +352 -0
  230. package/src/core/media/size.spec.ts +19 -0
  231. package/src/core/media/size.ts +3 -0
  232. package/src/core/media/trim.spec.ts +75 -0
  233. package/src/core/media/trim.ts +60 -0
  234. package/src/core/media/types.ts +147 -0
  235. package/src/core/media/uploadLimits.ts +18 -0
  236. package/src/core/media/validate.spec.ts +106 -0
  237. package/src/core/media/validate.ts +131 -0
  238. package/src/core/media/videoCuts.spec.ts +327 -0
  239. package/src/core/media/videoCuts.ts +355 -0
  240. package/src/core/navigation/breadcrumbCompat.ts +44 -0
  241. package/src/core/navigation/coverageTest.ts +54 -0
  242. package/src/core/navigation/index.ts +50 -0
  243. package/src/core/navigation/migrateNavPreferences.ts +119 -0
  244. package/src/core/navigation/navigation.spec.ts +388 -0
  245. package/src/core/navigation/navigationModel.ts +185 -0
  246. package/src/core/navigation/navigationSelectors.ts +206 -0
  247. package/src/core/navigation/searchCatalogue.ts +89 -0
  248. package/src/core/net/reconnectSchedule.spec.ts +49 -0
  249. package/src/core/net/reconnectSchedule.ts +78 -0
  250. package/src/core/palette/ShortcutRegistry.ts +69 -0
  251. package/src/core/palette/fuzzyRank.ts +78 -0
  252. package/src/core/palette/hotkeys.ts +132 -0
  253. package/src/core/palette/index.ts +22 -0
  254. package/src/core/palette/palette.spec.ts +160 -0
  255. package/src/core/schema/projection.spec.ts +122 -0
  256. package/src/core/schema/projection.ts +225 -0
  257. package/src/core/session/claims.ts +181 -0
  258. package/src/core/sharing/index.ts +33 -0
  259. package/src/core/sharing/model.spec.ts +104 -0
  260. package/src/core/sharing/model.ts +296 -0
  261. package/src/core/site/siteCopy.ts +115 -0
  262. package/src/core/slots.ts +38 -0
  263. package/src/core/styles/constructableSheet.ts +86 -0
  264. package/src/core/tokens/registry.spec.ts +160 -0
  265. package/src/core/tokens/registry.ts +536 -0
  266. package/src/core/uiScale/index.ts +32 -0
  267. package/src/core/uiScale/scale.ts +63 -0
  268. package/src/core/uiScale/uiScale.spec.ts +77 -0
  269. package/src/core/uiScale/uiScaleStore.ts +100 -0
  270. package/src/core/wire/__fixtures__/rowsMarshalWorker.ts +24 -0
  271. package/src/core/wire/index.ts +56 -0
  272. package/src/core/wire/ingress.spec.ts +276 -0
  273. package/src/core/wire/ingress.ts +182 -0
  274. package/src/core/wire/marshalWorker.spec.ts +196 -0
  275. package/src/core/wire/marshalWorker.ts +361 -0
  276. package/src/core/wire/marshalling.spec.ts +139 -0
  277. package/src/core/wire/marshalling.ts +238 -0
  278. package/src/core/wire/wireHub.spec.ts +137 -0
  279. package/src/core/wire/wireHub.ts +213 -0
  280. package/src/ctgr/base64url.ts +44 -0
  281. package/src/ctgr/chunkAccumulator.ts +403 -0
  282. package/src/ctgr/chunkStore.spec.ts +101 -0
  283. package/src/ctgr/chunkStore.ts +168 -0
  284. package/src/ctgr/codec.spec.ts +208 -0
  285. package/src/ctgr/codec.ts +202 -0
  286. package/src/ctgr/compress.ts +21 -0
  287. package/src/ctgr/crc32.ts +23 -0
  288. package/src/ctgr/digest.ts +14 -0
  289. package/src/ctgr/index.ts +39 -0
  290. package/src/ctgr/types.ts +80 -0
  291. package/src/ctgr/v0compat.spec.ts +73 -0
  292. package/src/ctgr/v0compat.ts +156 -0
  293. package/src/declaredDepsAreImported.spec.ts +87 -0
  294. package/src/index.ts +25 -0
  295. package/src/leafSubpathsImportNothing.spec.ts +287 -0
  296. package/src/lockfileIsTracked.spec.ts +50 -0
  297. package/src/namedSubpathsResolve.spec.ts +122 -0
  298. package/src/noPathDeps.spec.ts +24 -0
  299. package/src/publishShape.spec.ts +89 -0
  300. package/src/shared/errors.ts +153 -0
  301. package/src/skippedTestsAreAnswered.spec.ts +221 -0
  302. package/src/testTmpRoot.spec.ts +102 -0
  303. package/src/testing/bytes.ts +38 -0
  304. package/src/testing/domInspect.spec.ts +79 -0
  305. package/src/testing/domInspect.ts +127 -0
  306. package/src/testing/fakeFetch.ts +26 -0
  307. package/src/testing/index.ts +11 -0
@@ -0,0 +1,6 @@
1
+ // src/core/category — reusable hierarchical category/folder model, reaching consumers
2
+ // through `cursedbelt/react`'s categoryTree. Pure and
3
+ // React-free (the `TreeView` adapter lives in `cursedbelt/react`), so servers
4
+ // can import the contract + store without pulling any UI runtime.
5
+ export { buildCategoryForest, categoryPath, descendantCategoryIds, flattenCategoryForest, wouldCreateCycle, } from './tree';
6
+ export { createMemoryCategoryStore } from './memoryStore';
@@ -0,0 +1,21 @@
1
+ import type { Category, CategoryCreateInput } from './types';
2
+ /**
3
+ * Zero-config in-memory {@link CategoryStore}: a per-boot `Map` per tenant, so a
4
+ * host that wires nothing up still gets a working (non-persistent) category
5
+ * tree — the same role `createMemoryNotesStore`/`createMemoryAuditSink` play.
6
+ * Swap in a persistent store (f's `createSqliteCategoryStore`) for real data.
7
+ *
8
+ * Deliberately not annotated `: CategoryStore` — the interface allows async
9
+ * returns, which would force callers (and these tests) to `await` values that
10
+ * are always synchronous here. Structural typing keeps it assignable wherever a
11
+ * `CategoryStore` is expected.
12
+ */
13
+ export declare function createMemoryCategoryStore(idFactory?: () => string): {
14
+ list(tenant: string): Category[];
15
+ create(tenant: string, input: CategoryCreateInput): Category;
16
+ rename(tenant: string, id: string, label: string): Category;
17
+ move(tenant: string, id: string, parentId: string | null, order?: number): Category;
18
+ /** Remove a category, promoting its direct children to the removed node's
19
+ * own parent so no subtree is orphaned. */
20
+ remove(tenant: string, id: string): void;
21
+ };
@@ -0,0 +1,97 @@
1
+ import { wouldCreateCycle } from './tree';
2
+ /**
3
+ * Zero-config in-memory {@link CategoryStore}: a per-boot `Map` per tenant, so a
4
+ * host that wires nothing up still gets a working (non-persistent) category
5
+ * tree — the same role `createMemoryNotesStore`/`createMemoryAuditSink` play.
6
+ * Swap in a persistent store (f's `createSqliteCategoryStore`) for real data.
7
+ *
8
+ * Deliberately not annotated `: CategoryStore` — the interface allows async
9
+ * returns, which would force callers (and these tests) to `await` values that
10
+ * are always synchronous here. Structural typing keeps it assignable wherever a
11
+ * `CategoryStore` is expected.
12
+ */
13
+ export function createMemoryCategoryStore(idFactory = () => crypto.randomUUID()) {
14
+ const byTenant = new Map();
15
+ const tenantMap = (tenant) => {
16
+ let map = byTenant.get(tenant);
17
+ if (!map) {
18
+ map = new Map();
19
+ byTenant.set(tenant, map);
20
+ }
21
+ return map;
22
+ };
23
+ const requireCategory = (map, id) => {
24
+ const cat = map.get(id);
25
+ if (!cat)
26
+ throw new Error(`no category with id ${id}`);
27
+ return cat;
28
+ };
29
+ /** Next order value at a level = max sibling order + 1 (append to the end). */
30
+ const nextOrder = (map, parentId) => {
31
+ let max = -1;
32
+ for (const c of map.values()) {
33
+ if (c.parentId === parentId && c.order > max)
34
+ max = c.order;
35
+ }
36
+ return max + 1;
37
+ };
38
+ return {
39
+ list(tenant) {
40
+ return [...tenantMap(tenant).values()];
41
+ },
42
+ create(tenant, input) {
43
+ const map = tenantMap(tenant);
44
+ const parentId = input.parentId ?? null;
45
+ if (parentId !== null && !map.has(parentId)) {
46
+ throw new Error(`no parent category with id ${parentId}`);
47
+ }
48
+ const category = {
49
+ id: idFactory(),
50
+ parentId,
51
+ label: input.label,
52
+ order: input.order ?? nextOrder(map, parentId),
53
+ tenant,
54
+ };
55
+ map.set(category.id, category);
56
+ return category;
57
+ },
58
+ rename(tenant, id, label) {
59
+ const map = tenantMap(tenant);
60
+ const cat = requireCategory(map, id);
61
+ const updated = { ...cat, label };
62
+ map.set(id, updated);
63
+ return updated;
64
+ },
65
+ move(tenant, id, parentId, order) {
66
+ const map = tenantMap(tenant);
67
+ const cat = requireCategory(map, id);
68
+ if (parentId !== null && !map.has(parentId)) {
69
+ throw new Error(`no parent category with id ${parentId}`);
70
+ }
71
+ if (wouldCreateCycle([...map.values()], id, parentId)) {
72
+ throw new Error(`moving ${id} under ${parentId} would create a cycle`);
73
+ }
74
+ const updated = {
75
+ ...cat,
76
+ parentId,
77
+ order: order ?? nextOrder(map, parentId),
78
+ };
79
+ map.set(id, updated);
80
+ return updated;
81
+ },
82
+ /** Remove a category, promoting its direct children to the removed node's
83
+ * own parent so no subtree is orphaned. */
84
+ remove(tenant, id) {
85
+ const map = tenantMap(tenant);
86
+ const cat = map.get(id);
87
+ if (!cat)
88
+ return;
89
+ for (const child of map.values()) {
90
+ if (child.parentId === id) {
91
+ map.set(child.id, { ...child, parentId: cat.parentId });
92
+ }
93
+ }
94
+ map.delete(id);
95
+ },
96
+ };
97
+ }
@@ -0,0 +1,26 @@
1
+ import type { Category, CategoryNode } from './types';
2
+ /**
3
+ * Would setting `childId`'s parent to `candidateParentId` create a cycle?
4
+ * True when the candidate parent is the child itself or one of its
5
+ * descendants — i.e. walking UP from the candidate reaches the child.
6
+ */
7
+ export declare function wouldCreateCycle(cats: Category[], childId: string, candidateParentId: string | null): boolean;
8
+ /**
9
+ * Assemble a flat category list into a sorted forest of {@link CategoryNode}s.
10
+ * Defensive by construction — a category is treated as a root when its
11
+ * `parentId` is `null`, points at a missing category (orphan), or would form a
12
+ * cycle. So malformed data never loses a node or infinite-loops; every input
13
+ * category appears exactly once in the output.
14
+ */
15
+ export declare function buildCategoryForest(cats: Category[]): CategoryNode[];
16
+ /** Flatten a forest into pre-order (parent before its children), preserving the
17
+ * sibling ordering {@link buildCategoryForest} established. */
18
+ export declare function flattenCategoryForest(forest: CategoryNode[]): CategoryNode[];
19
+ /**
20
+ * The root→node breadcrumb path for `id` (inclusive), or `[]` when the id is
21
+ * unknown. Cycle-safe — stops if the parent chain ever revisits a node.
22
+ */
23
+ export declare function categoryPath(cats: Category[], id: string): Category[];
24
+ /** Every descendant id of `id` (excluding `id` itself), depth-first. Used by a
25
+ * store's `remove`/`move` to reason about a subtree. */
26
+ export declare function descendantCategoryIds(cats: Category[], id: string): string[];
@@ -0,0 +1,113 @@
1
+ /** Sort siblings by explicit `order`, then label (locale-aware, stable). */
2
+ function compareSiblings(a, b) {
3
+ return a.order - b.order || a.label.localeCompare(b.label);
4
+ }
5
+ /**
6
+ * Would setting `childId`'s parent to `candidateParentId` create a cycle?
7
+ * True when the candidate parent is the child itself or one of its
8
+ * descendants — i.e. walking UP from the candidate reaches the child.
9
+ */
10
+ export function wouldCreateCycle(cats, childId, candidateParentId) {
11
+ if (candidateParentId === null)
12
+ return false;
13
+ if (candidateParentId === childId)
14
+ return true;
15
+ const byId = new Map(cats.map((c) => [c.id, c]));
16
+ let cursor = candidateParentId;
17
+ const seen = new Set();
18
+ while (cursor !== null) {
19
+ if (cursor === childId)
20
+ return true;
21
+ if (seen.has(cursor))
22
+ break; // pre-existing cycle in the data — stop, don't hang
23
+ seen.add(cursor);
24
+ cursor = byId.get(cursor)?.parentId ?? null;
25
+ }
26
+ return false;
27
+ }
28
+ /**
29
+ * Assemble a flat category list into a sorted forest of {@link CategoryNode}s.
30
+ * Defensive by construction — a category is treated as a root when its
31
+ * `parentId` is `null`, points at a missing category (orphan), or would form a
32
+ * cycle. So malformed data never loses a node or infinite-loops; every input
33
+ * category appears exactly once in the output.
34
+ */
35
+ export function buildCategoryForest(cats) {
36
+ const byId = new Map(cats.map((c) => [c.id, { ...c, children: [], depth: 0 }]));
37
+ const roots = [];
38
+ for (const node of byId.values()) {
39
+ const parent = node.parentId !== null ? byId.get(node.parentId) : undefined;
40
+ if (parent && !wouldCreateCycle(cats, node.id, node.parentId)) {
41
+ parent.children.push(node);
42
+ }
43
+ else {
44
+ roots.push(node);
45
+ }
46
+ }
47
+ const sortLevel = (level, depth) => {
48
+ level.sort(compareSiblings);
49
+ for (const node of level) {
50
+ node.depth = depth;
51
+ sortLevel(node.children, depth + 1);
52
+ }
53
+ };
54
+ sortLevel(roots, 0);
55
+ return roots;
56
+ }
57
+ /** Flatten a forest into pre-order (parent before its children), preserving the
58
+ * sibling ordering {@link buildCategoryForest} established. */
59
+ export function flattenCategoryForest(forest) {
60
+ const out = [];
61
+ const visit = (nodes) => {
62
+ for (const node of nodes) {
63
+ out.push(node);
64
+ visit(node.children);
65
+ }
66
+ };
67
+ visit(forest);
68
+ return out;
69
+ }
70
+ /**
71
+ * The root→node breadcrumb path for `id` (inclusive), or `[]` when the id is
72
+ * unknown. Cycle-safe — stops if the parent chain ever revisits a node.
73
+ */
74
+ export function categoryPath(cats, id) {
75
+ const byId = new Map(cats.map((c) => [c.id, c]));
76
+ const trail = [];
77
+ const seen = new Set();
78
+ let cursor = id;
79
+ while (cursor !== null) {
80
+ const cat = byId.get(cursor);
81
+ if (!cat || seen.has(cursor))
82
+ break;
83
+ seen.add(cursor);
84
+ trail.push(cat);
85
+ cursor = cat.parentId;
86
+ }
87
+ return trail.reverse();
88
+ }
89
+ /** Every descendant id of `id` (excluding `id` itself), depth-first. Used by a
90
+ * store's `remove`/`move` to reason about a subtree. */
91
+ export function descendantCategoryIds(cats, id) {
92
+ const childrenByParent = new Map();
93
+ for (const c of cats) {
94
+ if (c.parentId === null)
95
+ continue;
96
+ const list = childrenByParent.get(c.parentId) ?? [];
97
+ list.push(c);
98
+ childrenByParent.set(c.parentId, list);
99
+ }
100
+ const out = [];
101
+ const seen = new Set([id]);
102
+ const visit = (parentId) => {
103
+ for (const child of childrenByParent.get(parentId) ?? []) {
104
+ if (seen.has(child.id))
105
+ continue; // cycle guard
106
+ seen.add(child.id);
107
+ out.push(child.id);
108
+ visit(child.id);
109
+ }
110
+ };
111
+ visit(id);
112
+ return out;
113
+ }
@@ -0,0 +1,48 @@
1
+ /** One node in a category tree. Roots have `parentId === null`. `order` sorts
2
+ * siblings ascending; `tenant` scopes every category to its owner so two
3
+ * tenants' trees never collide. */
4
+ export interface Category {
5
+ id: string;
6
+ parentId: string | null;
7
+ label: string;
8
+ order: number;
9
+ tenant: string;
10
+ }
11
+ /** Fields a caller supplies when creating a category — the store assigns `id`,
12
+ * `tenant` (from the call), and a default `order` when omitted. */
13
+ export interface CategoryCreateInput {
14
+ label: string;
15
+ parentId?: string | null;
16
+ order?: number;
17
+ }
18
+ /**
19
+ * A `Category` assembled into a tree by {@link buildCategoryForest} — its
20
+ * resolved `children` plus a `depth` (0 at the root) for indentation without a
21
+ * second traversal.
22
+ */
23
+ export interface CategoryNode extends Category {
24
+ children: CategoryNode[];
25
+ depth: number;
26
+ }
27
+ /**
28
+ * Storage-agnostic, tenant-scoped category store. The host injects a real
29
+ * implementation (f backs it with SQLite); {@link createMemoryCategoryStore}
30
+ * is the zero-config in-memory default. Every method is tenant-first, matching
31
+ * the `NotesStore`/`AliasStore` contracts so a host can resolve the tenant to a
32
+ * per-role DB handle identically.
33
+ *
34
+ * `remove` promotes a removed category's direct children to its own parent
35
+ * (they are never silently dropped); a host that also stores per-item
36
+ * `categoryId`s is responsible for re-pointing those items (to the parent or
37
+ * `null`) in the same transaction.
38
+ */
39
+ export interface CategoryStore {
40
+ list(tenant: string): Category[] | Promise<Category[]>;
41
+ create(tenant: string, input: CategoryCreateInput): Category | Promise<Category>;
42
+ rename(tenant: string, id: string, label: string): Category | Promise<Category>;
43
+ /** Reparent (and optionally reorder) a category. Passing `parentId` equal to
44
+ * `id` or to one of the category's own descendants is rejected — it would
45
+ * orphan a subtree by creating a cycle. */
46
+ move(tenant: string, id: string, parentId: string | null, order?: number): Category | Promise<Category>;
47
+ remove(tenant: string, id: string): void | Promise<void>;
48
+ }
@@ -0,0 +1,8 @@
1
+ // src/core/category — a reusable, hierarchical category/folder model shared
2
+ // by content plugins (notes today; snippets/aliases/… later). React-free and
3
+ // storage-agnostic: the pure `Category` data + a host-injected `CategoryStore`
4
+ // contract, mirroring how `cwip`'s `AuditSink` and cursedshell's `NotesStore`
5
+ // ship a pure contract that a host backs with its own (SQLite) implementation.
6
+ // The cursedbelt `TreeView` adapter lives in the React layer, so importing this
7
+ // module on a server (e.g. f's covert-GET backend) never pulls React.
8
+ export {};
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Pick the readable foreground keyword (`'black'` or `'white'`) for text on a solid
3
+ * hex background, using the WCAG relative-luminance formula (sRGB gamma-expanded).
4
+ *
5
+ * Returns CSS color keywords (not hex) so a call site can drop the result straight
6
+ * into a `color:` style. Accepts 3- or 6-digit hex, with or without a leading `#`;
7
+ * an unparseable input falls back to `'white'` (legible on most accent colors).
8
+ *
9
+ * This is the canonical contrast primitive. The nav toolkit's `readableTextOn`
10
+ * (`react/nav/colors.ts`) and the spreadsheet theme's `contrastOn` deliberately
11
+ * return tuned near-black/near-white *hex* with their own thresholds for those
12
+ * surfaces; this returns pure keywords on the standard WCAG threshold.
13
+ */
14
+ export declare function contrastText(hex: string): 'black' | 'white';
@@ -0,0 +1,32 @@
1
+ // Framework-agnostic color helpers. Pure functions over hex strings — no React,
2
+ // no DOM — so they're usable in the apps, on the server, and in tests.
3
+ /**
4
+ * Pick the readable foreground keyword (`'black'` or `'white'`) for text on a solid
5
+ * hex background, using the WCAG relative-luminance formula (sRGB gamma-expanded).
6
+ *
7
+ * Returns CSS color keywords (not hex) so a call site can drop the result straight
8
+ * into a `color:` style. Accepts 3- or 6-digit hex, with or without a leading `#`;
9
+ * an unparseable input falls back to `'white'` (legible on most accent colors).
10
+ *
11
+ * This is the canonical contrast primitive. The nav toolkit's `readableTextOn`
12
+ * (`react/nav/colors.ts`) and the spreadsheet theme's `contrastOn` deliberately
13
+ * return tuned near-black/near-white *hex* with their own thresholds for those
14
+ * surfaces; this returns pure keywords on the standard WCAG threshold.
15
+ */
16
+ export function contrastText(hex) {
17
+ const c = hex.replace('#', '').trim();
18
+ const full = c.length === 3
19
+ ? c
20
+ .split('')
21
+ .map((ch) => ch + ch)
22
+ .join('')
23
+ : c;
24
+ if (!/^[0-9a-f]{6}$/i.test(full))
25
+ return 'white';
26
+ const r = Number.parseInt(full.slice(0, 2), 16) / 255;
27
+ const g = Number.parseInt(full.slice(2, 4), 16) / 255;
28
+ const b = Number.parseInt(full.slice(4, 6), 16) / 255;
29
+ const [rs, gs, bs] = [r, g, b].map((v) => v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4);
30
+ const luminance = 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
31
+ return luminance > 0.179 ? 'black' : 'white';
32
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * The color-scheme store: holds the current light/dark preference, persists it,
3
+ * applies it to the document root (the `.dark` class from `theme.css`'s contract),
4
+ * and notifies subscribers. Split from the React hook so it works before React
5
+ * mounts (call {@link initColorScheme} early to apply the saved scheme with no
6
+ * flash) and so its logic is testable without a DOM.
7
+ *
8
+ * Three modes, two resolved schemes: `'system'` (the default when nothing is
9
+ * stored) follows `prefers-color-scheme` live; picking light/dark via the toggle
10
+ * stores an explicit override. Side-effects (storage, applying to the page, the
11
+ * OS preference) are injected, so a test drives a real store with fakes; the
12
+ * exported default singleton binds them to `localStorage`, the `<html>` element,
13
+ * and `matchMedia`.
14
+ */
15
+ /** What the user chose. `'system'` = no explicit choice, follow the OS. */
16
+ export type ColorSchemeMode = 'light' | 'dark' | 'system';
17
+ /** What the page actually renders. */
18
+ export type ResolvedColorScheme = 'light' | 'dark';
19
+ /** The slice of the Web Storage API the store needs (so a test can fake it). */
20
+ export interface ColorSchemeStorage {
21
+ getItem(key: string): string | null;
22
+ setItem(key: string, value: string): void;
23
+ removeItem(key: string): void;
24
+ }
25
+ export interface ColorSchemeStore {
26
+ /** Current mode (lazily hydrated from storage on first read). */
27
+ getMode(): ColorSchemeMode;
28
+ /** The scheme the page renders: the mode, or the OS preference under `'system'`. */
29
+ getResolved(): ResolvedColorScheme;
30
+ /** Set, persist, apply, and notify. `'system'` clears the stored override. */
31
+ setMode(mode: ColorSchemeMode): void;
32
+ /** Flip to the opposite of the currently *resolved* scheme (always explicit). */
33
+ toggle(): void;
34
+ /** Apply the persisted scheme to the page + start following OS changes while in
35
+ * `'system'` mode (call once on app boot). Idempotent. */
36
+ init(): void;
37
+ subscribe(listener: () => void): () => void;
38
+ }
39
+ export interface ColorSchemeStoreOptions {
40
+ storage?: ColorSchemeStorage | null;
41
+ /** Apply a resolved scheme to the page. Default toggles `.dark` on `<html>`. */
42
+ apply?: (scheme: ResolvedColorScheme) => void;
43
+ /** Read the OS preference (default: `matchMedia('(prefers-color-scheme: dark)')`). */
44
+ getSystemScheme?: () => ResolvedColorScheme;
45
+ /** Subscribe to OS preference changes; returns an unsubscribe. */
46
+ onSystemChange?: (listener: () => void) => () => void;
47
+ /** Subscribe to the root element's `.dark` class being changed by something OTHER
48
+ * than this store (devtools, a screenshot/visual-test harness driving the DOM
49
+ * directly). Returns an unsubscribe. Default: a `MutationObserver` on
50
+ * `document.documentElement`'s `class`/`style` attributes. */
51
+ onExternalChange?: (listener: () => void) => () => void;
52
+ /** localStorage key. */
53
+ key?: string;
54
+ }
55
+ export declare const COLOR_SCHEME_STORAGE_KEY = "cwip.colorScheme";
56
+ export declare function createColorSchemeStore(options?: ColorSchemeStoreOptions): ColorSchemeStore;
57
+ /** Apply a resolved scheme to the document root via the `theme.css` contract:
58
+ * the `.dark` class on `<html>` (never `data-theme`, never the media query —
59
+ * `dark:` utilities and every token consumer key off this class). SSR-safe. */
60
+ export declare function applyColorSchemeToDocument(scheme: ResolvedColorScheme): void;
61
+ /** The app-wide singleton bound to localStorage + the page + the OS preference. */
62
+ export declare const colorSchemeStore: ColorSchemeStore;
63
+ export declare const getColorSchemeMode: () => ColorSchemeMode;
64
+ export declare const getResolvedColorScheme: () => ResolvedColorScheme;
65
+ export declare const setColorSchemeMode: (mode: ColorSchemeMode) => void;
66
+ export declare const toggleColorScheme: () => void;
67
+ export declare const subscribeColorScheme: (listener: () => void) => (() => void);
68
+ /** Apply the saved scheme on app boot, before React mounts (no flash), and start
69
+ * following the OS preference while no explicit choice is stored. */
70
+ export declare const initColorScheme: () => void;
@@ -0,0 +1,123 @@
1
+ /**
2
+ * The color-scheme store: holds the current light/dark preference, persists it,
3
+ * applies it to the document root (the `.dark` class from `theme.css`'s contract),
4
+ * and notifies subscribers. Split from the React hook so it works before React
5
+ * mounts (call {@link initColorScheme} early to apply the saved scheme with no
6
+ * flash) and so its logic is testable without a DOM.
7
+ *
8
+ * Three modes, two resolved schemes: `'system'` (the default when nothing is
9
+ * stored) follows `prefers-color-scheme` live; picking light/dark via the toggle
10
+ * stores an explicit override. Side-effects (storage, applying to the page, the
11
+ * OS preference) are injected, so a test drives a real store with fakes; the
12
+ * exported default singleton binds them to `localStorage`, the `<html>` element,
13
+ * and `matchMedia`.
14
+ */
15
+ export const COLOR_SCHEME_STORAGE_KEY = 'cwip.colorScheme';
16
+ const isMode = (v) => v === 'light' || v === 'dark';
17
+ export function createColorSchemeStore(options = {}) {
18
+ const { storage = null, apply, getSystemScheme = () => 'light', onSystemChange, onExternalChange, key = COLOR_SCHEME_STORAGE_KEY, } = options;
19
+ const listeners = new Set();
20
+ let mode; // undefined until first hydrate
21
+ let watchingSystem = false;
22
+ let watchingExternal = false;
23
+ const hydrate = () => {
24
+ if (mode !== undefined)
25
+ return mode;
26
+ const raw = storage?.getItem(key);
27
+ mode = isMode(raw) ? raw : 'system';
28
+ return mode;
29
+ };
30
+ const resolve = () => {
31
+ const m = hydrate();
32
+ return m === 'system' ? getSystemScheme() : m;
33
+ };
34
+ const notify = () => {
35
+ for (const l of listeners)
36
+ l();
37
+ };
38
+ const commit = (next) => {
39
+ mode = next;
40
+ if (next === 'system')
41
+ storage?.removeItem(key);
42
+ else
43
+ storage?.setItem(key, next);
44
+ apply?.(resolve());
45
+ notify();
46
+ };
47
+ return {
48
+ getMode: hydrate,
49
+ getResolved: resolve,
50
+ setMode: commit,
51
+ toggle: () => commit(resolve() === 'dark' ? 'light' : 'dark'),
52
+ init() {
53
+ apply?.(resolve());
54
+ if (!watchingSystem && onSystemChange) {
55
+ watchingSystem = true;
56
+ onSystemChange(() => {
57
+ // Only the resolved scheme changed; re-apply + notify while following the OS.
58
+ if (hydrate() === 'system') {
59
+ apply?.(resolve());
60
+ notify();
61
+ }
62
+ });
63
+ }
64
+ if (!watchingExternal && onExternalChange) {
65
+ watchingExternal = true;
66
+ onExternalChange(() => {
67
+ // Something outside this store (devtools, a visual-test harness) touched
68
+ // the root's `.dark` class/localStorage directly — drop the cached mode
69
+ // so the next read re-hydrates from storage, and tell every subscriber
70
+ // (the theme toggle, etc.) to re-check so they can't render stale.
71
+ mode = undefined;
72
+ notify();
73
+ });
74
+ }
75
+ },
76
+ subscribe(listener) {
77
+ listeners.add(listener);
78
+ return () => listeners.delete(listener);
79
+ },
80
+ };
81
+ }
82
+ /** Apply a resolved scheme to the document root via the `theme.css` contract:
83
+ * the `.dark` class on `<html>` (never `data-theme`, never the media query —
84
+ * `dark:` utilities and every token consumer key off this class). SSR-safe. */
85
+ export function applyColorSchemeToDocument(scheme) {
86
+ if (typeof document === 'undefined')
87
+ return;
88
+ document.documentElement.classList.toggle('dark', scheme === 'dark');
89
+ // Hint native form controls/scrollbars so they match the app chrome.
90
+ document.documentElement.style.colorScheme = scheme;
91
+ }
92
+ const defaultStorage = typeof localStorage !== 'undefined' ? localStorage : null;
93
+ const darkQuery = () => typeof window !== 'undefined' && typeof window.matchMedia === 'function'
94
+ ? window.matchMedia('(prefers-color-scheme: dark)')
95
+ : null;
96
+ /** The app-wide singleton bound to localStorage + the page + the OS preference. */
97
+ export const colorSchemeStore = createColorSchemeStore({
98
+ storage: defaultStorage,
99
+ apply: applyColorSchemeToDocument,
100
+ getSystemScheme: () => (darkQuery()?.matches ? 'dark' : 'light'),
101
+ onSystemChange: (listener) => {
102
+ const q = darkQuery();
103
+ if (!q)
104
+ return () => { };
105
+ q.addEventListener('change', listener);
106
+ return () => q.removeEventListener('change', listener);
107
+ },
108
+ onExternalChange: (listener) => {
109
+ if (typeof document === 'undefined' || typeof MutationObserver === 'undefined')
110
+ return () => { };
111
+ const observer = new MutationObserver(listener);
112
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class', 'style'] });
113
+ return () => observer.disconnect();
114
+ },
115
+ });
116
+ export const getColorSchemeMode = () => colorSchemeStore.getMode();
117
+ export const getResolvedColorScheme = () => colorSchemeStore.getResolved();
118
+ export const setColorSchemeMode = (mode) => colorSchemeStore.setMode(mode);
119
+ export const toggleColorScheme = () => colorSchemeStore.toggle();
120
+ export const subscribeColorScheme = (listener) => colorSchemeStore.subscribe(listener);
121
+ /** Apply the saved scheme on app boot, before React mounts (no flash), and start
122
+ * following the OS preference while no explicit choice is stored. */
123
+ export const initColorScheme = () => colorSchemeStore.init();
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Color-scheme core (framework-free) — the localStorage/document store behind the
3
+ * app-wide light/dark toggle. `initColorScheme()` applies the saved (or OS) scheme
4
+ * on app boot (before React mounts → no flash); the React control + hook live in
5
+ * `cursedbelt/react` (`ThemeToggle`, `useColorScheme`).
6
+ */
7
+ export { applyColorSchemeToDocument, COLOR_SCHEME_STORAGE_KEY, type ColorSchemeMode, type ColorSchemeStorage, type ColorSchemeStore, type ColorSchemeStoreOptions, colorSchemeStore, createColorSchemeStore, getColorSchemeMode, getResolvedColorScheme, initColorScheme, type ResolvedColorScheme, setColorSchemeMode, subscribeColorScheme, toggleColorScheme, } from './colorSchemeStore';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Color-scheme core (framework-free) — the localStorage/document store behind the
3
+ * app-wide light/dark toggle. `initColorScheme()` applies the saved (or OS) scheme
4
+ * on app boot (before React mounts → no flash); the React control + hook live in
5
+ * `cursedbelt/react` (`ThemeToggle`, `useColorScheme`).
6
+ */
7
+ export { applyColorSchemeToDocument, COLOR_SCHEME_STORAGE_KEY, colorSchemeStore, createColorSchemeStore, getColorSchemeMode, getResolvedColorScheme, initColorScheme, setColorSchemeMode, subscribeColorScheme, toggleColorScheme, } from './colorSchemeStore';
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Which engine owns which storage domain (01 §2.3/§5).
3
+ *
4
+ * Lives in `core/` because BOTH tiers need it and neither may import the other's
5
+ * graph: the server's CDC bridge fans a domain's writes to that engine's
6
+ * instances, and the client's DevStudio needs the domains an app can snapshot.
7
+ * A second copy on the client would be a map that drifts the day a domain moves
8
+ * — so there is one, here, with no dependency beyond cwip's domain names.
9
+ *
10
+ * `search` is knowledge's derived index and `staging` is a dev-time sandbox, so
11
+ * neither gets an engine of its own.
12
+ */
13
+ import type { DomainName } from 'cwip/domains';
14
+ export declare const DOMAIN_OWNER_ENGINE: Readonly<Record<DomainName, string>>;
15
+ /**
16
+ * The domains a set of active engines owns, in DDL order. `staging` is excluded:
17
+ * it is never part of an exported app's storage map (V5 refuses it upstream).
18
+ */
19
+ export declare const domainsForEngines: (engines: readonly string[]) => DomainName[];
@@ -0,0 +1,15 @@
1
+ export const DOMAIN_OWNER_ENGINE = Object.freeze({
2
+ knowledge: 'knowledge',
3
+ media: 'asset',
4
+ search: 'knowledge',
5
+ system: 'system',
6
+ staging: 'dev',
7
+ });
8
+ /**
9
+ * The domains a set of active engines owns, in DDL order. `staging` is excluded:
10
+ * it is never part of an exported app's storage map (V5 refuses it upstream).
11
+ */
12
+ export const domainsForEngines = (engines) => {
13
+ const active = new Set(engines);
14
+ return Object.keys(DOMAIN_OWNER_ENGINE).filter((domain) => domain !== 'staging' && active.has(DOMAIN_OWNER_ENGINE[domain]));
15
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Trigger a browser file download from in-memory data — the canonical client-side
3
+ * "save this blob" primitive (there was no shared one; call sites hand-rolled
4
+ * `createObjectURL` + `<a download>` + revoke). DOM-only: it no-ops off the browser
5
+ * so importing modules stay server-safe.
6
+ *
7
+ * ```ts
8
+ * downloadBlob(csvText, 'notes.csv', 'text/csv');
9
+ * downloadBlob(sqliteBytes, 'snapshot.sqlite', 'application/x-sqlite3');
10
+ * ```
11
+ */
12
+ export declare function downloadBlob(data: BlobPart, filename: string, mime: string): void;
13
+ /** Decode a base64 payload (e.g. an exported `.sqlite` snapshot) to bytes for {@link downloadBlob}. */
14
+ export declare function base64ToBytes(base64: string): Uint8Array;