ocean-brain 0.4.2 → 0.6.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 +660 -31
- package/package.json +2 -1
- package/server/client/dist/assets/{Calendar-DI0PhQTq.js → Calendar-BytW4PeT.js} +1 -1
- package/server/client/dist/assets/Callout-9sJgGNRb.js +1 -0
- package/server/client/dist/assets/Graph-o5u-NzrA.js +1 -0
- package/server/client/dist/assets/{Image-DlJ4MDo3.js → Image-DxWhlIDG.js} +1 -1
- package/server/client/dist/assets/{Image.es-COwqkQuh.js → Image.es-B0LL3pb7.js} +1 -1
- package/server/client/dist/assets/Note-CYoYzZNu.js +21 -0
- package/server/client/dist/assets/{Plus.es-KeV96R_e.js → Plus.es-DpxzgRtY.js} +1 -1
- package/server/client/dist/assets/Reminders-DAepTZg1.js +1 -0
- package/server/client/dist/assets/Search-6KH7An44.js +1 -0
- package/server/client/dist/assets/{SurfaceCard-CHjcCuqZ.js → SurfaceCard-EMs50xSF.js} +1 -1
- package/server/client/dist/assets/Tag-C89j8x56.js +1 -0
- package/server/client/dist/assets/TagNotes-BTbu-f6v.js +1 -0
- package/server/client/dist/assets/{Trash.es-BqRTZPrI.js → Trash.es-C5x35NKF.js} +1 -1
- package/server/client/dist/assets/ViewNotes-DPEuki3y.js +1 -0
- package/server/client/dist/assets/Views-BGkuNhVd.js +1 -0
- package/server/client/dist/assets/{image.api-CMdQbCyN.js → image.api-DRhRQ0Gf.js} +1 -1
- package/server/client/dist/assets/{index-D2gRB1ig.css → index-Cd81UxEh.css} +1 -1
- package/server/client/dist/assets/index-DYlYUrjH.js +327 -0
- package/server/client/dist/assets/index-DswSFFMb.js +1 -0
- package/server/client/dist/assets/manage-image-C6sHhWnI.js +1 -0
- package/server/client/dist/assets/manage-image-detail-CJkfybR9.js +1 -0
- package/server/client/dist/assets/{mcp-DqGLZv90.js → mcp-piouFDNm.js} +1 -1
- package/server/client/dist/assets/note-core-Z6kqulGB.js +180 -0
- package/server/client/dist/assets/note-vendor-i1M2FSI2.js +54 -0
- package/server/client/dist/assets/{placeholder-BXLtvdtN.js → placeholder-DGz3CHej.js} +4 -4
- package/server/client/dist/assets/properties-CM4khczg.js +1 -0
- package/server/client/dist/assets/trash-C2lItz-b.js +1 -0
- package/server/client/dist/assets/{useReminderMutate-v1EXqM90.js → useReminderMutate-DHjDDHKg.js} +1 -1
- package/server/client/dist/assets/view-dashboard-C17WsdL7.js +99 -0
- package/server/client/dist/index.html +4 -4
- package/server/dist/features/note/graphql/note.field.resolver.js +16 -1
- package/server/dist/features/note/graphql/note.field.resolver.js.map +1 -1
- package/server/dist/features/note/graphql/note.mutation.resolver.js +192 -36
- package/server/dist/features/note/graphql/note.mutation.resolver.js.map +1 -1
- package/server/dist/features/note/graphql/note.query.resolver.js +88 -76
- package/server/dist/features/note/graphql/note.query.resolver.js.map +1 -1
- package/server/dist/features/note/graphql/note.resolver.js +3 -2
- package/server/dist/features/note/graphql/note.resolver.js.map +1 -1
- package/server/dist/features/note/graphql/note.type-defs.js +104 -1
- package/server/dist/features/note/graphql/note.type-defs.js.map +1 -1
- package/server/dist/features/note/http/mcp.js +394 -1
- package/server/dist/features/note/http/mcp.js.map +1 -1
- package/server/dist/features/note/services/cleanup.js +4 -3
- package/server/dist/features/note/services/cleanup.js.map +1 -1
- package/server/dist/features/note/services/content-blocks.js +113 -0
- package/server/dist/features/note/services/content-blocks.js.map +1 -0
- package/server/dist/features/note/services/markdown-intent-write.js +478 -0
- package/server/dist/features/note/services/markdown-intent-write.js.map +1 -0
- package/server/dist/features/note/services/markdown-patch.js +601 -0
- package/server/dist/features/note/services/markdown-patch.js.map +1 -0
- package/server/dist/features/note/services/properties.js +874 -0
- package/server/dist/features/note/services/properties.js.map +1 -0
- package/server/dist/features/note/services/search.js +10 -29
- package/server/dist/features/note/services/search.js.map +1 -1
- package/server/dist/features/note/services/snapshot.js +260 -18
- package/server/dist/features/note/services/snapshot.js.map +1 -1
- package/server/dist/features/note/services/trash.js +70 -2
- package/server/dist/features/note/services/trash.js.map +1 -1
- package/server/dist/features/note/services/write-conflict.js +87 -0
- package/server/dist/features/note/services/write-conflict.js.map +1 -0
- package/server/dist/features/note/services/write.js +121 -0
- package/server/dist/features/note/services/write.js.map +1 -0
- package/server/dist/features/view/graphql/view.query.resolver.js +31 -1
- package/server/dist/features/view/graphql/view.query.resolver.js.map +1 -1
- package/server/dist/features/view/graphql/view.type-defs.js +57 -1
- package/server/dist/features/view/graphql/view.type-defs.js.map +1 -1
- package/server/dist/features/view/services/workspace.js +367 -43
- package/server/dist/features/view/services/workspace.js.map +1 -1
- package/server/dist/modules/blocknote-tree.js +41 -0
- package/server/dist/modules/blocknote-tree.js.map +1 -0
- package/server/dist/modules/blocknote.js +47 -2
- package/server/dist/modules/blocknote.js.map +1 -1
- package/server/dist/modules/error-handler.js +7 -4
- package/server/dist/modules/error-handler.js.map +1 -1
- package/server/dist/modules/recovery-retention.js +2 -2
- package/server/dist/modules/recovery-retention.js.map +1 -1
- package/server/dist/routes/mcp.js +21 -1
- package/server/dist/routes/mcp.js.map +1 -1
- package/server/prisma/migrations/20260527073000_0017_note_properties/migration.sql +105 -0
- package/server/prisma/migrations/20260531073000_0018_view_query/migration.sql +3 -0
- package/server/prisma/schema.prisma +226 -130
- package/server/client/dist/assets/Callout-BAqbXI0L.js +0 -1
- package/server/client/dist/assets/Graph-Dhuog4tc.js +0 -1
- package/server/client/dist/assets/Note-ChUWU3Su.js +0 -21
- package/server/client/dist/assets/Reminders-D7MCTNxb.js +0 -1
- package/server/client/dist/assets/Search-B-fwTr5y.js +0 -1
- package/server/client/dist/assets/Tag-BbV2z-Db.js +0 -1
- package/server/client/dist/assets/TagNotes-2HKF09Jm.js +0 -1
- package/server/client/dist/assets/ViewNotes-TfM3Uzbp.js +0 -1
- package/server/client/dist/assets/Views-CUwtnfa8.js +0 -1
- package/server/client/dist/assets/index-B4l2UvuC.js +0 -232
- package/server/client/dist/assets/index-zhxZjg7V.js +0 -1
- package/server/client/dist/assets/manage-image-DdXb5Bkj.js +0 -1
- package/server/client/dist/assets/manage-image-detail-CrrXCQIX.js +0 -1
- package/server/client/dist/assets/note-core-GgFcksAF.js +0 -178
- package/server/client/dist/assets/note-vendor-B0t5F04o.js +0 -54
- package/server/client/dist/assets/trash-3eUAjFRh.js +0 -1
- package/server/client/dist/assets/view-dashboard-T6gmohr-.js +0 -90
- package/server/dist/features/note/graphql/note.graphql.shared.js +0 -23
- package/server/dist/features/note/graphql/note.graphql.shared.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/features/note/services/snapshot.ts"],"sourcesContent":["import models, { type NoteLayout } from '~/models.js';\nimport {\n createRetentionCutoff,\n RECOVERY_CLEANUP_BATCH_LIMIT,\n SNAPSHOT_MAX_PER_NOTE,\n SNAPSHOT_RETENTION_DAYS,\n} from '~/modules/recovery-retention.js';\nimport { buildNoteSearchProjection } from './search.js';\n\ninterface NoteRecord {\n id: number;\n title: string;\n content: string;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n updatedAt?: Date;\n}\n\ninterface NoteSnapshotRecord {\n id: number;\n noteId: number;\n title: string;\n payload: string;\n editSessionId: string | null;\n meta: string | null;\n createdAt: Date;\n}\n\ninterface NoteSnapshotDeps {\n findNoteById: (id: number) => Promise<NoteRecord | null>;\n findSnapshotByEditSessionId: (noteId: number, editSessionId: string) => Promise<NoteSnapshotRecord | null>;\n findLatestSnapshot: (noteId: number) => Promise<NoteSnapshotRecord | null>;\n createSnapshot: (input: {\n noteId: number;\n title: string;\n payload: string;\n editSessionId?: string;\n meta?: string;\n }) => Promise<NoteSnapshotRecord>;\n listSnapshots: (noteId: number, limit: number) => Promise<NoteSnapshotRecord[]>;\n findSnapshotById: (id: number) => Promise<NoteSnapshotRecord | null>;\n purgeExpiredSnapshots: (before: Date, limit: number) => Promise<number>;\n trimOverflowSnapshots: (noteId: number, keep: number, limit: number) => Promise<number>;\n updateNote: (\n id: number,\n input: {\n title: string;\n content: string;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n },\n ) => Promise<NoteRecord>;\n}\n\nexport interface NoteSnapshotMeta {\n entrypoint?: 'web' | 'mobile' | 'mcp';\n label?: string;\n}\n\ninterface NoteSnapshotPayload {\n title: string;\n content: string;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n}\n\nexport interface NoteSnapshotSummary {\n id: string;\n title: string;\n createdAt: string;\n meta: NoteSnapshotMeta;\n}\n\nconst parseMeta = (value?: string | null): NoteSnapshotMeta => {\n if (!value) {\n return {};\n }\n\n try {\n const parsed = JSON.parse(value) as NoteSnapshotMeta;\n return {\n ...(parsed.entrypoint ? { entrypoint: parsed.entrypoint } : {}),\n ...(parsed.label ? { label: parsed.label } : {}),\n };\n } catch {\n return {};\n }\n};\n\nconst serializeSnapshot = (snapshot: NoteSnapshotRecord): NoteSnapshotSummary => ({\n id: String(snapshot.id),\n title: snapshot.title,\n createdAt: snapshot.createdAt.toISOString(),\n meta: parseMeta(snapshot.meta),\n});\n\nconst serializePayload = (note: NoteRecord): string => {\n const payload: NoteSnapshotPayload = {\n title: note.title,\n content: note.content,\n pinned: note.pinned,\n order: note.order,\n layout: note.layout,\n };\n\n return JSON.stringify(payload);\n};\n\nconst hasSameSnapshotPayload = (snapshot: NoteSnapshotRecord | null, payload: string) => {\n return snapshot?.payload === payload;\n};\n\nconst parsePayload = (payload: string): NoteSnapshotPayload => {\n const parsed = JSON.parse(payload) as Partial<NoteSnapshotPayload>;\n\n if (\n typeof parsed.title !== 'string' ||\n typeof parsed.content !== 'string' ||\n typeof parsed.pinned !== 'boolean' ||\n typeof parsed.order !== 'number' ||\n (parsed.layout !== 'narrow' && parsed.layout !== 'wide' && parsed.layout !== 'full')\n ) {\n throw new Error('INVALID_NOTE_SNAPSHOT_PAYLOAD');\n }\n\n return parsed as NoteSnapshotPayload;\n};\n\nexport const createSnapshotMetaFromUserAgent = (userAgent?: string | null): string | undefined => {\n const normalized = userAgent?.toLowerCase() ?? '';\n const isMobile = /iphone|ipad|android|mobile/.test(normalized);\n\n return JSON.stringify({\n entrypoint: isMobile ? 'mobile' : 'web',\n label: isMobile ? 'Mobile browser' : 'Web browser',\n } satisfies NoteSnapshotMeta);\n};\n\nexport const MCP_SNAPSHOT_META = JSON.stringify({\n entrypoint: 'mcp',\n label: 'MCP',\n} satisfies NoteSnapshotMeta);\n\nconst defaultPurgeExpiredSnapshots = async (before: Date, limit: number) => {\n const expiredSnapshots = await models.noteSnapshot.findMany({\n where: { createdAt: { lt: before } },\n orderBy: { createdAt: 'asc' },\n take: limit,\n select: { id: true },\n });\n\n if (expiredSnapshots.length === 0) {\n return 0;\n }\n\n const deleted = await models.noteSnapshot.deleteMany({\n where: { id: { in: expiredSnapshots.map((snapshot) => snapshot.id) } },\n });\n\n return deleted.count;\n};\n\nconst defaultTrimOverflowSnapshots = async (noteId: number, keep: number, limit: number) => {\n const overflowSnapshots = await models.noteSnapshot.findMany({\n where: { noteId },\n orderBy: [{ createdAt: 'desc' }, { id: 'desc' }],\n skip: keep,\n take: limit,\n select: { id: true },\n });\n\n if (overflowSnapshots.length === 0) {\n return 0;\n }\n\n const deleted = await models.noteSnapshot.deleteMany({\n where: { id: { in: overflowSnapshots.map((snapshot) => snapshot.id) } },\n });\n\n return deleted.count;\n};\n\nexport const createNoteSnapshotService = (deps: NoteSnapshotDeps) => ({\n captureBaseline: async (input: { noteId: number; editSessionId?: string; meta?: string }) => {\n await deps.purgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n\n const existing = input.editSessionId\n ? await deps.findSnapshotByEditSessionId(input.noteId, input.editSessionId)\n : null;\n\n if (existing) {\n return serializeSnapshot(existing);\n }\n\n const note = await deps.findNoteById(input.noteId);\n\n if (!note) {\n return null;\n }\n\n const payload = serializePayload(note);\n const latestSnapshot = await deps.findLatestSnapshot(note.id);\n\n if (latestSnapshot && hasSameSnapshotPayload(latestSnapshot, payload)) {\n return serializeSnapshot(latestSnapshot);\n }\n\n const snapshot = await deps.createSnapshot({\n noteId: note.id,\n title: note.title,\n payload,\n ...(input.editSessionId ? { editSessionId: input.editSessionId } : {}),\n ...(input.meta ? { meta: input.meta } : {}),\n });\n\n await deps.trimOverflowSnapshots(note.id, SNAPSHOT_MAX_PER_NOTE, RECOVERY_CLEANUP_BATCH_LIMIT);\n\n return serializeSnapshot(snapshot);\n },\n\n listSnapshots: async (noteId: number, limit = 5) => {\n await deps.purgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n await deps.trimOverflowSnapshots(noteId, SNAPSHOT_MAX_PER_NOTE, RECOVERY_CLEANUP_BATCH_LIMIT);\n const snapshots = await deps.listSnapshots(noteId, limit);\n return snapshots.map(serializeSnapshot);\n },\n\n restoreSnapshot: async (snapshotId: number, options?: { meta?: string }) => {\n await deps.purgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n\n const snapshot = await deps.findSnapshotById(snapshotId);\n\n if (!snapshot) {\n return null;\n }\n\n const note = await deps.findNoteById(snapshot.noteId);\n\n if (!note) {\n return null;\n }\n\n const currentPayload = serializePayload(note);\n const latestSnapshot = await deps.findLatestSnapshot(note.id);\n\n if (!hasSameSnapshotPayload(latestSnapshot, currentPayload)) {\n await deps.createSnapshot({\n noteId: note.id,\n title: note.title,\n payload: currentPayload,\n ...(options?.meta ? { meta: options.meta } : {}),\n });\n }\n\n await deps.trimOverflowSnapshots(note.id, SNAPSHOT_MAX_PER_NOTE, RECOVERY_CLEANUP_BATCH_LIMIT);\n\n const payload = parsePayload(snapshot.payload);\n\n return deps.updateNote(snapshot.noteId, payload);\n },\n});\n\nexport const defaultNoteSnapshotService = createNoteSnapshotService({\n findNoteById: async (id) => {\n return models.note.findUnique({ where: { id } });\n },\n findSnapshotByEditSessionId: async (noteId, editSessionId) => {\n return models.noteSnapshot.findFirst({\n where: {\n noteId,\n editSessionId,\n },\n });\n },\n findLatestSnapshot: async (noteId) => {\n return models.noteSnapshot.findFirst({\n where: { noteId },\n orderBy: [{ createdAt: 'desc' }, { id: 'desc' }],\n });\n },\n createSnapshot: async (input) => {\n return models.noteSnapshot.create({\n data: {\n noteId: input.noteId,\n title: input.title,\n payload: input.payload,\n ...(input.editSessionId ? { editSessionId: input.editSessionId } : {}),\n ...(input.meta ? { meta: input.meta } : {}),\n },\n });\n },\n purgeExpiredSnapshots: defaultPurgeExpiredSnapshots,\n trimOverflowSnapshots: defaultTrimOverflowSnapshots,\n listSnapshots: async (noteId, limit) => {\n return models.noteSnapshot.findMany({\n where: { noteId },\n orderBy: [{ createdAt: 'desc' }, { id: 'desc' }],\n take: limit,\n });\n },\n findSnapshotById: async (id) => {\n return models.noteSnapshot.findUnique({ where: { id } });\n },\n updateNote: async (id, input) => {\n return models.note.update({\n where: { id },\n data: {\n ...input,\n ...buildNoteSearchProjection({\n title: input.title,\n content: input.content,\n }),\n },\n });\n },\n});\n\nexport const captureNoteBaseline = async (input: { noteId: number; editSessionId?: string; meta?: string }) => {\n return defaultNoteSnapshotService.captureBaseline(input);\n};\n\nexport const listNoteSnapshots = async (noteId: number, limit?: number) => {\n return defaultNoteSnapshotService.listSnapshots(noteId, limit);\n};\n\nexport const restoreNoteSnapshot = async (snapshotId: number, options?: { meta?: string }) => {\n return defaultNoteSnapshotService.restoreSnapshot(snapshotId, options);\n};\n\nexport const purgeExpiredNoteSnapshots = async () => {\n return defaultPurgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n};\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/features/note/services/snapshot.ts"],"sourcesContent":["import { ensureTagByName } from '~/features/tag/services/organization.js';\nimport models, { type NoteLayout, type PropertyValueType } from '~/models.js';\nimport { blocksToMarkdown, extractTagIdsFromContentJson } from '~/modules/blocknote.js';\nimport { type BlockNoteTreeNode, parseBlockNoteContent, walkBlockNoteTree } from '~/modules/blocknote-tree.js';\nimport {\n createRetentionCutoff,\n RECOVERY_CLEANUP_BATCH_LIMIT,\n SNAPSHOT_MAX_PER_NOTE,\n SNAPSHOT_RETENTION_DAYS,\n} from '~/modules/recovery-retention.js';\nimport { buildNoteSearchProjection, extractVisibleSearchTextFromContent } from './search.js';\n\nconst SNAPSHOT_CONTENT_PREVIEW_MAX_LENGTH = 240;\n\ninterface NoteRecord {\n id: number;\n title: string;\n content: string;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n updatedAt?: Date;\n properties?: SnapshotNoteProperty[];\n}\n\ninterface SnapshotNoteProperty {\n key: string;\n name: string;\n valueType: PropertyValueType;\n textValue?: string | null;\n textValueNormalized?: string | null;\n numberValue?: number | null;\n dateValue?: Date | string | null;\n boolValue?: boolean | null;\n optionValue?: string | null;\n optionLabel?: string | null;\n optionColor?: string | null;\n}\n\ninterface NoteSnapshotRecord {\n id: number;\n noteId: number;\n title: string;\n payload: string;\n editSessionId: string | null;\n meta: string | null;\n createdAt: Date;\n}\n\ninterface NoteSnapshotDeps {\n findNoteById: (id: number) => Promise<NoteRecord | null>;\n findSnapshotByEditSessionId: (noteId: number, editSessionId: string) => Promise<NoteSnapshotRecord | null>;\n findLatestSnapshot: (noteId: number) => Promise<NoteSnapshotRecord | null>;\n createSnapshot: (input: {\n noteId: number;\n title: string;\n payload: string;\n editSessionId?: string;\n meta?: string;\n }) => Promise<NoteSnapshotRecord>;\n listSnapshots: (noteId: number, limit: number) => Promise<NoteSnapshotRecord[]>;\n findSnapshotById: (id: number) => Promise<NoteSnapshotRecord | null>;\n purgeExpiredSnapshots: (before: Date, limit: number) => Promise<number>;\n trimOverflowSnapshots: (noteId: number, keep: number, limit: number) => Promise<number>;\n resolveRestoredTags?: (content: string) => Promise<ResolvedRestoredSnapshotTags | null>;\n updateNote: (\n id: number,\n input: {\n title: string;\n content: string;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n tagIds?: number[];\n properties?: SnapshotNoteProperty[];\n },\n ) => Promise<NoteRecord>;\n}\n\nexport interface NoteSnapshotMeta {\n entrypoint?: 'web' | 'mobile' | 'mcp';\n label?: string;\n}\n\ninterface NoteSnapshotPayload {\n title: string;\n content: string;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n properties?: SnapshotNoteProperty[];\n}\n\ninterface TagRecord {\n id: number;\n name: string;\n}\n\ninterface ResolvedRestoredSnapshotTags {\n content: string;\n tagIds: number[];\n}\n\ninterface ResolveRestoredSnapshotTagsDeps {\n ensureTagByName: (name: string) => Promise<TagRecord>;\n findTagsByIds: (ids: number[]) => Promise<TagRecord[]>;\n}\n\nexport interface NoteSnapshotSummary {\n id: string;\n title: string;\n createdAt: string;\n meta: NoteSnapshotMeta;\n contentPreview: string;\n contentAsMarkdown?: string;\n payload?: string;\n}\n\nconst parseMeta = (value?: string | null): NoteSnapshotMeta => {\n if (!value) {\n return {};\n }\n\n try {\n const parsed = JSON.parse(value) as NoteSnapshotMeta;\n return {\n ...(parsed.entrypoint ? { entrypoint: parsed.entrypoint } : {}),\n ...(parsed.label ? { label: parsed.label } : {}),\n };\n } catch {\n return {};\n }\n};\n\nconst readSnapshotContent = (payload: string) => {\n try {\n return parsePayload(payload).content;\n } catch {\n return '';\n }\n};\n\nconst buildSnapshotContentPreview = (content: string) => {\n const preview = extractVisibleSearchTextFromContent(content);\n\n if (preview.length <= SNAPSHOT_CONTENT_PREVIEW_MAX_LENGTH) {\n return preview;\n }\n\n return `${preview.slice(0, SNAPSHOT_CONTENT_PREVIEW_MAX_LENGTH).trimEnd()}...`;\n};\n\nconst serializeSnapshot = (snapshot: NoteSnapshotRecord): NoteSnapshotSummary => ({\n id: String(snapshot.id),\n title: snapshot.title,\n contentPreview: buildSnapshotContentPreview(readSnapshotContent(snapshot.payload)),\n createdAt: snapshot.createdAt.toISOString(),\n meta: parseMeta(snapshot.meta),\n payload: snapshot.payload,\n});\n\nconst serializePayload = (note: NoteRecord): string => {\n const payload: NoteSnapshotPayload = {\n title: note.title,\n content: note.content,\n pinned: note.pinned,\n order: note.order,\n layout: note.layout,\n ...(note.properties\n ? {\n properties: note.properties.map((property) => ({\n ...property,\n ...(property.dateValue instanceof Date ? { dateValue: property.dateValue.toISOString() } : {}),\n })),\n }\n : {}),\n };\n\n return JSON.stringify(payload);\n};\n\nconst hasSameSnapshotPayload = (snapshot: NoteSnapshotRecord | null, payload: string) => {\n return snapshot?.payload === payload;\n};\n\nconst parsePayload = (payload: string): NoteSnapshotPayload => {\n const parsed = JSON.parse(payload) as Partial<NoteSnapshotPayload>;\n\n if (\n typeof parsed.title !== 'string' ||\n typeof parsed.content !== 'string' ||\n typeof parsed.pinned !== 'boolean' ||\n typeof parsed.order !== 'number' ||\n (parsed.layout !== 'narrow' && parsed.layout !== 'wide' && parsed.layout !== 'full')\n ) {\n throw new Error('INVALID_NOTE_SNAPSHOT_PAYLOAD');\n }\n\n return parsed as NoteSnapshotPayload;\n};\n\nconst extractRestoredTagIds = (content: string): number[] | null => {\n try {\n return extractTagIdsFromContentJson(content)\n .map(normalizeRestoredTagId)\n .filter((id): id is number => id !== null);\n } catch {\n return null;\n }\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> => {\n return typeof value === 'object' && value !== null;\n};\n\nconst normalizeRestoredTagId = (value: unknown) => {\n if (value === undefined || value === null) {\n return null;\n }\n\n const normalized = typeof value === 'string' ? value.trim() : value;\n\n if (normalized === '') {\n return null;\n }\n\n const numericId = Number(normalized);\n return Number.isSafeInteger(numericId) && numericId > 0 ? numericId : null;\n};\n\nconst normalizeRestoredTagName = (value: unknown) => {\n if (typeof value !== 'string') {\n return null;\n }\n\n const normalized = value.trim();\n return normalized ? normalized : null;\n};\n\nexport const resolveRestoredSnapshotTags = async (\n content: string,\n deps: ResolveRestoredSnapshotTagsDeps,\n): Promise<ResolvedRestoredSnapshotTags | null> => {\n const parsed = parseBlockNoteContent(content);\n\n if (!parsed) {\n return null;\n }\n\n const tagRefs: Array<{\n id: number | null;\n name: string | null;\n node: BlockNoteTreeNode;\n props: Record<string, unknown>;\n }> = [];\n\n walkBlockNoteTree(parsed, (node) => {\n if (node.type !== 'tag' || !isRecord(node.props)) {\n return;\n }\n\n tagRefs.push({\n id: normalizeRestoredTagId(node.props.id),\n name: normalizeRestoredTagName(node.props.tag),\n node,\n props: node.props,\n });\n });\n\n if (tagRefs.length === 0) {\n return { content, tagIds: [] };\n }\n\n const candidateIds = Array.from(new Set(tagRefs.map((tag) => tag.id).filter((id): id is number => id !== null)));\n const existingTags = candidateIds.length > 0 ? await deps.findTagsByIds(candidateIds) : [];\n const existingTagIds = new Set(existingTags.map((tag) => tag.id));\n const tagIds = new Set<number>();\n let contentChanged = false;\n\n for (const tagRef of tagRefs) {\n if (tagRef.id !== null && existingTagIds.has(tagRef.id)) {\n tagIds.add(tagRef.id);\n continue;\n }\n\n if (!tagRef.name) {\n continue;\n }\n\n try {\n const resolvedTag = await deps.ensureTagByName(tagRef.name);\n tagRef.node.props = {\n ...tagRef.props,\n id: String(resolvedTag.id),\n tag: resolvedTag.name,\n };\n tagIds.add(resolvedTag.id);\n contentChanged = true;\n } catch {\n continue;\n }\n }\n\n return {\n content: contentChanged ? JSON.stringify(parsed) : content,\n tagIds: [...tagIds],\n };\n};\n\nexport const renderNoteSnapshotContentAsMarkdown = async (payload: string) => {\n try {\n const snapshotPayload = parsePayload(payload);\n return blocksToMarkdown(snapshotPayload.content);\n } catch {\n return '';\n }\n};\n\nconst serializeSnapshotWithContent = async (snapshot: NoteSnapshotRecord): Promise<NoteSnapshotSummary> => {\n const contentAsMarkdown = await renderNoteSnapshotContentAsMarkdown(snapshot.payload);\n\n return {\n ...serializeSnapshot(snapshot),\n contentAsMarkdown,\n };\n};\n\nexport const createSnapshotMetaFromUserAgent = (userAgent?: string | null): string | undefined => {\n const normalized = userAgent?.toLowerCase() ?? '';\n const isMobile = /iphone|ipad|android|mobile/.test(normalized);\n\n return JSON.stringify({\n entrypoint: isMobile ? 'mobile' : 'web',\n label: isMobile ? 'Mobile browser' : 'Web browser',\n } satisfies NoteSnapshotMeta);\n};\n\nexport const MCP_SNAPSHOT_META = JSON.stringify({\n entrypoint: 'mcp',\n label: 'MCP',\n} satisfies NoteSnapshotMeta);\n\nconst defaultPurgeExpiredSnapshots = async (before: Date, limit: number) => {\n const expiredSnapshots = await models.noteSnapshot.findMany({\n where: { createdAt: { lt: before } },\n orderBy: { createdAt: 'asc' },\n take: limit,\n select: { id: true },\n });\n\n if (expiredSnapshots.length === 0) {\n return 0;\n }\n\n const deleted = await models.noteSnapshot.deleteMany({\n where: { id: { in: expiredSnapshots.map((snapshot) => snapshot.id) } },\n });\n\n return deleted.count;\n};\n\nconst defaultTrimOverflowSnapshots = async (noteId: number, keep: number, limit: number) => {\n const overflowSnapshots = await models.noteSnapshot.findMany({\n where: { noteId },\n orderBy: [{ createdAt: 'desc' }, { id: 'desc' }],\n skip: keep,\n take: limit,\n select: { id: true },\n });\n\n if (overflowSnapshots.length === 0) {\n return 0;\n }\n\n const deleted = await models.noteSnapshot.deleteMany({\n where: { id: { in: overflowSnapshots.map((snapshot) => snapshot.id) } },\n });\n\n return deleted.count;\n};\n\nexport const createNoteSnapshotService = (deps: NoteSnapshotDeps) => ({\n captureBaseline: async (input: {\n noteId: number;\n editSessionId?: string;\n meta?: string;\n baseline?: NoteRecord;\n force?: boolean;\n }) => {\n await deps.purgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n\n const existing =\n input.editSessionId && !input.force\n ? await deps.findSnapshotByEditSessionId(input.noteId, input.editSessionId)\n : null;\n\n if (existing) {\n return serializeSnapshot(existing);\n }\n\n const note = input.baseline ?? (await deps.findNoteById(input.noteId));\n\n if (!note) {\n return null;\n }\n\n const payload = serializePayload(note);\n const latestSnapshot = await deps.findLatestSnapshot(note.id);\n\n if (latestSnapshot && hasSameSnapshotPayload(latestSnapshot, payload)) {\n return serializeSnapshot(latestSnapshot);\n }\n\n const snapshot = await deps.createSnapshot({\n noteId: note.id,\n title: note.title,\n payload,\n ...(input.editSessionId && !input.force ? { editSessionId: input.editSessionId } : {}),\n ...(input.meta ? { meta: input.meta } : {}),\n });\n\n await deps.trimOverflowSnapshots(note.id, SNAPSHOT_MAX_PER_NOTE, RECOVERY_CLEANUP_BATCH_LIMIT);\n\n return serializeSnapshot(snapshot);\n },\n\n listSnapshots: async (noteId: number, limit = SNAPSHOT_MAX_PER_NOTE) => {\n await deps.purgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n await deps.trimOverflowSnapshots(noteId, SNAPSHOT_MAX_PER_NOTE, RECOVERY_CLEANUP_BATCH_LIMIT);\n const snapshots = await deps.listSnapshots(noteId, limit);\n return snapshots.map(serializeSnapshot);\n },\n\n getSnapshot: async (snapshotId: number) => {\n await deps.purgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n const snapshot = await deps.findSnapshotById(snapshotId);\n\n if (!snapshot) {\n return null;\n }\n\n return serializeSnapshotWithContent(snapshot);\n },\n\n restoreSnapshot: async (snapshotId: number, options?: { meta?: string }) => {\n await deps.purgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n\n const snapshot = await deps.findSnapshotById(snapshotId);\n\n if (!snapshot) {\n return null;\n }\n\n const note = await deps.findNoteById(snapshot.noteId);\n\n if (!note) {\n return null;\n }\n\n const payload = parsePayload(snapshot.payload);\n const resolvedTags = deps.resolveRestoredTags\n ? await deps.resolveRestoredTags(payload.content)\n : extractRestoredTagIds(payload.content);\n const restoredPayload =\n resolvedTags && !Array.isArray(resolvedTags)\n ? {\n ...payload,\n content: resolvedTags.content,\n }\n : payload;\n const restoredTagIds = Array.isArray(resolvedTags) ? resolvedTags : resolvedTags?.tagIds;\n\n const currentPayload = serializePayload(note);\n const latestSnapshot = await deps.findLatestSnapshot(note.id);\n\n if (!hasSameSnapshotPayload(latestSnapshot, currentPayload)) {\n await deps.createSnapshot({\n noteId: note.id,\n title: note.title,\n payload: currentPayload,\n ...(options?.meta ? { meta: options.meta } : {}),\n });\n }\n\n const restoredNote = await deps.updateNote(snapshot.noteId, {\n ...restoredPayload,\n ...(restoredTagIds !== undefined ? { tagIds: restoredTagIds } : {}),\n });\n\n await deps.trimOverflowSnapshots(note.id, SNAPSHOT_MAX_PER_NOTE, RECOVERY_CLEANUP_BATCH_LIMIT);\n\n return restoredNote;\n },\n});\n\nexport const defaultNoteSnapshotService = createNoteSnapshotService({\n findNoteById: async (id) => {\n const note = await models.note.findUnique({\n where: { id },\n include: {\n properties: {\n include: { definition: true, option: true },\n },\n },\n });\n\n if (!note) {\n return null;\n }\n\n return {\n ...note,\n properties: note.properties.map((property) => ({\n key: property.definition.key,\n name: property.definition.name,\n valueType: property.definition.valueType,\n textValue: property.textValue,\n textValueNormalized: property.textValueNormalized,\n numberValue: property.numberValue,\n dateValue: property.dateValue,\n boolValue: property.boolValue,\n optionValue: property.option?.value ?? null,\n optionLabel: property.option?.label ?? null,\n optionColor: property.option?.color ?? null,\n })),\n };\n },\n findSnapshotByEditSessionId: async (noteId, editSessionId) => {\n return models.noteSnapshot.findFirst({\n where: {\n noteId,\n editSessionId,\n },\n });\n },\n findLatestSnapshot: async (noteId) => {\n return models.noteSnapshot.findFirst({\n where: { noteId },\n orderBy: [{ createdAt: 'desc' }, { id: 'desc' }],\n });\n },\n createSnapshot: async (input) => {\n return models.noteSnapshot.create({\n data: {\n noteId: input.noteId,\n title: input.title,\n payload: input.payload,\n ...(input.editSessionId ? { editSessionId: input.editSessionId } : {}),\n ...(input.meta ? { meta: input.meta } : {}),\n },\n });\n },\n purgeExpiredSnapshots: defaultPurgeExpiredSnapshots,\n trimOverflowSnapshots: defaultTrimOverflowSnapshots,\n resolveRestoredTags: async (content) => {\n return resolveRestoredSnapshotTags(content, {\n ensureTagByName: async (name) => {\n const result = await ensureTagByName(name);\n return {\n id: Number(result.tag.id),\n name: result.tag.name,\n };\n },\n findTagsByIds: async (ids) => {\n return models.tag.findMany({\n where: { id: { in: ids } },\n select: {\n id: true,\n name: true,\n },\n });\n },\n });\n },\n listSnapshots: async (noteId, limit) => {\n return models.noteSnapshot.findMany({\n where: { noteId },\n orderBy: [{ createdAt: 'desc' }, { id: 'desc' }],\n take: limit,\n });\n },\n findSnapshotById: async (id) => {\n return models.noteSnapshot.findUnique({ where: { id } });\n },\n updateNote: async (id, input) => {\n const { tagIds, properties, ...noteInput } = input;\n\n return models.$transaction(async (tx) => {\n const note = await tx.note.update({\n where: { id },\n data: {\n ...noteInput,\n ...buildNoteSearchProjection({\n title: noteInput.title,\n content: noteInput.content,\n }),\n ...(tagIds !== undefined ? { tags: { set: tagIds.map((tagId) => ({ id: tagId })) } } : {}),\n },\n });\n\n if (properties !== undefined) {\n await tx.noteProperty.deleteMany({ where: { noteId: id } });\n\n for (const property of properties) {\n const definition = await tx.propertyDefinition.upsert({\n where: { key: property.key },\n create: {\n key: property.key,\n name: property.name,\n valueType: property.valueType,\n },\n update: {\n name: property.name,\n valueType: property.valueType,\n },\n });\n\n const option =\n property.valueType === 'select' && property.optionValue\n ? await tx.propertyOption.upsert({\n where: {\n propertyDefinitionId_value: {\n propertyDefinitionId: definition.id,\n value: property.optionValue,\n },\n },\n create: {\n propertyDefinitionId: definition.id,\n value: property.optionValue,\n label: property.optionLabel ?? property.optionValue,\n color: property.optionColor ?? null,\n },\n update: {\n label: property.optionLabel ?? property.optionValue,\n color: property.optionColor ?? null,\n },\n })\n : null;\n\n await tx.noteProperty.create({\n data: {\n noteId: id,\n propertyDefinitionId: definition.id,\n optionId: option?.id ?? null,\n textValue: property.textValue ?? null,\n textValueNormalized: property.textValueNormalized ?? null,\n numberValue: property.numberValue ?? null,\n dateValue: property.dateValue ? new Date(property.dateValue) : null,\n boolValue: property.boolValue ?? null,\n },\n });\n }\n }\n\n return note;\n });\n },\n});\n\nexport const captureNoteBaseline = async (input: {\n noteId: number;\n editSessionId?: string;\n meta?: string;\n baseline?: NoteRecord;\n force?: boolean;\n}) => {\n return defaultNoteSnapshotService.captureBaseline(input);\n};\n\nexport const listNoteSnapshots = async (noteId: number, limit?: number) => {\n return defaultNoteSnapshotService.listSnapshots(noteId, limit);\n};\n\nexport const getNoteSnapshot = async (snapshotId: number) => {\n return defaultNoteSnapshotService.getSnapshot(snapshotId);\n};\n\nexport const restoreNoteSnapshot = async (snapshotId: number, options?: { meta?: string }) => {\n return defaultNoteSnapshotService.restoreSnapshot(snapshotId, options);\n};\n\nexport const purgeExpiredNoteSnapshots = async () => {\n return defaultPurgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n};\n"],"mappings":"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","names":[]}
|
|
@@ -120,7 +120,8 @@ const createNoteTrashService = (deps) => ({
|
|
|
120
120
|
});
|
|
121
121
|
const includeDeletedNote = {
|
|
122
122
|
reminders: { orderBy: { reminderDate: "asc" } },
|
|
123
|
-
tags: { orderBy: { name: "asc" } }
|
|
123
|
+
tags: { orderBy: { name: "asc" } },
|
|
124
|
+
properties: { orderBy: { key: "asc" } }
|
|
124
125
|
};
|
|
125
126
|
const defaultPurgeExpiredDeletedNotes = async (before, limit) => {
|
|
126
127
|
const expiredNotes = await models.deletedNote.findMany({
|
|
@@ -148,7 +149,11 @@ const noteTrashService = createNoteTrashService({
|
|
|
148
149
|
where: { id },
|
|
149
150
|
include: {
|
|
150
151
|
reminders: { orderBy: { reminderDate: "asc" } },
|
|
151
|
-
tags: { orderBy: { name: "asc" } }
|
|
152
|
+
tags: { orderBy: { name: "asc" } },
|
|
153
|
+
properties: {
|
|
154
|
+
orderBy: { definition: { key: "asc" } },
|
|
155
|
+
include: { definition: true, option: true }
|
|
156
|
+
}
|
|
152
157
|
}
|
|
153
158
|
});
|
|
154
159
|
},
|
|
@@ -194,6 +199,23 @@ const noteTrashService = createNoteTrashService({
|
|
|
194
199
|
createdAt: reminder.createdAt,
|
|
195
200
|
updatedAt: reminder.updatedAt
|
|
196
201
|
}))
|
|
202
|
+
},
|
|
203
|
+
properties: {
|
|
204
|
+
create: (note.properties ?? []).map((property) => ({
|
|
205
|
+
key: property.definition.key,
|
|
206
|
+
name: property.definition.name,
|
|
207
|
+
valueType: property.definition.valueType,
|
|
208
|
+
textValue: property.textValue,
|
|
209
|
+
textValueNormalized: property.textValueNormalized,
|
|
210
|
+
numberValue: property.numberValue,
|
|
211
|
+
dateValue: property.dateValue,
|
|
212
|
+
boolValue: property.boolValue,
|
|
213
|
+
optionValue: property.option?.value ?? null,
|
|
214
|
+
optionLabel: property.option?.label ?? null,
|
|
215
|
+
optionColor: property.option?.color ?? null,
|
|
216
|
+
createdAt: property.createdAt,
|
|
217
|
+
updatedAt: property.updatedAt
|
|
218
|
+
}))
|
|
197
219
|
}
|
|
198
220
|
}
|
|
199
221
|
});
|
|
@@ -266,6 +288,52 @@ const noteTrashService = createNoteTrashService({
|
|
|
266
288
|
} : {}
|
|
267
289
|
}
|
|
268
290
|
});
|
|
291
|
+
for (const property of deletedNote.properties ?? []) {
|
|
292
|
+
const definition = await tx.propertyDefinition.upsert({
|
|
293
|
+
where: { key: property.key },
|
|
294
|
+
create: {
|
|
295
|
+
key: property.key,
|
|
296
|
+
name: property.name,
|
|
297
|
+
valueType: property.valueType
|
|
298
|
+
},
|
|
299
|
+
update: {
|
|
300
|
+
name: property.name,
|
|
301
|
+
valueType: property.valueType
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
const option = property.valueType === "select" && property.optionValue ? await tx.propertyOption.upsert({
|
|
305
|
+
where: {
|
|
306
|
+
propertyDefinitionId_value: {
|
|
307
|
+
propertyDefinitionId: definition.id,
|
|
308
|
+
value: property.optionValue
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
create: {
|
|
312
|
+
propertyDefinitionId: definition.id,
|
|
313
|
+
value: property.optionValue,
|
|
314
|
+
label: property.optionLabel ?? property.optionValue,
|
|
315
|
+
color: property.optionColor
|
|
316
|
+
},
|
|
317
|
+
update: {
|
|
318
|
+
label: property.optionLabel ?? property.optionValue,
|
|
319
|
+
color: property.optionColor
|
|
320
|
+
}
|
|
321
|
+
}) : null;
|
|
322
|
+
await tx.noteProperty.create({
|
|
323
|
+
data: {
|
|
324
|
+
noteId: note.id,
|
|
325
|
+
propertyDefinitionId: definition.id,
|
|
326
|
+
optionId: option?.id ?? null,
|
|
327
|
+
textValue: property.textValue,
|
|
328
|
+
textValueNormalized: property.textValueNormalized,
|
|
329
|
+
numberValue: property.numberValue,
|
|
330
|
+
dateValue: property.dateValue,
|
|
331
|
+
boolValue: property.boolValue,
|
|
332
|
+
createdAt: property.createdAt,
|
|
333
|
+
updatedAt: property.updatedAt
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
}
|
|
269
337
|
await tx.deletedNote.delete({ where: { id: deletedNote.id } });
|
|
270
338
|
return note;
|
|
271
339
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/features/note/services/trash.ts"],"sourcesContent":["import models, { type NoteLayout, type ReminderPriority } from '~/models.js';\nimport {\n createRetentionCutoff,\n RECOVERY_CLEANUP_BATCH_LIMIT,\n TRASH_RETENTION_DAYS,\n} from '~/modules/recovery-retention.js';\nimport { buildNoteSearchProjection, extractVisibleSearchTextFromContent } from './search.js';\n\nconst TRASHED_NOTE_CONTENT_PREVIEW_MAX_LENGTH = 240;\n\ninterface LiveTagRecord {\n id: number;\n name: string;\n}\n\ninterface LiveReminderRecord {\n id: number;\n reminderDate: Date;\n completed: boolean;\n priority: ReminderPriority;\n content: string | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\ninterface LiveNoteRecord {\n id: number;\n title: string;\n content: string;\n createdAt: Date;\n updatedAt: Date;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n tags: LiveTagRecord[];\n reminders: LiveReminderRecord[];\n}\n\ninterface RestoredNoteRecord {\n id: number;\n title: string;\n content: string;\n createdAt: Date;\n updatedAt: Date;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n}\n\ninterface DeletedTagRecord {\n name: string;\n}\n\ninterface DeletedReminderRecord {\n originalId: number | null;\n reminderDate: Date;\n completed: boolean;\n priority: ReminderPriority;\n content: string | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\ninterface DeletedNoteRecord {\n id: number;\n title: string;\n content: string;\n createdAt: Date;\n updatedAt: Date;\n deletedAt: Date;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n tags: DeletedTagRecord[];\n reminders: DeletedReminderRecord[];\n}\n\ninterface BlockNoteNode {\n type: string;\n props?: Record<string, unknown>;\n content?: BlockNoteNode[];\n children?: BlockNoteNode[];\n}\n\nexport interface TrashedNoteSummary {\n id: string;\n title: string;\n createdAt: string;\n updatedAt: string;\n deletedAt: string;\n contentPreview: string;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n tagNames: string[];\n}\n\nexport interface TrashedNotesResult {\n totalCount: number;\n notes: TrashedNoteSummary[];\n}\n\nexport interface TrashedNoteDetail extends TrashedNoteSummary {\n contentAsMarkdown: string;\n}\n\nexport class NoteRestoreConflictError extends Error {\n constructor(message = 'A live note with the same id already exists.') {\n super(message);\n this.name = 'NoteRestoreConflictError';\n }\n}\n\ninterface NoteTrashServiceDeps {\n countDeletedNotes: () => Promise<number>;\n findDeletedNote: (id: number) => Promise<DeletedNoteRecord | null>;\n findLiveNote: (id: number) => Promise<LiveNoteRecord | null>;\n listDeletedNotes: (skip: number, take: number) => Promise<DeletedNoteRecord[]>;\n liveNoteExists: (id: number) => Promise<boolean>;\n moveNoteToTrash: (note: LiveNoteRecord) => Promise<DeletedNoteRecord>;\n purgeDeletedNote: (note: DeletedNoteRecord) => Promise<void>;\n purgeExpiredDeletedNotes: (before: Date, limit: number) => Promise<number>;\n restoreDeletedNote: (note: DeletedNoteRecord) => Promise<RestoredNoteRecord>;\n}\n\nconst buildTrashedNoteContentPreview = (content: string) => {\n const text = extractVisibleSearchTextFromContent(content);\n\n if (text.length <= TRASHED_NOTE_CONTENT_PREVIEW_MAX_LENGTH) {\n return text;\n }\n\n return `${text.slice(0, TRASHED_NOTE_CONTENT_PREVIEW_MAX_LENGTH).trimEnd()}…`;\n};\n\nconst serializeTrashedNote = (note: DeletedNoteRecord): TrashedNoteSummary => {\n return {\n id: String(note.id),\n title: note.title,\n createdAt: note.createdAt.toISOString(),\n updatedAt: note.updatedAt.toISOString(),\n deletedAt: note.deletedAt.toISOString(),\n contentPreview: buildTrashedNoteContentPreview(note.content),\n pinned: note.pinned,\n order: note.order,\n layout: note.layout,\n tagNames: note.tags.map((tag) => tag.name),\n };\n};\n\nconst restoreTagIdsInContent = (content: string, tagIdByName: Map<string, number>) => {\n const rewriteNodes = (nodes: BlockNoteNode[]): BlockNoteNode[] => {\n return nodes.map((node) => ({\n ...node,\n content: node.content?.map((contentNode) => {\n if (contentNode.type !== 'tag') {\n return contentNode;\n }\n\n const tagName = typeof contentNode.props?.tag === 'string' ? contentNode.props.tag : null;\n const restoredTagId = tagName ? tagIdByName.get(tagName) : null;\n\n if (!restoredTagId) {\n return contentNode;\n }\n\n return {\n ...contentNode,\n props: {\n ...contentNode.props,\n id: String(restoredTagId),\n },\n };\n }),\n children: node.children ? rewriteNodes(node.children) : node.children,\n }));\n };\n\n try {\n const parsed = JSON.parse(content) as BlockNoteNode[];\n return JSON.stringify(rewriteNodes(parsed));\n } catch {\n return content;\n }\n};\n\nexport const createNoteTrashService = (deps: NoteTrashServiceDeps) => ({\n listTrashedNotes: async (input?: { limit?: number; offset?: number }): Promise<TrashedNotesResult> => {\n await deps.purgeExpiredDeletedNotes(createRetentionCutoff(TRASH_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n\n const limit = Math.max(1, Number(input?.limit ?? 25));\n const offset = Math.max(0, Number(input?.offset ?? 0));\n const [totalCount, notes] = await Promise.all([deps.countDeletedNotes(), deps.listDeletedNotes(offset, limit)]);\n\n return {\n totalCount,\n notes: notes.map(serializeTrashedNote),\n };\n },\n\n trashNoteById: async (id: number): Promise<TrashedNoteSummary | null> => {\n await deps.purgeExpiredDeletedNotes(createRetentionCutoff(TRASH_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n\n const note = await deps.findLiveNote(id);\n\n if (!note) {\n return null;\n }\n\n const trashedNote = await deps.moveNoteToTrash(note);\n return serializeTrashedNote(trashedNote);\n },\n\n getNoteById: async (id: number): Promise<TrashedNoteDetail | null> => {\n await deps.purgeExpiredDeletedNotes(createRetentionCutoff(TRASH_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n\n const deletedNote = await deps.findDeletedNote(id);\n\n if (!deletedNote) {\n return null;\n }\n\n const { blocksToMarkdown } = await import('~/modules/blocknote.js');\n\n return {\n ...serializeTrashedNote(deletedNote),\n contentAsMarkdown: await blocksToMarkdown(deletedNote.content),\n };\n },\n\n restoreNoteById: async (id: number) => {\n await deps.purgeExpiredDeletedNotes(createRetentionCutoff(TRASH_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n\n const deletedNote = await deps.findDeletedNote(id);\n\n if (!deletedNote) {\n return null;\n }\n\n if (await deps.liveNoteExists(id)) {\n throw new NoteRestoreConflictError();\n }\n\n return deps.restoreDeletedNote(deletedNote);\n },\n\n purgeNoteById: async (id: number): Promise<TrashedNoteSummary | null> => {\n await deps.purgeExpiredDeletedNotes(createRetentionCutoff(TRASH_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n\n const deletedNote = await deps.findDeletedNote(id);\n\n if (!deletedNote) {\n return null;\n }\n\n const summary = serializeTrashedNote(deletedNote);\n await deps.purgeDeletedNote(deletedNote);\n return summary;\n },\n});\n\nconst includeDeletedNote = {\n reminders: { orderBy: { reminderDate: 'asc' as const } },\n tags: { orderBy: { name: 'asc' as const } },\n};\n\nconst defaultPurgeExpiredDeletedNotes = async (before: Date, limit: number) => {\n const expiredNotes = await models.deletedNote.findMany({\n where: { deletedAt: { lt: before } },\n orderBy: { deletedAt: 'asc' },\n take: limit,\n select: { id: true },\n });\n\n if (expiredNotes.length === 0) {\n return 0;\n }\n\n const deleted = await models.deletedNote.deleteMany({ where: { id: { in: expiredNotes.map((note) => note.id) } } });\n\n return deleted.count;\n};\n\nconst noteTrashService = createNoteTrashService({\n countDeletedNotes: async () => models.deletedNote.count(),\n findDeletedNote: async (id) => {\n return models.deletedNote.findUnique({\n where: { id },\n include: includeDeletedNote,\n });\n },\n findLiveNote: async (id) => {\n return models.note.findUnique({\n where: { id },\n include: {\n reminders: { orderBy: { reminderDate: 'asc' } },\n tags: { orderBy: { name: 'asc' } },\n },\n });\n },\n listDeletedNotes: async (skip, take) => {\n return models.deletedNote.findMany({\n skip,\n take,\n orderBy: { deletedAt: 'desc' },\n include: includeDeletedNote,\n });\n },\n liveNoteExists: async (id) => {\n const note = await models.note.findUnique({\n where: { id },\n select: { id: true },\n });\n return Boolean(note);\n },\n purgeExpiredDeletedNotes: defaultPurgeExpiredDeletedNotes,\n purgeDeletedNote: async (deletedNote) => {\n await models.deletedNote.delete({ where: { id: deletedNote.id } });\n },\n moveNoteToTrash: async (note) => {\n return models.$transaction(async (tx) => {\n await tx.deletedNote.create({\n data: {\n id: note.id,\n title: note.title,\n content: note.content,\n createdAt: note.createdAt,\n updatedAt: note.updatedAt,\n pinned: note.pinned,\n order: note.order,\n layout: note.layout,\n tags: { create: note.tags.map((tag) => ({ name: tag.name })) },\n reminders: {\n create: note.reminders.map((reminder) => ({\n originalId: reminder.id,\n reminderDate: reminder.reminderDate,\n completed: reminder.completed,\n priority: reminder.priority,\n content: reminder.content,\n createdAt: reminder.createdAt,\n updatedAt: reminder.updatedAt,\n })),\n },\n },\n });\n\n await tx.note.delete({ where: { id: note.id } });\n\n if (note.tags.length > 0) {\n await tx.tag.deleteMany({\n where: {\n id: { in: note.tags.map((tag) => tag.id) },\n notes: { none: {} },\n },\n });\n }\n\n return tx.deletedNote.findUniqueOrThrow({\n where: { id: note.id },\n include: includeDeletedNote,\n });\n });\n },\n restoreDeletedNote: async (deletedNote) => {\n return models.$transaction(async (tx) => {\n const tagNames = Array.from(new Set(deletedNote.tags.map((tag) => tag.name).filter(Boolean)));\n\n if (tagNames.length > 0) {\n const existingTags = await tx.tag.findMany({\n where: { name: { in: tagNames } },\n select: { name: true },\n });\n const existingTagNames = new Set(existingTags.map((tag) => tag.name));\n const missingTagNames = tagNames.filter((tagName) => !existingTagNames.has(tagName));\n\n for (const tagName of missingTagNames) {\n await tx.tag.create({ data: { name: tagName } });\n }\n }\n\n const restoreTagIds =\n tagNames.length > 0\n ? await tx.tag.findMany({\n where: { name: { in: tagNames } },\n select: {\n id: true,\n name: true,\n },\n })\n : [];\n\n const restoredContent =\n restoreTagIds.length > 0\n ? restoreTagIdsInContent(\n deletedNote.content,\n new Map(restoreTagIds.map((tag) => [tag.name, tag.id])),\n )\n : deletedNote.content;\n\n const note = await tx.note.create({\n data: {\n id: deletedNote.id,\n title: deletedNote.title,\n content: restoredContent,\n ...buildNoteSearchProjection({\n title: deletedNote.title,\n content: restoredContent,\n }),\n createdAt: deletedNote.createdAt,\n updatedAt: deletedNote.updatedAt,\n pinned: deletedNote.pinned,\n order: deletedNote.order,\n layout: deletedNote.layout,\n ...(restoreTagIds.length > 0\n ? { tags: { connect: restoreTagIds.map((tag) => ({ id: tag.id })) } }\n : {}),\n ...(deletedNote.reminders.length > 0\n ? {\n reminders: {\n create: deletedNote.reminders.map((reminder) => ({\n reminderDate: reminder.reminderDate,\n completed: reminder.completed,\n priority: reminder.priority,\n content: reminder.content,\n createdAt: reminder.createdAt,\n updatedAt: reminder.updatedAt,\n })),\n },\n }\n : {}),\n },\n });\n\n await tx.deletedNote.delete({ where: { id: deletedNote.id } });\n\n return note;\n });\n },\n});\n\nexport const listTrashedNotes = noteTrashService.listTrashedNotes;\nexport const getTrashedNoteById = noteTrashService.getNoteById;\nexport const trashNoteById = noteTrashService.trashNoteById;\nexport const restoreTrashedNoteById = noteTrashService.restoreNoteById;\nexport const purgeTrashedNoteById = noteTrashService.purgeNoteById;\nexport const purgeExpiredTrashedNotes = async () => {\n return defaultPurgeExpiredDeletedNotes(createRetentionCutoff(TRASH_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n};\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/features/note/services/trash.ts"],"sourcesContent":["import models, { type NoteLayout, type PropertyValueType, type ReminderPriority } from '~/models.js';\nimport {\n createRetentionCutoff,\n RECOVERY_CLEANUP_BATCH_LIMIT,\n TRASH_RETENTION_DAYS,\n} from '~/modules/recovery-retention.js';\nimport { buildNoteSearchProjection, extractVisibleSearchTextFromContent } from './search.js';\n\nconst TRASHED_NOTE_CONTENT_PREVIEW_MAX_LENGTH = 240;\n\ninterface LiveTagRecord {\n id: number;\n name: string;\n}\n\ninterface LiveReminderRecord {\n id: number;\n reminderDate: Date;\n completed: boolean;\n priority: ReminderPriority;\n content: string | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\ninterface LiveNoteRecord {\n id: number;\n title: string;\n content: string;\n createdAt: Date;\n updatedAt: Date;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n tags: LiveTagRecord[];\n reminders: LiveReminderRecord[];\n properties?: LivePropertyRecord[];\n}\n\ninterface LivePropertyRecord {\n id: number;\n textValue: string | null;\n textValueNormalized: string | null;\n numberValue: number | null;\n dateValue: Date | null;\n boolValue: boolean | null;\n option: { value: string; label: string; color: string | null } | null;\n createdAt: Date;\n updatedAt: Date;\n definition: {\n key: string;\n name: string;\n valueType: PropertyValueType;\n };\n}\n\ninterface RestoredNoteRecord {\n id: number;\n title: string;\n content: string;\n createdAt: Date;\n updatedAt: Date;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n}\n\ninterface DeletedTagRecord {\n name: string;\n}\n\ninterface DeletedReminderRecord {\n originalId: number | null;\n reminderDate: Date;\n completed: boolean;\n priority: ReminderPriority;\n content: string | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\ninterface DeletedNoteRecord {\n id: number;\n title: string;\n content: string;\n createdAt: Date;\n updatedAt: Date;\n deletedAt: Date;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n tags: DeletedTagRecord[];\n reminders: DeletedReminderRecord[];\n properties?: DeletedPropertyRecord[];\n}\n\ninterface DeletedPropertyRecord {\n key: string;\n name: string;\n valueType: PropertyValueType;\n textValue: string | null;\n textValueNormalized: string | null;\n numberValue: number | null;\n dateValue: Date | null;\n boolValue: boolean | null;\n optionValue: string | null;\n optionLabel: string | null;\n optionColor: string | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\ninterface BlockNoteNode {\n type: string;\n props?: Record<string, unknown>;\n content?: BlockNoteNode[];\n children?: BlockNoteNode[];\n}\n\nexport interface TrashedNoteSummary {\n id: string;\n title: string;\n createdAt: string;\n updatedAt: string;\n deletedAt: string;\n contentPreview: string;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n tagNames: string[];\n}\n\nexport interface TrashedNotesResult {\n totalCount: number;\n notes: TrashedNoteSummary[];\n}\n\nexport interface TrashedNoteDetail extends TrashedNoteSummary {\n contentAsMarkdown: string;\n}\n\nexport class NoteRestoreConflictError extends Error {\n constructor(message = 'A live note with the same id already exists.') {\n super(message);\n this.name = 'NoteRestoreConflictError';\n }\n}\n\ninterface NoteTrashServiceDeps {\n countDeletedNotes: () => Promise<number>;\n findDeletedNote: (id: number) => Promise<DeletedNoteRecord | null>;\n findLiveNote: (id: number) => Promise<LiveNoteRecord | null>;\n listDeletedNotes: (skip: number, take: number) => Promise<DeletedNoteRecord[]>;\n liveNoteExists: (id: number) => Promise<boolean>;\n moveNoteToTrash: (note: LiveNoteRecord) => Promise<DeletedNoteRecord>;\n purgeDeletedNote: (note: DeletedNoteRecord) => Promise<void>;\n purgeExpiredDeletedNotes: (before: Date, limit: number) => Promise<number>;\n restoreDeletedNote: (note: DeletedNoteRecord) => Promise<RestoredNoteRecord>;\n}\n\nconst buildTrashedNoteContentPreview = (content: string) => {\n const text = extractVisibleSearchTextFromContent(content);\n\n if (text.length <= TRASHED_NOTE_CONTENT_PREVIEW_MAX_LENGTH) {\n return text;\n }\n\n return `${text.slice(0, TRASHED_NOTE_CONTENT_PREVIEW_MAX_LENGTH).trimEnd()}…`;\n};\n\nconst serializeTrashedNote = (note: DeletedNoteRecord): TrashedNoteSummary => {\n return {\n id: String(note.id),\n title: note.title,\n createdAt: note.createdAt.toISOString(),\n updatedAt: note.updatedAt.toISOString(),\n deletedAt: note.deletedAt.toISOString(),\n contentPreview: buildTrashedNoteContentPreview(note.content),\n pinned: note.pinned,\n order: note.order,\n layout: note.layout,\n tagNames: note.tags.map((tag) => tag.name),\n };\n};\n\nconst restoreTagIdsInContent = (content: string, tagIdByName: Map<string, number>) => {\n const rewriteNodes = (nodes: BlockNoteNode[]): BlockNoteNode[] => {\n return nodes.map((node) => ({\n ...node,\n content: node.content?.map((contentNode) => {\n if (contentNode.type !== 'tag') {\n return contentNode;\n }\n\n const tagName = typeof contentNode.props?.tag === 'string' ? contentNode.props.tag : null;\n const restoredTagId = tagName ? tagIdByName.get(tagName) : null;\n\n if (!restoredTagId) {\n return contentNode;\n }\n\n return {\n ...contentNode,\n props: {\n ...contentNode.props,\n id: String(restoredTagId),\n },\n };\n }),\n children: node.children ? rewriteNodes(node.children) : node.children,\n }));\n };\n\n try {\n const parsed = JSON.parse(content) as BlockNoteNode[];\n return JSON.stringify(rewriteNodes(parsed));\n } catch {\n return content;\n }\n};\n\nexport const createNoteTrashService = (deps: NoteTrashServiceDeps) => ({\n listTrashedNotes: async (input?: { limit?: number; offset?: number }): Promise<TrashedNotesResult> => {\n await deps.purgeExpiredDeletedNotes(createRetentionCutoff(TRASH_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n\n const limit = Math.max(1, Number(input?.limit ?? 25));\n const offset = Math.max(0, Number(input?.offset ?? 0));\n const [totalCount, notes] = await Promise.all([deps.countDeletedNotes(), deps.listDeletedNotes(offset, limit)]);\n\n return {\n totalCount,\n notes: notes.map(serializeTrashedNote),\n };\n },\n\n trashNoteById: async (id: number): Promise<TrashedNoteSummary | null> => {\n await deps.purgeExpiredDeletedNotes(createRetentionCutoff(TRASH_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n\n const note = await deps.findLiveNote(id);\n\n if (!note) {\n return null;\n }\n\n const trashedNote = await deps.moveNoteToTrash(note);\n return serializeTrashedNote(trashedNote);\n },\n\n getNoteById: async (id: number): Promise<TrashedNoteDetail | null> => {\n await deps.purgeExpiredDeletedNotes(createRetentionCutoff(TRASH_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n\n const deletedNote = await deps.findDeletedNote(id);\n\n if (!deletedNote) {\n return null;\n }\n\n const { blocksToMarkdown } = await import('~/modules/blocknote.js');\n\n return {\n ...serializeTrashedNote(deletedNote),\n contentAsMarkdown: await blocksToMarkdown(deletedNote.content),\n };\n },\n\n restoreNoteById: async (id: number) => {\n await deps.purgeExpiredDeletedNotes(createRetentionCutoff(TRASH_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n\n const deletedNote = await deps.findDeletedNote(id);\n\n if (!deletedNote) {\n return null;\n }\n\n if (await deps.liveNoteExists(id)) {\n throw new NoteRestoreConflictError();\n }\n\n return deps.restoreDeletedNote(deletedNote);\n },\n\n purgeNoteById: async (id: number): Promise<TrashedNoteSummary | null> => {\n await deps.purgeExpiredDeletedNotes(createRetentionCutoff(TRASH_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n\n const deletedNote = await deps.findDeletedNote(id);\n\n if (!deletedNote) {\n return null;\n }\n\n const summary = serializeTrashedNote(deletedNote);\n await deps.purgeDeletedNote(deletedNote);\n return summary;\n },\n});\n\nconst includeDeletedNote = {\n reminders: { orderBy: { reminderDate: 'asc' as const } },\n tags: { orderBy: { name: 'asc' as const } },\n properties: { orderBy: { key: 'asc' as const } },\n};\n\nconst defaultPurgeExpiredDeletedNotes = async (before: Date, limit: number) => {\n const expiredNotes = await models.deletedNote.findMany({\n where: { deletedAt: { lt: before } },\n orderBy: { deletedAt: 'asc' },\n take: limit,\n select: { id: true },\n });\n\n if (expiredNotes.length === 0) {\n return 0;\n }\n\n const deleted = await models.deletedNote.deleteMany({ where: { id: { in: expiredNotes.map((note) => note.id) } } });\n\n return deleted.count;\n};\n\nconst noteTrashService = createNoteTrashService({\n countDeletedNotes: async () => models.deletedNote.count(),\n findDeletedNote: async (id) => {\n return models.deletedNote.findUnique({\n where: { id },\n include: includeDeletedNote,\n });\n },\n findLiveNote: async (id) => {\n return models.note.findUnique({\n where: { id },\n include: {\n reminders: { orderBy: { reminderDate: 'asc' } },\n tags: { orderBy: { name: 'asc' } },\n properties: {\n orderBy: { definition: { key: 'asc' } },\n include: { definition: true, option: true },\n },\n },\n });\n },\n listDeletedNotes: async (skip, take) => {\n return models.deletedNote.findMany({\n skip,\n take,\n orderBy: { deletedAt: 'desc' },\n include: includeDeletedNote,\n });\n },\n liveNoteExists: async (id) => {\n const note = await models.note.findUnique({\n where: { id },\n select: { id: true },\n });\n return Boolean(note);\n },\n purgeExpiredDeletedNotes: defaultPurgeExpiredDeletedNotes,\n purgeDeletedNote: async (deletedNote) => {\n await models.deletedNote.delete({ where: { id: deletedNote.id } });\n },\n moveNoteToTrash: async (note) => {\n return models.$transaction(async (tx) => {\n await tx.deletedNote.create({\n data: {\n id: note.id,\n title: note.title,\n content: note.content,\n createdAt: note.createdAt,\n updatedAt: note.updatedAt,\n pinned: note.pinned,\n order: note.order,\n layout: note.layout,\n tags: { create: note.tags.map((tag) => ({ name: tag.name })) },\n reminders: {\n create: note.reminders.map((reminder) => ({\n originalId: reminder.id,\n reminderDate: reminder.reminderDate,\n completed: reminder.completed,\n priority: reminder.priority,\n content: reminder.content,\n createdAt: reminder.createdAt,\n updatedAt: reminder.updatedAt,\n })),\n },\n properties: {\n create: (note.properties ?? []).map((property) => ({\n key: property.definition.key,\n name: property.definition.name,\n valueType: property.definition.valueType,\n textValue: property.textValue,\n textValueNormalized: property.textValueNormalized,\n numberValue: property.numberValue,\n dateValue: property.dateValue,\n boolValue: property.boolValue,\n optionValue: property.option?.value ?? null,\n optionLabel: property.option?.label ?? null,\n optionColor: property.option?.color ?? null,\n createdAt: property.createdAt,\n updatedAt: property.updatedAt,\n })),\n },\n },\n });\n\n await tx.note.delete({ where: { id: note.id } });\n\n if (note.tags.length > 0) {\n await tx.tag.deleteMany({\n where: {\n id: { in: note.tags.map((tag) => tag.id) },\n notes: { none: {} },\n },\n });\n }\n\n return tx.deletedNote.findUniqueOrThrow({\n where: { id: note.id },\n include: includeDeletedNote,\n });\n });\n },\n restoreDeletedNote: async (deletedNote) => {\n return models.$transaction(async (tx) => {\n const tagNames = Array.from(new Set(deletedNote.tags.map((tag) => tag.name).filter(Boolean)));\n\n if (tagNames.length > 0) {\n const existingTags = await tx.tag.findMany({\n where: { name: { in: tagNames } },\n select: { name: true },\n });\n const existingTagNames = new Set(existingTags.map((tag) => tag.name));\n const missingTagNames = tagNames.filter((tagName) => !existingTagNames.has(tagName));\n\n for (const tagName of missingTagNames) {\n await tx.tag.create({ data: { name: tagName } });\n }\n }\n\n const restoreTagIds =\n tagNames.length > 0\n ? await tx.tag.findMany({\n where: { name: { in: tagNames } },\n select: {\n id: true,\n name: true,\n },\n })\n : [];\n\n const restoredContent =\n restoreTagIds.length > 0\n ? restoreTagIdsInContent(\n deletedNote.content,\n new Map(restoreTagIds.map((tag) => [tag.name, tag.id])),\n )\n : deletedNote.content;\n\n const note = await tx.note.create({\n data: {\n id: deletedNote.id,\n title: deletedNote.title,\n content: restoredContent,\n ...buildNoteSearchProjection({\n title: deletedNote.title,\n content: restoredContent,\n }),\n createdAt: deletedNote.createdAt,\n updatedAt: deletedNote.updatedAt,\n pinned: deletedNote.pinned,\n order: deletedNote.order,\n layout: deletedNote.layout,\n ...(restoreTagIds.length > 0\n ? { tags: { connect: restoreTagIds.map((tag) => ({ id: tag.id })) } }\n : {}),\n ...(deletedNote.reminders.length > 0\n ? {\n reminders: {\n create: deletedNote.reminders.map((reminder) => ({\n reminderDate: reminder.reminderDate,\n completed: reminder.completed,\n priority: reminder.priority,\n content: reminder.content,\n createdAt: reminder.createdAt,\n updatedAt: reminder.updatedAt,\n })),\n },\n }\n : {}),\n },\n });\n\n for (const property of deletedNote.properties ?? []) {\n const definition = await tx.propertyDefinition.upsert({\n where: { key: property.key },\n create: {\n key: property.key,\n name: property.name,\n valueType: property.valueType,\n },\n update: {\n name: property.name,\n valueType: property.valueType,\n },\n });\n\n const option =\n property.valueType === 'select' && property.optionValue\n ? await tx.propertyOption.upsert({\n where: {\n propertyDefinitionId_value: {\n propertyDefinitionId: definition.id,\n value: property.optionValue,\n },\n },\n create: {\n propertyDefinitionId: definition.id,\n value: property.optionValue,\n label: property.optionLabel ?? property.optionValue,\n color: property.optionColor,\n },\n update: {\n label: property.optionLabel ?? property.optionValue,\n color: property.optionColor,\n },\n })\n : null;\n\n await tx.noteProperty.create({\n data: {\n noteId: note.id,\n propertyDefinitionId: definition.id,\n optionId: option?.id ?? null,\n textValue: property.textValue,\n textValueNormalized: property.textValueNormalized,\n numberValue: property.numberValue,\n dateValue: property.dateValue,\n boolValue: property.boolValue,\n createdAt: property.createdAt,\n updatedAt: property.updatedAt,\n },\n });\n }\n\n await tx.deletedNote.delete({ where: { id: deletedNote.id } });\n\n return note;\n });\n },\n});\n\nexport const listTrashedNotes = noteTrashService.listTrashedNotes;\nexport const getTrashedNoteById = noteTrashService.getNoteById;\nexport const trashNoteById = noteTrashService.trashNoteById;\nexport const restoreTrashedNoteById = noteTrashService.restoreNoteById;\nexport const purgeTrashedNoteById = noteTrashService.purgeNoteById;\nexport const purgeExpiredTrashedNotes = async () => {\n return defaultPurgeExpiredDeletedNotes(createRetentionCutoff(TRASH_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n};\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const NOTE_UPDATE_CONFLICT_CODE = "NOTE_UPDATE_CONFLICT";
|
|
2
|
+
const INVALID_NOTE_VERSION_CODE = "INVALID_NOTE_VERSION";
|
|
3
|
+
const MISSING_NOTE_VERSION_CODE = "MISSING_NOTE_VERSION";
|
|
4
|
+
class InvalidNoteVersionError extends Error {
|
|
5
|
+
code = INVALID_NOTE_VERSION_CODE;
|
|
6
|
+
constructor() {
|
|
7
|
+
super("Invalid note version.");
|
|
8
|
+
this.name = "InvalidNoteVersionError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
class MissingNoteVersionError extends Error {
|
|
12
|
+
code = MISSING_NOTE_VERSION_CODE;
|
|
13
|
+
constructor() {
|
|
14
|
+
super("Expected note update time is required for this write.");
|
|
15
|
+
this.name = "MissingNoteVersionError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
class NoteVersionConflictError extends Error {
|
|
19
|
+
code = NOTE_UPDATE_CONFLICT_CODE;
|
|
20
|
+
currentUpdatedAt;
|
|
21
|
+
expectedUpdatedAt;
|
|
22
|
+
constructor(input) {
|
|
23
|
+
super("This note changed elsewhere. Reload the latest version before saving.");
|
|
24
|
+
this.name = "NoteVersionConflictError";
|
|
25
|
+
this.currentUpdatedAt = String(input.currentUpdatedAt);
|
|
26
|
+
this.expectedUpdatedAt = String(input.expectedUpdatedAt);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const parseNoteVersion = (value) => {
|
|
30
|
+
if (value === void 0 || value === null || value === "") {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const timestamp = /^\d+$/.test(value) ? Number(value) : Date.parse(value);
|
|
34
|
+
if (!Number.isFinite(timestamp)) {
|
|
35
|
+
throw new InvalidNoteVersionError();
|
|
36
|
+
}
|
|
37
|
+
return timestamp;
|
|
38
|
+
};
|
|
39
|
+
const createNoteVersionConflictError = ({
|
|
40
|
+
expectedUpdatedAt,
|
|
41
|
+
currentUpdatedAt
|
|
42
|
+
}) => {
|
|
43
|
+
return new NoteVersionConflictError({
|
|
44
|
+
expectedUpdatedAt,
|
|
45
|
+
currentUpdatedAt
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
const assertExpectedNoteVersion = ({
|
|
49
|
+
expectedUpdatedAt,
|
|
50
|
+
currentUpdatedAt
|
|
51
|
+
}) => {
|
|
52
|
+
const expectedTimestamp = parseNoteVersion(expectedUpdatedAt);
|
|
53
|
+
if (expectedTimestamp === null) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const currentTimestamp = currentUpdatedAt.getTime();
|
|
57
|
+
if (expectedTimestamp !== currentTimestamp) {
|
|
58
|
+
throw createNoteVersionConflictError({
|
|
59
|
+
expectedUpdatedAt: expectedTimestamp,
|
|
60
|
+
currentUpdatedAt: currentTimestamp
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const isNoteVersionConflictError = (error) => {
|
|
65
|
+
return error instanceof NoteVersionConflictError;
|
|
66
|
+
};
|
|
67
|
+
const isInvalidNoteVersionError = (error) => {
|
|
68
|
+
return error instanceof InvalidNoteVersionError;
|
|
69
|
+
};
|
|
70
|
+
const isMissingNoteVersionError = (error) => {
|
|
71
|
+
return error instanceof MissingNoteVersionError;
|
|
72
|
+
};
|
|
73
|
+
export {
|
|
74
|
+
INVALID_NOTE_VERSION_CODE,
|
|
75
|
+
InvalidNoteVersionError,
|
|
76
|
+
MISSING_NOTE_VERSION_CODE,
|
|
77
|
+
MissingNoteVersionError,
|
|
78
|
+
NOTE_UPDATE_CONFLICT_CODE,
|
|
79
|
+
NoteVersionConflictError,
|
|
80
|
+
assertExpectedNoteVersion,
|
|
81
|
+
createNoteVersionConflictError,
|
|
82
|
+
isInvalidNoteVersionError,
|
|
83
|
+
isMissingNoteVersionError,
|
|
84
|
+
isNoteVersionConflictError,
|
|
85
|
+
parseNoteVersion
|
|
86
|
+
};
|
|
87
|
+
//# sourceMappingURL=write-conflict.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/features/note/services/write-conflict.ts"],"sourcesContent":["export const NOTE_UPDATE_CONFLICT_CODE = 'NOTE_UPDATE_CONFLICT';\nexport const INVALID_NOTE_VERSION_CODE = 'INVALID_NOTE_VERSION';\nexport const MISSING_NOTE_VERSION_CODE = 'MISSING_NOTE_VERSION';\n\nexport class InvalidNoteVersionError extends Error {\n code = INVALID_NOTE_VERSION_CODE;\n\n constructor() {\n super('Invalid note version.');\n this.name = 'InvalidNoteVersionError';\n }\n}\n\nexport class MissingNoteVersionError extends Error {\n code = MISSING_NOTE_VERSION_CODE;\n\n constructor() {\n super('Expected note update time is required for this write.');\n this.name = 'MissingNoteVersionError';\n }\n}\n\nexport class NoteVersionConflictError extends Error {\n code = NOTE_UPDATE_CONFLICT_CODE;\n currentUpdatedAt: string;\n expectedUpdatedAt: string;\n\n constructor(input: { currentUpdatedAt: string | number; expectedUpdatedAt: string | number }) {\n super('This note changed elsewhere. Reload the latest version before saving.');\n this.name = 'NoteVersionConflictError';\n this.currentUpdatedAt = String(input.currentUpdatedAt);\n this.expectedUpdatedAt = String(input.expectedUpdatedAt);\n }\n}\n\nexport const parseNoteVersion = (value?: string | null) => {\n if (value === undefined || value === null || value === '') {\n return null;\n }\n\n const timestamp = /^\\d+$/.test(value) ? Number(value) : Date.parse(value);\n\n if (!Number.isFinite(timestamp)) {\n throw new InvalidNoteVersionError();\n }\n\n return timestamp;\n};\n\nexport const createNoteVersionConflictError = ({\n expectedUpdatedAt,\n currentUpdatedAt,\n}: {\n expectedUpdatedAt: string | number;\n currentUpdatedAt: string | number;\n}) => {\n return new NoteVersionConflictError({\n expectedUpdatedAt,\n currentUpdatedAt,\n });\n};\n\nexport const assertExpectedNoteVersion = ({\n expectedUpdatedAt,\n currentUpdatedAt,\n}: {\n expectedUpdatedAt?: string | null;\n currentUpdatedAt: Date;\n}) => {\n const expectedTimestamp = parseNoteVersion(expectedUpdatedAt);\n\n if (expectedTimestamp === null) {\n return;\n }\n\n const currentTimestamp = currentUpdatedAt.getTime();\n\n if (expectedTimestamp !== currentTimestamp) {\n throw createNoteVersionConflictError({\n expectedUpdatedAt: expectedTimestamp,\n currentUpdatedAt: currentTimestamp,\n });\n }\n};\n\nexport const isNoteVersionConflictError = (error: unknown): error is NoteVersionConflictError => {\n return error instanceof NoteVersionConflictError;\n};\n\nexport const isInvalidNoteVersionError = (error: unknown): error is InvalidNoteVersionError => {\n return error instanceof InvalidNoteVersionError;\n};\n\nexport const isMissingNoteVersionError = (error: unknown): error is MissingNoteVersionError => {\n return error instanceof MissingNoteVersionError;\n};\n"],"mappings":"AAAO,MAAM,4BAA4B;AAClC,MAAM,4BAA4B;AAClC,MAAM,4BAA4B;AAElC,MAAM,gCAAgC,MAAM;AAAA,EAC/C,OAAO;AAAA,EAEP,cAAc;AACV,UAAM,uBAAuB;AAC7B,SAAK,OAAO;AAAA,EAChB;AACJ;AAEO,MAAM,gCAAgC,MAAM;AAAA,EAC/C,OAAO;AAAA,EAEP,cAAc;AACV,UAAM,uDAAuD;AAC7D,SAAK,OAAO;AAAA,EAChB;AACJ;AAEO,MAAM,iCAAiC,MAAM;AAAA,EAChD,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EAEA,YAAY,OAAkF;AAC1F,UAAM,uEAAuE;AAC7E,SAAK,OAAO;AACZ,SAAK,mBAAmB,OAAO,MAAM,gBAAgB;AACrD,SAAK,oBAAoB,OAAO,MAAM,iBAAiB;AAAA,EAC3D;AACJ;AAEO,MAAM,mBAAmB,CAAC,UAA0B;AACvD,MAAI,UAAU,UAAa,UAAU,QAAQ,UAAU,IAAI;AACvD,WAAO;AAAA,EACX;AAEA,QAAM,YAAY,QAAQ,KAAK,KAAK,IAAI,OAAO,KAAK,IAAI,KAAK,MAAM,KAAK;AAExE,MAAI,CAAC,OAAO,SAAS,SAAS,GAAG;AAC7B,UAAM,IAAI,wBAAwB;AAAA,EACtC;AAEA,SAAO;AACX;AAEO,MAAM,iCAAiC,CAAC;AAAA,EAC3C;AAAA,EACA;AACJ,MAGM;AACF,SAAO,IAAI,yBAAyB;AAAA,IAChC;AAAA,IACA;AAAA,EACJ,CAAC;AACL;AAEO,MAAM,4BAA4B,CAAC;AAAA,EACtC;AAAA,EACA;AACJ,MAGM;AACF,QAAM,oBAAoB,iBAAiB,iBAAiB;AAE5D,MAAI,sBAAsB,MAAM;AAC5B;AAAA,EACJ;AAEA,QAAM,mBAAmB,iBAAiB,QAAQ;AAElD,MAAI,sBAAsB,kBAAkB;AACxC,UAAM,+BAA+B;AAAA,MACjC,mBAAmB;AAAA,MACnB,kBAAkB;AAAA,IACtB,CAAC;AAAA,EACL;AACJ;AAEO,MAAM,6BAA6B,CAAC,UAAsD;AAC7F,SAAO,iBAAiB;AAC5B;AAEO,MAAM,4BAA4B,CAAC,UAAqD;AAC3F,SAAO,iBAAiB;AAC5B;AAEO,MAAM,4BAA4B,CAAC,UAAqD;AAC3F,SAAO,iBAAiB;AAC5B;","names":[]}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import models, { Prisma } from "../../../models.js";
|
|
2
|
+
import { buildNoteSearchProjection } from "./search.js";
|
|
3
|
+
import { captureNoteBaseline } from "./snapshot.js";
|
|
4
|
+
import { createNoteVersionConflictError, MissingNoteVersionError, parseNoteVersion } from "./write-conflict.js";
|
|
5
|
+
const isRecordNotFoundError = (error) => {
|
|
6
|
+
return error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2025";
|
|
7
|
+
};
|
|
8
|
+
const createNoteWriteService = (deps) => {
|
|
9
|
+
const updateNoteWithVersionGuardAndSnapshot2 = async ({
|
|
10
|
+
id,
|
|
11
|
+
data,
|
|
12
|
+
editSessionId,
|
|
13
|
+
expectedUpdatedAt,
|
|
14
|
+
snapshotMeta,
|
|
15
|
+
force = false
|
|
16
|
+
}) => {
|
|
17
|
+
const existingNote = await deps.findNoteForWrite(id);
|
|
18
|
+
if (!existingNote) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const expectedTimestamp = parseNoteVersion(expectedUpdatedAt);
|
|
22
|
+
if (expectedTimestamp === null && !force) {
|
|
23
|
+
throw new MissingNoteVersionError();
|
|
24
|
+
}
|
|
25
|
+
const nextTitle = data.title ?? existingNote.title;
|
|
26
|
+
const nextContent = data.content ?? existingNote.content;
|
|
27
|
+
const where = force ? { id } : { id, updatedAt: new Date(expectedTimestamp ?? existingNote.updatedAt.getTime()) };
|
|
28
|
+
try {
|
|
29
|
+
const updatedNote = await deps.updateNote({
|
|
30
|
+
where,
|
|
31
|
+
data: {
|
|
32
|
+
...data.title !== void 0 ? { title: data.title } : {},
|
|
33
|
+
...data.content !== void 0 ? { content: data.content } : {},
|
|
34
|
+
...data.layout !== void 0 ? { layout: data.layout } : {},
|
|
35
|
+
...buildNoteSearchProjection({
|
|
36
|
+
title: nextTitle,
|
|
37
|
+
content: nextContent
|
|
38
|
+
}),
|
|
39
|
+
...data.tagIds ? { tags: { set: data.tagIds.map((tagId) => ({ id: tagId })) } } : {}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
const snapshot = await deps.captureBaseline({
|
|
43
|
+
noteId: id,
|
|
44
|
+
baseline: {
|
|
45
|
+
id,
|
|
46
|
+
title: existingNote.title,
|
|
47
|
+
content: existingNote.content,
|
|
48
|
+
pinned: existingNote.pinned,
|
|
49
|
+
order: existingNote.order,
|
|
50
|
+
layout: existingNote.layout
|
|
51
|
+
},
|
|
52
|
+
...editSessionId && !force ? { editSessionId } : {},
|
|
53
|
+
...snapshotMeta ? { meta: snapshotMeta } : {},
|
|
54
|
+
...force ? { force: true } : {}
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
note: updatedNote,
|
|
58
|
+
snapshot
|
|
59
|
+
};
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (deps.isRecordNotFoundError(error)) {
|
|
62
|
+
const currentNote = await deps.findNoteVersion(id);
|
|
63
|
+
if (!currentNote) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
if (expectedTimestamp === null || currentNote.updatedAt.getTime() === expectedTimestamp) {
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
throw createNoteVersionConflictError({
|
|
70
|
+
expectedUpdatedAt: expectedTimestamp,
|
|
71
|
+
currentUpdatedAt: currentNote.updatedAt.getTime()
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
return {
|
|
78
|
+
updateNoteWithVersionGuard: async (input) => {
|
|
79
|
+
const result = await updateNoteWithVersionGuardAndSnapshot2(input);
|
|
80
|
+
return result?.note ?? null;
|
|
81
|
+
},
|
|
82
|
+
updateNoteWithVersionGuardAndSnapshot: updateNoteWithVersionGuardAndSnapshot2
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
const defaultNoteWriteService = createNoteWriteService({
|
|
86
|
+
findNoteForWrite: (id) => models.note.findUnique({
|
|
87
|
+
where: { id },
|
|
88
|
+
select: {
|
|
89
|
+
title: true,
|
|
90
|
+
content: true,
|
|
91
|
+
updatedAt: true,
|
|
92
|
+
pinned: true,
|
|
93
|
+
order: true,
|
|
94
|
+
layout: true
|
|
95
|
+
}
|
|
96
|
+
}),
|
|
97
|
+
findNoteVersion: (id) => models.note.findUnique({
|
|
98
|
+
where: { id },
|
|
99
|
+
select: {
|
|
100
|
+
updatedAt: true
|
|
101
|
+
}
|
|
102
|
+
}),
|
|
103
|
+
captureBaseline: captureNoteBaseline,
|
|
104
|
+
updateNote: ({ where, data }) => models.note.update({
|
|
105
|
+
where,
|
|
106
|
+
data
|
|
107
|
+
}),
|
|
108
|
+
isRecordNotFoundError
|
|
109
|
+
});
|
|
110
|
+
const updateNoteWithVersionGuard = async (input) => {
|
|
111
|
+
return defaultNoteWriteService.updateNoteWithVersionGuard(input);
|
|
112
|
+
};
|
|
113
|
+
const updateNoteWithVersionGuardAndSnapshot = async (input) => {
|
|
114
|
+
return defaultNoteWriteService.updateNoteWithVersionGuardAndSnapshot(input);
|
|
115
|
+
};
|
|
116
|
+
export {
|
|
117
|
+
createNoteWriteService,
|
|
118
|
+
updateNoteWithVersionGuard,
|
|
119
|
+
updateNoteWithVersionGuardAndSnapshot
|
|
120
|
+
};
|
|
121
|
+
//# sourceMappingURL=write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/features/note/services/write.ts"],"sourcesContent":["import models, { type NoteLayout, Prisma } from '~/models.js';\nimport { buildNoteSearchProjection } from './search.js';\nimport { captureNoteBaseline } from './snapshot.js';\nimport { createNoteVersionConflictError, MissingNoteVersionError, parseNoteVersion } from './write-conflict.js';\n\nexport interface NoteWriteRecord {\n id: number;\n title: string;\n content: string;\n layout: NoteLayout;\n createdAt: Date;\n updatedAt: Date;\n pinned: boolean;\n order: number;\n}\n\ntype NoteWriteData = {\n title?: string;\n content?: string;\n layout?: NoteLayout;\n tagIds?: number[];\n};\n\ninterface UpdateNoteWithVersionGuardInput {\n id: number;\n data: NoteWriteData;\n editSessionId?: string;\n expectedUpdatedAt?: string;\n snapshotMeta?: string;\n force?: boolean;\n}\n\nexport interface GuardedNoteWriteResult {\n note: NoteWriteRecord;\n snapshot: unknown;\n}\n\ninterface NoteWriteDeps {\n findNoteForWrite: (\n id: number,\n ) => Promise<Pick<NoteWriteRecord, 'title' | 'content' | 'updatedAt' | 'pinned' | 'order' | 'layout'> | null>;\n findNoteVersion: (id: number) => Promise<Pick<NoteWriteRecord, 'updatedAt'> | null>;\n captureBaseline: (input: {\n noteId: number;\n editSessionId?: string;\n meta?: string;\n baseline: Pick<NoteWriteRecord, 'id' | 'title' | 'content' | 'pinned' | 'order' | 'layout'>;\n force?: boolean;\n }) => Promise<unknown>;\n updateNote: (input: {\n where: {\n id: number;\n updatedAt?: Date;\n };\n data: {\n title?: string;\n content?: string;\n layout?: NoteLayout;\n searchableText: string;\n searchableTextVersion: number;\n tags?: {\n set: Array<{ id: number }>;\n };\n };\n }) => Promise<NoteWriteRecord>;\n isRecordNotFoundError: (error: unknown) => boolean;\n}\n\nconst isRecordNotFoundError = (error: unknown) => {\n return error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2025';\n};\n\nexport const createNoteWriteService = (deps: NoteWriteDeps) => {\n const updateNoteWithVersionGuardAndSnapshot = async ({\n id,\n data,\n editSessionId,\n expectedUpdatedAt,\n snapshotMeta,\n force = false,\n }: UpdateNoteWithVersionGuardInput): Promise<GuardedNoteWriteResult | null> => {\n const existingNote = await deps.findNoteForWrite(id);\n\n if (!existingNote) {\n return null;\n }\n\n const expectedTimestamp = parseNoteVersion(expectedUpdatedAt);\n\n if (expectedTimestamp === null && !force) {\n throw new MissingNoteVersionError();\n }\n\n const nextTitle = data.title ?? existingNote.title;\n const nextContent = data.content ?? existingNote.content;\n const where = force\n ? { id }\n : { id, updatedAt: new Date(expectedTimestamp ?? existingNote.updatedAt.getTime()) };\n\n try {\n const updatedNote = await deps.updateNote({\n where,\n data: {\n ...(data.title !== undefined ? { title: data.title } : {}),\n ...(data.content !== undefined ? { content: data.content } : {}),\n ...(data.layout !== undefined ? { layout: data.layout } : {}),\n ...buildNoteSearchProjection({\n title: nextTitle,\n content: nextContent,\n }),\n ...(data.tagIds ? { tags: { set: data.tagIds.map((tagId) => ({ id: tagId })) } } : {}),\n },\n });\n\n const snapshot = await deps.captureBaseline({\n noteId: id,\n baseline: {\n id,\n title: existingNote.title,\n content: existingNote.content,\n pinned: existingNote.pinned,\n order: existingNote.order,\n layout: existingNote.layout,\n },\n ...(editSessionId && !force ? { editSessionId } : {}),\n ...(snapshotMeta ? { meta: snapshotMeta } : {}),\n ...(force ? { force: true } : {}),\n });\n\n return {\n note: updatedNote,\n snapshot,\n };\n } catch (error) {\n if (deps.isRecordNotFoundError(error)) {\n const currentNote = await deps.findNoteVersion(id);\n\n if (!currentNote) {\n return null;\n }\n\n if (expectedTimestamp === null || currentNote.updatedAt.getTime() === expectedTimestamp) {\n throw error;\n }\n\n throw createNoteVersionConflictError({\n expectedUpdatedAt: expectedTimestamp,\n currentUpdatedAt: currentNote.updatedAt.getTime(),\n });\n }\n\n throw error;\n }\n };\n\n return {\n updateNoteWithVersionGuard: async (input: UpdateNoteWithVersionGuardInput): Promise<NoteWriteRecord | null> => {\n const result = await updateNoteWithVersionGuardAndSnapshot(input);\n return result?.note ?? null;\n },\n updateNoteWithVersionGuardAndSnapshot,\n };\n};\n\nconst defaultNoteWriteService = createNoteWriteService({\n findNoteForWrite: (id) =>\n models.note.findUnique({\n where: { id },\n select: {\n title: true,\n content: true,\n updatedAt: true,\n pinned: true,\n order: true,\n layout: true,\n },\n }),\n findNoteVersion: (id) =>\n models.note.findUnique({\n where: { id },\n select: {\n updatedAt: true,\n },\n }),\n captureBaseline: captureNoteBaseline,\n updateNote: ({ where, data }) =>\n models.note.update({\n where,\n data,\n }),\n isRecordNotFoundError,\n});\n\nexport const updateNoteWithVersionGuard = async (input: UpdateNoteWithVersionGuardInput) => {\n return defaultNoteWriteService.updateNoteWithVersionGuard(input);\n};\n\nexport const updateNoteWithVersionGuardAndSnapshot = async (input: UpdateNoteWithVersionGuardInput) => {\n return defaultNoteWriteService.updateNoteWithVersionGuardAndSnapshot(input);\n};\n"],"mappings":"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","names":["updateNoteWithVersionGuardAndSnapshot"]}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { GraphQLError } from "graphql";
|
|
2
|
+
import { InvalidNotePropertyInputError } from "../../../features/note/services/properties.js";
|
|
3
|
+
import {
|
|
4
|
+
getNotesByProperties,
|
|
5
|
+
getViewSectionById,
|
|
6
|
+
getViewSectionNotes,
|
|
7
|
+
getViewWorkspace
|
|
8
|
+
} from "../../../features/view/services/workspace.js";
|
|
2
9
|
const viewQueryResolvers = {
|
|
3
10
|
viewWorkspace: async () => {
|
|
4
11
|
return getViewWorkspace();
|
|
@@ -21,6 +28,29 @@ const viewQueryResolvers = {
|
|
|
21
28
|
throw "NOT FOUND";
|
|
22
29
|
}
|
|
23
30
|
return sectionNotes;
|
|
31
|
+
},
|
|
32
|
+
notesByProperties: async (_, {
|
|
33
|
+
input,
|
|
34
|
+
pagination = {
|
|
35
|
+
limit: 20,
|
|
36
|
+
offset: 0
|
|
37
|
+
}
|
|
38
|
+
}) => {
|
|
39
|
+
try {
|
|
40
|
+
return await getNotesByProperties(input, {
|
|
41
|
+
limit: Number(pagination.limit),
|
|
42
|
+
offset: Number(pagination.offset)
|
|
43
|
+
});
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (error instanceof InvalidNotePropertyInputError) {
|
|
46
|
+
throw new GraphQLError(error.message, {
|
|
47
|
+
extensions: {
|
|
48
|
+
code: "INVALID_NOTE_PROPERTY_INPUT"
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
24
54
|
}
|
|
25
55
|
};
|
|
26
56
|
export {
|