ocean-brain 0.3.3 → 0.4.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.
- package/dist/mcp.js +151 -37
- package/package.json +5 -5
- package/scripts/postinstall-prisma.mjs +3 -2
- package/server/client/dist/assets/Calendar-CSXEuWmQ.js +25 -0
- package/server/client/dist/assets/Callout-Ap1OcxWN.js +1 -0
- package/server/client/dist/assets/Graph-C0Ax9qjI.js +1 -0
- package/server/client/dist/assets/{Image-ZxF459sC.js → Image-CSbcG-5a.js} +1 -1
- package/server/client/dist/assets/{Image.es-CFTbON7k.js → Image.es-Fbe7aDDo.js} +1 -1
- package/server/client/dist/assets/{ModalActionRow-Hg6MY61u.js → ModalActionRow-DjI5b4bl.js} +1 -1
- package/server/client/dist/assets/Note-BY6WgYLq.js +1 -0
- package/server/client/dist/assets/Note-W4f6H9K0.css +1 -0
- package/server/client/dist/assets/Reminders-vRksvSQE.js +1 -0
- package/server/client/dist/assets/Search-Cq4tuiJO.js +1 -0
- package/server/client/dist/assets/{SurfaceCard-BV6y4bCo.js → SurfaceCard-Br6TjX5W.js} +1 -1
- package/server/client/dist/assets/Tag-ARDLA-Pb.js +1 -0
- package/server/client/dist/assets/TagNotes-CDGVrCJU.js +1 -0
- package/server/client/dist/assets/{Trash.es-CPtRLE0y.js → Trash.es-BfCEiL3-.js} +1 -1
- package/server/client/dist/assets/ViewNotes-ZBOQ1adH.js +1 -0
- package/server/client/dist/assets/Views-DQHPfdwM.js +1 -0
- package/server/client/dist/assets/{image.api-Dsrx9S-G.js → image.api-DGXlQ_Ca.js} +1 -1
- package/server/client/dist/assets/index-B28TO4FC.js +1 -0
- package/server/client/dist/assets/{index-CD2bFEqK.css → index-Bm9l_L20.css} +1 -1
- package/server/client/dist/assets/index-Bsx4P7EA.js +210 -0
- package/server/client/dist/assets/manage-image-Dg297A_h.js +1 -0
- package/server/client/dist/assets/manage-image-detail-hk6jWyiB.js +1 -0
- package/server/client/dist/assets/mcp-CSjmBjFR.js +1 -0
- package/server/client/dist/assets/{note-core-BCgMq5QA.js → note-core-GgFcksAF.js} +1 -1
- package/server/client/dist/assets/{note-vendor-BofYbzmZ.js → note-vendor-5N5mRkIT.js} +3 -3
- package/server/client/dist/assets/{placeholder-MNsco0AT.js → placeholder-Bmiowo28.js} +4 -4
- package/server/client/dist/assets/trash-CM7SbVtW.js +1 -0
- package/server/client/dist/assets/{useReminderMutate-yJak8FzP.js → useReminderMutate-DsIvVpCQ.js} +2 -2
- package/server/client/dist/assets/view-dashboard-CP8nhfxQ.js +90 -0
- package/server/client/dist/index.html +4 -4
- package/server/dist/app.js +8 -84
- package/server/dist/app.js.map +1 -1
- package/server/dist/features/auth/http/api.js +43 -0
- package/server/dist/features/auth/http/api.js.map +1 -0
- package/server/dist/features/auth/http/login-page.js +28 -0
- package/server/dist/features/auth/http/login-page.js.map +1 -0
- package/server/dist/features/auth/http/pages.js +50 -0
- package/server/dist/features/auth/http/pages.js.map +1 -0
- package/server/dist/features/auth/service.js +94 -0
- package/server/dist/features/auth/service.js.map +1 -0
- package/server/dist/features/cache/graphql/cache.field.resolver.js +5 -0
- package/server/dist/features/cache/graphql/cache.field.resolver.js.map +1 -0
- package/server/dist/features/cache/graphql/cache.mutation.resolver.js +49 -0
- package/server/dist/features/cache/graphql/cache.mutation.resolver.js.map +1 -0
- package/server/dist/features/cache/graphql/cache.query.resolver.js +21 -0
- package/server/dist/features/cache/graphql/cache.query.resolver.js.map +1 -0
- package/server/dist/features/cache/graphql/cache.resolver.js +12 -0
- package/server/dist/features/cache/graphql/cache.resolver.js.map +1 -0
- package/server/dist/features/cache/graphql/cache.type-defs.js +32 -0
- package/server/dist/features/cache/graphql/cache.type-defs.js.map +1 -0
- package/server/dist/features/cache/graphql/index.js +21 -0
- package/server/dist/features/cache/graphql/index.js.map +1 -0
- package/server/dist/features/image/graphql/image.field.resolver.js +14 -0
- package/server/dist/features/image/graphql/image.field.resolver.js.map +1 -0
- package/server/dist/features/image/graphql/image.mutation.resolver.js +21 -0
- package/server/dist/features/image/graphql/image.mutation.resolver.js.map +1 -0
- package/server/dist/features/image/graphql/image.query.resolver.js +40 -0
- package/server/dist/features/image/graphql/image.query.resolver.js.map +1 -0
- package/server/dist/features/image/graphql/image.resolver.js +12 -0
- package/server/dist/features/image/graphql/image.resolver.js.map +1 -0
- package/server/dist/features/image/graphql/image.type-defs.js +31 -0
- package/server/dist/features/image/graphql/image.type-defs.js.map +1 -0
- package/server/dist/features/image/graphql/index.js +16 -0
- package/server/dist/features/image/graphql/index.js.map +1 -0
- package/server/dist/features/image/http/upload.js +48 -0
- package/server/dist/features/image/http/upload.js.map +1 -0
- package/server/dist/features/image/services/delete.js +52 -0
- package/server/dist/features/image/services/delete.js.map +1 -0
- package/server/dist/{modules/remote-image.js → features/image/services/remote-fetch.js} +1 -1
- package/server/dist/features/image/services/remote-fetch.js.map +1 -0
- package/server/dist/{modules/image-upload.js → features/image/services/upload.js} +4 -8
- package/server/dist/features/image/services/upload.js.map +1 -0
- package/server/dist/{views/mcp-admin.js → features/mcp-admin/http/handlers.js} +3 -3
- package/server/dist/features/mcp-admin/http/handlers.js.map +1 -0
- package/server/dist/{modules/mcp-admin.js → features/mcp-admin/service.js} +3 -3
- package/server/dist/features/mcp-admin/service.js.map +1 -0
- package/server/dist/features/note/graphql/index.js +8 -0
- package/server/dist/features/note/graphql/index.js.map +1 -0
- package/server/dist/features/note/graphql/note.field.resolver.js +12 -0
- package/server/dist/features/note/graphql/note.field.resolver.js.map +1 -0
- package/server/dist/features/note/graphql/note.graphql.shared.js +23 -0
- package/server/dist/features/note/graphql/note.graphql.shared.js.map +1 -0
- package/server/dist/features/note/graphql/note.mutation.resolver.js +140 -0
- package/server/dist/features/note/graphql/note.mutation.resolver.js.map +1 -0
- package/server/dist/features/note/graphql/note.query.resolver.js +382 -0
- package/server/dist/features/note/graphql/note.query.resolver.js.map +1 -0
- package/server/dist/features/note/graphql/note.resolver.js +14 -0
- package/server/dist/features/note/graphql/note.resolver.js.map +1 -0
- package/server/dist/features/note/graphql/note.type-defs.js +184 -0
- package/server/dist/features/note/graphql/note.type-defs.js.map +1 -0
- package/server/dist/{views/note.js → features/note/http/mcp.js} +27 -15
- package/server/dist/features/note/http/mcp.js.map +1 -0
- package/server/dist/{modules/note-authoring.js → features/note/services/authoring.js} +25 -7
- package/server/dist/features/note/services/authoring.js.map +1 -0
- package/server/dist/{modules/note-cleanup.js → features/note/services/cleanup.js} +14 -23
- package/server/dist/features/note/services/cleanup.js.map +1 -0
- package/server/dist/features/note/services/search-backfill.js +84 -0
- package/server/dist/features/note/services/search-backfill.js.map +1 -0
- package/server/dist/features/note/services/search.js +197 -0
- package/server/dist/features/note/services/search.js.map +1 -0
- package/server/dist/{modules/note-snapshot.js → features/note/services/snapshot.js} +51 -49
- package/server/dist/features/note/services/snapshot.js.map +1 -0
- package/server/dist/features/note/services/tag-filter.js +18 -0
- package/server/dist/features/note/services/tag-filter.js.map +1 -0
- package/server/dist/{modules/note-trash.js → features/note/services/trash.js} +28 -23
- package/server/dist/features/note/services/trash.js.map +1 -0
- package/server/dist/features/placeholder/graphql/index.js +27 -0
- package/server/dist/features/placeholder/graphql/index.js.map +1 -0
- package/server/dist/features/placeholder/graphql/placeholder.field.resolver.js +5 -0
- package/server/dist/features/placeholder/graphql/placeholder.field.resolver.js.map +1 -0
- package/server/dist/features/placeholder/graphql/placeholder.mutation.resolver.js +72 -0
- package/server/dist/features/placeholder/graphql/placeholder.mutation.resolver.js.map +1 -0
- package/server/dist/features/placeholder/graphql/placeholder.query.resolver.js +36 -0
- package/server/dist/features/placeholder/graphql/placeholder.query.resolver.js.map +1 -0
- package/server/dist/features/placeholder/graphql/placeholder.resolver.js +12 -0
- package/server/dist/features/placeholder/graphql/placeholder.resolver.js.map +1 -0
- package/server/dist/features/placeholder/graphql/placeholder.type-defs.js +50 -0
- package/server/dist/features/placeholder/graphql/placeholder.type-defs.js.map +1 -0
- package/server/dist/features/reminder/graphql/index.js +27 -0
- package/server/dist/features/reminder/graphql/index.js.map +1 -0
- package/server/dist/features/reminder/graphql/reminder.field.resolver.js +10 -0
- package/server/dist/features/reminder/graphql/reminder.field.resolver.js.map +1 -0
- package/server/dist/features/reminder/graphql/reminder.mutation.resolver.js +59 -0
- package/server/dist/features/reminder/graphql/reminder.mutation.resolver.js.map +1 -0
- package/server/dist/features/reminder/graphql/reminder.query.resolver.js +79 -0
- package/server/dist/features/reminder/graphql/reminder.query.resolver.js.map +1 -0
- package/server/dist/features/reminder/graphql/reminder.resolver.js +12 -0
- package/server/dist/features/reminder/graphql/reminder.resolver.js.map +1 -0
- package/server/dist/features/reminder/graphql/reminder.type-defs.js +51 -0
- package/server/dist/features/reminder/graphql/reminder.type-defs.js.map +1 -0
- package/server/dist/features/tag/graphql/index.js +14 -0
- package/server/dist/features/tag/graphql/index.js.map +1 -0
- package/server/dist/features/tag/graphql/tag.field.resolver.js +10 -0
- package/server/dist/features/tag/graphql/tag.field.resolver.js.map +1 -0
- package/server/dist/features/tag/graphql/tag.mutation.resolver.js +15 -0
- package/server/dist/features/tag/graphql/tag.mutation.resolver.js.map +1 -0
- package/server/dist/features/tag/graphql/tag.query.resolver.js +26 -0
- package/server/dist/features/tag/graphql/tag.query.resolver.js.map +1 -0
- package/server/dist/features/tag/graphql/tag.resolver.js +12 -0
- package/server/dist/features/tag/graphql/tag.resolver.js.map +1 -0
- package/server/dist/features/tag/graphql/tag.type-defs.js +46 -0
- package/server/dist/features/tag/graphql/tag.type-defs.js.map +1 -0
- package/server/dist/{views/tag.js → features/tag/http/mcp.js} +3 -3
- package/server/dist/features/tag/http/mcp.js.map +1 -0
- package/server/dist/{modules/tag-organization.js → features/tag/services/organization.js} +38 -8
- package/server/dist/features/tag/services/organization.js.map +1 -0
- package/server/dist/features/view/graphql/index.js +11 -0
- package/server/dist/features/view/graphql/index.js.map +1 -0
- package/server/dist/features/view/graphql/view.mutation.resolver.js +53 -0
- package/server/dist/features/view/graphql/view.mutation.resolver.js.map +1 -0
- package/server/dist/features/view/graphql/view.query.resolver.js +29 -0
- package/server/dist/features/view/graphql/view.query.resolver.js.map +1 -0
- package/server/dist/features/view/graphql/view.resolver.js +10 -0
- package/server/dist/features/view/graphql/view.resolver.js.map +1 -0
- package/server/dist/features/view/graphql/view.type-defs.js +63 -0
- package/server/dist/features/view/graphql/view.type-defs.js.map +1 -0
- package/server/dist/features/view/services/workspace.js +432 -0
- package/server/dist/features/view/services/workspace.js.map +1 -0
- package/server/dist/main.js +14 -0
- package/server/dist/main.js.map +1 -1
- package/server/dist/modules/auth-guard.js +10 -9
- package/server/dist/modules/auth-guard.js.map +1 -1
- package/server/dist/modules/auth-mode.js +1 -3
- package/server/dist/modules/auth-mode.js.map +1 -1
- package/server/dist/modules/blocknote.js +96 -33
- package/server/dist/modules/blocknote.js.map +1 -1
- package/server/dist/modules/data-maintenance.js +75 -0
- package/server/dist/modules/data-maintenance.js.map +1 -0
- package/server/dist/modules/error-handler.js.map +1 -1
- package/server/dist/modules/logger.js +5 -23
- package/server/dist/modules/logger.js.map +1 -1
- package/server/dist/modules/mcp-auth.js +4 -5
- package/server/dist/modules/mcp-auth.js.map +1 -1
- package/server/dist/modules/mcp-token.js.map +1 -1
- package/server/dist/modules/prisma-runtime.js +2 -7
- package/server/dist/modules/prisma-runtime.js.map +1 -1
- package/server/dist/modules/recovery-retention.js +1 -1
- package/server/dist/modules/recovery-retention.js.map +1 -1
- package/server/dist/modules/server-events-handler.js +34 -0
- package/server/dist/modules/server-events-handler.js.map +1 -0
- package/server/dist/modules/server-events.js +32 -0
- package/server/dist/modules/server-events.js.map +1 -0
- package/server/dist/modules/use-async.js.map +1 -1
- package/server/dist/paths.js.map +1 -1
- package/server/dist/routes/api.js +34 -0
- package/server/dist/routes/api.js.map +1 -0
- package/server/dist/routes/auth-pages.js +11 -0
- package/server/dist/routes/auth-pages.js.map +1 -0
- package/server/dist/routes/client.js +27 -0
- package/server/dist/routes/client.js.map +1 -0
- package/server/dist/routes/graphql.js +33 -0
- package/server/dist/routes/graphql.js.map +1 -0
- package/server/dist/routes/index.js +11 -0
- package/server/dist/routes/index.js.map +1 -0
- package/server/dist/routes/mcp.js +30 -0
- package/server/dist/routes/mcp.js.map +1 -0
- package/server/dist/schema/index.js +11 -8
- package/server/dist/schema/index.js.map +1 -1
- package/server/dist/types/index.js.map +1 -1
- package/server/prisma/migrations/20260413143000_0014_tag_name_unique/migration.sql +32 -0
- package/server/prisma/migrations/20260414143000_0015_note_searchable_text/migration.sql +5 -0
- package/server/prisma/migrations/20260416090000_0016_view_workspace/migration.sql +50 -0
- package/server/prisma/schema.prisma +66 -12
- package/server/client/dist/assets/Calendar-DyXzs5M3.js +0 -25
- package/server/client/dist/assets/Graph-C8K4PJOn.js +0 -1
- package/server/client/dist/assets/Note-BbgZvrC3.js +0 -1
- package/server/client/dist/assets/Note-PEevDKBh.css +0 -1
- package/server/client/dist/assets/Reminders-jFhljND4.js +0 -1
- package/server/client/dist/assets/Search-X6QIR_m2.js +0 -1
- package/server/client/dist/assets/Tag-Ba2Dw4gH.js +0 -1
- package/server/client/dist/assets/TagNotes-BRJv6sDI.js +0 -1
- package/server/client/dist/assets/index-BglnLSet.js +0 -205
- package/server/client/dist/assets/index-DpqKngJz.js +0 -1
- package/server/client/dist/assets/manage-image-C6Y_0M5H.js +0 -1
- package/server/client/dist/assets/manage-image-detail-Dv3xofpe.js +0 -1
- package/server/client/dist/assets/mcp-BijQkpfY.js +0 -1
- package/server/client/dist/assets/trash-CiFJm_PK.js +0 -1
- package/server/dist/modules/auth.js +0 -25
- package/server/dist/modules/auth.js.map +0 -1
- package/server/dist/modules/image-upload.js.map +0 -1
- package/server/dist/modules/mcp-admin.js.map +0 -1
- package/server/dist/modules/note-authoring.js.map +0 -1
- package/server/dist/modules/note-cleanup.js.map +0 -1
- package/server/dist/modules/note-snapshot.js.map +0 -1
- package/server/dist/modules/note-trash.js.map +0 -1
- package/server/dist/modules/remote-image.js.map +0 -1
- package/server/dist/modules/tag-organization.js.map +0 -1
- package/server/dist/schema/cache/index.js +0 -58
- package/server/dist/schema/cache/index.js.map +0 -1
- package/server/dist/schema/image/index.js +0 -88
- package/server/dist/schema/image/index.js.map +0 -1
- package/server/dist/schema/note/index.js +0 -551
- package/server/dist/schema/note/index.js.map +0 -1
- package/server/dist/schema/placeholder/index.js +0 -105
- package/server/dist/schema/placeholder/index.js.map +0 -1
- package/server/dist/schema/reminder/index.js +0 -161
- package/server/dist/schema/reminder/index.js.map +0 -1
- package/server/dist/schema/tag/index.js +0 -81
- package/server/dist/schema/tag/index.js.map +0 -1
- package/server/dist/urls.js +0 -9
- package/server/dist/urls.js.map +0 -1
- package/server/dist/views/auth.js +0 -305
- package/server/dist/views/auth.js.map +0 -1
- package/server/dist/views/image.js +0 -44
- package/server/dist/views/image.js.map +0 -1
- package/server/dist/views/index.js +0 -6
- package/server/dist/views/index.js.map +0 -1
- package/server/dist/views/mcp-admin.js.map +0 -1
- package/server/dist/views/note.js.map +0 -1
- package/server/dist/views/tag.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/features/note/graphql/note.resolver.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\nimport { noteFieldResolvers } from './note.field.resolver.js';\nimport { noteMutationResolvers } from './note.mutation.resolver.js';\nimport { createAllNotesQueryResolver, noteQueryResolvers } from './note.query.resolver.js';\n\nexport { createAllNotesQueryResolver } from './note.query.resolver.js';\n\nexport const noteResolvers: IResolvers = {\n Query: noteQueryResolvers,\n Mutation: noteMutationResolvers,\n Note: noteFieldResolvers,\n};\n"],"mappings":"AACA,SAAS,0BAA0B;AACnC,SAAS,6BAA6B;AACtC,SAAsC,0BAA0B;AAEhE,SAAS,+BAAAA,oCAAmC;AAErC,MAAM,gBAA4B;AAAA,EACrC,OAAO;AAAA,EACP,UAAU;AAAA,EACV,MAAM;AACV;","names":["createAllNotesQueryResolver"]}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { gql } from "../../../modules/graphql.js";
|
|
2
|
+
const noteType = gql`
|
|
3
|
+
input PaginationInput {
|
|
4
|
+
limit: Int!
|
|
5
|
+
offset: Int!
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
input SearchFilterInput {
|
|
9
|
+
query: String!
|
|
10
|
+
sortBy: String
|
|
11
|
+
sortOrder: String
|
|
12
|
+
pinnedFirst: Boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
input DateRangeInput {
|
|
16
|
+
start: String!
|
|
17
|
+
end: String!
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
enum NoteLayout {
|
|
21
|
+
narrow
|
|
22
|
+
wide
|
|
23
|
+
full
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
enum TagMatchMode {
|
|
27
|
+
and
|
|
28
|
+
or
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
input NoteInput {
|
|
32
|
+
title: String
|
|
33
|
+
content: String
|
|
34
|
+
layout: NoteLayout
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
input NoteOrderInput {
|
|
38
|
+
id: ID!
|
|
39
|
+
order: Int!
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type Tag {
|
|
43
|
+
id: ID!
|
|
44
|
+
name: String!
|
|
45
|
+
createdAt: String!
|
|
46
|
+
updatedAt: String!
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
type Note {
|
|
50
|
+
id: ID!
|
|
51
|
+
title: String!
|
|
52
|
+
content: String!
|
|
53
|
+
contentAsMarkdown: String!
|
|
54
|
+
createdAt: String!
|
|
55
|
+
updatedAt: String!
|
|
56
|
+
pinned: Boolean!
|
|
57
|
+
order: Int!
|
|
58
|
+
layout: NoteLayout!
|
|
59
|
+
tags: [Tag!]!
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type Notes {
|
|
63
|
+
totalCount: Int!
|
|
64
|
+
notes: [Note!]!
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type NoteCleanupBackReference {
|
|
68
|
+
id: ID!
|
|
69
|
+
title: String!
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type NoteCleanupCandidate {
|
|
73
|
+
id: ID!
|
|
74
|
+
title: String!
|
|
75
|
+
updatedAt: String!
|
|
76
|
+
pinned: Boolean!
|
|
77
|
+
tagNames: [String!]!
|
|
78
|
+
reminderCount: Int!
|
|
79
|
+
backReferenceCount: Int!
|
|
80
|
+
matchedTerms: [String!]!
|
|
81
|
+
reasons: [String!]!
|
|
82
|
+
requiresForce: Boolean!
|
|
83
|
+
forceReasons: [String!]!
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
type NoteCleanupPreview {
|
|
87
|
+
id: ID!
|
|
88
|
+
title: String!
|
|
89
|
+
updatedAt: String!
|
|
90
|
+
pinned: Boolean!
|
|
91
|
+
tagNames: [String!]!
|
|
92
|
+
reminderCount: Int!
|
|
93
|
+
backReferences: [NoteCleanupBackReference!]!
|
|
94
|
+
orphanedTagNames: [String!]!
|
|
95
|
+
requiresForce: Boolean!
|
|
96
|
+
forceReasons: [String!]!
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
type NoteSnapshotMeta {
|
|
100
|
+
entrypoint: String
|
|
101
|
+
label: String
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
type NoteSnapshot {
|
|
105
|
+
id: ID!
|
|
106
|
+
title: String!
|
|
107
|
+
createdAt: String!
|
|
108
|
+
meta: NoteSnapshotMeta!
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
type DeletedNote {
|
|
112
|
+
id: ID!
|
|
113
|
+
title: String!
|
|
114
|
+
createdAt: String!
|
|
115
|
+
updatedAt: String!
|
|
116
|
+
deletedAt: String!
|
|
117
|
+
pinned: Boolean!
|
|
118
|
+
order: Int!
|
|
119
|
+
layout: NoteLayout!
|
|
120
|
+
tagNames: [String!]!
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
type DeletedNotes {
|
|
124
|
+
totalCount: Int!
|
|
125
|
+
notes: [DeletedNote!]!
|
|
126
|
+
}
|
|
127
|
+
`;
|
|
128
|
+
const noteQuery = gql`
|
|
129
|
+
type GraphNode {
|
|
130
|
+
id: ID!
|
|
131
|
+
title: String!
|
|
132
|
+
connections: Int!
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
type GraphLink {
|
|
136
|
+
source: ID!
|
|
137
|
+
target: ID!
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
type NoteGraph {
|
|
141
|
+
nodes: [GraphNode!]!
|
|
142
|
+
links: [GraphLink!]!
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
type Query {
|
|
146
|
+
allNotes(searchFilter: SearchFilterInput, pagination: PaginationInput): Notes!
|
|
147
|
+
tagNotes(searchFilter: SearchFilterInput, pagination: PaginationInput): Notes!
|
|
148
|
+
notesByTagNames(tagNames: [String!]!, mode: TagMatchMode!, pagination: PaginationInput): Notes!
|
|
149
|
+
notesInDateRange(dateRange: DateRangeInput): [Note!]!
|
|
150
|
+
pinnedNotes: [Note!]!
|
|
151
|
+
imageNotes(src: String!): [Note!]!
|
|
152
|
+
backReferences(id: ID!): [Note]!
|
|
153
|
+
note(id: ID!): Note!
|
|
154
|
+
noteCleanupCandidates(query: String, pagination: PaginationInput): [NoteCleanupCandidate!]!
|
|
155
|
+
noteCleanupPreview(id: ID!): NoteCleanupPreview
|
|
156
|
+
noteSnapshots(id: ID!, limit: Int): [NoteSnapshot!]!
|
|
157
|
+
trashedNotes(pagination: PaginationInput): DeletedNotes!
|
|
158
|
+
noteGraph: NoteGraph!
|
|
159
|
+
}
|
|
160
|
+
`;
|
|
161
|
+
const noteMutation = gql`
|
|
162
|
+
type Mutation {
|
|
163
|
+
createNote(note: NoteInput!): Note!
|
|
164
|
+
updateNote(id: ID!, note: NoteInput!, editSessionId: String): Note!
|
|
165
|
+
deleteNote(id: ID!): Boolean!
|
|
166
|
+
restoreNoteSnapshot(id: ID!): Note!
|
|
167
|
+
restoreTrashedNote(id: ID!): Note!
|
|
168
|
+
purgeTrashedNote(id: ID!): Boolean!
|
|
169
|
+
pinNote(id: ID!, pinned: Boolean!): Note!
|
|
170
|
+
reorderNotes(notes: [NoteOrderInput!]!): [Note!]!
|
|
171
|
+
}
|
|
172
|
+
`;
|
|
173
|
+
const noteTypeDefs = `
|
|
174
|
+
${noteType}
|
|
175
|
+
${noteQuery}
|
|
176
|
+
${noteMutation}
|
|
177
|
+
`;
|
|
178
|
+
export {
|
|
179
|
+
noteMutation,
|
|
180
|
+
noteQuery,
|
|
181
|
+
noteType,
|
|
182
|
+
noteTypeDefs
|
|
183
|
+
};
|
|
184
|
+
//# sourceMappingURL=note.type-defs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/features/note/graphql/note.type-defs.ts"],"sourcesContent":["import { gql } from '~/modules/graphql.js';\n\nexport const noteType = gql`\n input PaginationInput {\n limit: Int!\n offset: Int!\n }\n\n input SearchFilterInput {\n query: String!\n sortBy: String\n sortOrder: String\n pinnedFirst: Boolean\n }\n\n input DateRangeInput {\n start: String!\n end: String!\n }\n\n enum NoteLayout {\n narrow\n wide\n full\n }\n\n enum TagMatchMode {\n and\n or\n }\n\n input NoteInput {\n title: String\n content: String\n layout: NoteLayout\n }\n\n input NoteOrderInput {\n id: ID!\n order: Int!\n }\n\n type Tag {\n id: ID!\n name: String!\n createdAt: String!\n updatedAt: String!\n }\n\n type Note {\n id: ID!\n title: String!\n content: String!\n contentAsMarkdown: String!\n createdAt: String!\n updatedAt: String!\n pinned: Boolean!\n order: Int!\n layout: NoteLayout!\n tags: [Tag!]!\n }\n\n type Notes {\n totalCount: Int!\n notes: [Note!]!\n }\n\n type NoteCleanupBackReference {\n id: ID!\n title: String!\n }\n\n type NoteCleanupCandidate {\n id: ID!\n title: String!\n updatedAt: String!\n pinned: Boolean!\n tagNames: [String!]!\n reminderCount: Int!\n backReferenceCount: Int!\n matchedTerms: [String!]!\n reasons: [String!]!\n requiresForce: Boolean!\n forceReasons: [String!]!\n }\n\n type NoteCleanupPreview {\n id: ID!\n title: String!\n updatedAt: String!\n pinned: Boolean!\n tagNames: [String!]!\n reminderCount: Int!\n backReferences: [NoteCleanupBackReference!]!\n orphanedTagNames: [String!]!\n requiresForce: Boolean!\n forceReasons: [String!]!\n }\n\n type NoteSnapshotMeta {\n entrypoint: String\n label: String\n }\n\n type NoteSnapshot {\n id: ID!\n title: String!\n createdAt: String!\n meta: NoteSnapshotMeta!\n }\n\n type DeletedNote {\n id: ID!\n title: String!\n createdAt: String!\n updatedAt: String!\n deletedAt: String!\n pinned: Boolean!\n order: Int!\n layout: NoteLayout!\n tagNames: [String!]!\n }\n\n type DeletedNotes {\n totalCount: Int!\n notes: [DeletedNote!]!\n }\n`;\n\nexport const noteQuery = gql`\n type GraphNode {\n id: ID!\n title: String!\n connections: Int!\n }\n\n type GraphLink {\n source: ID!\n target: ID!\n }\n\n type NoteGraph {\n nodes: [GraphNode!]!\n links: [GraphLink!]!\n }\n\n type Query {\n allNotes(searchFilter: SearchFilterInput, pagination: PaginationInput): Notes!\n tagNotes(searchFilter: SearchFilterInput, pagination: PaginationInput): Notes!\n notesByTagNames(tagNames: [String!]!, mode: TagMatchMode!, pagination: PaginationInput): Notes!\n notesInDateRange(dateRange: DateRangeInput): [Note!]!\n pinnedNotes: [Note!]!\n imageNotes(src: String!): [Note!]!\n backReferences(id: ID!): [Note]!\n note(id: ID!): Note!\n noteCleanupCandidates(query: String, pagination: PaginationInput): [NoteCleanupCandidate!]!\n noteCleanupPreview(id: ID!): NoteCleanupPreview\n noteSnapshots(id: ID!, limit: Int): [NoteSnapshot!]!\n trashedNotes(pagination: PaginationInput): DeletedNotes!\n noteGraph: NoteGraph!\n }\n`;\n\nexport const noteMutation = gql`\n type Mutation {\n createNote(note: NoteInput!): Note!\n updateNote(id: ID!, note: NoteInput!, editSessionId: String): Note!\n deleteNote(id: ID!): Boolean!\n restoreNoteSnapshot(id: ID!): Note!\n restoreTrashedNote(id: ID!): Note!\n purgeTrashedNote(id: ID!): Boolean!\n pinNote(id: ID!, pinned: Boolean!): Note!\n reorderNotes(notes: [NoteOrderInput!]!): [Note!]!\n }\n`;\n\nexport const noteTypeDefs = `\n ${noteType}\n ${noteQuery}\n ${noteMutation}\n`;\n"],"mappings":"AAAA,SAAS,WAAW;AAEb,MAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+HjB,MAAM,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkClB,MAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAarB,MAAM,eAAe;AAAA,MACtB,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,YAAY;AAAA;","names":[]}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { createAppError } from "../modules/error-handler.js";
|
|
2
1
|
import {
|
|
3
2
|
createNoteFromMarkdown,
|
|
4
3
|
InvalidNoteAuthoringInputError,
|
|
5
4
|
updateNoteFromMarkdown
|
|
6
|
-
} from "
|
|
7
|
-
import { deleteNoteById } from "
|
|
8
|
-
import { MCP_SNAPSHOT_META } from "
|
|
5
|
+
} from "../../../features/note/services/authoring.js";
|
|
6
|
+
import { deleteNoteById } from "../../../features/note/services/cleanup.js";
|
|
7
|
+
import { MCP_SNAPSHOT_META } from "../../../features/note/services/snapshot.js";
|
|
8
|
+
import { createAppError } from "../../../modules/error-handler.js";
|
|
9
|
+
import { emitServerEvent } from "../../../modules/server-events.js";
|
|
9
10
|
const NOTE_LAYOUTS = /* @__PURE__ */ new Set(["narrow", "wide", "full"]);
|
|
10
11
|
const resolveNoteLayout = (value) => {
|
|
11
12
|
if (value === void 0) {
|
|
@@ -16,7 +17,7 @@ const resolveNoteLayout = (value) => {
|
|
|
16
17
|
}
|
|
17
18
|
return null;
|
|
18
19
|
};
|
|
19
|
-
const createMcpCreateNoteHandler = (createNote = createNoteFromMarkdown) => {
|
|
20
|
+
const createMcpCreateNoteHandler = (createNote = createNoteFromMarkdown, emitEvent = emitServerEvent) => {
|
|
20
21
|
return async (req, res) => {
|
|
21
22
|
const { title, markdown, layout } = req.body ?? {};
|
|
22
23
|
const resolvedLayout = resolveNoteLayout(layout);
|
|
@@ -35,6 +36,12 @@ const createMcpCreateNoteHandler = (createNote = createNoteFromMarkdown) => {
|
|
|
35
36
|
...markdown !== void 0 ? { markdown } : {},
|
|
36
37
|
...resolvedLayout ? { layout: resolvedLayout } : {}
|
|
37
38
|
});
|
|
39
|
+
emitEvent({
|
|
40
|
+
type: "mcp.note.created",
|
|
41
|
+
source: "mcp",
|
|
42
|
+
noteId: note.id,
|
|
43
|
+
updatedAt: note.updatedAt
|
|
44
|
+
});
|
|
38
45
|
res.status(200).json({
|
|
39
46
|
created: true,
|
|
40
47
|
note
|
|
@@ -47,15 +54,9 @@ const createMcpCreateNoteHandler = (createNote = createNoteFromMarkdown) => {
|
|
|
47
54
|
}
|
|
48
55
|
};
|
|
49
56
|
};
|
|
50
|
-
const createMcpUpdateNoteHandler = (updateNote = updateNoteFromMarkdown) => {
|
|
57
|
+
const createMcpUpdateNoteHandler = (updateNote = updateNoteFromMarkdown, emitEvent = emitServerEvent) => {
|
|
51
58
|
return async (req, res) => {
|
|
52
|
-
const {
|
|
53
|
-
id,
|
|
54
|
-
title,
|
|
55
|
-
markdown,
|
|
56
|
-
layout,
|
|
57
|
-
editSessionId
|
|
58
|
-
} = req.body ?? {};
|
|
59
|
+
const { id, title, markdown, layout, editSessionId } = req.body ?? {};
|
|
59
60
|
const noteId = Number(id);
|
|
60
61
|
const resolvedLayout = resolveNoteLayout(layout);
|
|
61
62
|
if (!Number.isInteger(noteId) || noteId <= 0) {
|
|
@@ -88,6 +89,12 @@ const createMcpUpdateNoteHandler = (updateNote = updateNoteFromMarkdown) => {
|
|
|
88
89
|
if (!note) {
|
|
89
90
|
throw createAppError(404, "NOTE_NOT_FOUND", "The requested note was not found.");
|
|
90
91
|
}
|
|
92
|
+
emitEvent({
|
|
93
|
+
type: "mcp.note.updated",
|
|
94
|
+
source: "mcp",
|
|
95
|
+
noteId: note.id,
|
|
96
|
+
updatedAt: note.updatedAt
|
|
97
|
+
});
|
|
91
98
|
res.status(200).json({
|
|
92
99
|
updated: true,
|
|
93
100
|
note
|
|
@@ -100,7 +107,7 @@ const createMcpUpdateNoteHandler = (updateNote = updateNoteFromMarkdown) => {
|
|
|
100
107
|
}
|
|
101
108
|
};
|
|
102
109
|
};
|
|
103
|
-
const createMcpDeleteNoteHandler = (deleteNote = deleteNoteById) => {
|
|
110
|
+
const createMcpDeleteNoteHandler = (deleteNote = deleteNoteById, emitEvent = emitServerEvent) => {
|
|
104
111
|
return async (req, res) => {
|
|
105
112
|
const id = Number(req.body?.id);
|
|
106
113
|
if (!Number.isInteger(id) || id <= 0) {
|
|
@@ -110,6 +117,11 @@ const createMcpDeleteNoteHandler = (deleteNote = deleteNoteById) => {
|
|
|
110
117
|
if (!deletedNote) {
|
|
111
118
|
throw createAppError(404, "NOTE_NOT_FOUND", "The requested note was not found.");
|
|
112
119
|
}
|
|
120
|
+
emitEvent({
|
|
121
|
+
type: "mcp.note.deleted",
|
|
122
|
+
source: "mcp",
|
|
123
|
+
noteId: deletedNote.id
|
|
124
|
+
});
|
|
113
125
|
res.status(200).json({
|
|
114
126
|
deleted: true,
|
|
115
127
|
note: deletedNote
|
|
@@ -121,4 +133,4 @@ export {
|
|
|
121
133
|
createMcpDeleteNoteHandler,
|
|
122
134
|
createMcpUpdateNoteHandler
|
|
123
135
|
};
|
|
124
|
-
//# sourceMappingURL=
|
|
136
|
+
//# sourceMappingURL=mcp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/features/note/http/mcp.ts"],"sourcesContent":["import {\n createNoteFromMarkdown,\n InvalidNoteAuthoringInputError,\n updateNoteFromMarkdown,\n} from '~/features/note/services/authoring.js';\nimport { deleteNoteById } from '~/features/note/services/cleanup.js';\nimport { MCP_SNAPSHOT_META } from '~/features/note/services/snapshot.js';\nimport type { NoteLayout } from '~/models.js';\nimport { createAppError } from '~/modules/error-handler.js';\nimport { emitServerEvent, type ServerEventInput } from '~/modules/server-events.js';\nimport type { Controller } from '~/types/index.js';\n\nconst NOTE_LAYOUTS = new Set<NoteLayout>(['narrow', 'wide', 'full']);\n\nconst resolveNoteLayout = (value: unknown): NoteLayout | null | undefined => {\n if (value === undefined) {\n return undefined;\n }\n\n if (typeof value === 'string' && NOTE_LAYOUTS.has(value as NoteLayout)) {\n return value as NoteLayout;\n }\n\n return null;\n};\n\ntype EmitServerEvent = (event: ServerEventInput) => unknown;\n\nexport const createMcpCreateNoteHandler = (\n createNote = createNoteFromMarkdown,\n emitEvent: EmitServerEvent = emitServerEvent,\n): Controller => {\n return async (req, res) => {\n const { title, markdown, layout } = req.body ?? {};\n const resolvedLayout = resolveNoteLayout(layout);\n\n if (typeof title !== 'string') {\n throw createAppError(400, 'INVALID_NOTE_TITLE', 'A note title is required.');\n }\n\n if (markdown !== undefined && typeof markdown !== 'string') {\n throw createAppError(400, 'INVALID_NOTE_MARKDOWN', 'Note markdown must be a string.');\n }\n\n if (layout !== undefined && resolvedLayout === null) {\n throw createAppError(400, 'INVALID_NOTE_LAYOUT', 'Note layout must be one of narrow, wide, or full.');\n }\n\n try {\n const note = await createNote({\n title,\n ...(markdown !== undefined ? { markdown } : {}),\n ...(resolvedLayout ? { layout: resolvedLayout } : {}),\n });\n\n emitEvent({\n type: 'mcp.note.created',\n source: 'mcp',\n noteId: note.id,\n updatedAt: note.updatedAt,\n });\n\n res.status(200)\n .json({\n created: true,\n note,\n })\n .end();\n } catch (error) {\n if (error instanceof InvalidNoteAuthoringInputError) {\n throw createAppError(400, 'INVALID_NOTE_INPUT', error.message);\n }\n\n throw error;\n }\n };\n};\n\nexport const createMcpUpdateNoteHandler = (\n updateNote = updateNoteFromMarkdown,\n emitEvent: EmitServerEvent = emitServerEvent,\n): Controller => {\n return async (req, res) => {\n const { id, title, markdown, layout, editSessionId } = req.body ?? {};\n const noteId = Number(id);\n const resolvedLayout = resolveNoteLayout(layout);\n\n if (!Number.isInteger(noteId) || noteId <= 0) {\n throw createAppError(400, 'INVALID_NOTE_ID', 'A valid note id is required.');\n }\n\n if (title !== undefined && typeof title !== 'string') {\n throw createAppError(400, 'INVALID_NOTE_TITLE', 'Note title must be a string.');\n }\n\n if (markdown !== undefined && typeof markdown !== 'string') {\n throw createAppError(400, 'INVALID_NOTE_MARKDOWN', 'Note markdown must be a string.');\n }\n\n if (layout !== undefined && resolvedLayout === null) {\n throw createAppError(400, 'INVALID_NOTE_LAYOUT', 'Note layout must be one of narrow, wide, or full.');\n }\n\n if (editSessionId !== undefined && typeof editSessionId !== 'string') {\n throw createAppError(400, 'INVALID_EDIT_SESSION_ID', 'Edit session id must be a string.');\n }\n\n if (title === undefined && markdown === undefined && layout === undefined) {\n throw createAppError(400, 'INVALID_NOTE_INPUT', 'At least one note field must be provided for update.');\n }\n\n try {\n const note = await updateNote({\n id: noteId,\n ...(title !== undefined ? { title } : {}),\n ...(markdown !== undefined ? { markdown } : {}),\n ...(resolvedLayout ? { layout: resolvedLayout } : {}),\n ...(editSessionId !== undefined ? { editSessionId } : {}),\n snapshotMeta: MCP_SNAPSHOT_META,\n });\n\n if (!note) {\n throw createAppError(404, 'NOTE_NOT_FOUND', 'The requested note was not found.');\n }\n\n emitEvent({\n type: 'mcp.note.updated',\n source: 'mcp',\n noteId: note.id,\n updatedAt: note.updatedAt,\n });\n\n res.status(200)\n .json({\n updated: true,\n note,\n })\n .end();\n } catch (error) {\n if (error instanceof InvalidNoteAuthoringInputError) {\n throw createAppError(400, 'INVALID_NOTE_INPUT', error.message);\n }\n\n throw error;\n }\n };\n};\n\nexport const createMcpDeleteNoteHandler = (\n deleteNote = deleteNoteById,\n emitEvent: EmitServerEvent = emitServerEvent,\n): Controller => {\n return async (req, res) => {\n const id = Number(req.body?.id);\n\n if (!Number.isInteger(id) || id <= 0) {\n throw createAppError(400, 'INVALID_NOTE_ID', 'A valid note id is required.');\n }\n\n const deletedNote = await deleteNote(id);\n\n if (!deletedNote) {\n throw createAppError(404, 'NOTE_NOT_FOUND', 'The requested note was not found.');\n }\n\n emitEvent({\n type: 'mcp.note.deleted',\n source: 'mcp',\n noteId: deletedNote.id,\n });\n\n res.status(200)\n .json({\n deleted: true,\n note: deletedNote,\n })\n .end();\n };\n};\n"],"mappings":"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","names":[]}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import models from "
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "./blocknote.js";
|
|
6
|
-
import { captureNoteBaseline } from "./note-snapshot.js";
|
|
1
|
+
import models from "../../../models.js";
|
|
2
|
+
import { extractTagIdsFromContentJson, markdownToBlocksJson } from "../../../modules/blocknote.js";
|
|
3
|
+
import { buildNoteSearchProjection } from "./search.js";
|
|
4
|
+
import { captureNoteBaseline } from "./snapshot.js";
|
|
7
5
|
class InvalidNoteAuthoringInputError extends Error {
|
|
8
6
|
constructor(message) {
|
|
9
7
|
super(message);
|
|
@@ -103,6 +101,10 @@ const defaultNoteAuthoringService = createNoteAuthoringService({
|
|
|
103
101
|
data: {
|
|
104
102
|
title: input.title,
|
|
105
103
|
content: input.content,
|
|
104
|
+
...buildNoteSearchProjection({
|
|
105
|
+
title: input.title,
|
|
106
|
+
content: input.content
|
|
107
|
+
}),
|
|
106
108
|
...input.layout ? { layout: input.layout } : {},
|
|
107
109
|
...input.tagIds ? { tags: { connect: input.tagIds.map((id) => ({ id: Number(id) })) } } : {}
|
|
108
110
|
}
|
|
@@ -127,12 +129,28 @@ const defaultNoteAuthoringService = createNoteAuthoringService({
|
|
|
127
129
|
extractTagIds: extractTagIdsFromContentJson,
|
|
128
130
|
captureBaseline: captureNoteBaseline,
|
|
129
131
|
updateNote: async (id, input) => {
|
|
132
|
+
const existingNote = await models.note.findUnique({
|
|
133
|
+
where: { id },
|
|
134
|
+
select: {
|
|
135
|
+
title: true,
|
|
136
|
+
content: true
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
if (!existingNote) {
|
|
140
|
+
throw new Error("NOTE_NOT_FOUND");
|
|
141
|
+
}
|
|
142
|
+
const nextTitle = input.title ?? existingNote.title;
|
|
143
|
+
const nextContent = input.content ?? existingNote.content;
|
|
130
144
|
return models.note.update({
|
|
131
145
|
where: { id },
|
|
132
146
|
data: {
|
|
133
147
|
title: input.title,
|
|
134
148
|
content: input.content,
|
|
135
149
|
layout: input.layout,
|
|
150
|
+
...buildNoteSearchProjection({
|
|
151
|
+
title: nextTitle,
|
|
152
|
+
content: nextContent
|
|
153
|
+
}),
|
|
136
154
|
...input.tagIds ? { tags: { set: input.tagIds.map((tagId) => ({ id: Number(tagId) })) } } : {}
|
|
137
155
|
}
|
|
138
156
|
});
|
|
@@ -150,4 +168,4 @@ export {
|
|
|
150
168
|
createNoteFromMarkdown,
|
|
151
169
|
updateNoteFromMarkdown
|
|
152
170
|
};
|
|
153
|
-
//# sourceMappingURL=
|
|
171
|
+
//# sourceMappingURL=authoring.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/features/note/services/authoring.ts"],"sourcesContent":["import models, { type NoteLayout } from '~/models.js';\nimport { extractTagIdsFromContentJson, markdownToBlocksJson } from '~/modules/blocknote.js';\nimport { buildNoteSearchProjection } from './search.js';\nimport { captureNoteBaseline } from './snapshot.js';\n\ninterface PlaceholderRecord {\n template: string;\n replacement: string;\n}\n\ninterface NoteRecord {\n id: number;\n title: string;\n content: string;\n layout: NoteLayout;\n createdAt: Date;\n updatedAt: Date;\n}\n\ninterface NoteAuthoringDeps {\n createNote: (input: {\n title: string;\n content: string;\n layout?: NoteLayout;\n tagIds?: string[];\n }) => Promise<NoteRecord>;\n findNoteById: (id: number) => Promise<NoteRecord | null>;\n findPlaceholders: (templates: string[]) => Promise<PlaceholderRecord[]>;\n parseMarkdownToContentJson: (markdown: string) => Promise<string>;\n extractTagIds: (contentJson: string) => string[];\n captureBaseline: (input: { noteId: number; editSessionId?: string; meta?: string }) => Promise<unknown>;\n updateNote: (\n id: number,\n input: {\n title?: string;\n content?: string;\n layout?: NoteLayout;\n tagIds?: string[];\n },\n ) => Promise<NoteRecord>;\n}\n\nexport interface CreateNoteAuthoringInput {\n title: string;\n markdown?: string;\n layout?: NoteLayout;\n}\n\nexport interface UpdateNoteAuthoringInput {\n id: number;\n title?: string;\n markdown?: string;\n layout?: NoteLayout;\n editSessionId?: string;\n snapshotMeta?: string;\n}\n\nexport interface AuthoredNoteSummary {\n id: string;\n title: string;\n layout: NoteLayout;\n createdAt: string;\n updatedAt: string;\n}\n\nexport class InvalidNoteAuthoringInputError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidNoteAuthoringInputError';\n }\n}\n\nconst PLACEHOLDER_PREFIX = '{%';\nconst PLACEHOLDER_SUFFIX = '%}';\n\nconst serializeNote = (note: NoteRecord): AuthoredNoteSummary => ({\n id: String(note.id),\n title: note.title,\n layout: note.layout,\n createdAt: note.createdAt.toISOString(),\n updatedAt: note.updatedAt.toISOString(),\n});\n\nconst extractPlaceholderTemplates = (value: string) => {\n return Array.from(\n new Set(\n Array.from(\n value.matchAll(new RegExp(`${PLACEHOLDER_PREFIX}([^}]+)${PLACEHOLDER_SUFFIX}`, 'g')),\n (match) => match[1],\n ),\n ),\n );\n};\n\nexport const createNoteAuthoringService = (deps: NoteAuthoringDeps) => {\n const replacePlaceholders = async (value: string) => {\n const templates = extractPlaceholderTemplates(value);\n\n if (templates.length === 0) {\n return value;\n }\n\n const placeholders = await deps.findPlaceholders(templates);\n let replacedValue = value;\n\n for (const placeholder of placeholders) {\n replacedValue = replacedValue.replace(\n new RegExp(`${PLACEHOLDER_PREFIX}${placeholder.template}${PLACEHOLDER_SUFFIX}`, 'g'),\n placeholder.replacement,\n );\n }\n\n return replacedValue;\n };\n\n return {\n createNote: async (input: CreateNoteAuthoringInput): Promise<AuthoredNoteSummary> => {\n const title = input.title.trim();\n\n if (!title) {\n throw new InvalidNoteAuthoringInputError('A note title is required.');\n }\n\n const replacedTitle = await replacePlaceholders(title);\n const replacedMarkdown = await replacePlaceholders(input.markdown ?? '');\n const content = await deps.parseMarkdownToContentJson(replacedMarkdown);\n const tagIds = deps.extractTagIds(content);\n const note = await deps.createNote({\n title: replacedTitle,\n content,\n tagIds,\n ...(input.layout ? { layout: input.layout } : {}),\n });\n\n return serializeNote(note);\n },\n\n updateNote: async (input: UpdateNoteAuthoringInput): Promise<AuthoredNoteSummary | null> => {\n if (input.title === undefined && input.markdown === undefined && input.layout === undefined) {\n throw new InvalidNoteAuthoringInputError('At least one note field must be provided for update.');\n }\n\n const existingNote = await deps.findNoteById(input.id);\n\n if (!existingNote) {\n return null;\n }\n\n const nextData: {\n title?: string;\n content?: string;\n layout?: NoteLayout;\n tagIds?: string[];\n } = {};\n\n if (input.title !== undefined) {\n const title = input.title.trim();\n\n if (!title) {\n throw new InvalidNoteAuthoringInputError('A note title is required.');\n }\n\n nextData.title = await replacePlaceholders(title);\n }\n\n if (input.markdown !== undefined) {\n const replacedMarkdown = await replacePlaceholders(input.markdown);\n nextData.content = await deps.parseMarkdownToContentJson(replacedMarkdown);\n nextData.tagIds = deps.extractTagIds(nextData.content);\n }\n\n if (input.layout !== undefined) {\n nextData.layout = input.layout;\n }\n\n await deps.captureBaseline({\n noteId: input.id,\n ...(input.editSessionId ? { editSessionId: input.editSessionId } : {}),\n ...(input.snapshotMeta ? { meta: input.snapshotMeta } : {}),\n });\n\n const updatedNote = await deps.updateNote(input.id, nextData);\n return serializeNote(updatedNote);\n },\n };\n};\n\nconst defaultNoteAuthoringService = createNoteAuthoringService({\n createNote: async (input) => {\n return models.note.create({\n data: {\n title: input.title,\n content: input.content,\n ...buildNoteSearchProjection({\n title: input.title,\n content: input.content,\n }),\n ...(input.layout ? { layout: input.layout } : {}),\n ...(input.tagIds ? { tags: { connect: input.tagIds.map((id) => ({ id: Number(id) })) } } : {}),\n },\n });\n },\n findNoteById: async (id) => {\n return models.note.findUnique({ where: { id } });\n },\n findPlaceholders: async (templates) => {\n if (templates.length === 0) {\n return [];\n }\n\n return models.placeholder.findMany({\n select: {\n template: true,\n replacement: true,\n },\n where: { template: { in: templates } },\n });\n },\n parseMarkdownToContentJson: markdownToBlocksJson,\n extractTagIds: extractTagIdsFromContentJson,\n captureBaseline: captureNoteBaseline,\n updateNote: async (id, input) => {\n const existingNote = await models.note.findUnique({\n where: { id },\n select: {\n title: true,\n content: true,\n },\n });\n\n if (!existingNote) {\n throw new Error('NOTE_NOT_FOUND');\n }\n\n const nextTitle = input.title ?? existingNote.title;\n const nextContent = input.content ?? existingNote.content;\n\n return models.note.update({\n where: { id },\n data: {\n title: input.title,\n content: input.content,\n layout: input.layout,\n ...buildNoteSearchProjection({\n title: nextTitle,\n content: nextContent,\n }),\n ...(input.tagIds ? { tags: { set: input.tagIds.map((tagId) => ({ id: Number(tagId) })) } } : {}),\n },\n });\n },\n});\n\nexport const createNoteFromMarkdown = async (input: CreateNoteAuthoringInput) => {\n return defaultNoteAuthoringService.createNote(input);\n};\n\nexport const updateNoteFromMarkdown = async (input: UpdateNoteAuthoringInput) => {\n return defaultNoteAuthoringService.updateNote(input);\n};\n"],"mappings":"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","names":[]}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import models from "
|
|
2
|
-
import { trashNoteById as moveNoteToTrashById } from "./
|
|
1
|
+
import models from "../../../models.js";
|
|
2
|
+
import { trashNoteById as moveNoteToTrashById } from "./trash.js";
|
|
3
3
|
const DEFAULT_CLEANUP_KEYWORDS = ["temp", "temporary", "tmp", "draft", "test", "wip"];
|
|
4
4
|
const normalizeCleanupKeywords = (keywords, query) => {
|
|
5
5
|
const queryKeywords = typeof query === "string" ? query.split(/[,\s]+/).map((keyword) => keyword.trim()) : [];
|
|
6
6
|
const source = keywords && keywords.length > 0 ? keywords : queryKeywords.length > 0 ? queryKeywords : DEFAULT_CLEANUP_KEYWORDS;
|
|
7
|
-
return Array.from(new Set(
|
|
8
|
-
source.map((keyword) => keyword.trim().toLowerCase()).filter(Boolean)
|
|
9
|
-
));
|
|
7
|
+
return Array.from(new Set(source.map((keyword) => keyword.trim().toLowerCase()).filter(Boolean)));
|
|
10
8
|
};
|
|
11
9
|
const buildForceReasons = (input) => {
|
|
12
10
|
const forceReasons = [];
|
|
@@ -78,10 +76,7 @@ const createNoteCleanupService = (deps) => {
|
|
|
78
76
|
orphanedTagNames: orphanedTags.map((tag) => tag.name),
|
|
79
77
|
orphanedTagIds: orphanedTags.map((tag) => tag.id),
|
|
80
78
|
requiresForce: candidate.requiresForce || orphanedTags.length > 0,
|
|
81
|
-
forceReasons: [
|
|
82
|
-
...candidate.forceReasons,
|
|
83
|
-
...orphanedTags.length > 0 ? ["orphan_tags"] : []
|
|
84
|
-
]
|
|
79
|
+
forceReasons: [...candidate.forceReasons, ...orphanedTags.length > 0 ? ["orphan_tags"] : []]
|
|
85
80
|
};
|
|
86
81
|
};
|
|
87
82
|
const getDeletePreview = async (id) => {
|
|
@@ -103,12 +98,14 @@ const createNoteCleanupService = (deps) => {
|
|
|
103
98
|
deps.countCandidateNotes(keywords, olderThanDays),
|
|
104
99
|
deps.findCandidateNotes(keywords, offset, limit, olderThanDays)
|
|
105
100
|
]);
|
|
106
|
-
const candidates = await Promise.all(
|
|
107
|
-
|
|
101
|
+
const candidates = await Promise.all(
|
|
102
|
+
notes.map((note) => {
|
|
103
|
+
const haystack = `${note.title}
|
|
108
104
|
${note.content}`.toLowerCase();
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
105
|
+
const matchedTerms = keywords.filter((keyword) => haystack.includes(keyword));
|
|
106
|
+
return buildCandidate(note, matchedTerms);
|
|
107
|
+
})
|
|
108
|
+
);
|
|
112
109
|
return {
|
|
113
110
|
keywords,
|
|
114
111
|
limit,
|
|
@@ -132,10 +129,7 @@ ${note.content}`.toLowerCase();
|
|
|
132
129
|
};
|
|
133
130
|
};
|
|
134
131
|
const buildKeywordClauses = (keywords) => {
|
|
135
|
-
return keywords.flatMap((keyword) => [
|
|
136
|
-
{ title: { contains: keyword } },
|
|
137
|
-
{ content: { contains: keyword } }
|
|
138
|
-
]);
|
|
132
|
+
return keywords.flatMap((keyword) => [{ title: { contains: keyword } }, { content: { contains: keyword } }]);
|
|
139
133
|
};
|
|
140
134
|
const buildStaleWhere = (olderThanDays) => {
|
|
141
135
|
if (olderThanDays <= 0) {
|
|
@@ -164,10 +158,7 @@ const noteCleanupService = createNoteCleanupService({
|
|
|
164
158
|
content: { contains: `reference","props":{"id":"${noteId}"` },
|
|
165
159
|
NOT: { id: noteId }
|
|
166
160
|
},
|
|
167
|
-
orderBy: [
|
|
168
|
-
{ pinned: "desc" },
|
|
169
|
-
{ updatedAt: "desc" }
|
|
170
|
-
]
|
|
161
|
+
orderBy: [{ pinned: "desc" }, { updatedAt: "desc" }]
|
|
171
162
|
}),
|
|
172
163
|
findCandidateNotes: (keywords, skip, take, olderThanDays) => models.note.findMany({
|
|
173
164
|
include: { tags: true },
|
|
@@ -204,4 +195,4 @@ export {
|
|
|
204
195
|
listNoteCleanupCandidates,
|
|
205
196
|
normalizeCleanupKeywords
|
|
206
197
|
};
|
|
207
|
-
//# sourceMappingURL=
|
|
198
|
+
//# sourceMappingURL=cleanup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/features/note/services/cleanup.ts"],"sourcesContent":["import models from '~/models.js';\nimport { trashNoteById as moveNoteToTrashById } from './trash.js';\n\nexport interface NoteCleanupBackReference {\n id: string;\n title: string;\n}\n\nexport interface NoteCleanupPreview {\n id: string;\n title: string;\n updatedAt: string;\n pinned: boolean;\n tagNames: string[];\n reminderCount: number;\n backReferences: NoteCleanupBackReference[];\n orphanedTagNames: string[];\n requiresForce: boolean;\n forceReasons: string[];\n}\n\nexport interface NoteCleanupCandidate {\n id: string;\n title: string;\n updatedAt: string;\n pinned: boolean;\n tagNames: string[];\n reminderCount: number;\n backReferenceCount: number;\n matchedTerms: string[];\n reasons: string[];\n requiresForce: boolean;\n forceReasons: string[];\n}\n\nexport interface NoteCleanupCandidatesResult {\n keywords: string[];\n limit: number;\n offset: number;\n olderThanDays: number;\n notes: NoteCleanupCandidate[];\n totalCount: number;\n}\n\nexport interface NoteCleanupService {\n deleteNoteById: (id: number) => Promise<NoteCleanupPreview | null>;\n getDeletePreview: (id: number) => Promise<NoteCleanupPreview | null>;\n listCleanupCandidates: (input?: {\n keywords?: string[];\n limit?: number;\n offset?: number;\n olderThanDays?: number;\n query?: string;\n }) => Promise<NoteCleanupCandidatesResult>;\n}\n\ninterface NoteRecord {\n id: number;\n title: string;\n content: string;\n updatedAt: Date;\n pinned: boolean;\n tags: Array<{\n id: number;\n name: string;\n }>;\n}\n\ninterface NoteCleanupPreviewInternal extends NoteCleanupPreview {\n orphanedTagIds: number[];\n}\n\nconst DEFAULT_CLEANUP_KEYWORDS = ['temp', 'temporary', 'tmp', 'draft', 'test', 'wip'];\n\nexport const normalizeCleanupKeywords = (keywords?: string[], query?: string) => {\n const queryKeywords = typeof query === 'string' ? query.split(/[,\\s]+/).map((keyword) => keyword.trim()) : [];\n const source =\n keywords && keywords.length > 0\n ? keywords\n : queryKeywords.length > 0\n ? queryKeywords\n : DEFAULT_CLEANUP_KEYWORDS;\n\n return Array.from(new Set(source.map((keyword) => keyword.trim().toLowerCase()).filter(Boolean)));\n};\n\nconst buildForceReasons = (input: { pinned: boolean; reminderCount: number; backReferenceCount: number }) => {\n const forceReasons: string[] = [];\n\n if (input.pinned) {\n forceReasons.push('note_is_pinned');\n }\n\n if (input.reminderCount > 0) {\n forceReasons.push('has_reminders');\n }\n\n if (input.backReferenceCount > 0) {\n forceReasons.push('has_back_references');\n }\n\n return forceReasons;\n};\n\nexport const createNoteCleanupService = (deps: {\n countCandidateNotes: (keywords: string[], olderThanDays: number) => Promise<number>;\n countReminders: (noteId: number) => Promise<number>;\n deleteNoteAndPruneTags: (noteId: number, orphanTagIds: number[]) => Promise<void>;\n findBackReferences: (noteId: number) => Promise<Array<{ id: number; title: string }>>;\n findCandidateNotes: (\n keywords: string[],\n skip: number,\n take: number,\n olderThanDays: number,\n ) => Promise<NoteRecord[]>;\n findNote: (id: number) => Promise<NoteRecord | null>;\n getTagNoteCounts: (tagIds: number[]) => Promise<Map<number, number>>;\n}): NoteCleanupService => {\n const buildCandidate = async (note: NoteRecord, matchedTerms?: string[]): Promise<NoteCleanupCandidate> => {\n const [reminderCount, backReferences] = await Promise.all([\n deps.countReminders(note.id),\n deps.findBackReferences(note.id),\n ]);\n const normalizedMatchedTerms = matchedTerms ?? [];\n const forceReasons = buildForceReasons({\n pinned: note.pinned,\n reminderCount,\n backReferenceCount: backReferences.length,\n });\n\n return {\n id: String(note.id),\n title: note.title,\n updatedAt: note.updatedAt.toISOString(),\n pinned: note.pinned,\n tagNames: note.tags.map((tag) => tag.name),\n reminderCount,\n backReferenceCount: backReferences.length,\n matchedTerms: normalizedMatchedTerms,\n reasons: [\n ...(normalizedMatchedTerms.length > 0 ? [`matched_terms:${normalizedMatchedTerms.join(',')}`] : []),\n ...(note.pinned ? [] : ['not_pinned']),\n ...(note.tags.length === 0 ? ['tagless'] : []),\n ...(reminderCount === 0 ? ['no_reminders'] : []),\n ...(backReferences.length === 0 ? ['no_back_references'] : []),\n ],\n requiresForce: forceReasons.length > 0,\n forceReasons,\n };\n };\n\n const getDeletePreviewInternal = async (id: number): Promise<NoteCleanupPreviewInternal | null> => {\n const note = await deps.findNote(id);\n\n if (!note) {\n return null;\n }\n\n const [candidate, backReferences, tagNoteCounts] = await Promise.all([\n buildCandidate(note),\n deps.findBackReferences(note.id),\n deps.getTagNoteCounts(note.tags.map((tag) => tag.id)),\n ]);\n\n const orphanedTags = note.tags.filter((tag) => (tagNoteCounts.get(tag.id) ?? 0) <= 1);\n\n return {\n id: candidate.id,\n title: candidate.title,\n updatedAt: candidate.updatedAt,\n pinned: candidate.pinned,\n tagNames: candidate.tagNames,\n reminderCount: candidate.reminderCount,\n backReferences: backReferences.map((backReference) => ({\n id: String(backReference.id),\n title: backReference.title,\n })),\n orphanedTagNames: orphanedTags.map((tag) => tag.name),\n orphanedTagIds: orphanedTags.map((tag) => tag.id),\n requiresForce: candidate.requiresForce || orphanedTags.length > 0,\n forceReasons: [...candidate.forceReasons, ...(orphanedTags.length > 0 ? ['orphan_tags'] : [])],\n };\n };\n\n const getDeletePreview = async (id: number): Promise<NoteCleanupPreview | null> => {\n const preview = await getDeletePreviewInternal(id);\n\n if (!preview) {\n return null;\n }\n\n const { orphanedTagIds, ...publicPreview } = preview;\n void orphanedTagIds;\n return publicPreview;\n };\n\n return {\n listCleanupCandidates: async (input = {}) => {\n const limit = Math.max(1, Number(input.limit ?? 20));\n const offset = Math.max(0, Number(input.offset ?? 0));\n const olderThanDays = Math.max(0, Number(input.olderThanDays ?? 0));\n const keywords = normalizeCleanupKeywords(input.keywords, input.query);\n\n const [totalCount, notes] = await Promise.all([\n deps.countCandidateNotes(keywords, olderThanDays),\n deps.findCandidateNotes(keywords, offset, limit, olderThanDays),\n ]);\n\n const candidates = await Promise.all(\n notes.map((note) => {\n const haystack = `${note.title}\\n${note.content}`.toLowerCase();\n const matchedTerms = keywords.filter((keyword) => haystack.includes(keyword));\n return buildCandidate(note, matchedTerms);\n }),\n );\n\n return {\n keywords,\n limit,\n offset,\n olderThanDays,\n notes: candidates,\n totalCount,\n };\n },\n getDeletePreview,\n deleteNoteById: async (id: number) => {\n const preview = await getDeletePreviewInternal(id);\n\n if (!preview) {\n return null;\n }\n\n await deps.deleteNoteAndPruneTags(id, preview.orphanedTagIds);\n\n const { orphanedTagIds, ...publicPreview } = preview;\n void orphanedTagIds;\n return publicPreview;\n },\n };\n};\n\nconst buildKeywordClauses = (keywords: string[]) => {\n return keywords.flatMap((keyword) => [{ title: { contains: keyword } }, { content: { contains: keyword } }]);\n};\n\nconst buildStaleWhere = (olderThanDays: number) => {\n if (olderThanDays <= 0) {\n return {};\n }\n\n return { updatedAt: { lte: new Date(Date.now() - olderThanDays * 24 * 60 * 60 * 1000) } };\n};\n\nconst noteCleanupService = createNoteCleanupService({\n countCandidateNotes: (keywords: string[], olderThanDays: number) =>\n models.note.count({\n where: {\n ...buildStaleWhere(olderThanDays),\n OR: buildKeywordClauses(keywords),\n },\n }),\n countReminders: (noteId: number) => models.reminder.count({ where: { noteId } }),\n deleteNoteAndPruneTags: async (noteId: number, orphanTagIds: number[]) => {\n void orphanTagIds;\n await moveNoteToTrashById(noteId);\n },\n findBackReferences: (noteId: number) =>\n models.note.findMany({\n select: {\n id: true,\n title: true,\n },\n where: {\n content: { contains: `reference\",\"props\":{\"id\":\"${noteId}\"` },\n NOT: { id: noteId },\n },\n orderBy: [{ pinned: 'desc' }, { updatedAt: 'desc' }],\n }),\n findCandidateNotes: (keywords: string[], skip: number, take: number, olderThanDays: number) =>\n models.note.findMany({\n include: { tags: true },\n orderBy: { updatedAt: 'asc' },\n skip,\n take,\n where: {\n ...buildStaleWhere(olderThanDays),\n OR: buildKeywordClauses(keywords),\n },\n }),\n findNote: (id: number) =>\n models.note.findUnique({\n where: { id },\n include: { tags: true },\n }),\n getTagNoteCounts: async (tagIds: number[]) => {\n if (tagIds.length === 0) {\n return new Map<number, number>();\n }\n\n const tags = await models.tag.findMany({\n where: { id: { in: tagIds } },\n include: { _count: { select: { notes: true } } },\n });\n\n return new Map(tags.map((tag) => [tag.id, tag._count.notes]));\n },\n});\n\nexport const listNoteCleanupCandidates = noteCleanupService.listCleanupCandidates;\nexport const getNoteCleanupPreview = noteCleanupService.getDeletePreview;\nexport const deleteNoteById = noteCleanupService.deleteNoteById;\n"],"mappings":"AAAA,OAAO,YAAY;AACnB,SAAS,iBAAiB,2BAA2B;AAuErD,MAAM,2BAA2B,CAAC,QAAQ,aAAa,OAAO,SAAS,QAAQ,KAAK;AAE7E,MAAM,2BAA2B,CAAC,UAAqB,UAAmB;AAC7E,QAAM,gBAAgB,OAAO,UAAU,WAAW,MAAM,MAAM,QAAQ,EAAE,IAAI,CAAC,YAAY,QAAQ,KAAK,CAAC,IAAI,CAAC;AAC5G,QAAM,SACF,YAAY,SAAS,SAAS,IACxB,WACA,cAAc,SAAS,IACrB,gBACA;AAEZ,SAAO,MAAM,KAAK,IAAI,IAAI,OAAO,IAAI,CAAC,YAAY,QAAQ,KAAK,EAAE,YAAY,CAAC,EAAE,OAAO,OAAO,CAAC,CAAC;AACpG;AAEA,MAAM,oBAAoB,CAAC,UAAkF;AACzG,QAAM,eAAyB,CAAC;AAEhC,MAAI,MAAM,QAAQ;AACd,iBAAa,KAAK,gBAAgB;AAAA,EACtC;AAEA,MAAI,MAAM,gBAAgB,GAAG;AACzB,iBAAa,KAAK,eAAe;AAAA,EACrC;AAEA,MAAI,MAAM,qBAAqB,GAAG;AAC9B,iBAAa,KAAK,qBAAqB;AAAA,EAC3C;AAEA,SAAO;AACX;AAEO,MAAM,2BAA2B,CAAC,SAaf;AACtB,QAAM,iBAAiB,OAAO,MAAkB,iBAA2D;AACvG,UAAM,CAAC,eAAe,cAAc,IAAI,MAAM,QAAQ,IAAI;AAAA,MACtD,KAAK,eAAe,KAAK,EAAE;AAAA,MAC3B,KAAK,mBAAmB,KAAK,EAAE;AAAA,IACnC,CAAC;AACD,UAAM,yBAAyB,gBAAgB,CAAC;AAChD,UAAM,eAAe,kBAAkB;AAAA,MACnC,QAAQ,KAAK;AAAA,MACb;AAAA,MACA,oBAAoB,eAAe;AAAA,IACvC,CAAC;AAED,WAAO;AAAA,MACH,IAAI,OAAO,KAAK,EAAE;AAAA,MAClB,OAAO,KAAK;AAAA,MACZ,WAAW,KAAK,UAAU,YAAY;AAAA,MACtC,QAAQ,KAAK;AAAA,MACb,UAAU,KAAK,KAAK,IAAI,CAAC,QAAQ,IAAI,IAAI;AAAA,MACzC;AAAA,MACA,oBAAoB,eAAe;AAAA,MACnC,cAAc;AAAA,MACd,SAAS;AAAA,QACL,GAAI,uBAAuB,SAAS,IAAI,CAAC,iBAAiB,uBAAuB,KAAK,GAAG,CAAC,EAAE,IAAI,CAAC;AAAA,QACjG,GAAI,KAAK,SAAS,CAAC,IAAI,CAAC,YAAY;AAAA,QACpC,GAAI,KAAK,KAAK,WAAW,IAAI,CAAC,SAAS,IAAI,CAAC;AAAA,QAC5C,GAAI,kBAAkB,IAAI,CAAC,cAAc,IAAI,CAAC;AAAA,QAC9C,GAAI,eAAe,WAAW,IAAI,CAAC,oBAAoB,IAAI,CAAC;AAAA,MAChE;AAAA,MACA,eAAe,aAAa,SAAS;AAAA,MACrC;AAAA,IACJ;AAAA,EACJ;AAEA,QAAM,2BAA2B,OAAO,OAA2D;AAC/F,UAAM,OAAO,MAAM,KAAK,SAAS,EAAE;AAEnC,QAAI,CAAC,MAAM;AACP,aAAO;AAAA,IACX;AAEA,UAAM,CAAC,WAAW,gBAAgB,aAAa,IAAI,MAAM,QAAQ,IAAI;AAAA,MACjE,eAAe,IAAI;AAAA,MACnB,KAAK,mBAAmB,KAAK,EAAE;AAAA,MAC/B,KAAK,iBAAiB,KAAK,KAAK,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;AAAA,IACxD,CAAC;AAED,UAAM,eAAe,KAAK,KAAK,OAAO,CAAC,SAAS,cAAc,IAAI,IAAI,EAAE,KAAK,MAAM,CAAC;AAEpF,WAAO;AAAA,MACH,IAAI,UAAU;AAAA,MACd,OAAO,UAAU;AAAA,MACjB,WAAW,UAAU;AAAA,MACrB,QAAQ,UAAU;AAAA,MAClB,UAAU,UAAU;AAAA,MACpB,eAAe,UAAU;AAAA,MACzB,gBAAgB,eAAe,IAAI,CAAC,mBAAmB;AAAA,QACnD,IAAI,OAAO,cAAc,EAAE;AAAA,QAC3B,OAAO,cAAc;AAAA,MACzB,EAAE;AAAA,MACF,kBAAkB,aAAa,IAAI,CAAC,QAAQ,IAAI,IAAI;AAAA,MACpD,gBAAgB,aAAa,IAAI,CAAC,QAAQ,IAAI,EAAE;AAAA,MAChD,eAAe,UAAU,iBAAiB,aAAa,SAAS;AAAA,MAChE,cAAc,CAAC,GAAG,UAAU,cAAc,GAAI,aAAa,SAAS,IAAI,CAAC,aAAa,IAAI,CAAC,CAAE;AAAA,IACjG;AAAA,EACJ;AAEA,QAAM,mBAAmB,OAAO,OAAmD;AAC/E,UAAM,UAAU,MAAM,yBAAyB,EAAE;AAEjD,QAAI,CAAC,SAAS;AACV,aAAO;AAAA,IACX;AAEA,UAAM,EAAE,gBAAgB,GAAG,cAAc,IAAI;AAC7C,SAAK;AACL,WAAO;AAAA,EACX;AAEA,SAAO;AAAA,IACH,uBAAuB,OAAO,QAAQ,CAAC,MAAM;AACzC,YAAM,QAAQ,KAAK,IAAI,GAAG,OAAO,MAAM,SAAS,EAAE,CAAC;AACnD,YAAM,SAAS,KAAK,IAAI,GAAG,OAAO,MAAM,UAAU,CAAC,CAAC;AACpD,YAAM,gBAAgB,KAAK,IAAI,GAAG,OAAO,MAAM,iBAAiB,CAAC,CAAC;AAClE,YAAM,WAAW,yBAAyB,MAAM,UAAU,MAAM,KAAK;AAErE,YAAM,CAAC,YAAY,KAAK,IAAI,MAAM,QAAQ,IAAI;AAAA,QAC1C,KAAK,oBAAoB,UAAU,aAAa;AAAA,QAChD,KAAK,mBAAmB,UAAU,QAAQ,OAAO,aAAa;AAAA,MAClE,CAAC;AAED,YAAM,aAAa,MAAM,QAAQ;AAAA,QAC7B,MAAM,IAAI,CAAC,SAAS;AAChB,gBAAM,WAAW,GAAG,KAAK,KAAK;AAAA,EAAK,KAAK,OAAO,GAAG,YAAY;AAC9D,gBAAM,eAAe,SAAS,OAAO,CAAC,YAAY,SAAS,SAAS,OAAO,CAAC;AAC5E,iBAAO,eAAe,MAAM,YAAY;AAAA,QAC5C,CAAC;AAAA,MACL;AAEA,aAAO;AAAA,QACH;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,OAAO;AAAA,QACP;AAAA,MACJ;AAAA,IACJ;AAAA,IACA;AAAA,IACA,gBAAgB,OAAO,OAAe;AAClC,YAAM,UAAU,MAAM,yBAAyB,EAAE;AAEjD,UAAI,CAAC,SAAS;AACV,eAAO;AAAA,MACX;AAEA,YAAM,KAAK,uBAAuB,IAAI,QAAQ,cAAc;AAE5D,YAAM,EAAE,gBAAgB,GAAG,cAAc,IAAI;AAC7C,WAAK;AACL,aAAO;AAAA,IACX;AAAA,EACJ;AACJ;AAEA,MAAM,sBAAsB,CAAC,aAAuB;AAChD,SAAO,SAAS,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,EAAE,UAAU,QAAQ,EAAE,GAAG,EAAE,SAAS,EAAE,UAAU,QAAQ,EAAE,CAAC,CAAC;AAC/G;AAEA,MAAM,kBAAkB,CAAC,kBAA0B;AAC/C,MAAI,iBAAiB,GAAG;AACpB,WAAO,CAAC;AAAA,EACZ;AAEA,SAAO,EAAE,WAAW,EAAE,KAAK,IAAI,KAAK,KAAK,IAAI,IAAI,gBAAgB,KAAK,KAAK,KAAK,GAAI,EAAE,EAAE;AAC5F;AAEA,MAAM,qBAAqB,yBAAyB;AAAA,EAChD,qBAAqB,CAAC,UAAoB,kBACtC,OAAO,KAAK,MAAM;AAAA,IACd,OAAO;AAAA,MACH,GAAG,gBAAgB,aAAa;AAAA,MAChC,IAAI,oBAAoB,QAAQ;AAAA,IACpC;AAAA,EACJ,CAAC;AAAA,EACL,gBAAgB,CAAC,WAAmB,OAAO,SAAS,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAAA,EAC/E,wBAAwB,OAAO,QAAgB,iBAA2B;AACtE,SAAK;AACL,UAAM,oBAAoB,MAAM;AAAA,EACpC;AAAA,EACA,oBAAoB,CAAC,WACjB,OAAO,KAAK,SAAS;AAAA,IACjB,QAAQ;AAAA,MACJ,IAAI;AAAA,MACJ,OAAO;AAAA,IACX;AAAA,IACA,OAAO;AAAA,MACH,SAAS,EAAE,UAAU,6BAA6B,MAAM,IAAI;AAAA,MAC5D,KAAK,EAAE,IAAI,OAAO;AAAA,IACtB;AAAA,IACA,SAAS,CAAC,EAAE,QAAQ,OAAO,GAAG,EAAE,WAAW,OAAO,CAAC;AAAA,EACvD,CAAC;AAAA,EACL,oBAAoB,CAAC,UAAoB,MAAc,MAAc,kBACjE,OAAO,KAAK,SAAS;AAAA,IACjB,SAAS,EAAE,MAAM,KAAK;AAAA,IACtB,SAAS,EAAE,WAAW,MAAM;AAAA,IAC5B;AAAA,IACA;AAAA,IACA,OAAO;AAAA,MACH,GAAG,gBAAgB,aAAa;AAAA,MAChC,IAAI,oBAAoB,QAAQ;AAAA,IACpC;AAAA,EACJ,CAAC;AAAA,EACL,UAAU,CAAC,OACP,OAAO,KAAK,WAAW;AAAA,IACnB,OAAO,EAAE,GAAG;AAAA,IACZ,SAAS,EAAE,MAAM,KAAK;AAAA,EAC1B,CAAC;AAAA,EACL,kBAAkB,OAAO,WAAqB;AAC1C,QAAI,OAAO,WAAW,GAAG;AACrB,aAAO,oBAAI,IAAoB;AAAA,IACnC;AAEA,UAAM,OAAO,MAAM,OAAO,IAAI,SAAS;AAAA,MACnC,OAAO,EAAE,IAAI,EAAE,IAAI,OAAO,EAAE;AAAA,MAC5B,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,KAAK,EAAE,EAAE;AAAA,IACnD,CAAC;AAED,WAAO,IAAI,IAAI,KAAK,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,IAAI,OAAO,KAAK,CAAC,CAAC;AAAA,EAChE;AACJ,CAAC;AAEM,MAAM,4BAA4B,mBAAmB;AACrD,MAAM,wBAAwB,mBAAmB;AACjD,MAAM,iBAAiB,mBAAmB;","names":[]}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import models from "../../../models.js";
|
|
2
|
+
import { buildNoteSearchProjection, NOTE_SEARCH_TEXT_SCHEMA_VERSION } from "./search.js";
|
|
3
|
+
const DEFAULT_BACKFILL_BATCH_SIZE = 100;
|
|
4
|
+
let activeNoteSearchBackfillPromise = null;
|
|
5
|
+
const yieldToEventLoop = async () => {
|
|
6
|
+
await new Promise((resolve) => {
|
|
7
|
+
setTimeout(resolve, 0);
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
const createNoteSearchBackfillService = (deps) => {
|
|
11
|
+
const backfillBatch = async (limit = DEFAULT_BACKFILL_BATCH_SIZE) => {
|
|
12
|
+
const staleNotes = await deps.listStaleNotes(limit);
|
|
13
|
+
if (staleNotes.length === 0) {
|
|
14
|
+
return 0;
|
|
15
|
+
}
|
|
16
|
+
await deps.updateNotes(
|
|
17
|
+
staleNotes.map((note) => ({
|
|
18
|
+
id: note.id,
|
|
19
|
+
projection: buildNoteSearchProjection(note)
|
|
20
|
+
}))
|
|
21
|
+
);
|
|
22
|
+
return staleNotes.length;
|
|
23
|
+
};
|
|
24
|
+
const backfillAll = async (limit = DEFAULT_BACKFILL_BATCH_SIZE) => {
|
|
25
|
+
let total = 0;
|
|
26
|
+
while (true) {
|
|
27
|
+
const count = await backfillBatch(limit);
|
|
28
|
+
if (!count) {
|
|
29
|
+
return total;
|
|
30
|
+
}
|
|
31
|
+
total += count;
|
|
32
|
+
if (count < limit) {
|
|
33
|
+
return total;
|
|
34
|
+
}
|
|
35
|
+
await yieldToEventLoop();
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
backfillBatch,
|
|
40
|
+
backfillAll
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
const defaultNoteSearchBackfillService = createNoteSearchBackfillService({
|
|
44
|
+
listStaleNotes: async (limit) => {
|
|
45
|
+
return models.note.findMany({
|
|
46
|
+
where: { searchableTextVersion: { not: NOTE_SEARCH_TEXT_SCHEMA_VERSION } },
|
|
47
|
+
orderBy: { id: "asc" },
|
|
48
|
+
take: limit,
|
|
49
|
+
select: {
|
|
50
|
+
id: true,
|
|
51
|
+
title: true,
|
|
52
|
+
content: true
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
updateNotes: async (updates) => {
|
|
57
|
+
await models.$transaction(
|
|
58
|
+
updates.map(
|
|
59
|
+
({ id, projection }) => models.note.update({
|
|
60
|
+
where: { id },
|
|
61
|
+
data: projection
|
|
62
|
+
})
|
|
63
|
+
)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
const backfillStaleNoteSearchText = async (limit) => {
|
|
68
|
+
return defaultNoteSearchBackfillService.backfillAll(limit);
|
|
69
|
+
};
|
|
70
|
+
const runNoteSearchBackfillInBackground = (limit) => {
|
|
71
|
+
if (activeNoteSearchBackfillPromise) {
|
|
72
|
+
return activeNoteSearchBackfillPromise;
|
|
73
|
+
}
|
|
74
|
+
activeNoteSearchBackfillPromise = defaultNoteSearchBackfillService.backfillAll(limit).finally(() => {
|
|
75
|
+
activeNoteSearchBackfillPromise = null;
|
|
76
|
+
});
|
|
77
|
+
return activeNoteSearchBackfillPromise;
|
|
78
|
+
};
|
|
79
|
+
export {
|
|
80
|
+
backfillStaleNoteSearchText,
|
|
81
|
+
createNoteSearchBackfillService,
|
|
82
|
+
runNoteSearchBackfillInBackground
|
|
83
|
+
};
|
|
84
|
+
//# sourceMappingURL=search-backfill.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/features/note/services/search-backfill.ts"],"sourcesContent":["import models from '~/models.js';\nimport { buildNoteSearchProjection, NOTE_SEARCH_TEXT_SCHEMA_VERSION, type NoteSearchProjection } from './search.js';\n\ninterface BackfillCandidate {\n id: number;\n title: string;\n content: string;\n}\n\ninterface NoteSearchBackfillDeps {\n listStaleNotes: (limit: number) => Promise<BackfillCandidate[]>;\n updateNotes: (updates: Array<{ id: number; projection: NoteSearchProjection }>) => Promise<void>;\n}\n\nconst DEFAULT_BACKFILL_BATCH_SIZE = 100;\nlet activeNoteSearchBackfillPromise: Promise<number> | null = null;\n\nconst yieldToEventLoop = async () => {\n await new Promise<void>((resolve) => {\n setTimeout(resolve, 0);\n });\n};\n\nexport const createNoteSearchBackfillService = (deps: NoteSearchBackfillDeps) => {\n const backfillBatch = async (limit = DEFAULT_BACKFILL_BATCH_SIZE) => {\n const staleNotes = await deps.listStaleNotes(limit);\n\n if (staleNotes.length === 0) {\n return 0;\n }\n\n await deps.updateNotes(\n staleNotes.map((note) => ({\n id: note.id,\n projection: buildNoteSearchProjection(note),\n })),\n );\n\n return staleNotes.length;\n };\n\n const backfillAll = async (limit = DEFAULT_BACKFILL_BATCH_SIZE) => {\n let total = 0;\n\n while (true) {\n const count = await backfillBatch(limit);\n\n if (!count) {\n return total;\n }\n\n total += count;\n\n if (count < limit) {\n return total;\n }\n\n await yieldToEventLoop();\n }\n };\n\n return {\n backfillBatch,\n backfillAll,\n };\n};\n\nconst defaultNoteSearchBackfillService = createNoteSearchBackfillService({\n listStaleNotes: async (limit) => {\n return models.note.findMany({\n where: { searchableTextVersion: { not: NOTE_SEARCH_TEXT_SCHEMA_VERSION } },\n orderBy: { id: 'asc' },\n take: limit,\n select: {\n id: true,\n title: true,\n content: true,\n },\n });\n },\n updateNotes: async (updates) => {\n await models.$transaction(\n updates.map(({ id, projection }) =>\n models.note.update({\n where: { id },\n data: projection,\n }),\n ),\n );\n },\n});\n\nexport const backfillStaleNoteSearchText = async (limit?: number) => {\n return defaultNoteSearchBackfillService.backfillAll(limit);\n};\n\nexport const runNoteSearchBackfillInBackground = (limit?: number) => {\n if (activeNoteSearchBackfillPromise) {\n return activeNoteSearchBackfillPromise;\n }\n\n activeNoteSearchBackfillPromise = defaultNoteSearchBackfillService.backfillAll(limit).finally(() => {\n activeNoteSearchBackfillPromise = null;\n });\n\n return activeNoteSearchBackfillPromise;\n};\n"],"mappings":"AAAA,OAAO,YAAY;AACnB,SAAS,2BAA2B,uCAAkE;AAatG,MAAM,8BAA8B;AACpC,IAAI,kCAA0D;AAE9D,MAAM,mBAAmB,YAAY;AACjC,QAAM,IAAI,QAAc,CAAC,YAAY;AACjC,eAAW,SAAS,CAAC;AAAA,EACzB,CAAC;AACL;AAEO,MAAM,kCAAkC,CAAC,SAAiC;AAC7E,QAAM,gBAAgB,OAAO,QAAQ,gCAAgC;AACjE,UAAM,aAAa,MAAM,KAAK,eAAe,KAAK;AAElD,QAAI,WAAW,WAAW,GAAG;AACzB,aAAO;AAAA,IACX;AAEA,UAAM,KAAK;AAAA,MACP,WAAW,IAAI,CAAC,UAAU;AAAA,QACtB,IAAI,KAAK;AAAA,QACT,YAAY,0BAA0B,IAAI;AAAA,MAC9C,EAAE;AAAA,IACN;AAEA,WAAO,WAAW;AAAA,EACtB;AAEA,QAAM,cAAc,OAAO,QAAQ,gCAAgC;AAC/D,QAAI,QAAQ;AAEZ,WAAO,MAAM;AACT,YAAM,QAAQ,MAAM,cAAc,KAAK;AAEvC,UAAI,CAAC,OAAO;AACR,eAAO;AAAA,MACX;AAEA,eAAS;AAET,UAAI,QAAQ,OAAO;AACf,eAAO;AAAA,MACX;AAEA,YAAM,iBAAiB;AAAA,IAC3B;AAAA,EACJ;AAEA,SAAO;AAAA,IACH;AAAA,IACA;AAAA,EACJ;AACJ;AAEA,MAAM,mCAAmC,gCAAgC;AAAA,EACrE,gBAAgB,OAAO,UAAU;AAC7B,WAAO,OAAO,KAAK,SAAS;AAAA,MACxB,OAAO,EAAE,uBAAuB,EAAE,KAAK,gCAAgC,EAAE;AAAA,MACzE,SAAS,EAAE,IAAI,MAAM;AAAA,MACrB,MAAM;AAAA,MACN,QAAQ;AAAA,QACJ,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,SAAS;AAAA,MACb;AAAA,IACJ,CAAC;AAAA,EACL;AAAA,EACA,aAAa,OAAO,YAAY;AAC5B,UAAM,OAAO;AAAA,MACT,QAAQ;AAAA,QAAI,CAAC,EAAE,IAAI,WAAW,MAC1B,OAAO,KAAK,OAAO;AAAA,UACf,OAAO,EAAE,GAAG;AAAA,UACZ,MAAM;AAAA,QACV,CAAC;AAAA,MACL;AAAA,IACJ;AAAA,EACJ;AACJ,CAAC;AAEM,MAAM,8BAA8B,OAAO,UAAmB;AACjE,SAAO,iCAAiC,YAAY,KAAK;AAC7D;AAEO,MAAM,oCAAoC,CAAC,UAAmB;AACjE,MAAI,iCAAiC;AACjC,WAAO;AAAA,EACX;AAEA,oCAAkC,iCAAiC,YAAY,KAAK,EAAE,QAAQ,MAAM;AAChG,sCAAkC;AAAA,EACtC,CAAC;AAED,SAAO;AACX;","names":[]}
|