fuli-context 0.4.0 → 0.7.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 (207) hide show
  1. package/README.en.md +5 -0
  2. package/README.md +411 -173
  3. package/README.zh-CN.md +448 -0
  4. package/compose.graphiti.yml +6 -6
  5. package/dist/web/assets/AboutPage-DXh7Fwxt.css +1 -0
  6. package/dist/web/assets/AboutPage-DfnZo7Lo.js +1 -0
  7. package/dist/web/assets/ConnectionsPage-BloaSKmt.js +1 -0
  8. package/dist/web/assets/ConnectionsPage-CBHAdZXT.css +1 -0
  9. package/dist/web/assets/KnowledgeInspector-Bjhdu2mf.css +1 -0
  10. package/dist/web/assets/KnowledgeInspector-QvNpv0_k.js +1 -0
  11. package/dist/web/assets/KnowledgeOrganizerPage-Cx1w1jb2.css +1 -0
  12. package/dist/web/assets/KnowledgeOrganizerPage-DKq1jVD5.js +1 -0
  13. package/dist/web/assets/KnowledgePage-BWL0jT01.js +1 -0
  14. package/dist/web/assets/KnowledgeWorkspace-Czq6aZ7b.js +4 -0
  15. package/dist/web/assets/OverviewPage-5sJoO1Gh.js +1 -0
  16. package/dist/web/assets/{PersonalProfilePage-S8ZcG2LU.css → PersonalProfilePage-CdmEzyvZ.css} +1 -1
  17. package/dist/web/assets/PersonalProfilePage-tVChM9ik.js +1 -0
  18. package/dist/web/assets/PersonalProjectsPage-BPFrup4V.js +1 -0
  19. package/dist/web/assets/PublicProjectsPage-BPJSeREN.js +1 -0
  20. package/dist/web/assets/ReviewPage-B7EN817i.js +1 -0
  21. package/dist/web/assets/SearchableSelect-ClLN0rIb.js +1 -0
  22. package/dist/web/assets/SettingsPage-C6ouU0sR.css +1 -0
  23. package/dist/web/assets/SettingsPage-Chd_B3eH.js +1 -0
  24. package/dist/web/assets/_plugin-vue_export-helper-BDNMzG2s.js +1 -0
  25. package/dist/web/assets/console-CbDsKrsO.js +1 -0
  26. package/dist/web/assets/fuli-logo-Bu9WBuQe.js +1 -0
  27. package/dist/web/assets/fuli-logo-ER1wQ8Kk.png +0 -0
  28. package/dist/web/assets/i18n-BQcns1PH.js +5 -0
  29. package/dist/web/assets/identity-B8K4VYg6.js +1 -0
  30. package/dist/web/assets/index-CB8Xmsc2.js +2 -0
  31. package/dist/web/assets/index-CZeuPh-U.css +1 -0
  32. package/dist/web/assets/model-eF52GgPF.js +1 -0
  33. package/dist/web/assets/paths-hwz-Ac1g.js +1 -0
  34. package/dist/web/assets/useMinimumLoadingDisplay-BpCyBeko.css +1 -0
  35. package/dist/web/assets/useMinimumLoadingDisplay-wCgGDmHg.js +1 -0
  36. package/dist/web/assets/vue-router-BsEp_XwI.js +1 -0
  37. package/dist/web/index.html +9 -6
  38. package/docs/external-knowledge-architecture.md +279 -0
  39. package/examples/external-knowledge/markdown-folder.mjs +235 -0
  40. package/graph-provider/fuli_graph/app.py +57 -1
  41. package/graph-provider/fuli_graph/graph_models.py +3 -0
  42. package/graph-provider/fuli_graph/graph_query.py +62 -16
  43. package/graph-provider/fuli_graph/knowledge_review.py +612 -0
  44. package/graph-provider/fuli_graph/knowledge_review_models.py +127 -0
  45. package/graph-provider/fuli_graph/knowledge_search.py +51 -4
  46. package/graph-provider/fuli_graph/models.py +19 -0
  47. package/graph-provider/fuli_graph/runtime.py +15 -0
  48. package/graph-provider/fuli_graph/store_knowledge.py +2 -1
  49. package/graph-provider/pyproject.toml +1 -1
  50. package/npm-shrinkwrap.json +5 -407
  51. package/package.json +7 -4
  52. package/skills/capturing-session-knowledge/SKILL.md +24 -4
  53. package/skills/flreview/SKILL.md +73 -0
  54. package/skills/flreview/agents/openai.yaml +6 -0
  55. package/src/agent-tools/graph-definitions.js +79 -4
  56. package/src/agent-tools/graph-handlers.js +12 -0
  57. package/src/cli/command-registry.js +3 -3
  58. package/src/cli/local-runtime-command.js +60 -23
  59. package/src/cli/local-runtime-options.js +16 -6
  60. package/src/cli/setup-agent-selection.js +12 -12
  61. package/src/cli/setup-command.js +41 -18
  62. package/src/cli/shell-argument.js +10 -0
  63. package/src/cli/uninstall-command.js +17 -15
  64. package/src/cli/update-command.js +38 -30
  65. package/src/external-knowledge/catalog.js +61 -0
  66. package/src/external-knowledge/conflict-policy.js +58 -0
  67. package/src/external-knowledge/connected-search.js +166 -0
  68. package/src/external-knowledge/connectors/custom.js +133 -0
  69. package/src/external-knowledge/connectors/feishu.js +461 -0
  70. package/src/external-knowledge/connectors/mcp.js +438 -0
  71. package/src/external-knowledge/connectors/notion.js +307 -0
  72. package/src/external-knowledge/connectors/retrieval-api.js +222 -0
  73. package/src/external-knowledge/document-mapping.js +151 -0
  74. package/src/external-knowledge/graph-projection.js +17 -0
  75. package/src/external-knowledge/index.js +8 -0
  76. package/src/external-knowledge/registry.js +91 -0
  77. package/src/external-knowledge/runtime.js +68 -0
  78. package/src/external-knowledge/safe-diagnostic.js +14 -0
  79. package/src/external-knowledge/service.js +811 -0
  80. package/src/graphiti/agent-knowledge-workflows.js +7 -2
  81. package/src/graphiti/federated-application.js +66 -41
  82. package/src/graphiti/knowledge-item-role.js +21 -0
  83. package/src/graphiti/knowledge-review.js +43 -0
  84. package/src/graphiti/project-profile-mapping.js +32 -0
  85. package/src/graphiti/provider-client.js +22 -1
  86. package/src/graphiti/source-marker.js +54 -3
  87. package/src/graphiti/task-knowledge-recall.js +250 -0
  88. package/src/http/api-router.js +23 -108
  89. package/src/http/external-knowledge-api-router.js +104 -0
  90. package/src/http/graph-api-router.js +12 -1
  91. package/src/http/request-policy.js +38 -3
  92. package/src/local-runtime/lifecycle.js +37 -16
  93. package/src/mcp/create-mcp-server.js +1 -0
  94. package/src/mcp/instructions.js +7 -7
  95. package/src/mcp/tool-annotations.js +6 -0
  96. package/src/mcp/tool-result.js +1 -0
  97. package/src/server/application-lifecycle.js +2 -11
  98. package/src/server/blocked-ports.js +11 -0
  99. package/src/server/lan-access.js +52 -0
  100. package/src/server.js +103 -25
  101. package/src/setup/agent-installation-status.js +2 -1
  102. package/src/setup/agents.js +2 -1
  103. package/src/setup/claude-code-config.js +2 -1
  104. package/src/setup/codex-bootstrap.js +12 -7
  105. package/src/setup/container-runtime.js +25 -21
  106. package/src/setup/graph-runtime.js +219 -58
  107. package/src/setup/node-runtime.js +3 -3
  108. package/src/setup/options.js +2 -4
  109. package/src/setup/paths.js +18 -3
  110. package/src/setup/review-skill-adapters.js +49 -0
  111. package/src/setup/setup.js +26 -2
  112. package/src/setup/uninstall.js +4 -2
  113. package/src/system/resource-monitor.js +317 -0
  114. package/src/system/runtime-settings.js +131 -0
  115. package/src/system/system-service.js +119 -0
  116. package/dist/web/assets/ConnectionsPage-CCKWm-6p.js +0 -1
  117. package/dist/web/assets/KnowledgeInspector-BZkB2wJn.js +0 -1
  118. package/dist/web/assets/KnowledgeInspector-CrGYxEob.css +0 -1
  119. package/dist/web/assets/KnowledgeOrganizerPage-CJCNI2Wd.css +0 -1
  120. package/dist/web/assets/KnowledgeOrganizerPage-PXH-TlGt.js +0 -1
  121. package/dist/web/assets/KnowledgePage-CNQXd15y.js +0 -1
  122. package/dist/web/assets/KnowledgeWorkspace-CXPE4GCD.js +0 -4
  123. package/dist/web/assets/OverviewPage-ul7g7HXh.js +0 -1
  124. package/dist/web/assets/PersonalProfilePage-CC40Pw2L.js +0 -1
  125. package/dist/web/assets/PersonalProjectsPage-CPIEcShD.js +0 -1
  126. package/dist/web/assets/PublicProjectsPage-C700sR7z.js +0 -1
  127. package/dist/web/assets/ReviewPage-7MDDUDgA.js +0 -1
  128. package/dist/web/assets/console-B_Ena2DS.js +0 -5
  129. package/dist/web/assets/fuli-logo-DGuyqchQ.png +0 -0
  130. package/dist/web/assets/identity-CjBwYSMX.js +0 -1
  131. package/dist/web/assets/index-BQRkOwPx.js +0 -2
  132. package/dist/web/assets/index-CpLDx3of.css +0 -1
  133. package/dist/web/assets/model-C-5RVIJ2.js +0 -1
  134. package/dist/web/assets/paths-BkULqmGI.js +0 -1
  135. package/dist/web/assets/vue-router-DnD3hUm7.js +0 -1
  136. package/src/agent/candidate-read-service.js +0 -53
  137. package/src/agent/context-pack-service.js +0 -83
  138. package/src/agent/public-read-policy.js +0 -39
  139. package/src/agent/public-read-service.js +0 -69
  140. package/src/agent/read-projection.js +0 -107
  141. package/src/agent/search-service.js +0 -61
  142. package/src/agent/space-boundary.js +0 -33
  143. package/src/app/bootstrap.js +0 -8
  144. package/src/app/create-application.js +0 -143
  145. package/src/app/state-service.js +0 -38
  146. package/src/candidates.js +0 -78
  147. package/src/classifier.js +0 -63
  148. package/src/cli/command-arguments.js +0 -14
  149. package/src/context-pack.js +0 -206
  150. package/src/extractor.js +0 -77
  151. package/src/fact-history.js +0 -39
  152. package/src/facts/fact-service.js +0 -89
  153. package/src/http/bounded-json-array.js +0 -19
  154. package/src/http/lens-route.js +0 -50
  155. package/src/ingestion.js +0 -161
  156. package/src/lens/lens-correction-service.js +0 -81
  157. package/src/lens/lens-fact-writer.js +0 -85
  158. package/src/lens/lens-input.js +0 -62
  159. package/src/lens/lens-query-projection.js +0 -87
  160. package/src/lens/lens-query-ranking.js +0 -62
  161. package/src/lens/lens-query.js +0 -143
  162. package/src/lens/lens-resource-projection.js +0 -93
  163. package/src/lens/lens-resource-selection.js +0 -45
  164. package/src/lens/lens-resource-service.js +0 -103
  165. package/src/lens/lens-search-query.js +0 -122
  166. package/src/lens/lens-service.js +0 -183
  167. package/src/models.js +0 -44
  168. package/src/observer.js +0 -83
  169. package/src/project-rules.js +0 -33
  170. package/src/publication/outbox-service.js +0 -46
  171. package/src/publication/publication-envelope-schema.js +0 -75
  172. package/src/publication/publication-envelope.js +0 -37
  173. package/src/publication/publication-policy.js +0 -82
  174. package/src/publication/publication-service.js +0 -48
  175. package/src/router.js +0 -58
  176. package/src/runtime-options.js +0 -138
  177. package/src/security/episode-visibility.js +0 -14
  178. package/src/security/fact-visibility.js +0 -15
  179. package/src/security/source-metadata-policy.js +0 -8
  180. package/src/storage/episode-evidence-preview.js +0 -100
  181. package/src/storage/fact-search-order.js +0 -18
  182. package/src/storage/file/episode-evidence.js +0 -37
  183. package/src/storage/file/episode-index.js +0 -59
  184. package/src/storage/file/fact-search.js +0 -60
  185. package/src/storage/file-store.js +0 -437
  186. package/src/storage/migrate.js +0 -128
  187. package/src/storage/migrations/001-initial.sql +0 -83
  188. package/src/storage/migrations/002-storage-integrity.sql +0 -113
  189. package/src/storage/record-validation.js +0 -46
  190. package/src/storage/snapshot-defaults.js +0 -54
  191. package/src/storage/sqlite/candidates-repository.js +0 -84
  192. package/src/storage/sqlite/episode-evidence-repository.js +0 -154
  193. package/src/storage/sqlite/episode-privacy-sql.js +0 -33
  194. package/src/storage/sqlite/episodes-repository.js +0 -52
  195. package/src/storage/sqlite/fact-search-repository.js +0 -162
  196. package/src/storage/sqlite/facts-repository.js +0 -146
  197. package/src/storage/sqlite/imports-repository.js +0 -39
  198. package/src/storage/sqlite/mapper.js +0 -84
  199. package/src/storage/sqlite/open-database.js +0 -15
  200. package/src/storage/sqlite/outbox-repository.js +0 -103
  201. package/src/storage/sqlite/snapshot-repository.js +0 -69
  202. package/src/storage/sqlite/spaces-repository.js +0 -52
  203. package/src/storage/sqlite/subscriptions-repository.js +0 -54
  204. package/src/storage/sqlite-store.js +0 -159
  205. package/src/storage/stable-json.js +0 -24
  206. package/src/storage/store-port.js +0 -20
  207. package/src/store.js +0 -2
@@ -1,146 +0,0 @@
1
- import { randomUUID } from 'node:crypto';
2
-
3
- import { FactStatus, nowIso } from '../../models.js';
4
- import { validateFactRecord } from '../record-validation.js';
5
- import { FactSearchRepository } from './fact-search-repository.js';
6
- import { mapFact } from './mapper.js';
7
-
8
- export class FactsRepository {
9
- constructor(db) {
10
- this.factSearch = new FactSearchRepository(db);
11
- this.insertStatement = db.prepare(`
12
- INSERT INTO facts (
13
- id, space_id, subject, predicate, object, source_episode_id, status,
14
- confidence, sensitivity, scope, valid_at, invalid_at, replaced_by_fact_id
15
- ) VALUES (
16
- @id, @spaceId, @subject, @predicate, @object, @sourceEpisodeId, @status,
17
- @confidence, @sensitivity, @scope, @validAt, @invalidAt, @replacedByFactId
18
- )
19
- `);
20
- this.getStatement = db.prepare('SELECT * FROM facts WHERE id = ?');
21
- this.updateStatement = db.prepare(`
22
- UPDATE facts SET
23
- space_id = @spaceId,
24
- subject = @subject,
25
- predicate = @predicate,
26
- object = @object,
27
- source_episode_id = @sourceEpisodeId,
28
- status = @status,
29
- confidence = @confidence,
30
- sensitivity = @sensitivity,
31
- scope = @scope,
32
- valid_at = @validAt,
33
- invalid_at = @invalidAt,
34
- replaced_by_fact_id = @replacedByFactId
35
- WHERE id = @id
36
- `);
37
- this.currentStatement = db.prepare(`
38
- SELECT * FROM facts
39
- WHERE space_id = ?
40
- AND invalid_at IS NULL
41
- AND status NOT IN ('rejected', 'deprecated')
42
- ORDER BY rowid
43
- `);
44
- this.currentListStatement = db.prepare(`
45
- SELECT * FROM facts
46
- WHERE invalid_at IS NULL AND status NOT IN ('rejected', 'deprecated')
47
- ORDER BY rowid
48
- `);
49
- this.fullListStatement = db.prepare('SELECT * FROM facts ORDER BY rowid');
50
- this.timelineStatement = db.prepare(`
51
- SELECT * FROM facts
52
- WHERE space_id = ? AND subject = ?
53
- ORDER BY valid_at, rowid
54
- `);
55
- this.replaceLinkStatement = db.prepare(`
56
- UPDATE facts SET replaced_by_fact_id = ? WHERE id = ?
57
- `);
58
- this.deleteStatement = db.prepare('DELETE FROM facts');
59
- }
60
-
61
- add(fact) {
62
- if (fact.id && this.get(fact.id)) {
63
- throw new Error(`Duplicate fact id: ${fact.id}`);
64
- }
65
-
66
- const stored = {
67
- id: fact.id || randomUUID(),
68
- spaceId: fact.spaceId,
69
- subject: fact.subject,
70
- predicate: fact.predicate,
71
- object: fact.object,
72
- sourceEpisodeId: fact.sourceEpisodeId,
73
- status: fact.status ?? FactStatus.CONFIRMED,
74
- confidence: fact.confidence ?? 1,
75
- sensitivity: fact.sensitivity ?? 'normal',
76
- scope: fact.scope ?? 'personal',
77
- validAt: fact.validAt ?? nowIso(),
78
- invalidAt: fact.invalidAt ?? null,
79
- replacedByFactId: fact.replacedByFactId ?? null
80
- };
81
- validateFactRecord(stored);
82
- this.insertStatement.run(stored);
83
- return this.get(stored.id);
84
- }
85
-
86
- get(id) {
87
- return mapFact(this.getStatement.get(id));
88
- }
89
-
90
- update(id, updates) {
91
- const existing = this.get(id);
92
- if (!existing) throw new Error(`Fact not found: ${id}`);
93
-
94
- const stored = { ...existing, ...updates, id };
95
- validateFactRecord(stored);
96
- this.updateStatement.run(stored);
97
- return this.get(id);
98
- }
99
-
100
- invalidate(id, replacedByFactId = null) {
101
- const fact = this.get(id);
102
- if (!fact || fact.invalidAt) return;
103
- this.updateStatement.run({
104
- ...fact,
105
- invalidAt: nowIso(),
106
- replacedByFactId
107
- });
108
- }
109
-
110
- current(spaceId) {
111
- return this.currentStatement.all(spaceId).map(mapFact);
112
- }
113
-
114
- list({ includeHistorical = false } = {}) {
115
- const statement = includeHistorical ? this.fullListStatement : this.currentListStatement;
116
- return statement.all().map(mapFact);
117
- }
118
-
119
- timeline(spaceId, subject) {
120
- return this.timelineStatement.all(spaceId, subject).map(mapFact);
121
- }
122
-
123
- search(spaceIds, query, options = {}) {
124
- return this.factSearch.search(spaceIds, query, options);
125
- }
126
-
127
- searchPage(spaceIds, query, options = {}) {
128
- return this.factSearch.searchPage(spaceIds, query, options);
129
- }
130
-
131
- insertSnapshot(fact, { deferReplacement = false } = {}) {
132
- validateFactRecord(fact);
133
- this.insertStatement.run({
134
- ...fact,
135
- replacedByFactId: deferReplacement ? null : fact.replacedByFactId
136
- });
137
- }
138
-
139
- restoreReplacement(id, replacedByFactId) {
140
- this.replaceLinkStatement.run(replacedByFactId, id);
141
- }
142
-
143
- deleteAll() {
144
- this.deleteStatement.run();
145
- }
146
- }
@@ -1,39 +0,0 @@
1
- import { mapImport } from './mapper.js';
2
-
3
- export class ImportsRepository {
4
- constructor(db) {
5
- this.insertStatement = db.prepare(`
6
- INSERT INTO imports (content_hash, source_path, imported_at)
7
- VALUES (@contentHash, @sourcePath, @importedAt)
8
- `);
9
- this.hasStatement = db.prepare(`
10
- SELECT 1 FROM imports WHERE content_hash = ?
11
- `);
12
- this.listStatement = db.prepare('SELECT * FROM imports ORDER BY rowid');
13
- this.deleteStatement = db.prepare('DELETE FROM imports');
14
- }
15
-
16
- has(contentHash) {
17
- return this.hasStatement.get(contentHash) !== undefined;
18
- }
19
-
20
- record(record) {
21
- if (this.has(record.contentHash)) {
22
- throw new Error(`Duplicate import content hash: ${record.contentHash}`);
23
- }
24
- this.insertStatement.run(record);
25
- return { ...record };
26
- }
27
-
28
- listAll() {
29
- return this.listStatement.all().map(mapImport);
30
- }
31
-
32
- insertSnapshot(record) {
33
- this.insertStatement.run(record);
34
- }
35
-
36
- deleteAll() {
37
- this.deleteStatement.run();
38
- }
39
- }
@@ -1,84 +0,0 @@
1
- export function mapSpace(row) {
2
- return row ? {
3
- id: row.id,
4
- name: row.name,
5
- kind: row.kind,
6
- description: row.description,
7
- createdAt: row.created_at
8
- } : null;
9
- }
10
-
11
- export function mapSubscription(row) {
12
- return row ? {
13
- personalSpaceId: row.personal_space_id,
14
- spaceId: row.space_id,
15
- mode: row.mode,
16
- createdAt: row.created_at
17
- } : null;
18
- }
19
-
20
- export function mapEpisode(row) {
21
- return row ? {
22
- id: row.id,
23
- spaceId: row.space_id,
24
- sourceKind: row.source_kind,
25
- body: row.body,
26
- sourceUri: row.source_uri,
27
- metadata: JSON.parse(row.metadata_json),
28
- createdAt: row.created_at
29
- } : null;
30
- }
31
-
32
- export function mapFact(row) {
33
- return row ? {
34
- id: row.id,
35
- spaceId: row.space_id,
36
- subject: row.subject,
37
- predicate: row.predicate,
38
- object: row.object,
39
- sourceEpisodeId: row.source_episode_id,
40
- status: row.status,
41
- confidence: row.confidence,
42
- sensitivity: row.sensitivity,
43
- scope: row.scope,
44
- validAt: row.valid_at,
45
- invalidAt: row.invalid_at,
46
- replacedByFactId: row.replaced_by_fact_id
47
- } : null;
48
- }
49
-
50
- export function mapCandidate(row) {
51
- return row ? {
52
- id: row.id,
53
- personalSpaceId: row.personal_space_id,
54
- targetSpaceId: row.target_space_id,
55
- episodeId: row.episode_id,
56
- reason: row.reason,
57
- status: row.status,
58
- createdAt: row.created_at,
59
- decidedAt: row.decided_at
60
- } : null;
61
- }
62
-
63
- export function mapOutbox(row) {
64
- return row ? {
65
- id: row.id,
66
- kind: row.kind,
67
- aggregateId: row.aggregate_id,
68
- payload: JSON.parse(row.payload_json),
69
- status: row.status,
70
- attempts: row.attempts,
71
- nextAttemptAt: row.next_attempt_at,
72
- createdAt: row.created_at,
73
- sentAt: row.sent_at,
74
- lastError: row.last_error
75
- } : null;
76
- }
77
-
78
- export function mapImport(row) {
79
- return row ? {
80
- contentHash: row.content_hash,
81
- sourcePath: row.source_path,
82
- importedAt: row.imported_at
83
- } : null;
84
- }
@@ -1,15 +0,0 @@
1
- import { mkdirSync } from 'node:fs';
2
- import { dirname, resolve } from 'node:path';
3
-
4
- import Database from 'better-sqlite3';
5
-
6
- export function openDatabase(filePath) {
7
- if (isFileDatabase(filePath)) {
8
- mkdirSync(dirname(resolve(filePath)), { recursive: true });
9
- }
10
- return new Database(filePath);
11
- }
12
-
13
- export function isFileDatabase(filePath) {
14
- return typeof filePath === 'string' && filePath !== ':memory:' && filePath !== '';
15
- }
@@ -1,103 +0,0 @@
1
- import { randomUUID } from 'node:crypto';
2
-
3
- import { nowIso } from '../../models.js';
4
- import { validateOutboxRecord } from '../record-validation.js';
5
- import { mapOutbox } from './mapper.js';
6
-
7
- export class OutboxRepository {
8
- constructor(db) {
9
- this.insertStatement = db.prepare(`
10
- INSERT INTO outbox (
11
- id, kind, aggregate_id, payload_json, status, attempts,
12
- next_attempt_at, created_at, sent_at, last_error
13
- ) VALUES (
14
- @id, @kind, @aggregateId, @payloadJson, @status, @attempts,
15
- @nextAttemptAt, @createdAt, @sentAt, @lastError
16
- )
17
- `);
18
- this.getStatement = db.prepare('SELECT * FROM outbox WHERE id = ?');
19
- this.listStatement = db.prepare('SELECT * FROM outbox ORDER BY rowid');
20
- this.pendingStatement = db.prepare(`
21
- SELECT * FROM outbox
22
- WHERE status = 'pending'
23
- AND (next_attempt_at IS NULL OR next_attempt_at <= ?)
24
- ORDER BY created_at, rowid
25
- `);
26
- this.markSentStatement = db.prepare(`
27
- UPDATE outbox SET
28
- status = 'sent', next_attempt_at = NULL, sent_at = ?, last_error = NULL
29
- WHERE id = ?
30
- `);
31
- this.markFailedStatement = db.prepare(`
32
- UPDATE outbox SET
33
- status = 'pending', attempts = attempts + 1,
34
- next_attempt_at = ?, last_error = ?
35
- WHERE id = ?
36
- `);
37
- this.deleteStatement = db.prepare('DELETE FROM outbox');
38
- }
39
-
40
- enqueue(entry) {
41
- if (entry.id && this.get(entry.id)) {
42
- throw new Error(`Duplicate outbox id: ${entry.id}`);
43
- }
44
-
45
- const stored = {
46
- id: entry.id || randomUUID(),
47
- kind: entry.kind,
48
- aggregateId: entry.aggregateId,
49
- payloadJson: JSON.stringify(entry.payload),
50
- status: entry.status ?? 'pending',
51
- attempts: entry.attempts ?? 0,
52
- nextAttemptAt: entry.nextAttemptAt ?? null,
53
- createdAt: entry.createdAt ?? nowIso(),
54
- sentAt: entry.sentAt ?? null,
55
- lastError: entry.lastError ?? null
56
- };
57
- validateOutboxRecord(stored);
58
- this.insertStatement.run(stored);
59
- return this.get(stored.id);
60
- }
61
-
62
- listPending(at = nowIso()) {
63
- return this.pendingStatement.all(at).map(mapOutbox);
64
- }
65
-
66
- markSent(id, sentAt = nowIso()) {
67
- this.require(id);
68
- this.markSentStatement.run(sentAt, id);
69
- return this.get(id);
70
- }
71
-
72
- markFailed(id, error, nextAttemptAt = null) {
73
- this.require(id);
74
- this.markFailedStatement.run(nextAttemptAt, error, id);
75
- return this.get(id);
76
- }
77
-
78
- get(id) {
79
- return mapOutbox(this.getStatement.get(id));
80
- }
81
-
82
- listAll() {
83
- return this.listStatement.all().map(mapOutbox);
84
- }
85
-
86
- insertSnapshot(entry) {
87
- validateOutboxRecord(entry);
88
- this.insertStatement.run({
89
- ...entry,
90
- payloadJson: JSON.stringify(entry.payload)
91
- });
92
- }
93
-
94
- deleteAll() {
95
- this.deleteStatement.run();
96
- }
97
-
98
- require(id) {
99
- const row = this.get(id);
100
- if (!row) throw new Error(`Outbox row not found: ${id}`);
101
- return row;
102
- }
103
- }
@@ -1,69 +0,0 @@
1
- import { cloneSnapshot, normalizeSnapshot } from '../snapshot-defaults.js';
2
-
3
- export class SnapshotRepository {
4
- constructor(db, repositories) {
5
- this.db = db;
6
- this.repositories = repositories;
7
- }
8
-
9
- export() {
10
- const repositories = this.repositories;
11
- return cloneSnapshot({
12
- spaces: repositories.spaces.list(),
13
- subscriptions: repositories.subscriptions.list(),
14
- episodes: repositories.episodes.list(),
15
- facts: repositories.facts.list({ includeHistorical: true }),
16
- candidates: repositories.candidates.list(),
17
- outbox: repositories.outbox.listAll(),
18
- imports: repositories.imports.listAll()
19
- });
20
- }
21
-
22
- import(snapshot) {
23
- const current = this.export();
24
- const normalized = normalizeSnapshot(snapshot, {
25
- outbox: current.outbox,
26
- imports: current.imports
27
- });
28
- const replaceOutbox = Object.hasOwn(snapshot, 'outbox');
29
- const replaceImports = Object.hasOwn(snapshot, 'imports');
30
- const apply = () => this.replace(normalized, { replaceOutbox, replaceImports });
31
-
32
- if (this.db.inTransaction) apply();
33
- else this.db.transaction(apply)();
34
- }
35
-
36
- replace(snapshot, { replaceOutbox, replaceImports }) {
37
- const repositories = this.repositories;
38
- repositories.subscriptions.deleteAll();
39
- repositories.candidates.deleteAll();
40
- repositories.facts.deleteAll();
41
- repositories.episodes.deleteAll();
42
- if (replaceOutbox) repositories.outbox.deleteAll();
43
- if (replaceImports) repositories.imports.deleteAll();
44
- repositories.spaces.deleteAll();
45
-
46
- for (const space of snapshot.spaces) repositories.spaces.insertSnapshot(space);
47
- for (const subscription of snapshot.subscriptions) {
48
- repositories.subscriptions.insertSnapshot(subscription);
49
- }
50
- for (const episode of snapshot.episodes) repositories.episodes.insertSnapshot(episode);
51
- for (const fact of snapshot.facts) {
52
- repositories.facts.insertSnapshot(fact, { deferReplacement: true });
53
- }
54
- for (const fact of snapshot.facts) {
55
- if (fact.replacedByFactId) {
56
- repositories.facts.restoreReplacement(fact.id, fact.replacedByFactId);
57
- }
58
- }
59
- for (const candidate of snapshot.candidates) {
60
- repositories.candidates.insertSnapshot(candidate);
61
- }
62
- if (replaceOutbox) {
63
- for (const entry of snapshot.outbox) repositories.outbox.insertSnapshot(entry);
64
- }
65
- if (replaceImports) {
66
- for (const record of snapshot.imports) repositories.imports.insertSnapshot(record);
67
- }
68
- }
69
- }
@@ -1,52 +0,0 @@
1
- import { randomUUID } from 'node:crypto';
2
-
3
- import { nowIso } from '../../models.js';
4
- import { mapSpace } from './mapper.js';
5
-
6
- export class SpacesRepository {
7
- constructor(db) {
8
- this.insertStatement = db.prepare(`
9
- INSERT INTO spaces (id, name, kind, description, created_at)
10
- VALUES (@id, @name, @kind, @description, @createdAt)
11
- `);
12
- this.listStatement = db.prepare('SELECT * FROM spaces ORDER BY rowid');
13
- this.findByNameStatement = db.prepare('SELECT * FROM spaces WHERE name = ?');
14
- this.getStatement = db.prepare('SELECT * FROM spaces WHERE id = ?');
15
- this.deleteStatement = db.prepare('DELETE FROM spaces');
16
- }
17
-
18
- create(name, kind, description = null) {
19
- const existing = this.findByName(name);
20
- if (existing) return existing;
21
-
22
- const space = {
23
- id: randomUUID(),
24
- name,
25
- kind,
26
- description,
27
- createdAt: nowIso()
28
- };
29
- this.insertStatement.run(space);
30
- return this.get(space.id);
31
- }
32
-
33
- list() {
34
- return this.listStatement.all().map(mapSpace);
35
- }
36
-
37
- findByName(name) {
38
- return mapSpace(this.findByNameStatement.get(name));
39
- }
40
-
41
- get(id) {
42
- return mapSpace(this.getStatement.get(id));
43
- }
44
-
45
- insertSnapshot(space) {
46
- this.insertStatement.run(space);
47
- }
48
-
49
- deleteAll() {
50
- this.deleteStatement.run();
51
- }
52
- }
@@ -1,54 +0,0 @@
1
- import { nowIso } from '../../models.js';
2
- import { mapSubscription } from './mapper.js';
3
-
4
- export class SubscriptionsRepository {
5
- constructor(db) {
6
- this.insertStatement = db.prepare(`
7
- INSERT INTO subscriptions (personal_space_id, space_id, mode, created_at)
8
- VALUES (@personalSpaceId, @spaceId, @mode, @createdAt)
9
- `);
10
- this.getStatement = db.prepare(`
11
- SELECT * FROM subscriptions
12
- WHERE personal_space_id = ? AND space_id = ?
13
- `);
14
- this.listStatement = db.prepare('SELECT * FROM subscriptions ORDER BY rowid');
15
- this.forPersonalSpaceStatement = db.prepare(`
16
- SELECT * FROM subscriptions WHERE personal_space_id = ? ORDER BY rowid
17
- `);
18
- this.deleteStatement = db.prepare('DELETE FROM subscriptions');
19
- }
20
-
21
- subscribe(personalSpaceId, spaceId, mode = 'latest') {
22
- const existing = this.get(personalSpaceId, spaceId);
23
- if (existing) return existing;
24
-
25
- const subscription = {
26
- personalSpaceId,
27
- spaceId,
28
- mode,
29
- createdAt: nowIso()
30
- };
31
- this.insertStatement.run(subscription);
32
- return this.get(personalSpaceId, spaceId);
33
- }
34
-
35
- list() {
36
- return this.listStatement.all().map(mapSubscription);
37
- }
38
-
39
- forPersonalSpace(personalSpaceId) {
40
- return this.forPersonalSpaceStatement.all(personalSpaceId).map(mapSubscription);
41
- }
42
-
43
- insertSnapshot(subscription) {
44
- this.insertStatement.run(subscription);
45
- }
46
-
47
- deleteAll() {
48
- this.deleteStatement.run();
49
- }
50
-
51
- get(personalSpaceId, spaceId) {
52
- return mapSubscription(this.getStatement.get(personalSpaceId, spaceId));
53
- }
54
- }