ocean-brain 0.11.1 → 0.12.1
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.
- package/README.md +1 -1
- package/package.json +4 -2
- package/server/client/dist/assets/ArrowRight.es-DZTjsBaI.js +1 -0
- package/server/client/dist/assets/AuxiliaryPanelHeader-Bp3iRjm6.js +1 -0
- package/server/client/dist/assets/Calendar-CBYfyLwm.js +25 -0
- package/server/client/dist/assets/Callout-Cye6_6BM.js +1 -0
- package/server/client/dist/assets/CheckCircle.es-BTloiuti.js +1 -0
- package/server/client/dist/assets/Copy.es-CkLUgwY-.js +1 -0
- package/server/client/dist/assets/File.es-c83QExqr.js +1 -0
- package/server/client/dist/assets/FlowArrow.es-DWiIXWQG.js +1 -0
- package/server/client/dist/assets/Graph-jbPLcne9.js +46 -0
- package/server/client/dist/assets/Image.es-vY-nTyOe.js +1 -0
- package/server/client/dist/assets/Note-BTQLB80Y.js +25 -0
- package/server/client/dist/assets/{NotePropertiesPanel-CsaNhRnE.js → NotePropertiesPanel-uO-JKy19.js} +2 -2
- package/server/client/dist/assets/Plus.es-CaRgmXc_.js +1 -0
- package/server/client/dist/assets/Reminders-BAgZEUwV.js +1 -0
- package/server/client/dist/assets/Search-D0yR4DTH.js +31 -0
- package/server/client/dist/assets/SurfaceCard-BAlcul6z.js +1 -0
- package/server/client/dist/assets/Tag-B8UYGbt9.js +1 -0
- package/server/client/dist/assets/TagNotes-CDVHz74G.js +1 -0
- package/server/client/dist/assets/Trash.es-prxIxo9c.js +1 -0
- package/server/client/dist/assets/ViewChip-Cq_YzRvB.js +1 -0
- package/server/client/dist/assets/Views-Cjdbbut-.js +1 -0
- package/server/client/dist/assets/{abnfDiagram-VRR7QNED-CWlV5dXw.js → abnfDiagram-VRR7QNED-2cuaaDg0.js} +1 -1
- package/server/client/dist/assets/app-Clwvoiwx.js +139 -0
- package/server/client/dist/assets/app-DGFNtdpS.css +1 -0
- package/server/client/dist/assets/{arc-BV-n6RUX.js → arc-Cr-oYJoQ.js} +1 -1
- package/server/client/dist/assets/{architectureDiagram-ZJ3FMSHR-CekzMXdu.js → architectureDiagram-ZJ3FMSHR-HF8-bEM0.js} +1 -1
- package/server/client/dist/assets/{blockDiagram-677ZJIJ3-B49yHoxq.js → blockDiagram-677ZJIJ3-evR8rlpQ.js} +1 -1
- package/server/client/dist/assets/{c4Diagram-LMCZKHZV-0AfB9NBC.js → c4Diagram-LMCZKHZV-zCjuWUmC.js} +1 -1
- package/server/client/dist/assets/channel-Cq5ZBtfo.js +1 -0
- package/server/client/dist/assets/{chunk-2Q5K7J3B-DaPQAU1q.js → chunk-2Q5K7J3B-P0v1gRui.js} +1 -1
- package/server/client/dist/assets/{chunk-32BRIVSS-CfL3kZAg.js → chunk-32BRIVSS-102-EyWU.js} +1 -1
- package/server/client/dist/assets/{chunk-5VM5RSS4-DeOwWPE_.js → chunk-5VM5RSS4-uqI_1K1p.js} +1 -1
- package/server/client/dist/assets/{chunk-EX3LRPZG-C3W1oc9y.js → chunk-EX3LRPZG-k5hlNinR.js} +1 -1
- package/server/client/dist/assets/{chunk-JWPE2WC7-_Oqh79OY.js → chunk-JWPE2WC7-PLvgEy_B.js} +1 -1
- package/server/client/dist/assets/{chunk-MOJQB5TN-DDKNNzI6.js → chunk-MOJQB5TN-B-LmIwhJ.js} +1 -1
- package/server/client/dist/assets/{chunk-RYQCIY6F-DCHah7CC.js → chunk-RYQCIY6F-CaNSMki0.js} +1 -1
- package/server/client/dist/assets/{chunk-V7JOEXUC-uUOIzgxj.js → chunk-V7JOEXUC-CmSzAkUx.js} +1 -1
- package/server/client/dist/assets/{chunk-VR4S4FIN-DDVw2WPs.js → chunk-VR4S4FIN-IiOfWaBG.js} +1 -1
- package/server/client/dist/assets/{chunk-XXDRQBXY-Coo4UFm2.js → chunk-XXDRQBXY-DRXrI6SM.js} +1 -1
- package/server/client/dist/assets/classDiagram-OUVF2IWQ-BqUaa8a6.js +1 -0
- package/server/client/dist/assets/classDiagram-v2-EOCWNBFH-BqUaa8a6.js +1 -0
- package/server/client/dist/assets/{cose-bilkent-JH36ORCC-C3pHmHpD.js → cose-bilkent-JH36ORCC-COcEaZRS.js} +1 -1
- package/server/client/dist/assets/{cynefinDiagram-TSTJHNR4-DZ6iiJFo.js → cynefinDiagram-TSTJHNR4-CSHqcg9K.js} +1 -1
- package/server/client/dist/assets/{dagre-VKFMJZFB-DNELbhwC.js → dagre-VKFMJZFB-XAWCPOS0.js} +1 -1
- package/server/client/dist/assets/{diagram-FQU43EPY-CMEvZJ61.js → diagram-FQU43EPY-scBCDUnE.js} +1 -1
- package/server/client/dist/assets/{diagram-G47NLZAW-BYDtf6j9.js → diagram-G47NLZAW-nPKP5kd8.js} +1 -1
- package/server/client/dist/assets/{diagram-NH7WQ7WH-CmXfcCQA.js → diagram-NH7WQ7WH-DcWalY4j.js} +1 -1
- package/server/client/dist/assets/{diagram-OA4YK3LP-BflTKJxU.js → diagram-OA4YK3LP-CXTlHB5A.js} +4 -4
- package/server/client/dist/assets/{diagram-WEI45ONY-KW3fKqna.js → diagram-WEI45ONY-D1uvaJub.js} +1 -1
- package/server/client/dist/assets/{ebnfDiagram-CCIWWBDH-CbyhY5Nj.js → ebnfDiagram-CCIWWBDH-nHt3vRNt.js} +1 -1
- package/server/client/dist/assets/{erDiagram-Q63AITRT-CC2eZV8g.js → erDiagram-Q63AITRT-K6stUUV2.js} +1 -1
- package/server/client/dist/assets/{flowDiagram-23GEKE2U-unplo7Qm.js → flowDiagram-23GEKE2U-DDWtKrEp.js} +4 -4
- package/server/client/dist/assets/{ganttDiagram-NO4QXBWP-BcBK5HcZ.js → ganttDiagram-NO4QXBWP-Daqh7b4I.js} +1 -1
- package/server/client/dist/assets/{gitGraphDiagram-IHSO6WYX-DgVbbb5k.js → gitGraphDiagram-IHSO6WYX-D73CLkcG.js} +1 -1
- package/server/client/dist/assets/{image.api-CyQisInP.js → image.api-Bbwmcw0T.js} +1 -1
- package/server/client/dist/assets/index-PjoL3frH.js +1 -0
- package/server/client/dist/assets/{infoDiagram-FWYZ7A6U-N_D9RdfG.js → infoDiagram-FWYZ7A6U-Bc1YI5Iu.js} +1 -1
- package/server/client/dist/assets/{ishikawaDiagram-FXEZZL3T-BBKrzmB2.js → ishikawaDiagram-FXEZZL3T-BJDXbmaY.js} +1 -1
- package/server/client/dist/assets/{journeyDiagram-5HDEW3XC-NdCxLmQB.js → journeyDiagram-5HDEW3XC-DlyJx4xT.js} +1 -1
- package/server/client/dist/assets/{kanban-definition-HUTT4EX6-Ck7t4-tt.js → kanban-definition-HUTT4EX6-AmTeXesc.js} +1 -1
- package/server/client/dist/assets/manage-image-DCBgglLx.js +1 -0
- package/server/client/dist/assets/manage-image-detail-CLPXVSZf.js +1 -0
- package/server/client/dist/assets/{mcp-CCaFd0zm.js → mcp-Y2p95Lm9.js} +1 -1
- package/server/client/dist/assets/mcp-admin-adapter-z9OaMVrl.js +1 -0
- package/server/client/dist/assets/{mermaid.core-B14ZAmWA.js → mermaid.core-B528P54n.js} +4 -4
- package/server/client/dist/assets/{mindmap-definition-LN4V7U3C-BTUqSdcP.js → mindmap-definition-LN4V7U3C-C_6tKVmY.js} +1 -1
- package/server/client/dist/assets/{note-ui-CtijppHK.js → note-ui-Cv6quEYh.js} +1 -1
- package/server/client/dist/assets/{pegDiagram-2B236MQR-Cjcu3mVz.js → pegDiagram-2B236MQR-CGKCNdrV.js} +1 -1
- package/server/client/dist/assets/{pieDiagram-ENE6RG2P-BxI_c22Z.js → pieDiagram-ENE6RG2P-C3cMh7d5.js} +1 -1
- package/server/client/dist/assets/{placeholder-DR75IAi_.js → placeholder-D4Lkz_or.js} +1 -1
- package/server/client/dist/assets/properties-CwxDtivz.js +1 -0
- package/server/client/dist/assets/{quadrantDiagram-ABIIQ3AL-DGm0a-yR.js → quadrantDiagram-ABIIQ3AL-CVVzGxBd.js} +1 -1
- package/server/client/dist/assets/{railroadDiagram-RFXS5EU6-DmCDu89O.js → railroadDiagram-RFXS5EU6-wa-4aJR9.js} +1 -1
- package/server/client/dist/assets/{requirementDiagram-TGXJPOKE-CBpUt7Ig.js → requirementDiagram-TGXJPOKE-DyuC9W7q.js} +1 -1
- package/server/client/dist/assets/route-preload.js +2 -2
- package/server/client/dist/assets/{sankeyDiagram-HTMAVEWB-BjGnDZlp.js → sankeyDiagram-HTMAVEWB-B70y-MQg.js} +1 -1
- package/server/client/dist/assets/search-BlbdHNDA.js +1 -0
- package/server/client/dist/assets/search-admin-adapter-BLMYNm9w.js +1 -0
- package/server/client/dist/assets/{sequenceDiagram-DBY2YBRQ-B7rpCfS6.js → sequenceDiagram-DBY2YBRQ-mGkh3IEy.js} +1 -1
- package/server/client/dist/assets/{sizeCapture-X5ZJPWSS-CUJhD0uW.js → sizeCapture-X5ZJPWSS-BhnCNP5z.js} +1 -1
- package/server/client/dist/assets/sortable.esm-DgSW3bAA.js +5 -0
- package/server/client/dist/assets/{stateDiagram-2N3HPSRC-Y9-3XqRX.js → stateDiagram-2N3HPSRC-BqSKSfKi.js} +1 -1
- package/server/client/dist/assets/{stateDiagram-v2-6OUMAXLB-DbHXhT02.js → stateDiagram-v2-6OUMAXLB-Wl7lGTf3.js} +1 -1
- package/server/client/dist/assets/{swimlanes-5IMT3BWC-Cs1NO9ng.js → swimlanes-5IMT3BWC-CZ7ckdmY.js} +2 -2
- package/server/client/dist/assets/swimlanesDiagram-G3AALYLV-BZL3DQ-c.js +8 -0
- package/server/client/dist/assets/{tag.api-DNSQpqwk.js → tag.api-bU52U0tT.js} +1 -1
- package/server/client/dist/assets/time-DZKkPFUi.js +361 -0
- package/server/client/dist/assets/{timeline-definition-FHXFAJF6-8fqD4WNL.js → timeline-definition-FHXFAJF6-C-t9-x4M.js} +3 -3
- package/server/client/dist/assets/trash-BNeUFJnC.js +1 -0
- package/server/client/dist/assets/{url-9nrbQs7Z.js → url-BvjSC8PU.js} +1 -1
- package/server/client/dist/assets/useNoteMutate-VJYw0Vt_.js +9 -0
- package/server/client/dist/assets/useReminderMutate-DaO0QssD.js +121 -0
- package/server/client/dist/assets/{vennDiagram-L72KCM5P-BPqKkd5a.js → vennDiagram-L72KCM5P-CP014Nl5.js} +1 -1
- package/server/client/dist/assets/{wardleyDiagram-EHGQE667-Cq11kU2M.js → wardleyDiagram-EHGQE667-B7Ghd-94.js} +1 -1
- package/server/client/dist/assets/{xychartDiagram-FW5EYKEG-DJ6UzFj-.js → xychartDiagram-FW5EYKEG-C4YGS0YJ.js} +1 -1
- package/server/client/dist/index.html +6 -6
- package/server/dist/app.js +5 -5
- package/server/dist/features/note/graphql/note.query.resolver.js +9 -1
- package/server/dist/features/note/graphql/note.type-defs.js +2 -0
- package/server/dist/features/note/services/content-blocks.js +6 -1
- package/server/dist/features/reminder/graphql/reminder.query.resolver.js +62 -0
- package/server/dist/features/reminder/graphql/reminder.type-defs.js +25 -0
- package/server/dist/features/search/sqlite-vector-index.js +71 -138
- package/server/dist/features/view/graphql/view.query.resolver.js +40 -5
- package/server/dist/features/view/graphql/view.type-defs.js +23 -1
- package/server/dist/features/view/services/workspace.js +108 -5
- package/server/dist/main.js +1 -35
- package/server/dist/routes/api.js +6 -1
- package/server/dist/routes/client.js +15 -4
- package/server/dist/server.js +40 -0
- package/server/prisma/schema.prisma +1 -0
- package/server/client/dist/assets/ArrowRight.es-BEhnlwfe.js +0 -1
- package/server/client/dist/assets/AuxiliaryPanelHeader-B1k7pZpn.js +0 -1
- package/server/client/dist/assets/Calendar-BR83066I.js +0 -25
- package/server/client/dist/assets/Callout-BTd1BYtV.js +0 -1
- package/server/client/dist/assets/Copy.es-BoLjilxv.js +0 -1
- package/server/client/dist/assets/File.es-Ct7fscBg.js +0 -1
- package/server/client/dist/assets/Graph-DYUb7gQb.js +0 -46
- package/server/client/dist/assets/Graph-LY_IgMYy.js +0 -1
- package/server/client/dist/assets/Image.es-OYyN3BYt.js +0 -1
- package/server/client/dist/assets/Note-DnXDfDTk.js +0 -25
- package/server/client/dist/assets/PencilLine.es-0JpEgxFY.js +0 -1
- package/server/client/dist/assets/Plus.es-DRfLW_bI.js +0 -1
- package/server/client/dist/assets/Reminders-COr1OHvF.js +0 -1
- package/server/client/dist/assets/Search-BqPROwkA.js +0 -31
- package/server/client/dist/assets/SurfaceCard-t7pdfhEe.js +0 -1
- package/server/client/dist/assets/Tag-C4fNi3KQ.js +0 -1
- package/server/client/dist/assets/TagNotes-BXgkwXMd.js +0 -1
- package/server/client/dist/assets/Trash.es-Bo1VyK9Q.js +0 -1
- package/server/client/dist/assets/ViewChip-DlCkL4z4.js +0 -1
- package/server/client/dist/assets/ViewNotes-BJPiV66I.js +0 -1
- package/server/client/dist/assets/ViewSectionDialog-DhH8harC.js +0 -102
- package/server/client/dist/assets/Views-DMizFO-d.js +0 -1
- package/server/client/dist/assets/app-1lOWtkRA.js +0 -26
- package/server/client/dist/assets/app-BHmwRmwM.css +0 -1
- package/server/client/dist/assets/channel-CigOulR-.js +0 -1
- package/server/client/dist/assets/classDiagram-OUVF2IWQ-D13ISIOW.js +0 -1
- package/server/client/dist/assets/classDiagram-v2-EOCWNBFH-D13ISIOW.js +0 -1
- package/server/client/dist/assets/index-BBzpADtD.js +0 -1
- package/server/client/dist/assets/manage-image-azmo5iVw.js +0 -1
- package/server/client/dist/assets/manage-image-detail-ojV6kSsu.js +0 -1
- package/server/client/dist/assets/mcp-admin-adapter-PMmgPupc.js +0 -1
- package/server/client/dist/assets/note.api-D3oJmG3q.js +0 -356
- package/server/client/dist/assets/properties-BrNJLkxR.js +0 -1
- package/server/client/dist/assets/search-D-xKTPdO.js +0 -1
- package/server/client/dist/assets/search-admin-adapter-CJnJmL6w.js +0 -1
- package/server/client/dist/assets/swimlanesDiagram-G3AALYLV-DQnrq2ZC.js +0 -8
- package/server/client/dist/assets/time-D5LRMumI.js +0 -9
- package/server/client/dist/assets/trash-X_dnKA9T.js +0 -1
- package/server/client/dist/assets/useReminderMutate-BrGO9M0o.js +0 -82
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { mkdir } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { DatabaseSync } from "node:sqlite";
|
|
4
|
+
import { setImmediate as yieldToEventLoop } from "node:timers/promises";
|
|
3
5
|
import { getLoadablePath } from "sqlite-vec";
|
|
4
|
-
import sqlite3 from "sqlite3";
|
|
5
6
|
const SEMANTIC_INDEX_SCHEMA_VERSION = 2;
|
|
6
7
|
const SEARCH_DATABASE_SCHEMA_VERSION = 1;
|
|
7
8
|
const SEARCH_DATABASE_SCHEMA_SQL = `
|
|
@@ -50,69 +51,27 @@ const openDatabase = async (filePath) => {
|
|
|
50
51
|
if (filePath !== ":memory:") {
|
|
51
52
|
await mkdir(path.dirname(filePath), { recursive: true });
|
|
52
53
|
}
|
|
53
|
-
const database =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
await new Promise((resolve, reject) => {
|
|
63
|
-
database.loadExtension(getLoadablePath(), (error) => {
|
|
64
|
-
if (error) {
|
|
65
|
-
reject(error);
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
resolve();
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
return database;
|
|
54
|
+
const database = new DatabaseSync(filePath, { allowExtension: true });
|
|
55
|
+
try {
|
|
56
|
+
database.loadExtension(getLoadablePath());
|
|
57
|
+
database.enableLoadExtension(false);
|
|
58
|
+
return database;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
database.close();
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
72
63
|
};
|
|
73
64
|
const run = (database, sql, parameters = []) => {
|
|
74
|
-
return
|
|
75
|
-
database.run(sql, parameters, function onRun(error) {
|
|
76
|
-
if (error) {
|
|
77
|
-
reject(error);
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
resolve(this);
|
|
81
|
-
});
|
|
82
|
-
});
|
|
65
|
+
return database.prepare(sql).run(...parameters);
|
|
83
66
|
};
|
|
84
67
|
const get = (database, sql, parameters = []) => {
|
|
85
|
-
return
|
|
86
|
-
database.get(sql, parameters, (error, row) => {
|
|
87
|
-
if (error) {
|
|
88
|
-
reject(error);
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
resolve(row);
|
|
92
|
-
});
|
|
93
|
-
});
|
|
68
|
+
return database.prepare(sql).get(...parameters);
|
|
94
69
|
};
|
|
95
70
|
const all = (database, sql, parameters = []) => {
|
|
96
|
-
return
|
|
97
|
-
database.all(sql, parameters, (error, rows) => {
|
|
98
|
-
if (error) {
|
|
99
|
-
reject(error);
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
resolve(rows);
|
|
103
|
-
});
|
|
104
|
-
});
|
|
71
|
+
return database.prepare(sql).all(...parameters);
|
|
105
72
|
};
|
|
106
73
|
const exec = (database, sql) => {
|
|
107
|
-
|
|
108
|
-
database.exec(sql, (error) => {
|
|
109
|
-
if (error) {
|
|
110
|
-
reject(error);
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
resolve();
|
|
114
|
-
});
|
|
115
|
-
});
|
|
74
|
+
database.exec(sql);
|
|
116
75
|
};
|
|
117
76
|
const initializeSearchDatabase = async (database) => {
|
|
118
77
|
await exec(
|
|
@@ -134,19 +93,35 @@ const initializeSearchDatabase = async (database) => {
|
|
|
134
93
|
await exec(database, `PRAGMA user_version = ${SEARCH_DATABASE_SCHEMA_VERSION};`);
|
|
135
94
|
};
|
|
136
95
|
const close = (database) => {
|
|
137
|
-
|
|
138
|
-
database.close((error) => {
|
|
139
|
-
if (error) {
|
|
140
|
-
reject(error);
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
resolve();
|
|
144
|
-
});
|
|
145
|
-
});
|
|
96
|
+
database.close();
|
|
146
97
|
};
|
|
147
98
|
const serializeEmbedding = (embedding) => {
|
|
148
99
|
return Buffer.from(Float32Array.from(embedding).buffer);
|
|
149
100
|
};
|
|
101
|
+
const INSERT_YIELD_INTERVAL = 250;
|
|
102
|
+
const insertChunks = async (database, chunks) => {
|
|
103
|
+
const statement = database.prepare(`
|
|
104
|
+
INSERT INTO search_chunks (
|
|
105
|
+
note_id,
|
|
106
|
+
chunk_index,
|
|
107
|
+
source_hash,
|
|
108
|
+
text,
|
|
109
|
+
embedding
|
|
110
|
+
) VALUES (?, ?, ?, ?, ?)
|
|
111
|
+
`);
|
|
112
|
+
for (const [index, chunk] of chunks.entries()) {
|
|
113
|
+
statement.run(
|
|
114
|
+
chunk.noteId,
|
|
115
|
+
chunk.chunkIndex,
|
|
116
|
+
chunk.sourceHash,
|
|
117
|
+
chunk.text,
|
|
118
|
+
serializeEmbedding(chunk.embedding)
|
|
119
|
+
);
|
|
120
|
+
if ((index + 1) % INSERT_YIELD_INTERVAL === 0 && index + 1 < chunks.length) {
|
|
121
|
+
await yieldToEventLoop();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
};
|
|
150
125
|
const isSemanticIndexProfile = (value) => {
|
|
151
126
|
if (!value || typeof value !== "object") {
|
|
152
127
|
return false;
|
|
@@ -217,27 +192,7 @@ class SqliteSemanticVectorIndex {
|
|
|
217
192
|
try {
|
|
218
193
|
await run(database, "DELETE FROM search_chunks");
|
|
219
194
|
await run(database, "DELETE FROM search_index_state");
|
|
220
|
-
|
|
221
|
-
await run(
|
|
222
|
-
database,
|
|
223
|
-
`
|
|
224
|
-
INSERT INTO search_chunks (
|
|
225
|
-
note_id,
|
|
226
|
-
chunk_index,
|
|
227
|
-
source_hash,
|
|
228
|
-
text,
|
|
229
|
-
embedding
|
|
230
|
-
) VALUES (?, ?, ?, ?, ?)
|
|
231
|
-
`,
|
|
232
|
-
[
|
|
233
|
-
chunk.noteId,
|
|
234
|
-
chunk.chunkIndex,
|
|
235
|
-
chunk.sourceHash,
|
|
236
|
-
chunk.text,
|
|
237
|
-
serializeEmbedding(chunk.embedding)
|
|
238
|
-
]
|
|
239
|
-
);
|
|
240
|
-
}
|
|
195
|
+
await insertChunks(database, chunks);
|
|
241
196
|
await run(
|
|
242
197
|
database,
|
|
243
198
|
`
|
|
@@ -472,37 +427,35 @@ class SqliteSemanticVectorIndex {
|
|
|
472
427
|
async getNoteSyncQueueStatus() {
|
|
473
428
|
return this.runExclusive(async () => {
|
|
474
429
|
const database = await this.getDatabase();
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
)
|
|
505
|
-
]);
|
|
430
|
+
const queue = get(
|
|
431
|
+
database,
|
|
432
|
+
`
|
|
433
|
+
SELECT
|
|
434
|
+
COUNT(*) AS pendingNoteCount,
|
|
435
|
+
MIN(first_queued_at) AS oldestQueuedAt
|
|
436
|
+
FROM search_note_sync_queue
|
|
437
|
+
`
|
|
438
|
+
);
|
|
439
|
+
const state = get(
|
|
440
|
+
database,
|
|
441
|
+
`
|
|
442
|
+
SELECT
|
|
443
|
+
last_synced_at AS lastSyncedAt,
|
|
444
|
+
last_reconciled_at AS lastReconciledAt
|
|
445
|
+
FROM search_note_sync_state
|
|
446
|
+
WHERE id = 1
|
|
447
|
+
`
|
|
448
|
+
);
|
|
449
|
+
const latestError = get(
|
|
450
|
+
database,
|
|
451
|
+
`
|
|
452
|
+
SELECT last_error AS error
|
|
453
|
+
FROM search_note_sync_queue
|
|
454
|
+
WHERE last_error IS NOT NULL
|
|
455
|
+
ORDER BY next_attempt_at DESC, note_id ASC
|
|
456
|
+
LIMIT 1
|
|
457
|
+
`
|
|
458
|
+
);
|
|
506
459
|
return {
|
|
507
460
|
pendingNoteCount: queue?.pendingNoteCount ?? 0,
|
|
508
461
|
oldestQueuedAt: queue?.oldestQueuedAt === null || queue?.oldestQueuedAt === void 0 ? null : new Date(queue.oldestQueuedAt).toISOString(),
|
|
@@ -526,27 +479,7 @@ class SqliteSemanticVectorIndex {
|
|
|
526
479
|
await exec(database, "BEGIN IMMEDIATE;");
|
|
527
480
|
try {
|
|
528
481
|
await run(database, "DELETE FROM search_chunks WHERE note_id = ?", [noteId]);
|
|
529
|
-
|
|
530
|
-
await run(
|
|
531
|
-
database,
|
|
532
|
-
`
|
|
533
|
-
INSERT INTO search_chunks (
|
|
534
|
-
note_id,
|
|
535
|
-
chunk_index,
|
|
536
|
-
source_hash,
|
|
537
|
-
text,
|
|
538
|
-
embedding
|
|
539
|
-
) VALUES (?, ?, ?, ?, ?)
|
|
540
|
-
`,
|
|
541
|
-
[
|
|
542
|
-
chunk.noteId,
|
|
543
|
-
chunk.chunkIndex,
|
|
544
|
-
chunk.sourceHash,
|
|
545
|
-
chunk.text,
|
|
546
|
-
serializeEmbedding(chunk.embedding)
|
|
547
|
-
]
|
|
548
|
-
);
|
|
549
|
-
}
|
|
482
|
+
await insertChunks(database, chunks);
|
|
550
483
|
await this.refreshIndexCounts(database);
|
|
551
484
|
await exec(database, "COMMIT;");
|
|
552
485
|
} catch (error) {
|
|
@@ -2,6 +2,7 @@ import { GraphQLError } from "graphql";
|
|
|
2
2
|
import { InvalidNotePropertyInputError } from "../../../features/note/services/properties.js";
|
|
3
3
|
import {
|
|
4
4
|
getNotesByProperties,
|
|
5
|
+
getViewSectionBoardColumn,
|
|
5
6
|
getViewSectionById,
|
|
6
7
|
getViewSectionNotes,
|
|
7
8
|
getViewWorkspace
|
|
@@ -18,17 +19,51 @@ const viewQueryResolvers = {
|
|
|
18
19
|
pagination = {
|
|
19
20
|
limit: 25,
|
|
20
21
|
offset: 0
|
|
21
|
-
}
|
|
22
|
+
},
|
|
23
|
+
sortBy,
|
|
24
|
+
sortOrder
|
|
22
25
|
}) => {
|
|
23
|
-
const sectionNotes = await getViewSectionNotes(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
const sectionNotes = await getViewSectionNotes(
|
|
27
|
+
id,
|
|
28
|
+
{
|
|
29
|
+
limit: Number(pagination.limit),
|
|
30
|
+
offset: Number(pagination.offset)
|
|
31
|
+
},
|
|
32
|
+
{ sortBy, sortOrder }
|
|
33
|
+
);
|
|
27
34
|
if (!sectionNotes) {
|
|
28
35
|
throw "NOT FOUND";
|
|
29
36
|
}
|
|
30
37
|
return sectionNotes;
|
|
31
38
|
},
|
|
39
|
+
viewSectionBoardColumn: async (_, {
|
|
40
|
+
id,
|
|
41
|
+
optionValue = null,
|
|
42
|
+
pagination = {
|
|
43
|
+
limit: 5,
|
|
44
|
+
offset: 0
|
|
45
|
+
}
|
|
46
|
+
}) => {
|
|
47
|
+
try {
|
|
48
|
+
const columnNotes = await getViewSectionBoardColumn(id, optionValue, {
|
|
49
|
+
limit: Number(pagination.limit),
|
|
50
|
+
offset: Number(pagination.offset)
|
|
51
|
+
});
|
|
52
|
+
if (!columnNotes) {
|
|
53
|
+
throw "NOT FOUND";
|
|
54
|
+
}
|
|
55
|
+
return columnNotes;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (error instanceof InvalidNotePropertyInputError) {
|
|
58
|
+
throw new GraphQLError(error.message, {
|
|
59
|
+
extensions: {
|
|
60
|
+
code: "INVALID_NOTE_PROPERTY_INPUT"
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
},
|
|
32
67
|
notesByProperties: async (_, {
|
|
33
68
|
input,
|
|
34
69
|
pagination = {
|
|
@@ -37,11 +37,25 @@ const viewType = gql`
|
|
|
37
37
|
|
|
38
38
|
type ViewDisplayOptions {
|
|
39
39
|
tableColumns: [ViewTableColumn!]!
|
|
40
|
+
tablePropertyKeys: [String!]!
|
|
41
|
+
boardGroupByPropertyKey: String
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type ViewBoardNote {
|
|
45
|
+
id: ID!
|
|
46
|
+
title: String!
|
|
47
|
+
updatedAt: String!
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type ViewBoardColumnNotes {
|
|
51
|
+
totalCount: Int!
|
|
52
|
+
notes: [ViewBoardNote!]!
|
|
40
53
|
}
|
|
41
54
|
|
|
42
55
|
enum ViewDisplayType {
|
|
43
56
|
list
|
|
44
57
|
table
|
|
58
|
+
board
|
|
45
59
|
calendar
|
|
46
60
|
}
|
|
47
61
|
|
|
@@ -84,6 +98,8 @@ const viewType = gql`
|
|
|
84
98
|
|
|
85
99
|
input ViewDisplayOptionsInput {
|
|
86
100
|
tableColumns: [ViewTableColumn!]
|
|
101
|
+
tablePropertyKeys: [String!]
|
|
102
|
+
boardGroupByPropertyKey: String
|
|
87
103
|
}
|
|
88
104
|
|
|
89
105
|
input ViewSectionInput {
|
|
@@ -110,7 +126,13 @@ const viewQuery = gql`
|
|
|
110
126
|
type Query {
|
|
111
127
|
viewWorkspace: ViewWorkspace!
|
|
112
128
|
viewSection(id: ID!): ViewSection
|
|
113
|
-
viewSectionNotes(
|
|
129
|
+
viewSectionNotes(
|
|
130
|
+
id: ID!
|
|
131
|
+
pagination: PaginationInput
|
|
132
|
+
sortBy: ViewSortBy
|
|
133
|
+
sortOrder: ViewSortOrder
|
|
134
|
+
): Notes!
|
|
135
|
+
viewSectionBoardColumn(id: ID!, optionValue: String, pagination: PaginationInput): ViewBoardColumnNotes!
|
|
114
136
|
notesByProperties(input: NotesByPropertiesInput!, pagination: PaginationInput): Notes!
|
|
115
137
|
}
|
|
116
138
|
`;
|
|
@@ -12,6 +12,7 @@ const MAX_VIEW_SECTION_LIMIT = 20;
|
|
|
12
12
|
const DEFAULT_VIEW_NOTES_QUERY_LIMIT = 20;
|
|
13
13
|
const MAX_VIEW_NOTES_QUERY_LIMIT = 50;
|
|
14
14
|
const MAX_VIEW_PROPERTY_FILTERS = 10;
|
|
15
|
+
const MAX_VIEW_TABLE_PROPERTY_COLUMNS = 6;
|
|
15
16
|
const DEFAULT_VIEW_DISPLAY_TYPE = "list";
|
|
16
17
|
const DEFAULT_VIEW_SORT_BY = "updatedAt";
|
|
17
18
|
const DEFAULT_VIEW_SORT_ORDER = "desc";
|
|
@@ -54,7 +55,7 @@ const isViewTableColumn = (value) => {
|
|
|
54
55
|
return value === "title" || value === "tags" || value === "properties" || value === "createdAt" || value === "updatedAt";
|
|
55
56
|
};
|
|
56
57
|
const normalizeViewDisplayType = (value) => {
|
|
57
|
-
if (value === "table" || value === "calendar") {
|
|
58
|
+
if (value === "table" || value === "board" || value === "calendar") {
|
|
58
59
|
return value;
|
|
59
60
|
}
|
|
60
61
|
return DEFAULT_VIEW_DISPLAY_TYPE;
|
|
@@ -73,9 +74,12 @@ const normalizeViewTableColumns = (columns) => {
|
|
|
73
74
|
}
|
|
74
75
|
return uniqueColumns.includes("title") ? uniqueColumns : ["title", ...uniqueColumns];
|
|
75
76
|
};
|
|
77
|
+
const normalizeViewTablePropertyKeys = (keys) => Array.from(new Set((keys ?? []).map(normalizePropertyKey))).slice(0, MAX_VIEW_TABLE_PROPERTY_COLUMNS);
|
|
76
78
|
const normalizeViewDisplayOptions = (options) => {
|
|
77
79
|
return {
|
|
78
|
-
tableColumns: normalizeViewTableColumns(options?.tableColumns)
|
|
80
|
+
tableColumns: normalizeViewTableColumns(options?.tableColumns),
|
|
81
|
+
tablePropertyKeys: normalizeViewTablePropertyKeys(options?.tablePropertyKeys),
|
|
82
|
+
boardGroupByPropertyKey: options?.boardGroupByPropertyKey ? normalizePropertyKey(options.boardGroupByPropertyKey) : null
|
|
79
83
|
};
|
|
80
84
|
};
|
|
81
85
|
const normalizeFilterValue = ({
|
|
@@ -207,9 +211,16 @@ const normalizeViewSectionInput = (input) => {
|
|
|
207
211
|
const sortBy = normalizeViewSortBy(input.sortBy);
|
|
208
212
|
const sortOrder = normalizeViewSortOrder(input.sortOrder);
|
|
209
213
|
const displayOptions = normalizeViewDisplayOptions(input.displayOptions);
|
|
214
|
+
const displayType = normalizeViewDisplayType(input.displayType);
|
|
215
|
+
if (displayType === "board" && !displayOptions.boardGroupByPropertyKey) {
|
|
216
|
+
throw new InvalidNotePropertyInputError("Board sections require a grouping property.");
|
|
217
|
+
}
|
|
218
|
+
if (displayType === "board" && propertyFilters.some((filter) => filter.key === displayOptions.boardGroupByPropertyKey)) {
|
|
219
|
+
throw new InvalidNotePropertyInputError("A board grouping property cannot also be used as a section filter.");
|
|
220
|
+
}
|
|
210
221
|
return {
|
|
211
222
|
title: trimmedTitle || buildDefaultSectionTitle(tagNames, propertyFilters),
|
|
212
|
-
displayType
|
|
223
|
+
displayType,
|
|
213
224
|
displayOptions,
|
|
214
225
|
tagNames,
|
|
215
226
|
mode: input.mode === "or" ? "or" : "and",
|
|
@@ -358,6 +369,23 @@ const hydratePropertyFilters = async (db, filters, options = {}) => {
|
|
|
358
369
|
};
|
|
359
370
|
const buildStoredViewQuery = async (db, section) => {
|
|
360
371
|
const propertyFilters = await hydratePropertyFilters(db, section.propertyFilters, { validateSelectOptions: true });
|
|
372
|
+
if (section.displayType === "board") {
|
|
373
|
+
const groupingProperty = await db.propertyDefinition.findUnique({
|
|
374
|
+
where: { key: section.displayOptions.boardGroupByPropertyKey ?? "" },
|
|
375
|
+
select: { valueType: true, options: { select: { id: true }, take: 1 } }
|
|
376
|
+
});
|
|
377
|
+
if (!groupingProperty) {
|
|
378
|
+
throw new InvalidNotePropertyInputError(
|
|
379
|
+
`Property ${section.displayOptions.boardGroupByPropertyKey ?? ""} is not defined.`
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
if (groupingProperty.valueType !== "select") {
|
|
383
|
+
throw new InvalidNotePropertyInputError("Board sections can only group by a select property.");
|
|
384
|
+
}
|
|
385
|
+
if (groupingProperty.options.length === 0) {
|
|
386
|
+
throw new InvalidNotePropertyInputError("Board grouping properties require at least one option.");
|
|
387
|
+
}
|
|
388
|
+
}
|
|
361
389
|
return serializeStoredViewQuery({
|
|
362
390
|
propertyFilters,
|
|
363
391
|
sortBy: section.sortBy,
|
|
@@ -521,13 +549,26 @@ const buildViewNotesWhere = (query) => {
|
|
|
521
549
|
const buildViewSectionWhere = (section) => {
|
|
522
550
|
return buildViewNotesWhere(section);
|
|
523
551
|
};
|
|
552
|
+
const buildViewBoardColumnWhere = (section, propertyDefinitionId, optionId) => {
|
|
553
|
+
const sectionWhere = buildViewSectionWhere(section);
|
|
554
|
+
const columnWhere = optionId === null ? {
|
|
555
|
+
properties: {
|
|
556
|
+
none: { propertyDefinitionId }
|
|
557
|
+
}
|
|
558
|
+
} : {
|
|
559
|
+
properties: {
|
|
560
|
+
some: { propertyDefinitionId, optionId }
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
return Object.keys(sectionWhere).length === 0 ? columnWhere : { AND: [sectionWhere, columnWhere] };
|
|
564
|
+
};
|
|
524
565
|
const buildViewNotesOrderBy = (query) => {
|
|
525
566
|
return [{ [query.sortBy]: query.sortOrder }, { id: "asc" }];
|
|
526
567
|
};
|
|
527
568
|
const buildViewSectionOrderBy = (section) => {
|
|
528
569
|
return buildViewNotesOrderBy(section);
|
|
529
570
|
};
|
|
530
|
-
const getViewSectionNotes = async (id, pagination) => {
|
|
571
|
+
const getViewSectionNotes = async (id, pagination, sort = {}) => {
|
|
531
572
|
const section = await models.viewSection.findUnique({
|
|
532
573
|
where: { id: parseViewId(id) },
|
|
533
574
|
include: orderedViewSectionTagsInclude
|
|
@@ -537,10 +578,14 @@ const getViewSectionNotes = async (id, pagination) => {
|
|
|
537
578
|
}
|
|
538
579
|
const serializedSection = serializeViewSection(section);
|
|
539
580
|
const where = buildViewSectionWhere(serializedSection);
|
|
581
|
+
const effectiveSort = {
|
|
582
|
+
sortBy: sort.sortBy ?? serializedSection.sortBy,
|
|
583
|
+
sortOrder: sort.sortOrder ?? serializedSection.sortOrder
|
|
584
|
+
};
|
|
540
585
|
const [totalCount, notes] = await Promise.all([
|
|
541
586
|
models.note.count({ where }),
|
|
542
587
|
models.note.findMany({
|
|
543
|
-
orderBy:
|
|
588
|
+
orderBy: buildViewNotesOrderBy(effectiveSort),
|
|
544
589
|
where,
|
|
545
590
|
take: Number(pagination.limit),
|
|
546
591
|
skip: Number(pagination.offset)
|
|
@@ -551,6 +596,61 @@ const getViewSectionNotes = async (id, pagination) => {
|
|
|
551
596
|
notes
|
|
552
597
|
};
|
|
553
598
|
};
|
|
599
|
+
const getViewSectionBoardColumn = async (id, optionValue, pagination) => {
|
|
600
|
+
const section = await models.viewSection.findUnique({
|
|
601
|
+
where: { id: parseViewId(id) },
|
|
602
|
+
include: orderedViewSectionTagsInclude
|
|
603
|
+
});
|
|
604
|
+
if (!section) {
|
|
605
|
+
return null;
|
|
606
|
+
}
|
|
607
|
+
const serializedSection = serializeViewSection(section);
|
|
608
|
+
if (serializedSection.displayType !== "board") {
|
|
609
|
+
throw new InvalidNotePropertyInputError("This section is not configured as a board.");
|
|
610
|
+
}
|
|
611
|
+
const groupingPropertyKey = serializedSection.displayOptions.boardGroupByPropertyKey;
|
|
612
|
+
if (!groupingPropertyKey) {
|
|
613
|
+
throw new InvalidNotePropertyInputError("Board sections require a grouping property.");
|
|
614
|
+
}
|
|
615
|
+
const groupingProperty = await models.propertyDefinition.findUnique({
|
|
616
|
+
where: { key: groupingPropertyKey },
|
|
617
|
+
select: {
|
|
618
|
+
id: true,
|
|
619
|
+
valueType: true,
|
|
620
|
+
options: {
|
|
621
|
+
orderBy: [{ order: "asc" }, { id: "asc" }],
|
|
622
|
+
select: { id: true, value: true }
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
if (!groupingProperty || groupingProperty.valueType !== "select") {
|
|
627
|
+
throw new InvalidNotePropertyInputError("The board grouping property is unavailable.");
|
|
628
|
+
}
|
|
629
|
+
const normalizedOptionValue = optionValue === null ? null : normalizeSelectFilterValue(optionValue);
|
|
630
|
+
const option = normalizedOptionValue === null ? null : groupingProperty.options.find((candidate) => candidate.value === normalizedOptionValue);
|
|
631
|
+
if (normalizedOptionValue !== null && !option) {
|
|
632
|
+
throw new InvalidNotePropertyInputError(
|
|
633
|
+
`Property ${groupingPropertyKey} option ${normalizedOptionValue} is not defined.`
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
const normalizedPagination = normalizeViewNotesPagination(pagination);
|
|
637
|
+
const where = buildViewBoardColumnWhere(serializedSection, groupingProperty.id, option?.id ?? null);
|
|
638
|
+
const [totalCount, notes] = await Promise.all([
|
|
639
|
+
models.note.count({ where }),
|
|
640
|
+
models.note.findMany({
|
|
641
|
+
orderBy: buildViewSectionOrderBy(serializedSection),
|
|
642
|
+
where,
|
|
643
|
+
take: normalizedPagination.limit,
|
|
644
|
+
skip: normalizedPagination.offset,
|
|
645
|
+
select: {
|
|
646
|
+
id: true,
|
|
647
|
+
title: true,
|
|
648
|
+
updatedAt: true
|
|
649
|
+
}
|
|
650
|
+
})
|
|
651
|
+
]);
|
|
652
|
+
return { totalCount, notes };
|
|
653
|
+
};
|
|
554
654
|
const getNotesByPropertiesWithDb = async (db, input, pagination) => {
|
|
555
655
|
const normalizedQuery = normalizeViewNotesQueryInput(input);
|
|
556
656
|
if (normalizedQuery.propertyFilters.length === 0) {
|
|
@@ -786,6 +886,7 @@ export {
|
|
|
786
886
|
MAX_VIEW_SECTION_LIMIT,
|
|
787
887
|
MIN_VIEW_SECTION_LIMIT,
|
|
788
888
|
buildPropertyFilterWhere,
|
|
889
|
+
buildViewBoardColumnWhere,
|
|
789
890
|
buildViewNotesWhere,
|
|
790
891
|
buildViewSectionWhere,
|
|
791
892
|
clampViewSectionLimit,
|
|
@@ -795,6 +896,7 @@ export {
|
|
|
795
896
|
deleteViewTab,
|
|
796
897
|
getNotesByProperties,
|
|
797
898
|
getNotesByPropertiesWithDb,
|
|
899
|
+
getViewSectionBoardColumn,
|
|
798
900
|
getViewSectionById,
|
|
799
901
|
getViewSectionNotes,
|
|
800
902
|
getViewWorkspace,
|
|
@@ -806,6 +908,7 @@ export {
|
|
|
806
908
|
normalizeViewSectionInput,
|
|
807
909
|
normalizeViewTabTitle,
|
|
808
910
|
normalizeViewTableColumns,
|
|
911
|
+
normalizeViewTablePropertyKeys,
|
|
809
912
|
normalizeViewTagNames,
|
|
810
913
|
pickNextActiveViewTabId,
|
|
811
914
|
reorderViewSections,
|
package/server/dist/main.js
CHANGED
|
@@ -1,38 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ensureNoteReferenceIndex } from "./features/note/services/note-reference-index.js";
|
|
3
|
-
import { getDefaultSemanticSearchManager } from "./features/search/search-manager.js";
|
|
4
|
-
import { logAuthConfig, resolveAuthConfig } from "./modules/auth-mode.js";
|
|
5
|
-
import { startDataMaintenanceScheduler } from "./modules/data-maintenance.js";
|
|
6
|
-
const PORT = Number(process.env.PORT || 6683);
|
|
7
|
-
const HOST = process.env.HOST || "0.0.0.0";
|
|
8
|
-
const startServer = async () => {
|
|
9
|
-
const authConfig = resolveAuthConfig(process.env);
|
|
10
|
-
logAuthConfig(authConfig);
|
|
11
|
-
const app = createApp(authConfig);
|
|
12
|
-
getDefaultSemanticSearchManager();
|
|
13
|
-
const rebuiltReferenceCount = await ensureNoteReferenceIndex();
|
|
14
|
-
if (rebuiltReferenceCount > 0) {
|
|
15
|
-
process.stdout.write(`[maintenance] Rebuilt ${rebuiltReferenceCount} note reference rows
|
|
16
|
-
`);
|
|
17
|
-
}
|
|
18
|
-
app.listen(PORT, HOST, () => {
|
|
19
|
-
process.stdout.write(`http server listen on ${HOST}:${PORT} (auth: ${authConfig.mode})
|
|
20
|
-
`);
|
|
21
|
-
startDataMaintenanceScheduler({
|
|
22
|
-
onResults: (results) => {
|
|
23
|
-
for (const result of results) {
|
|
24
|
-
process.stdout.write(`[maintenance] Reconciled ${result.processedCount} rows for ${result.key}
|
|
25
|
-
`);
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
onError: (error) => {
|
|
29
|
-
const message = error instanceof Error ? error.message : "Unknown data maintenance error";
|
|
30
|
-
process.stderr.write(`[maintenance] Background run failed: ${message}
|
|
31
|
-
`);
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
};
|
|
1
|
+
import { startServer } from "./server.js";
|
|
36
2
|
startServer().catch((error) => {
|
|
37
3
|
const message = error instanceof Error ? error.message : "Unknown auth configuration error";
|
|
38
4
|
process.stderr.write(`[auth] Startup failed: ${message}
|
|
@@ -83,7 +83,12 @@ const createApiRouter = (authConfig, mcpAdminService) => {
|
|
|
83
83
|
requireSession,
|
|
84
84
|
csrfProtection,
|
|
85
85
|
useAsync(createSearchAdminReindexHandler())
|
|
86
|
-
).post("/image", sessionAccessRateLimit, requireSession, csrfProtection, useAsync(createUploadImageHandler())).get("/events", sessionAccessRateLimit, requireSession, csrfProtection, createServerEventsHandler())
|
|
86
|
+
).post("/image", sessionAccessRateLimit, requireSession, csrfProtection, useAsync(createUploadImageHandler())).get("/events", sessionAccessRateLimit, requireSession, csrfProtection, createServerEventsHandler()).use((_req, res) => {
|
|
87
|
+
res.status(404).json({
|
|
88
|
+
code: "API_ROUTE_NOT_FOUND",
|
|
89
|
+
message: "The requested API route was not found."
|
|
90
|
+
}).end();
|
|
91
|
+
});
|
|
87
92
|
};
|
|
88
93
|
export {
|
|
89
94
|
createApiRouter
|
|
@@ -4,6 +4,9 @@ import path from "path";
|
|
|
4
4
|
import { createCsrfProtection, isAuthenticatedRequest } from "../modules/auth-guard.js";
|
|
5
5
|
import { paths } from "../paths.js";
|
|
6
6
|
const IMAGE_ASSET_RATE_LIMIT_MESSAGE = "Too many image asset requests. Please try again later.";
|
|
7
|
+
const isClientDocumentRequest = (req) => {
|
|
8
|
+
return req.method === "GET" && Boolean(req.headers.accept?.includes("text/html")) && path.extname(req.path) === "";
|
|
9
|
+
};
|
|
7
10
|
const shouldBlockClientRoute = (authConfig, requestPath, authenticated) => {
|
|
8
11
|
if (authConfig.mode !== "password" || authenticated) {
|
|
9
12
|
return false;
|
|
@@ -52,7 +55,17 @@ const createClientRouteCsrfTokenMiddleware = (authConfig) => {
|
|
|
52
55
|
csrfProtection(req, res, next);
|
|
53
56
|
};
|
|
54
57
|
};
|
|
55
|
-
const
|
|
58
|
+
const createProductionClientContentRouter = () => Router().use(express.static(paths.clientDist, { extensions: ["html"] })).get(/.*/, (req, res, next) => {
|
|
59
|
+
if (!isClientDocumentRequest(req)) {
|
|
60
|
+
next();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
res.sendFile("index.html", { root: paths.clientDist });
|
|
64
|
+
}).use((_req, res) => {
|
|
65
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
66
|
+
res.status(404).end();
|
|
67
|
+
});
|
|
68
|
+
const createClientRouter = (authConfig, clientContentMiddleware = createProductionClientContentRouter()) => Router().use(
|
|
56
69
|
"/assets/images",
|
|
57
70
|
createImageAssetAuthRateLimit(authConfig),
|
|
58
71
|
createProtectedImageAssetsMiddleware(authConfig),
|
|
@@ -78,9 +91,7 @@ const createClientRouter = (authConfig) => Router().use(
|
|
|
78
91
|
return;
|
|
79
92
|
}
|
|
80
93
|
next();
|
|
81
|
-
}).use(createClientRouteCsrfTokenMiddleware(authConfig)).use(
|
|
82
|
-
res.sendFile("index.html", { root: paths.clientDist });
|
|
83
|
-
});
|
|
94
|
+
}).use(createClientRouteCsrfTokenMiddleware(authConfig)).use(clientContentMiddleware);
|
|
84
95
|
export {
|
|
85
96
|
createClientRouter
|
|
86
97
|
};
|