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.
Files changed (102) hide show
  1. package/dist/mcp.js +660 -31
  2. package/package.json +2 -1
  3. package/server/client/dist/assets/{Calendar-DI0PhQTq.js → Calendar-BytW4PeT.js} +1 -1
  4. package/server/client/dist/assets/Callout-9sJgGNRb.js +1 -0
  5. package/server/client/dist/assets/Graph-o5u-NzrA.js +1 -0
  6. package/server/client/dist/assets/{Image-DlJ4MDo3.js → Image-DxWhlIDG.js} +1 -1
  7. package/server/client/dist/assets/{Image.es-COwqkQuh.js → Image.es-B0LL3pb7.js} +1 -1
  8. package/server/client/dist/assets/Note-CYoYzZNu.js +21 -0
  9. package/server/client/dist/assets/{Plus.es-KeV96R_e.js → Plus.es-DpxzgRtY.js} +1 -1
  10. package/server/client/dist/assets/Reminders-DAepTZg1.js +1 -0
  11. package/server/client/dist/assets/Search-6KH7An44.js +1 -0
  12. package/server/client/dist/assets/{SurfaceCard-CHjcCuqZ.js → SurfaceCard-EMs50xSF.js} +1 -1
  13. package/server/client/dist/assets/Tag-C89j8x56.js +1 -0
  14. package/server/client/dist/assets/TagNotes-BTbu-f6v.js +1 -0
  15. package/server/client/dist/assets/{Trash.es-BqRTZPrI.js → Trash.es-C5x35NKF.js} +1 -1
  16. package/server/client/dist/assets/ViewNotes-DPEuki3y.js +1 -0
  17. package/server/client/dist/assets/Views-BGkuNhVd.js +1 -0
  18. package/server/client/dist/assets/{image.api-CMdQbCyN.js → image.api-DRhRQ0Gf.js} +1 -1
  19. package/server/client/dist/assets/{index-D2gRB1ig.css → index-Cd81UxEh.css} +1 -1
  20. package/server/client/dist/assets/index-DYlYUrjH.js +327 -0
  21. package/server/client/dist/assets/index-DswSFFMb.js +1 -0
  22. package/server/client/dist/assets/manage-image-C6sHhWnI.js +1 -0
  23. package/server/client/dist/assets/manage-image-detail-CJkfybR9.js +1 -0
  24. package/server/client/dist/assets/{mcp-DqGLZv90.js → mcp-piouFDNm.js} +1 -1
  25. package/server/client/dist/assets/note-core-Z6kqulGB.js +180 -0
  26. package/server/client/dist/assets/note-vendor-i1M2FSI2.js +54 -0
  27. package/server/client/dist/assets/{placeholder-BXLtvdtN.js → placeholder-DGz3CHej.js} +4 -4
  28. package/server/client/dist/assets/properties-CM4khczg.js +1 -0
  29. package/server/client/dist/assets/trash-C2lItz-b.js +1 -0
  30. package/server/client/dist/assets/{useReminderMutate-v1EXqM90.js → useReminderMutate-DHjDDHKg.js} +1 -1
  31. package/server/client/dist/assets/view-dashboard-C17WsdL7.js +99 -0
  32. package/server/client/dist/index.html +4 -4
  33. package/server/dist/features/note/graphql/note.field.resolver.js +16 -1
  34. package/server/dist/features/note/graphql/note.field.resolver.js.map +1 -1
  35. package/server/dist/features/note/graphql/note.mutation.resolver.js +192 -36
  36. package/server/dist/features/note/graphql/note.mutation.resolver.js.map +1 -1
  37. package/server/dist/features/note/graphql/note.query.resolver.js +88 -76
  38. package/server/dist/features/note/graphql/note.query.resolver.js.map +1 -1
  39. package/server/dist/features/note/graphql/note.resolver.js +3 -2
  40. package/server/dist/features/note/graphql/note.resolver.js.map +1 -1
  41. package/server/dist/features/note/graphql/note.type-defs.js +104 -1
  42. package/server/dist/features/note/graphql/note.type-defs.js.map +1 -1
  43. package/server/dist/features/note/http/mcp.js +394 -1
  44. package/server/dist/features/note/http/mcp.js.map +1 -1
  45. package/server/dist/features/note/services/cleanup.js +4 -3
  46. package/server/dist/features/note/services/cleanup.js.map +1 -1
  47. package/server/dist/features/note/services/content-blocks.js +113 -0
  48. package/server/dist/features/note/services/content-blocks.js.map +1 -0
  49. package/server/dist/features/note/services/markdown-intent-write.js +478 -0
  50. package/server/dist/features/note/services/markdown-intent-write.js.map +1 -0
  51. package/server/dist/features/note/services/markdown-patch.js +601 -0
  52. package/server/dist/features/note/services/markdown-patch.js.map +1 -0
  53. package/server/dist/features/note/services/properties.js +874 -0
  54. package/server/dist/features/note/services/properties.js.map +1 -0
  55. package/server/dist/features/note/services/search.js +10 -29
  56. package/server/dist/features/note/services/search.js.map +1 -1
  57. package/server/dist/features/note/services/snapshot.js +260 -18
  58. package/server/dist/features/note/services/snapshot.js.map +1 -1
  59. package/server/dist/features/note/services/trash.js +70 -2
  60. package/server/dist/features/note/services/trash.js.map +1 -1
  61. package/server/dist/features/note/services/write-conflict.js +87 -0
  62. package/server/dist/features/note/services/write-conflict.js.map +1 -0
  63. package/server/dist/features/note/services/write.js +121 -0
  64. package/server/dist/features/note/services/write.js.map +1 -0
  65. package/server/dist/features/view/graphql/view.query.resolver.js +31 -1
  66. package/server/dist/features/view/graphql/view.query.resolver.js.map +1 -1
  67. package/server/dist/features/view/graphql/view.type-defs.js +57 -1
  68. package/server/dist/features/view/graphql/view.type-defs.js.map +1 -1
  69. package/server/dist/features/view/services/workspace.js +367 -43
  70. package/server/dist/features/view/services/workspace.js.map +1 -1
  71. package/server/dist/modules/blocknote-tree.js +41 -0
  72. package/server/dist/modules/blocknote-tree.js.map +1 -0
  73. package/server/dist/modules/blocknote.js +47 -2
  74. package/server/dist/modules/blocknote.js.map +1 -1
  75. package/server/dist/modules/error-handler.js +7 -4
  76. package/server/dist/modules/error-handler.js.map +1 -1
  77. package/server/dist/modules/recovery-retention.js +2 -2
  78. package/server/dist/modules/recovery-retention.js.map +1 -1
  79. package/server/dist/routes/mcp.js +21 -1
  80. package/server/dist/routes/mcp.js.map +1 -1
  81. package/server/prisma/migrations/20260527073000_0017_note_properties/migration.sql +105 -0
  82. package/server/prisma/migrations/20260531073000_0018_view_query/migration.sql +3 -0
  83. package/server/prisma/schema.prisma +226 -130
  84. package/server/client/dist/assets/Callout-BAqbXI0L.js +0 -1
  85. package/server/client/dist/assets/Graph-Dhuog4tc.js +0 -1
  86. package/server/client/dist/assets/Note-ChUWU3Su.js +0 -21
  87. package/server/client/dist/assets/Reminders-D7MCTNxb.js +0 -1
  88. package/server/client/dist/assets/Search-B-fwTr5y.js +0 -1
  89. package/server/client/dist/assets/Tag-BbV2z-Db.js +0 -1
  90. package/server/client/dist/assets/TagNotes-2HKF09Jm.js +0 -1
  91. package/server/client/dist/assets/ViewNotes-TfM3Uzbp.js +0 -1
  92. package/server/client/dist/assets/Views-CUwtnfa8.js +0 -1
  93. package/server/client/dist/assets/index-B4l2UvuC.js +0 -232
  94. package/server/client/dist/assets/index-zhxZjg7V.js +0 -1
  95. package/server/client/dist/assets/manage-image-DdXb5Bkj.js +0 -1
  96. package/server/client/dist/assets/manage-image-detail-CrrXCQIX.js +0 -1
  97. package/server/client/dist/assets/note-core-GgFcksAF.js +0 -178
  98. package/server/client/dist/assets/note-vendor-B0t5F04o.js +0 -54
  99. package/server/client/dist/assets/trash-3eUAjFRh.js +0 -1
  100. package/server/client/dist/assets/view-dashboard-T6gmohr-.js +0 -90
  101. package/server/dist/features/note/graphql/note.graphql.shared.js +0 -23
  102. package/server/dist/features/note/graphql/note.graphql.shared.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/features/note/services/properties.ts"],"sourcesContent":["import models, { type Note, type NoteLayout, Prisma, type PropertyValueType } from '~/models.js';\nimport { buildNoteSearchProjection } from './search.js';\nimport { captureNoteBaseline } from './snapshot.js';\nimport { createNoteVersionConflictError, MissingNoteVersionError, parseNoteVersion } from './write-conflict.js';\n\nexport interface SerializedNotePropertyOption {\n id: string;\n label: string;\n value: string;\n color?: string | null;\n order: number;\n}\n\nexport interface SerializedNoteProperty {\n key: string;\n name: string;\n value: string;\n valueType: PropertyValueType;\n option?: SerializedNotePropertyOption | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface SerializedNotePropertyKey {\n key: string;\n name: string;\n valueType: PropertyValueType;\n noteCount: number;\n options: SerializedNotePropertyOption[];\n updatedAt: string;\n}\n\nexport interface SerializedNotePropertyKeysResult {\n totalCount: number;\n keys: SerializedNotePropertyKey[];\n}\n\nexport interface SerializedNotePropertyDeleteResult {\n key: string;\n name: string;\n valueType: PropertyValueType;\n affectedNoteCount: number;\n deleted: boolean;\n}\n\nexport interface NotePropertySetInput {\n key: string;\n name?: string | null;\n value: string;\n valueType: PropertyValueType;\n}\n\nexport interface NotePropertiesPatchInput {\n set?: NotePropertySetInput[] | null;\n deleteKeys?: string[] | null;\n}\n\nexport interface NotePropertySetByKeyInput {\n key: string;\n value: string;\n}\n\nexport interface NotePropertiesByKeyPatchInput {\n set?: NotePropertySetByKeyInput[] | null;\n deleteKeys?: string[] | null;\n}\n\nexport interface NotePropertyOptionInput {\n label: string;\n value?: string | null;\n color?: string | null;\n order?: number | null;\n}\n\nexport interface NotePropertyOptionUpdateInput extends NotePropertyOptionInput {\n id?: string | number | null;\n}\n\nexport interface NotePropertyDefinitionInput {\n key: string;\n name?: string | null;\n valueType: PropertyValueType;\n options?: NotePropertyOptionInput[] | null;\n}\n\nexport interface NotePropertyDefinitionUpdateInput {\n name?: string | null;\n options?: NotePropertyOptionUpdateInput[] | null;\n}\n\ntype NotePropertyWithDefinition = Prisma.NotePropertyGetPayload<{\n include: { definition: true; option: true };\n}>;\n\ntype PropertyDefinitionWithOptions = Prisma.PropertyDefinitionGetPayload<{\n include: { options: true; _count: { select: { properties: true } } };\n}>;\n\ntype PropertyPatchDefinitionLookupClient = Pick<typeof models, 'propertyDefinition'>;\ntype PropertyPatchValueValidationClient = Pick<typeof models, 'propertyDefinition' | 'propertyOption'>;\n\nconst PROPERTY_KEY_MAX_LENGTH = 80;\nconst PROPERTY_NAME_MAX_LENGTH = 120;\nconst PROPERTY_TEXT_VALUE_MAX_LENGTH = 2000;\nconst PROPERTY_OPTION_LABEL_MAX_LENGTH = 80;\nconst PROPERTY_OPTION_VALUE_MAX_LENGTH = 80;\nconst PROPERTY_OPTION_LIMIT = 50;\nconst PROPERTY_PATCH_SET_LIMIT = 50;\nconst PROPERTY_PATCH_DELETE_LIMIT = 50;\n\nexport class InvalidNotePropertyInputError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidNotePropertyInputError';\n }\n}\n\nexport class NotePropertyDeleteConfirmationRequiredError extends Error {\n code = 'NOTE_PROPERTY_DELETE_CONFIRMATION_REQUIRED' as const;\n affectedNoteCount: number;\n\n constructor(key: string, affectedNoteCount: number) {\n super(`Property ${key} is used by ${affectedNoteCount} note(s). Confirm deletion to remove those values.`);\n this.name = 'NotePropertyDeleteConfirmationRequiredError';\n this.affectedNoteCount = affectedNoteCount;\n }\n}\n\nconst isRecordNotFoundError = (error: unknown) => {\n return error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2025';\n};\n\nexport const normalizePropertyKey = (value: string) => {\n const normalized = value.trim().toLowerCase().replace(/\\s+/g, '-');\n\n if (!normalized) {\n throw new InvalidNotePropertyInputError('Property key is required.');\n }\n\n if (normalized.length > PROPERTY_KEY_MAX_LENGTH) {\n throw new InvalidNotePropertyInputError(`Property key must be ${PROPERTY_KEY_MAX_LENGTH} characters or fewer.`);\n }\n\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(normalized)) {\n throw new InvalidNotePropertyInputError('Property key must use letters, numbers, dashes, or underscores.');\n }\n\n return normalized;\n};\n\nconst normalizePropertyName = (value: string | null | undefined, key: string) => {\n const normalized = value?.trim() || key;\n\n if (normalized.length > PROPERTY_NAME_MAX_LENGTH) {\n throw new InvalidNotePropertyInputError(\n `Property name must be ${PROPERTY_NAME_MAX_LENGTH} characters or fewer.`,\n );\n }\n\n return normalized;\n};\n\nconst normalizeOptionValue = (value: string) => {\n const normalized = value.trim().toLowerCase().replace(/\\s+/g, '-');\n\n if (!normalized) {\n throw new InvalidNotePropertyInputError('Property option value is required.');\n }\n\n if (normalized.length > PROPERTY_OPTION_VALUE_MAX_LENGTH) {\n throw new InvalidNotePropertyInputError(\n `Property option value must be ${PROPERTY_OPTION_VALUE_MAX_LENGTH} characters or fewer.`,\n );\n }\n\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(normalized)) {\n throw new InvalidNotePropertyInputError(\n 'Property option value must use letters, numbers, dashes, or underscores.',\n );\n }\n\n return normalized;\n};\n\nconst normalizeOptionLabel = (value: string) => {\n const normalized = value.trim();\n\n if (!normalized) {\n throw new InvalidNotePropertyInputError('Property option label is required.');\n }\n\n if (normalized.length > PROPERTY_OPTION_LABEL_MAX_LENGTH) {\n throw new InvalidNotePropertyInputError(\n `Property option label must be ${PROPERTY_OPTION_LABEL_MAX_LENGTH} characters or fewer.`,\n );\n }\n\n return normalized;\n};\n\nconst normalizePropertyOptions = (options: NotePropertyOptionInput[] | null | undefined) => {\n const normalizedOptions = options ?? [];\n\n if (normalizedOptions.length > PROPERTY_OPTION_LIMIT) {\n throw new InvalidNotePropertyInputError(`Select properties can have up to ${PROPERTY_OPTION_LIMIT} options.`);\n }\n\n const seenValues = new Set<string>();\n\n return normalizedOptions.map((option, index) => {\n const label = normalizeOptionLabel(option.label);\n const value = normalizeOptionValue(option.value ?? label);\n\n if (seenValues.has(value)) {\n throw new InvalidNotePropertyInputError('Property options contain duplicate values.');\n }\n\n seenValues.add(value);\n\n return {\n label,\n value,\n color: option.color?.trim() || null,\n order: Number.isFinite(Number(option.order)) ? Number(option.order) : index,\n };\n });\n};\n\nconst normalizePropertyOptionUpdates = (\n options: NotePropertyOptionUpdateInput[] | null | undefined,\n existingOptions: Array<{ id: number; value: string }>,\n) => {\n const normalizedOptions = options ?? [];\n\n if (normalizedOptions.length > PROPERTY_OPTION_LIMIT) {\n throw new InvalidNotePropertyInputError(`Select properties can have up to ${PROPERTY_OPTION_LIMIT} options.`);\n }\n\n const existingOptionById = new Map(existingOptions.map((option) => [String(option.id), option]));\n const seenValues = new Set<string>();\n\n return normalizedOptions.map((option, index) => {\n const label = normalizeOptionLabel(option.label);\n const existingOption =\n option.id === undefined || option.id === null ? null : existingOptionById.get(String(option.id));\n\n if (option.id !== undefined && option.id !== null && !existingOption) {\n throw new InvalidNotePropertyInputError('Property option does not belong to this property.');\n }\n\n const value = normalizeOptionValue(option.value ?? existingOption?.value ?? label);\n\n if (existingOption && value !== existingOption.value) {\n throw new InvalidNotePropertyInputError(\n `Property option ${existingOption.value} value cannot be changed. Create a new option instead.`,\n );\n }\n\n if (seenValues.has(value)) {\n throw new InvalidNotePropertyInputError('Property options contain duplicate values.');\n }\n\n seenValues.add(value);\n\n return {\n id: existingOption?.id,\n label,\n value,\n color: option.color?.trim() || null,\n order: Number.isFinite(Number(option.order)) ? Number(option.order) : index,\n };\n });\n};\n\nconst normalizeTextValue = (value: string) => {\n if (value.length > PROPERTY_TEXT_VALUE_MAX_LENGTH) {\n throw new InvalidNotePropertyInputError(\n `Property text value must be ${PROPERTY_TEXT_VALUE_MAX_LENGTH} characters or fewer.`,\n );\n }\n\n return value;\n};\n\nexport const normalizeUrlValue = (value: string) => {\n const trimmedValue = normalizeTextValue(value.trim());\n\n if (!trimmedValue) {\n throw new InvalidNotePropertyInputError('URL property value is required.');\n }\n\n let url: URL;\n\n try {\n url = new URL(trimmedValue);\n } catch {\n throw new InvalidNotePropertyInputError('URL property values must be valid http(s) URLs.');\n }\n\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new InvalidNotePropertyInputError('URL property values must use http or https.');\n }\n\n return url.toString();\n};\n\nconst normalizeDateValue = (value: string) => {\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) {\n throw new InvalidNotePropertyInputError('Date property values must use YYYY-MM-DD.');\n }\n\n const date = new Date(`${value}T00:00:00.000Z`);\n\n if (Number.isNaN(date.getTime()) || date.toISOString().slice(0, 10) !== value) {\n throw new InvalidNotePropertyInputError('Date property value is invalid.');\n }\n\n return date;\n};\n\nconst normalizeBooleanValue = (value: string) => {\n if (value === 'true') {\n return true;\n }\n\n if (value === 'false') {\n return false;\n }\n\n throw new InvalidNotePropertyInputError('Boolean property values must be true or false.');\n};\n\nconst normalizeNumberValue = (value: string) => {\n const numberValue = Number(value);\n\n if (!Number.isFinite(numberValue)) {\n throw new InvalidNotePropertyInputError('Number property value must be finite.');\n }\n\n return numberValue;\n};\n\nconst serializeOption = (option: NonNullable<NotePropertyWithDefinition['option']>): SerializedNotePropertyOption => ({\n id: String(option.id),\n label: option.label,\n value: option.value,\n color: option.color,\n order: option.order,\n});\n\nconst serializeDefinitionOption = (\n option: PropertyDefinitionWithOptions['options'][number],\n): SerializedNotePropertyOption => ({\n id: String(option.id),\n label: option.label,\n value: option.value,\n color: option.color,\n order: option.order,\n});\n\nconst serializePropertyValue = (property: NotePropertyWithDefinition) => {\n switch (property.definition.valueType) {\n case 'number':\n return property.numberValue === null ? '' : String(property.numberValue);\n case 'date':\n return property.dateValue ? property.dateValue.toISOString().slice(0, 10) : '';\n case 'boolean':\n return property.boolValue === null ? '' : String(property.boolValue);\n case 'select':\n return property.option?.value ?? '';\n case 'url':\n case 'text':\n default:\n return property.textValue ?? '';\n }\n};\n\nexport const serializeNoteProperty = (property: NotePropertyWithDefinition): SerializedNoteProperty => ({\n key: property.definition.key,\n name: property.definition.name,\n value: serializePropertyValue(property),\n valueType: property.definition.valueType,\n option: property.option ? serializeOption(property.option) : null,\n createdAt: property.createdAt.toISOString(),\n updatedAt: property.updatedAt.toISOString(),\n});\n\nexport const serializeNoteProperties = (properties: NotePropertyWithDefinition[]) => {\n return properties.map(serializeNoteProperty).sort((left, right) => left.key.localeCompare(right.key));\n};\n\nconst serializePropertyDefinition = (definition: PropertyDefinitionWithOptions): SerializedNotePropertyKey => ({\n key: definition.key,\n name: definition.name,\n valueType: definition.valueType,\n noteCount: definition._count.properties,\n options: definition.options.sort((left, right) => left.order - right.order).map(serializeDefinitionOption),\n updatedAt: definition.updatedAt.toISOString(),\n});\n\nexport const assertPropertyOptionUpdateKeepsUsedValues = ({\n existingOptions,\n nextOptions,\n}: {\n existingOptions: Array<{ value: string; _count: { properties: number } }>;\n nextOptions: Array<{ value: string }>;\n}) => {\n const nextValues = new Set(nextOptions.map((option) => option.value));\n const removedUsedOption = existingOptions.find(\n (option) => option._count.properties > 0 && !nextValues.has(option.value),\n );\n\n if (removedUsedOption) {\n throw new InvalidNotePropertyInputError(\n `Property option ${removedUsedOption.value} is used by notes and cannot be removed.`,\n );\n }\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> => {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n};\n\nconst parseViewQueryRecord = (value: string | null) => {\n if (!value) {\n return null;\n }\n\n try {\n const parsed = JSON.parse(value) as unknown;\n return isRecord(parsed) ? parsed : null;\n } catch {\n return null;\n }\n};\n\nconst getNormalizedViewFilterKey = (filter: Record<string, unknown>) => {\n if (typeof filter.key !== 'string') {\n return null;\n }\n\n try {\n return normalizePropertyKey(filter.key);\n } catch {\n return null;\n }\n};\n\nexport const renamePropertyFiltersInViewQuery = ({\n query,\n key,\n name,\n}: {\n query: string | null;\n key: string;\n name: string;\n}) => {\n const parsed = parseViewQueryRecord(query);\n\n if (!parsed || !Array.isArray(parsed.propertyFilters)) {\n return null;\n }\n\n let changed = false;\n const propertyFilters = parsed.propertyFilters.map((filter) => {\n if (!isRecord(filter) || getNormalizedViewFilterKey(filter) !== key) {\n return filter;\n }\n\n changed = true;\n return {\n ...filter,\n key,\n name,\n };\n });\n\n if (!changed) {\n return null;\n }\n\n return JSON.stringify({\n ...parsed,\n propertyFilters,\n });\n};\n\nconst normalizeReferencedOptionValue = (value: unknown) => {\n return typeof value === 'string' ? normalizeOptionValue(value) : null;\n};\n\nexport const findReferencedRemovedPropertyOptionValue = ({\n query,\n key,\n removedValues,\n}: {\n query: string | null;\n key: string;\n removedValues: Set<string>;\n}) => {\n const parsed = parseViewQueryRecord(query);\n\n if (!parsed || !Array.isArray(parsed.propertyFilters)) {\n return null;\n }\n\n for (const filter of parsed.propertyFilters) {\n if (!isRecord(filter) || getNormalizedViewFilterKey(filter) !== key) {\n continue;\n }\n\n if (filter.valueType !== 'select' || filter.operator === 'exists' || filter.operator === 'notExists') {\n continue;\n }\n\n try {\n const optionValue = normalizeReferencedOptionValue(filter.value);\n\n if (optionValue && removedValues.has(optionValue)) {\n return optionValue;\n }\n } catch {\n continue;\n }\n }\n\n return null;\n};\n\nconst buildTypedValueData = async (\n input: NotePropertySetInput,\n definitionId: number,\n tx: Pick<typeof models, 'propertyOption'> = models,\n) => {\n const resetValues = {\n textValue: null,\n textValueNormalized: null,\n numberValue: null,\n dateValue: null,\n boolValue: null,\n optionId: null,\n };\n\n switch (input.valueType) {\n case 'number': {\n const numberValue = normalizeNumberValue(input.value);\n return { ...resetValues, numberValue };\n }\n case 'date': {\n const dateValue = normalizeDateValue(input.value);\n return { ...resetValues, dateValue };\n }\n case 'boolean': {\n const boolValue = normalizeBooleanValue(input.value);\n return { ...resetValues, boolValue };\n }\n case 'select': {\n const optionValue = normalizeOptionValue(input.value);\n const option = await tx.propertyOption.findUnique({\n where: {\n propertyDefinitionId_value: {\n propertyDefinitionId: definitionId,\n value: optionValue,\n },\n },\n });\n\n if (!option) {\n throw new InvalidNotePropertyInputError(`Property option ${optionValue} is not defined.`);\n }\n\n return { ...resetValues, optionId: option.id };\n }\n case 'url': {\n const urlValue = normalizeUrlValue(input.value);\n return { ...resetValues, textValue: urlValue, textValueNormalized: urlValue.toLowerCase() };\n }\n case 'text': {\n const textValue = normalizeTextValue(input.value);\n return { ...resetValues, textValue, textValueNormalized: textValue.toLowerCase() };\n }\n default:\n throw new InvalidNotePropertyInputError('Unsupported property value type.');\n }\n};\n\nconst normalizePatch = (patch: NotePropertiesPatchInput) => {\n const set = patch.set ?? [];\n const deleteKeys = patch.deleteKeys ?? [];\n\n if (set.length === 0 && deleteKeys.length === 0) {\n throw new InvalidNotePropertyInputError('At least one property change is required.');\n }\n\n if (set.length > PROPERTY_PATCH_SET_LIMIT || deleteKeys.length > PROPERTY_PATCH_DELETE_LIMIT) {\n throw new InvalidNotePropertyInputError('Too many property changes in one request.');\n }\n\n const normalizedSet = set.map((item) => {\n const key = normalizePropertyKey(item.key);\n return {\n ...item,\n key,\n name: normalizePropertyName(item.name, key),\n };\n });\n const normalizedDeleteKeys = deleteKeys.map(normalizePropertyKey);\n const seenSetKeys = new Set<string>();\n const seenDeleteKeys = new Set<string>();\n\n for (const item of normalizedSet) {\n if (seenSetKeys.has(item.key)) {\n throw new InvalidNotePropertyInputError('Property patch contains duplicate keys.');\n }\n\n seenSetKeys.add(item.key);\n }\n\n for (const key of normalizedDeleteKeys) {\n if (seenDeleteKeys.has(key)) {\n throw new InvalidNotePropertyInputError('Property patch contains duplicate delete keys.');\n }\n\n if (seenSetKeys.has(key)) {\n throw new InvalidNotePropertyInputError('Property patch cannot set and delete the same key.');\n }\n\n seenDeleteKeys.add(key);\n }\n\n return { set: normalizedSet, deleteKeys: normalizedDeleteKeys };\n};\n\nconst normalizePatchByKey = (patch: NotePropertiesByKeyPatchInput) => {\n const set = patch.set ?? [];\n const deleteKeys = patch.deleteKeys ?? [];\n\n if (set.length === 0 && deleteKeys.length === 0) {\n throw new InvalidNotePropertyInputError('At least one property change is required.');\n }\n\n if (set.length > PROPERTY_PATCH_SET_LIMIT || deleteKeys.length > PROPERTY_PATCH_DELETE_LIMIT) {\n throw new InvalidNotePropertyInputError('Too many property changes in one request.');\n }\n\n const normalizedSet = set.map((item) => ({\n key: normalizePropertyKey(item.key),\n value: item.value,\n }));\n const normalizedDeleteKeys = deleteKeys.map(normalizePropertyKey);\n const seenSetKeys = new Set<string>();\n const seenDeleteKeys = new Set<string>();\n\n for (const item of normalizedSet) {\n if (seenSetKeys.has(item.key)) {\n throw new InvalidNotePropertyInputError('Property patch contains duplicate keys.');\n }\n\n seenSetKeys.add(item.key);\n }\n\n for (const key of normalizedDeleteKeys) {\n if (seenDeleteKeys.has(key)) {\n throw new InvalidNotePropertyInputError('Property patch contains duplicate delete keys.');\n }\n\n if (seenSetKeys.has(key)) {\n throw new InvalidNotePropertyInputError('Property patch cannot set and delete the same key.');\n }\n\n seenDeleteKeys.add(key);\n }\n\n return { set: normalizedSet, deleteKeys: normalizedDeleteKeys };\n};\n\nexport const resolveNotePropertiesPatchValueTypes = async (\n patch: NotePropertiesByKeyPatchInput,\n tx: PropertyPatchDefinitionLookupClient = models,\n): Promise<NotePropertiesPatchInput> => {\n const normalizedPatch = normalizePatchByKey(patch);\n\n if (normalizedPatch.set.length === 0) {\n return normalizePatch({ deleteKeys: normalizedPatch.deleteKeys });\n }\n\n const definitions = await tx.propertyDefinition.findMany({\n where: { key: { in: normalizedPatch.set.map((item) => item.key) } },\n select: {\n key: true,\n name: true,\n valueType: true,\n },\n });\n const definitionByKey = new Map(definitions.map((definition) => [definition.key, definition]));\n\n return normalizePatch({\n set: normalizedPatch.set.map((item) => {\n const definition = definitionByKey.get(item.key);\n\n if (!definition) {\n throw new InvalidNotePropertyInputError(\n `Property ${item.key} is not defined. Create it in property settings first.`,\n );\n }\n\n return {\n key: item.key,\n name: definition.name,\n value: item.value,\n valueType: definition.valueType,\n };\n }),\n deleteKeys: normalizedPatch.deleteKeys,\n });\n};\n\nexport const validateNotePropertiesPatchValues = async (\n patch: NotePropertiesPatchInput,\n tx: PropertyPatchValueValidationClient = models,\n) => {\n const normalizedPatch = normalizePatch(patch);\n\n for (const item of normalizedPatch.set) {\n const definition = await tx.propertyDefinition.findUnique({ where: { key: item.key } });\n\n if (!definition) {\n throw new InvalidNotePropertyInputError(\n `Property ${item.key} is not defined. Create it in property settings first.`,\n );\n }\n\n if (definition.valueType !== item.valueType) {\n throw new InvalidNotePropertyInputError(\n `Property ${item.key} already uses ${definition.valueType} values.`,\n );\n }\n\n await buildTypedValueData(item, definition.id, tx);\n }\n\n return normalizedPatch;\n};\n\nexport const listNoteProperties = async (noteId: number) => {\n const properties = await models.noteProperty.findMany({\n where: { noteId },\n include: { definition: true, option: true },\n orderBy: [{ definition: { key: 'asc' } }],\n });\n\n return serializeNoteProperties(properties);\n};\n\nexport const listNotePropertyKeys = async (input?: { query?: string; limit?: number; offset?: number }) => {\n const normalizedQuery = input?.query?.trim();\n const where = normalizedQuery\n ? {\n OR: [{ key: { contains: normalizedQuery } }, { name: { contains: normalizedQuery } }],\n }\n : undefined;\n const take = Math.max(1, Math.min(100, Number(input?.limit ?? 50)));\n const skip = Math.max(0, Number(input?.offset ?? 0));\n const [definitions, totalCount] = await Promise.all([\n models.propertyDefinition.findMany({\n where,\n orderBy: { key: 'asc' },\n take,\n skip,\n include: { options: { orderBy: { order: 'asc' } }, _count: { select: { properties: true } } },\n }),\n models.propertyDefinition.count({ where }),\n ]);\n\n return {\n totalCount,\n keys: definitions.map(serializePropertyDefinition),\n } satisfies SerializedNotePropertyKeysResult;\n};\n\nexport const createNotePropertyDefinition = async (input: NotePropertyDefinitionInput) => {\n const key = normalizePropertyKey(input.key);\n const name = normalizePropertyName(input.name, key);\n const options = normalizePropertyOptions(input.options);\n\n if (input.valueType !== 'select' && options.length > 0) {\n throw new InvalidNotePropertyInputError('Only select properties can have options.');\n }\n\n if (input.valueType === 'select' && options.length === 0) {\n throw new InvalidNotePropertyInputError('Select properties require at least one option.');\n }\n\n try {\n const definition = await models.propertyDefinition.create({\n data: {\n key,\n name,\n valueType: input.valueType,\n ...(input.valueType === 'select'\n ? {\n options: {\n create: options,\n },\n }\n : {}),\n },\n include: { options: { orderBy: { order: 'asc' } }, _count: { select: { properties: true } } },\n });\n\n return serializePropertyDefinition(definition);\n } catch (error) {\n if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2002') {\n throw new InvalidNotePropertyInputError(`Property ${key} already exists.`);\n }\n\n throw error;\n }\n};\n\nexport const updateNotePropertyDefinition = async ({\n key,\n input,\n}: {\n key: string;\n input: NotePropertyDefinitionUpdateInput;\n}): Promise<SerializedNotePropertyKey | null> => {\n const normalizedKey = normalizePropertyKey(key);\n\n return models.$transaction(async (tx) => {\n const definition = await tx.propertyDefinition.findUnique({\n where: { key: normalizedKey },\n include: {\n options: {\n orderBy: { order: 'asc' },\n include: { _count: { select: { properties: true } } },\n },\n _count: { select: { properties: true } },\n },\n });\n\n if (!definition) {\n return null;\n }\n\n const nextName = input.name !== undefined ? normalizePropertyName(input.name, definition.key) : definition.name;\n const shouldUpdateOptions = input.options !== undefined;\n let nextOptions: ReturnType<typeof normalizePropertyOptionUpdates> | null = null;\n\n if (shouldUpdateOptions) {\n nextOptions = normalizePropertyOptionUpdates(input.options, definition.options);\n\n if (definition.valueType !== 'select' && nextOptions.length > 0) {\n throw new InvalidNotePropertyInputError('Only select properties can have options.');\n }\n\n if (definition.valueType === 'select' && nextOptions.length === 0) {\n throw new InvalidNotePropertyInputError('Select properties require at least one option.');\n }\n\n assertPropertyOptionUpdateKeepsUsedValues({\n existingOptions: definition.options,\n nextOptions,\n });\n\n const nextValues = new Set(nextOptions.map((option) => option.value));\n const removedValues = new Set(\n definition.options.filter((option) => !nextValues.has(option.value)).map((option) => option.value),\n );\n\n if (removedValues.size > 0) {\n const referencingViewSections = await tx.viewSection.findMany({\n where: { query: { contains: definition.key } },\n select: { title: true, query: true },\n });\n\n for (const section of referencingViewSections) {\n const removedReferencedValue = findReferencedRemovedPropertyOptionValue({\n query: section.query,\n key: definition.key,\n removedValues,\n });\n\n if (removedReferencedValue) {\n throw new InvalidNotePropertyInputError(\n `Property option ${removedReferencedValue} is used by view ${section.title} and cannot be removed.`,\n );\n }\n }\n }\n }\n\n await tx.propertyDefinition.update({\n where: { id: definition.id },\n data: { name: nextName },\n });\n\n if (nextName !== definition.name) {\n const referencingViewSections = await tx.viewSection.findMany({\n where: { query: { contains: definition.key } },\n select: { id: true, query: true },\n });\n\n for (const section of referencingViewSections) {\n const nextQuery = renamePropertyFiltersInViewQuery({\n query: section.query,\n key: definition.key,\n name: nextName,\n });\n\n if (nextQuery) {\n await tx.viewSection.update({\n where: { id: section.id },\n data: { query: nextQuery },\n });\n }\n }\n }\n\n if (nextOptions) {\n const existingOptionByValue = new Map(definition.options.map((option) => [option.value, option]));\n const nextValues = new Set(nextOptions.map((option) => option.value));\n const removableOptionIds = definition.options\n .filter((option) => !nextValues.has(option.value) && option._count.properties === 0)\n .map((option) => option.id);\n\n if (removableOptionIds.length > 0) {\n await tx.propertyOption.deleteMany({\n where: {\n id: { in: removableOptionIds },\n },\n });\n }\n\n for (const option of nextOptions) {\n const existingOption = existingOptionByValue.get(option.value);\n\n if (existingOption) {\n await tx.propertyOption.update({\n where: { id: existingOption.id },\n data: {\n label: option.label,\n color: option.color,\n order: option.order,\n },\n });\n continue;\n }\n\n await tx.propertyOption.create({\n data: {\n propertyDefinitionId: definition.id,\n label: option.label,\n value: option.value,\n color: option.color,\n order: option.order,\n },\n });\n }\n }\n\n const updatedDefinition = await tx.propertyDefinition.findUniqueOrThrow({\n where: { id: definition.id },\n include: { options: { orderBy: { order: 'asc' } }, _count: { select: { properties: true } } },\n });\n\n return serializePropertyDefinition(updatedDefinition);\n });\n};\n\nexport const deleteNotePropertyDefinition = async ({\n key,\n confirmImpact = false,\n}: {\n key: string;\n confirmImpact?: boolean;\n}): Promise<SerializedNotePropertyDeleteResult | null> => {\n const normalizedKey = normalizePropertyKey(key);\n\n return models.$transaction(async (tx) => {\n const definition = await tx.propertyDefinition.findUnique({\n where: { key: normalizedKey },\n include: { _count: { select: { properties: true } } },\n });\n\n if (!definition) {\n return null;\n }\n\n const affectedNoteCount = definition._count.properties;\n\n if (affectedNoteCount > 0 && !confirmImpact) {\n throw new NotePropertyDeleteConfirmationRequiredError(normalizedKey, affectedNoteCount);\n }\n\n if (affectedNoteCount > 0) {\n await tx.note.updateMany({\n where: {\n properties: {\n some: {\n propertyDefinitionId: definition.id,\n },\n },\n },\n data: {\n updatedAt: new Date(),\n },\n });\n }\n\n await tx.propertyDefinition.delete({\n where: { id: definition.id },\n });\n\n return {\n key: definition.key,\n name: definition.name,\n valueType: definition.valueType,\n affectedNoteCount,\n deleted: true,\n };\n });\n};\n\ninterface UpdateNotePropertiesWithVersionGuardInput {\n id: number;\n patch: NotePropertiesPatchInput;\n editSessionId?: string;\n expectedUpdatedAt?: string;\n force?: boolean;\n noteData?: {\n title?: string;\n layout?: NoteLayout;\n };\n snapshotMeta?: string;\n}\n\nexport interface GuardedNotePropertiesWriteResult {\n note: Note;\n snapshot: unknown;\n}\n\nexport const updateNotePropertiesWithVersionGuardAndSnapshot = async ({\n id,\n patch,\n editSessionId,\n expectedUpdatedAt,\n force = false,\n noteData,\n snapshotMeta,\n}: UpdateNotePropertiesWithVersionGuardInput): Promise<GuardedNotePropertiesWriteResult | null> => {\n const normalizedPatch = normalizePatch(patch);\n const expectedTimestamp = parseNoteVersion(expectedUpdatedAt);\n\n if (expectedTimestamp === null && !force) {\n throw new MissingNoteVersionError();\n }\n\n try {\n const result = await models.$transaction(async (tx) => {\n const existingNote = await tx.note.findUnique({\n where: { id },\n include: { properties: { include: { definition: true, option: true } } },\n });\n\n if (!existingNote) {\n return null;\n }\n\n if (!force && existingNote.updatedAt.getTime() !== expectedTimestamp) {\n throw createNoteVersionConflictError({\n currentUpdatedAt: existingNote.updatedAt.getTime(),\n expectedUpdatedAt: expectedTimestamp ?? existingNote.updatedAt.getTime(),\n });\n }\n\n const baseline = {\n ...existingNote,\n properties: existingNote.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 for (const item of normalizedPatch.set) {\n const definition = await tx.propertyDefinition.findUnique({ where: { key: item.key } });\n\n if (!definition) {\n throw new InvalidNotePropertyInputError(\n `Property ${item.key} is not defined. Create it in property settings first.`,\n );\n }\n\n if (definition.valueType !== item.valueType) {\n throw new InvalidNotePropertyInputError(\n `Property ${item.key} already uses ${definition.valueType} values.`,\n );\n }\n\n const valueData = await buildTypedValueData(item, definition.id, tx);\n\n await tx.noteProperty.upsert({\n where: {\n noteId_propertyDefinitionId: {\n noteId: id,\n propertyDefinitionId: definition.id,\n },\n },\n create: {\n noteId: id,\n propertyDefinitionId: definition.id,\n ...valueData,\n },\n update: valueData,\n });\n }\n\n if (normalizedPatch.deleteKeys.length > 0) {\n const definitions = await tx.propertyDefinition.findMany({\n where: { key: { in: normalizedPatch.deleteKeys } },\n select: { id: true },\n });\n\n if (definitions.length > 0) {\n await tx.noteProperty.deleteMany({\n where: {\n noteId: id,\n propertyDefinitionId: { in: definitions.map((definition) => definition.id) },\n },\n });\n }\n }\n\n const updatedNote = await tx.note.update({\n where: { id },\n data: {\n ...(noteData?.title !== undefined ? { title: noteData.title } : {}),\n ...(noteData?.layout !== undefined ? { layout: noteData.layout } : {}),\n ...(noteData?.title !== undefined\n ? buildNoteSearchProjection({\n title: noteData.title,\n content: existingNote.content,\n })\n : {}),\n updatedAt: new Date(),\n },\n });\n\n return { updatedNote, baseline };\n });\n\n if (!result) {\n return null;\n }\n\n const snapshot = await captureNoteBaseline({\n noteId: id,\n baseline: result.baseline,\n ...(editSessionId && !force ? { editSessionId } : {}),\n ...(snapshotMeta ? { meta: snapshotMeta } : {}),\n ...(force ? { force: true } : {}),\n });\n\n return {\n note: result.updatedNote,\n snapshot,\n };\n } catch (error) {\n if (isRecordNotFoundError(error)) {\n return null;\n }\n\n throw error;\n }\n};\n\nexport const updateNotePropertiesWithVersionGuard = async (input: UpdateNotePropertiesWithVersionGuardInput) => {\n const result = await updateNotePropertiesWithVersionGuardAndSnapshot(input);\n return result?.note ?? null;\n};\n"],"mappings":"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","names":[]}
@@ -1,3 +1,4 @@
1
+ import { isBlockNoteTreeNode, walkBlockNoteTree } from "../../../modules/blocknote-tree.js";
1
2
  const NOTE_SEARCH_TEXT_SCHEMA_VERSION = 1;
2
3
  const FILE_BLOCK_VISIBLE_PROP_KEYS = ["name", "caption"];
3
4
  const NOTE_SEARCH_EXTRACTOR_NODE_TYPES = [
@@ -73,9 +74,6 @@ const isSearchExtractorNodeType = (type) => {
73
74
  };
74
75
  const normalizeSearchText = (value) => value.replace(/\s+/g, " ").trim();
75
76
  const normalizeSearchToken = (value) => normalizeSearchText(value).toLowerCase();
76
- const isRecord = (value) => {
77
- return typeof value === "object" && value !== null;
78
- };
79
77
  const pushVisibleSegment = (context, value) => {
80
78
  if (typeof value !== "string") {
81
79
  return;
@@ -87,7 +85,7 @@ const pushVisibleSegment = (context, value) => {
87
85
  context.segments.push(normalizedValue);
88
86
  };
89
87
  const pushVisibleProps = (context, props, keys) => {
90
- if (!isRecord(props)) {
88
+ if (!isBlockNoteTreeNode(props)) {
91
89
  return;
92
90
  }
93
91
  for (const key of keys) {
@@ -95,32 +93,15 @@ const pushVisibleProps = (context, props, keys) => {
95
93
  }
96
94
  };
97
95
  const collectVisibleSearchSegments = (node, context) => {
98
- if (Array.isArray(node)) {
99
- node.forEach((item) => collectVisibleSearchSegments(item, context));
100
- return;
101
- }
102
- if (!isRecord(node)) {
103
- return;
104
- }
105
- const type = typeof node.type === "string" ? node.type : void 0;
106
- if (type) {
107
- if (isSearchExtractorNodeType(type)) {
108
- SEARCH_NODE_EXTRACTORS[type](node, context);
96
+ walkBlockNoteTree(node, (item) => {
97
+ const type = typeof item.type === "string" ? item.type : void 0;
98
+ if (type) {
99
+ if (isSearchExtractorNodeType(type)) {
100
+ SEARCH_NODE_EXTRACTORS[type](item, context);
101
+ }
102
+ recordUnknownNoteSearchNodeType(type);
109
103
  }
110
- recordUnknownNoteSearchNodeType(type);
111
- }
112
- if ("content" in node) {
113
- collectVisibleSearchSegments(node.content, context);
114
- }
115
- if ("children" in node) {
116
- collectVisibleSearchSegments(node.children, context);
117
- }
118
- if ("rows" in node) {
119
- collectVisibleSearchSegments(node.rows, context);
120
- }
121
- if ("cells" in node) {
122
- collectVisibleSearchSegments(node.cells, context);
123
- }
104
+ });
124
105
  };
125
106
  const buildNoteSearchText = (note) => {
126
107
  const normalizedTitle = normalizeSearchText(note.title);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/features/note/services/search.ts"],"sourcesContent":["export interface NoteSearchQuery {\n included: string[];\n excluded: string[];\n hasFilters: boolean;\n}\n\ninterface SearchableNoteLike {\n title: string;\n content: string;\n searchableText?: string;\n searchableTextVersion?: number;\n}\n\nexport interface NoteSearchProjection {\n searchableText: string;\n searchableTextVersion: number;\n}\n\ninterface SearchProjectionContext {\n segments: string[];\n}\n\ntype SearchableJsonNode = Record<string, unknown>;\ntype SearchNodeExtractor = (node: SearchableJsonNode, context: SearchProjectionContext) => void;\ntype SearchExtractorNodeType = (typeof NOTE_SEARCH_EXTRACTOR_NODE_TYPES)[number];\n\n// If we later materialize searchable text into a DB column, bump this when the\n// extraction meaning changes so stored projections can be rebuilt safely.\nexport const NOTE_SEARCH_TEXT_SCHEMA_VERSION = 1;\n\nconst FILE_BLOCK_VISIBLE_PROP_KEYS = ['name', 'caption'] as const;\n\nexport const NOTE_SEARCH_EXTRACTOR_NODE_TYPES = [\n 'text',\n 'tag',\n 'reference',\n 'image',\n 'video',\n 'audio',\n 'file',\n] as const;\n\nexport const NOTE_SEARCH_PASS_THROUGH_NODE_TYPES = [\n 'link',\n 'paragraph',\n 'heading',\n 'bulletListItem',\n 'numberedListItem',\n 'checkListItem',\n 'toggleListItem',\n 'quote',\n 'codeBlock',\n 'divider',\n 'table',\n 'tableContent',\n 'tableCell',\n] as const;\n\nexport const NOTE_SEARCH_IGNORED_NODE_TYPES = ['tableOfContents'] as const;\n\nconst KNOWN_NOTE_SEARCH_NODE_TYPES = new Set<string>([\n ...NOTE_SEARCH_EXTRACTOR_NODE_TYPES,\n ...NOTE_SEARCH_PASS_THROUGH_NODE_TYPES,\n ...NOTE_SEARCH_IGNORED_NODE_TYPES,\n]);\nconst SEARCH_NODE_EXTRACTOR_TYPE_SET = new Set<string>(NOTE_SEARCH_EXTRACTOR_NODE_TYPES);\n\nconst unknownNoteSearchNodeTypeCounts = new Map<string, number>();\n\nconst SEARCH_NODE_EXTRACTORS: Record<SearchExtractorNodeType, SearchNodeExtractor> = {\n text: (node, context) => {\n pushVisibleSegment(context, node.text);\n },\n tag: (node, context) => {\n pushVisibleProps(context, node.props, ['tag']);\n },\n reference: (node, context) => {\n pushVisibleProps(context, node.props, ['title']);\n },\n image: (node, context) => {\n pushVisibleProps(context, node.props, FILE_BLOCK_VISIBLE_PROP_KEYS);\n },\n video: (node, context) => {\n pushVisibleProps(context, node.props, FILE_BLOCK_VISIBLE_PROP_KEYS);\n },\n audio: (node, context) => {\n pushVisibleProps(context, node.props, FILE_BLOCK_VISIBLE_PROP_KEYS);\n },\n file: (node, context) => {\n pushVisibleProps(context, node.props, FILE_BLOCK_VISIBLE_PROP_KEYS);\n },\n};\n\nconst recordUnknownNoteSearchNodeType = (type: string) => {\n if (KNOWN_NOTE_SEARCH_NODE_TYPES.has(type)) {\n return;\n }\n\n const nextCount = (unknownNoteSearchNodeTypeCounts.get(type) ?? 0) + 1;\n unknownNoteSearchNodeTypeCounts.set(type, nextCount);\n\n if (nextCount === 1) {\n process.emitWarning(\n `note-search encountered unsupported BlockNote node type \"${type}\". Only nested text will be indexed until explicit support is added.`,\n { code: 'OCEAN_BRAIN_NOTE_SEARCH_UNKNOWN_NODE' },\n );\n }\n};\n\nconst isSearchExtractorNodeType = (type: string): type is SearchExtractorNodeType => {\n return SEARCH_NODE_EXTRACTOR_TYPE_SET.has(type);\n};\n\nconst normalizeSearchText = (value: string) => value.replace(/\\s+/g, ' ').trim();\n\nconst normalizeSearchToken = (value: string) => normalizeSearchText(value).toLowerCase();\n\nconst isRecord = (value: unknown): value is SearchableJsonNode => {\n return typeof value === 'object' && value !== null;\n};\n\nconst pushVisibleSegment = (context: SearchProjectionContext, value: unknown) => {\n if (typeof value !== 'string') {\n return;\n }\n\n const normalizedValue = normalizeSearchText(value);\n\n if (!normalizedValue) {\n return;\n }\n\n context.segments.push(normalizedValue);\n};\n\nconst pushVisibleProps = (context: SearchProjectionContext, props: unknown, keys: readonly string[]) => {\n if (!isRecord(props)) {\n return;\n }\n\n for (const key of keys) {\n pushVisibleSegment(context, props[key]);\n }\n};\n\nconst collectVisibleSearchSegments = (node: unknown, context: SearchProjectionContext) => {\n if (Array.isArray(node)) {\n node.forEach((item) => collectVisibleSearchSegments(item, context));\n return;\n }\n\n if (!isRecord(node)) {\n return;\n }\n\n const type = typeof node.type === 'string' ? node.type : undefined;\n\n if (type) {\n if (isSearchExtractorNodeType(type)) {\n SEARCH_NODE_EXTRACTORS[type](node, context);\n }\n\n recordUnknownNoteSearchNodeType(type);\n }\n\n if ('content' in node) {\n collectVisibleSearchSegments(node.content, context);\n }\n\n if ('children' in node) {\n collectVisibleSearchSegments(node.children, context);\n }\n\n if ('rows' in node) {\n collectVisibleSearchSegments(node.rows, context);\n }\n\n if ('cells' in node) {\n collectVisibleSearchSegments(node.cells, context);\n }\n};\n\nexport const buildNoteSearchText = (note: Pick<SearchableNoteLike, 'title' | 'content'>) => {\n const normalizedTitle = normalizeSearchText(note.title);\n const normalizedContent = extractVisibleSearchTextFromContent(note.content);\n\n return normalizeSearchToken(`${normalizedTitle} ${normalizedContent}`);\n};\n\nexport const buildNoteSearchProjection = (\n note: Pick<SearchableNoteLike, 'title' | 'content'>,\n): NoteSearchProjection => {\n return {\n searchableText: buildNoteSearchText(note),\n searchableTextVersion: NOTE_SEARCH_TEXT_SCHEMA_VERSION,\n };\n};\n\nexport const parseNoteSearchQuery = (query: string): NoteSearchQuery => {\n const included: string[] = [];\n const excluded: string[] = [];\n\n for (const item of query\n .split(/\\s+/)\n .map((value) => value.trim())\n .filter(Boolean)) {\n if (item.startsWith('-') && item.length > 1) {\n excluded.push(normalizeSearchToken(item.slice(1)));\n continue;\n }\n\n included.push(normalizeSearchToken(item));\n }\n\n return {\n included,\n excluded,\n hasFilters: included.length > 0 || excluded.length > 0,\n };\n};\n\nexport const extractVisibleSearchTextFromContent = (content: string) => {\n try {\n const parsed = JSON.parse(content) as unknown;\n const context: SearchProjectionContext = { segments: [] };\n\n collectVisibleSearchSegments(parsed, context);\n\n return normalizeSearchText(context.segments.join(' '));\n } catch {\n return '';\n }\n};\n\nexport const matchesNoteSearchQuery = (note: SearchableNoteLike, query: string | NoteSearchQuery) => {\n const parsedQuery = typeof query === 'string' ? parseNoteSearchQuery(query) : query;\n\n if (!parsedQuery.hasFilters) {\n return true;\n }\n\n const haystack =\n note.searchableTextVersion === NOTE_SEARCH_TEXT_SCHEMA_VERSION && typeof note.searchableText === 'string'\n ? normalizeSearchToken(note.searchableText)\n : buildNoteSearchText(note);\n\n return (\n parsedQuery.included.every((term) => haystack.includes(term)) &&\n parsedQuery.excluded.every((term) => !haystack.includes(term))\n );\n};\n\nexport const filterNotesBySearchQuery = <T extends SearchableNoteLike>(notes: T[], query: string | NoteSearchQuery) => {\n const parsedQuery = typeof query === 'string' ? parseNoteSearchQuery(query) : query;\n\n if (!parsedQuery.hasFilters) {\n return notes;\n }\n\n return notes.filter((note) => matchesNoteSearchQuery(note, parsedQuery));\n};\n\nexport const getUnknownNoteSearchNodeTypeCounts = () => {\n return new Map(unknownNoteSearchNodeTypeCounts);\n};\n\nexport const resetUnknownNoteSearchNodeTypeCountsForTest = () => {\n unknownNoteSearchNodeTypeCounts.clear();\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/features/note/services/search.ts"],"sourcesContent":["import { isBlockNoteTreeNode, walkBlockNoteTree } from '~/modules/blocknote-tree.js';\n\nexport interface NoteSearchQuery {\n included: string[];\n excluded: string[];\n hasFilters: boolean;\n}\n\ninterface SearchableNoteLike {\n title: string;\n content: string;\n searchableText?: string;\n searchableTextVersion?: number;\n}\n\nexport interface NoteSearchProjection {\n searchableText: string;\n searchableTextVersion: number;\n}\n\ninterface SearchProjectionContext {\n segments: string[];\n}\n\ntype SearchableJsonNode = Record<string, unknown>;\ntype SearchNodeExtractor = (node: SearchableJsonNode, context: SearchProjectionContext) => void;\ntype SearchExtractorNodeType = (typeof NOTE_SEARCH_EXTRACTOR_NODE_TYPES)[number];\n\n// If we later materialize searchable text into a DB column, bump this when the\n// extraction meaning changes so stored projections can be rebuilt safely.\nexport const NOTE_SEARCH_TEXT_SCHEMA_VERSION = 1;\n\nconst FILE_BLOCK_VISIBLE_PROP_KEYS = ['name', 'caption'] as const;\n\nexport const NOTE_SEARCH_EXTRACTOR_NODE_TYPES = [\n 'text',\n 'tag',\n 'reference',\n 'image',\n 'video',\n 'audio',\n 'file',\n] as const;\n\nexport const NOTE_SEARCH_PASS_THROUGH_NODE_TYPES = [\n 'link',\n 'paragraph',\n 'heading',\n 'bulletListItem',\n 'numberedListItem',\n 'checkListItem',\n 'toggleListItem',\n 'quote',\n 'codeBlock',\n 'divider',\n 'table',\n 'tableContent',\n 'tableCell',\n] as const;\n\nexport const NOTE_SEARCH_IGNORED_NODE_TYPES = ['tableOfContents'] as const;\n\nconst KNOWN_NOTE_SEARCH_NODE_TYPES = new Set<string>([\n ...NOTE_SEARCH_EXTRACTOR_NODE_TYPES,\n ...NOTE_SEARCH_PASS_THROUGH_NODE_TYPES,\n ...NOTE_SEARCH_IGNORED_NODE_TYPES,\n]);\nconst SEARCH_NODE_EXTRACTOR_TYPE_SET = new Set<string>(NOTE_SEARCH_EXTRACTOR_NODE_TYPES);\n\nconst unknownNoteSearchNodeTypeCounts = new Map<string, number>();\n\nconst SEARCH_NODE_EXTRACTORS: Record<SearchExtractorNodeType, SearchNodeExtractor> = {\n text: (node, context) => {\n pushVisibleSegment(context, node.text);\n },\n tag: (node, context) => {\n pushVisibleProps(context, node.props, ['tag']);\n },\n reference: (node, context) => {\n pushVisibleProps(context, node.props, ['title']);\n },\n image: (node, context) => {\n pushVisibleProps(context, node.props, FILE_BLOCK_VISIBLE_PROP_KEYS);\n },\n video: (node, context) => {\n pushVisibleProps(context, node.props, FILE_BLOCK_VISIBLE_PROP_KEYS);\n },\n audio: (node, context) => {\n pushVisibleProps(context, node.props, FILE_BLOCK_VISIBLE_PROP_KEYS);\n },\n file: (node, context) => {\n pushVisibleProps(context, node.props, FILE_BLOCK_VISIBLE_PROP_KEYS);\n },\n};\n\nconst recordUnknownNoteSearchNodeType = (type: string) => {\n if (KNOWN_NOTE_SEARCH_NODE_TYPES.has(type)) {\n return;\n }\n\n const nextCount = (unknownNoteSearchNodeTypeCounts.get(type) ?? 0) + 1;\n unknownNoteSearchNodeTypeCounts.set(type, nextCount);\n\n if (nextCount === 1) {\n process.emitWarning(\n `note-search encountered unsupported BlockNote node type \"${type}\". Only nested text will be indexed until explicit support is added.`,\n { code: 'OCEAN_BRAIN_NOTE_SEARCH_UNKNOWN_NODE' },\n );\n }\n};\n\nconst isSearchExtractorNodeType = (type: string): type is SearchExtractorNodeType => {\n return SEARCH_NODE_EXTRACTOR_TYPE_SET.has(type);\n};\n\nconst normalizeSearchText = (value: string) => value.replace(/\\s+/g, ' ').trim();\n\nconst normalizeSearchToken = (value: string) => normalizeSearchText(value).toLowerCase();\n\nconst pushVisibleSegment = (context: SearchProjectionContext, value: unknown) => {\n if (typeof value !== 'string') {\n return;\n }\n\n const normalizedValue = normalizeSearchText(value);\n\n if (!normalizedValue) {\n return;\n }\n\n context.segments.push(normalizedValue);\n};\n\nconst pushVisibleProps = (context: SearchProjectionContext, props: unknown, keys: readonly string[]) => {\n if (!isBlockNoteTreeNode(props)) {\n return;\n }\n\n for (const key of keys) {\n pushVisibleSegment(context, props[key]);\n }\n};\n\nconst collectVisibleSearchSegments = (node: unknown, context: SearchProjectionContext) => {\n walkBlockNoteTree(node, (item) => {\n const type = typeof item.type === 'string' ? item.type : undefined;\n\n if (type) {\n if (isSearchExtractorNodeType(type)) {\n SEARCH_NODE_EXTRACTORS[type](item, context);\n }\n\n recordUnknownNoteSearchNodeType(type);\n }\n });\n};\n\nexport const buildNoteSearchText = (note: Pick<SearchableNoteLike, 'title' | 'content'>) => {\n const normalizedTitle = normalizeSearchText(note.title);\n const normalizedContent = extractVisibleSearchTextFromContent(note.content);\n\n return normalizeSearchToken(`${normalizedTitle} ${normalizedContent}`);\n};\n\nexport const buildNoteSearchProjection = (\n note: Pick<SearchableNoteLike, 'title' | 'content'>,\n): NoteSearchProjection => {\n return {\n searchableText: buildNoteSearchText(note),\n searchableTextVersion: NOTE_SEARCH_TEXT_SCHEMA_VERSION,\n };\n};\n\nexport const parseNoteSearchQuery = (query: string): NoteSearchQuery => {\n const included: string[] = [];\n const excluded: string[] = [];\n\n for (const item of query\n .split(/\\s+/)\n .map((value) => value.trim())\n .filter(Boolean)) {\n if (item.startsWith('-') && item.length > 1) {\n excluded.push(normalizeSearchToken(item.slice(1)));\n continue;\n }\n\n included.push(normalizeSearchToken(item));\n }\n\n return {\n included,\n excluded,\n hasFilters: included.length > 0 || excluded.length > 0,\n };\n};\n\nexport const extractVisibleSearchTextFromContent = (content: string) => {\n try {\n const parsed = JSON.parse(content) as unknown;\n const context: SearchProjectionContext = { segments: [] };\n\n collectVisibleSearchSegments(parsed, context);\n\n return normalizeSearchText(context.segments.join(' '));\n } catch {\n return '';\n }\n};\n\nexport const matchesNoteSearchQuery = (note: SearchableNoteLike, query: string | NoteSearchQuery) => {\n const parsedQuery = typeof query === 'string' ? parseNoteSearchQuery(query) : query;\n\n if (!parsedQuery.hasFilters) {\n return true;\n }\n\n const haystack =\n note.searchableTextVersion === NOTE_SEARCH_TEXT_SCHEMA_VERSION && typeof note.searchableText === 'string'\n ? normalizeSearchToken(note.searchableText)\n : buildNoteSearchText(note);\n\n return (\n parsedQuery.included.every((term) => haystack.includes(term)) &&\n parsedQuery.excluded.every((term) => !haystack.includes(term))\n );\n};\n\nexport const filterNotesBySearchQuery = <T extends SearchableNoteLike>(notes: T[], query: string | NoteSearchQuery) => {\n const parsedQuery = typeof query === 'string' ? parseNoteSearchQuery(query) : query;\n\n if (!parsedQuery.hasFilters) {\n return notes;\n }\n\n return notes.filter((note) => matchesNoteSearchQuery(note, parsedQuery));\n};\n\nexport const getUnknownNoteSearchNodeTypeCounts = () => {\n return new Map(unknownNoteSearchNodeTypeCounts);\n};\n\nexport const resetUnknownNoteSearchNodeTypeCountsForTest = () => {\n unknownNoteSearchNodeTypeCounts.clear();\n};\n"],"mappings":"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","names":[]}
@@ -1,11 +1,15 @@
1
+ import { ensureTagByName } from "../../../features/tag/services/organization.js";
1
2
  import models from "../../../models.js";
3
+ import { blocksToMarkdown, extractTagIdsFromContentJson } from "../../../modules/blocknote.js";
4
+ import { parseBlockNoteContent, walkBlockNoteTree } from "../../../modules/blocknote-tree.js";
2
5
  import {
3
6
  createRetentionCutoff,
4
7
  RECOVERY_CLEANUP_BATCH_LIMIT,
5
8
  SNAPSHOT_MAX_PER_NOTE,
6
9
  SNAPSHOT_RETENTION_DAYS
7
10
  } from "../../../modules/recovery-retention.js";
8
- import { buildNoteSearchProjection } from "./search.js";
11
+ import { buildNoteSearchProjection, extractVisibleSearchTextFromContent } from "./search.js";
12
+ const SNAPSHOT_CONTENT_PREVIEW_MAX_LENGTH = 240;
9
13
  const parseMeta = (value) => {
10
14
  if (!value) {
11
15
  return {};
@@ -20,11 +24,27 @@ const parseMeta = (value) => {
20
24
  return {};
21
25
  }
22
26
  };
27
+ const readSnapshotContent = (payload) => {
28
+ try {
29
+ return parsePayload(payload).content;
30
+ } catch {
31
+ return "";
32
+ }
33
+ };
34
+ const buildSnapshotContentPreview = (content) => {
35
+ const preview = extractVisibleSearchTextFromContent(content);
36
+ if (preview.length <= SNAPSHOT_CONTENT_PREVIEW_MAX_LENGTH) {
37
+ return preview;
38
+ }
39
+ return `${preview.slice(0, SNAPSHOT_CONTENT_PREVIEW_MAX_LENGTH).trimEnd()}...`;
40
+ };
23
41
  const serializeSnapshot = (snapshot) => ({
24
42
  id: String(snapshot.id),
25
43
  title: snapshot.title,
44
+ contentPreview: buildSnapshotContentPreview(readSnapshotContent(snapshot.payload)),
26
45
  createdAt: snapshot.createdAt.toISOString(),
27
- meta: parseMeta(snapshot.meta)
46
+ meta: parseMeta(snapshot.meta),
47
+ payload: snapshot.payload
28
48
  });
29
49
  const serializePayload = (note) => {
30
50
  const payload = {
@@ -32,7 +52,13 @@ const serializePayload = (note) => {
32
52
  content: note.content,
33
53
  pinned: note.pinned,
34
54
  order: note.order,
35
- layout: note.layout
55
+ layout: note.layout,
56
+ ...note.properties ? {
57
+ properties: note.properties.map((property) => ({
58
+ ...property,
59
+ ...property.dateValue instanceof Date ? { dateValue: property.dateValue.toISOString() } : {}
60
+ }))
61
+ } : {}
36
62
  };
37
63
  return JSON.stringify(payload);
38
64
  };
@@ -46,6 +72,100 @@ const parsePayload = (payload) => {
46
72
  }
47
73
  return parsed;
48
74
  };
75
+ const extractRestoredTagIds = (content) => {
76
+ try {
77
+ return extractTagIdsFromContentJson(content).map(normalizeRestoredTagId).filter((id) => id !== null);
78
+ } catch {
79
+ return null;
80
+ }
81
+ };
82
+ const isRecord = (value) => {
83
+ return typeof value === "object" && value !== null;
84
+ };
85
+ const normalizeRestoredTagId = (value) => {
86
+ if (value === void 0 || value === null) {
87
+ return null;
88
+ }
89
+ const normalized = typeof value === "string" ? value.trim() : value;
90
+ if (normalized === "") {
91
+ return null;
92
+ }
93
+ const numericId = Number(normalized);
94
+ return Number.isSafeInteger(numericId) && numericId > 0 ? numericId : null;
95
+ };
96
+ const normalizeRestoredTagName = (value) => {
97
+ if (typeof value !== "string") {
98
+ return null;
99
+ }
100
+ const normalized = value.trim();
101
+ return normalized ? normalized : null;
102
+ };
103
+ const resolveRestoredSnapshotTags = async (content, deps) => {
104
+ const parsed = parseBlockNoteContent(content);
105
+ if (!parsed) {
106
+ return null;
107
+ }
108
+ const tagRefs = [];
109
+ walkBlockNoteTree(parsed, (node) => {
110
+ if (node.type !== "tag" || !isRecord(node.props)) {
111
+ return;
112
+ }
113
+ tagRefs.push({
114
+ id: normalizeRestoredTagId(node.props.id),
115
+ name: normalizeRestoredTagName(node.props.tag),
116
+ node,
117
+ props: node.props
118
+ });
119
+ });
120
+ if (tagRefs.length === 0) {
121
+ return { content, tagIds: [] };
122
+ }
123
+ const candidateIds = Array.from(new Set(tagRefs.map((tag) => tag.id).filter((id) => id !== null)));
124
+ const existingTags = candidateIds.length > 0 ? await deps.findTagsByIds(candidateIds) : [];
125
+ const existingTagIds = new Set(existingTags.map((tag) => tag.id));
126
+ const tagIds = /* @__PURE__ */ new Set();
127
+ let contentChanged = false;
128
+ for (const tagRef of tagRefs) {
129
+ if (tagRef.id !== null && existingTagIds.has(tagRef.id)) {
130
+ tagIds.add(tagRef.id);
131
+ continue;
132
+ }
133
+ if (!tagRef.name) {
134
+ continue;
135
+ }
136
+ try {
137
+ const resolvedTag = await deps.ensureTagByName(tagRef.name);
138
+ tagRef.node.props = {
139
+ ...tagRef.props,
140
+ id: String(resolvedTag.id),
141
+ tag: resolvedTag.name
142
+ };
143
+ tagIds.add(resolvedTag.id);
144
+ contentChanged = true;
145
+ } catch {
146
+ continue;
147
+ }
148
+ }
149
+ return {
150
+ content: contentChanged ? JSON.stringify(parsed) : content,
151
+ tagIds: [...tagIds]
152
+ };
153
+ };
154
+ const renderNoteSnapshotContentAsMarkdown = async (payload) => {
155
+ try {
156
+ const snapshotPayload = parsePayload(payload);
157
+ return blocksToMarkdown(snapshotPayload.content);
158
+ } catch {
159
+ return "";
160
+ }
161
+ };
162
+ const serializeSnapshotWithContent = async (snapshot) => {
163
+ const contentAsMarkdown = await renderNoteSnapshotContentAsMarkdown(snapshot.payload);
164
+ return {
165
+ ...serializeSnapshot(snapshot),
166
+ contentAsMarkdown
167
+ };
168
+ };
49
169
  const createSnapshotMetaFromUserAgent = (userAgent) => {
50
170
  const normalized = userAgent?.toLowerCase() ?? "";
51
171
  const isMobile = /iphone|ipad|android|mobile/.test(normalized);
@@ -92,11 +212,11 @@ const defaultTrimOverflowSnapshots = async (noteId, keep, limit) => {
92
212
  const createNoteSnapshotService = (deps) => ({
93
213
  captureBaseline: async (input) => {
94
214
  await deps.purgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);
95
- const existing = input.editSessionId ? await deps.findSnapshotByEditSessionId(input.noteId, input.editSessionId) : null;
215
+ const existing = input.editSessionId && !input.force ? await deps.findSnapshotByEditSessionId(input.noteId, input.editSessionId) : null;
96
216
  if (existing) {
97
217
  return serializeSnapshot(existing);
98
218
  }
99
- const note = await deps.findNoteById(input.noteId);
219
+ const note = input.baseline ?? await deps.findNoteById(input.noteId);
100
220
  if (!note) {
101
221
  return null;
102
222
  }
@@ -109,18 +229,26 @@ const createNoteSnapshotService = (deps) => ({
109
229
  noteId: note.id,
110
230
  title: note.title,
111
231
  payload,
112
- ...input.editSessionId ? { editSessionId: input.editSessionId } : {},
232
+ ...input.editSessionId && !input.force ? { editSessionId: input.editSessionId } : {},
113
233
  ...input.meta ? { meta: input.meta } : {}
114
234
  });
115
235
  await deps.trimOverflowSnapshots(note.id, SNAPSHOT_MAX_PER_NOTE, RECOVERY_CLEANUP_BATCH_LIMIT);
116
236
  return serializeSnapshot(snapshot);
117
237
  },
118
- listSnapshots: async (noteId, limit = 5) => {
238
+ listSnapshots: async (noteId, limit = SNAPSHOT_MAX_PER_NOTE) => {
119
239
  await deps.purgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);
120
240
  await deps.trimOverflowSnapshots(noteId, SNAPSHOT_MAX_PER_NOTE, RECOVERY_CLEANUP_BATCH_LIMIT);
121
241
  const snapshots = await deps.listSnapshots(noteId, limit);
122
242
  return snapshots.map(serializeSnapshot);
123
243
  },
244
+ getSnapshot: async (snapshotId) => {
245
+ await deps.purgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);
246
+ const snapshot = await deps.findSnapshotById(snapshotId);
247
+ if (!snapshot) {
248
+ return null;
249
+ }
250
+ return serializeSnapshotWithContent(snapshot);
251
+ },
124
252
  restoreSnapshot: async (snapshotId, options) => {
125
253
  await deps.purgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);
126
254
  const snapshot = await deps.findSnapshotById(snapshotId);
@@ -131,6 +259,13 @@ const createNoteSnapshotService = (deps) => ({
131
259
  if (!note) {
132
260
  return null;
133
261
  }
262
+ const payload = parsePayload(snapshot.payload);
263
+ const resolvedTags = deps.resolveRestoredTags ? await deps.resolveRestoredTags(payload.content) : extractRestoredTagIds(payload.content);
264
+ const restoredPayload = resolvedTags && !Array.isArray(resolvedTags) ? {
265
+ ...payload,
266
+ content: resolvedTags.content
267
+ } : payload;
268
+ const restoredTagIds = Array.isArray(resolvedTags) ? resolvedTags : resolvedTags?.tagIds;
134
269
  const currentPayload = serializePayload(note);
135
270
  const latestSnapshot = await deps.findLatestSnapshot(note.id);
136
271
  if (!hasSameSnapshotPayload(latestSnapshot, currentPayload)) {
@@ -141,14 +276,43 @@ const createNoteSnapshotService = (deps) => ({
141
276
  ...options?.meta ? { meta: options.meta } : {}
142
277
  });
143
278
  }
279
+ const restoredNote = await deps.updateNote(snapshot.noteId, {
280
+ ...restoredPayload,
281
+ ...restoredTagIds !== void 0 ? { tagIds: restoredTagIds } : {}
282
+ });
144
283
  await deps.trimOverflowSnapshots(note.id, SNAPSHOT_MAX_PER_NOTE, RECOVERY_CLEANUP_BATCH_LIMIT);
145
- const payload = parsePayload(snapshot.payload);
146
- return deps.updateNote(snapshot.noteId, payload);
284
+ return restoredNote;
147
285
  }
148
286
  });
149
287
  const defaultNoteSnapshotService = createNoteSnapshotService({
150
288
  findNoteById: async (id) => {
151
- return models.note.findUnique({ where: { id } });
289
+ const note = await models.note.findUnique({
290
+ where: { id },
291
+ include: {
292
+ properties: {
293
+ include: { definition: true, option: true }
294
+ }
295
+ }
296
+ });
297
+ if (!note) {
298
+ return null;
299
+ }
300
+ return {
301
+ ...note,
302
+ properties: note.properties.map((property) => ({
303
+ key: property.definition.key,
304
+ name: property.definition.name,
305
+ valueType: property.definition.valueType,
306
+ textValue: property.textValue,
307
+ textValueNormalized: property.textValueNormalized,
308
+ numberValue: property.numberValue,
309
+ dateValue: property.dateValue,
310
+ boolValue: property.boolValue,
311
+ optionValue: property.option?.value ?? null,
312
+ optionLabel: property.option?.label ?? null,
313
+ optionColor: property.option?.color ?? null
314
+ }))
315
+ };
152
316
  },
153
317
  findSnapshotByEditSessionId: async (noteId, editSessionId) => {
154
318
  return models.noteSnapshot.findFirst({
@@ -177,6 +341,26 @@ const defaultNoteSnapshotService = createNoteSnapshotService({
177
341
  },
178
342
  purgeExpiredSnapshots: defaultPurgeExpiredSnapshots,
179
343
  trimOverflowSnapshots: defaultTrimOverflowSnapshots,
344
+ resolveRestoredTags: async (content) => {
345
+ return resolveRestoredSnapshotTags(content, {
346
+ ensureTagByName: async (name) => {
347
+ const result = await ensureTagByName(name);
348
+ return {
349
+ id: Number(result.tag.id),
350
+ name: result.tag.name
351
+ };
352
+ },
353
+ findTagsByIds: async (ids) => {
354
+ return models.tag.findMany({
355
+ where: { id: { in: ids } },
356
+ select: {
357
+ id: true,
358
+ name: true
359
+ }
360
+ });
361
+ }
362
+ });
363
+ },
180
364
  listSnapshots: async (noteId, limit) => {
181
365
  return models.noteSnapshot.findMany({
182
366
  where: { noteId },
@@ -188,15 +372,67 @@ const defaultNoteSnapshotService = createNoteSnapshotService({
188
372
  return models.noteSnapshot.findUnique({ where: { id } });
189
373
  },
190
374
  updateNote: async (id, input) => {
191
- return models.note.update({
192
- where: { id },
193
- data: {
194
- ...input,
195
- ...buildNoteSearchProjection({
196
- title: input.title,
197
- content: input.content
198
- })
375
+ const { tagIds, properties, ...noteInput } = input;
376
+ return models.$transaction(async (tx) => {
377
+ const note = await tx.note.update({
378
+ where: { id },
379
+ data: {
380
+ ...noteInput,
381
+ ...buildNoteSearchProjection({
382
+ title: noteInput.title,
383
+ content: noteInput.content
384
+ }),
385
+ ...tagIds !== void 0 ? { tags: { set: tagIds.map((tagId) => ({ id: tagId })) } } : {}
386
+ }
387
+ });
388
+ if (properties !== void 0) {
389
+ await tx.noteProperty.deleteMany({ where: { noteId: id } });
390
+ for (const property of properties) {
391
+ const definition = await tx.propertyDefinition.upsert({
392
+ where: { key: property.key },
393
+ create: {
394
+ key: property.key,
395
+ name: property.name,
396
+ valueType: property.valueType
397
+ },
398
+ update: {
399
+ name: property.name,
400
+ valueType: property.valueType
401
+ }
402
+ });
403
+ const option = property.valueType === "select" && property.optionValue ? await tx.propertyOption.upsert({
404
+ where: {
405
+ propertyDefinitionId_value: {
406
+ propertyDefinitionId: definition.id,
407
+ value: property.optionValue
408
+ }
409
+ },
410
+ create: {
411
+ propertyDefinitionId: definition.id,
412
+ value: property.optionValue,
413
+ label: property.optionLabel ?? property.optionValue,
414
+ color: property.optionColor ?? null
415
+ },
416
+ update: {
417
+ label: property.optionLabel ?? property.optionValue,
418
+ color: property.optionColor ?? null
419
+ }
420
+ }) : null;
421
+ await tx.noteProperty.create({
422
+ data: {
423
+ noteId: id,
424
+ propertyDefinitionId: definition.id,
425
+ optionId: option?.id ?? null,
426
+ textValue: property.textValue ?? null,
427
+ textValueNormalized: property.textValueNormalized ?? null,
428
+ numberValue: property.numberValue ?? null,
429
+ dateValue: property.dateValue ? new Date(property.dateValue) : null,
430
+ boolValue: property.boolValue ?? null
431
+ }
432
+ });
433
+ }
199
434
  }
435
+ return note;
200
436
  });
201
437
  }
202
438
  });
@@ -206,6 +442,9 @@ const captureNoteBaseline = async (input) => {
206
442
  const listNoteSnapshots = async (noteId, limit) => {
207
443
  return defaultNoteSnapshotService.listSnapshots(noteId, limit);
208
444
  };
445
+ const getNoteSnapshot = async (snapshotId) => {
446
+ return defaultNoteSnapshotService.getSnapshot(snapshotId);
447
+ };
209
448
  const restoreNoteSnapshot = async (snapshotId, options) => {
210
449
  return defaultNoteSnapshotService.restoreSnapshot(snapshotId, options);
211
450
  };
@@ -218,8 +457,11 @@ export {
218
457
  createNoteSnapshotService,
219
458
  createSnapshotMetaFromUserAgent,
220
459
  defaultNoteSnapshotService,
460
+ getNoteSnapshot,
221
461
  listNoteSnapshots,
222
462
  purgeExpiredNoteSnapshots,
463
+ renderNoteSnapshotContentAsMarkdown,
464
+ resolveRestoredSnapshotTags,
223
465
  restoreNoteSnapshot
224
466
  };
225
467
  //# sourceMappingURL=snapshot.js.map