ocean-brain 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/mcp.js +660 -31
  2. package/package.json +2 -1
  3. package/server/client/dist/assets/{Calendar-BU9XgX2N.js → Calendar-BJHLksEZ.js} +1 -1
  4. package/server/client/dist/assets/{Callout-C6tVCxOT.js → Callout-wFx9oCas.js} +1 -1
  5. package/server/client/dist/assets/Graph-9piERaNR.js +1 -0
  6. package/server/client/dist/assets/{Image-DxWhlIDG.js → Image-DK0lnaK2.js} +1 -1
  7. package/server/client/dist/assets/{Image.es-BjGK7y6Y.js → Image.es-Be7par_j.js} +1 -1
  8. package/server/client/dist/assets/Note-CC9ybxXL.js +21 -0
  9. package/server/client/dist/assets/{Plus.es-EcmtS_uw.js → Plus.es-BKm7uDYu.js} +1 -1
  10. package/server/client/dist/assets/Reminders-DRxA8v98.js +1 -0
  11. package/server/client/dist/assets/Search-BunSQrqv.js +1 -0
  12. package/server/client/dist/assets/{SurfaceCard-CO0oVxeL.js → SurfaceCard-FhlC8VSR.js} +1 -1
  13. package/server/client/dist/assets/Tag-z_crJ5JP.js +1 -0
  14. package/server/client/dist/assets/TagNotes-D7wIJ4jy.js +1 -0
  15. package/server/client/dist/assets/{Trash.es-DmwfSy3l.js → Trash.es-ByMVwx0d.js} +1 -1
  16. package/server/client/dist/assets/ViewNotes-Duo9XmcJ.js +1 -0
  17. package/server/client/dist/assets/Views-D4Z_O9Kd.js +1 -0
  18. package/server/client/dist/assets/{image.api-DeF9ck7g.js → image.api-B5Qi-Txv.js} +1 -1
  19. package/server/client/dist/assets/{index-BZUGWdOU.css → index-Cd81UxEh.css} +1 -1
  20. package/server/client/dist/assets/index-cHJQCo8V.js +1 -0
  21. package/server/client/dist/assets/index-nYj7BL3a.js +327 -0
  22. package/server/client/dist/assets/manage-image-detail-DcZggB1M.js +1 -0
  23. package/server/client/dist/assets/manage-image-glbtBibn.js +1 -0
  24. package/server/client/dist/assets/{mcp-Bh743HBz.js → mcp-BBUpQdS9.js} +1 -1
  25. package/server/client/dist/assets/{note-core-Z6kqulGB.js → note-core-DMZcZ4yV.js} +53 -53
  26. package/server/client/dist/assets/{note-vendor-i1M2FSI2.js → note-vendor-uuJO2MTD.js} +1 -1
  27. package/server/client/dist/assets/{placeholder-XQqo1Fvr.js → placeholder-B_qb_qkc.js} +5 -5
  28. package/server/client/dist/assets/properties-CiyQv6Tb.js +1 -0
  29. package/server/client/dist/assets/trash-CQnj4Rmv.js +1 -0
  30. package/server/client/dist/assets/{useReminderMutate-D21PurIS.js → useReminderMutate-CSKliBcc.js} +1 -1
  31. package/server/client/dist/assets/view-dashboard-B5fKgvk_.js +99 -0
  32. package/server/client/dist/index.html +4 -4
  33. package/server/dist/features/note/graphql/note.field.resolver.js +2 -0
  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 +119 -0
  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 +14 -0
  38. package/server/dist/features/note/graphql/note.query.resolver.js.map +1 -1
  39. package/server/dist/features/note/graphql/note.type-defs.js +100 -0
  40. package/server/dist/features/note/graphql/note.type-defs.js.map +1 -1
  41. package/server/dist/features/note/http/mcp.js +394 -1
  42. package/server/dist/features/note/http/mcp.js.map +1 -1
  43. package/server/dist/features/note/services/markdown-intent-write.js +495 -0
  44. package/server/dist/features/note/services/markdown-intent-write.js.map +1 -0
  45. package/server/dist/features/note/services/markdown-patch.js +601 -0
  46. package/server/dist/features/note/services/markdown-patch.js.map +1 -0
  47. package/server/dist/features/note/services/properties.js +874 -0
  48. package/server/dist/features/note/services/properties.js.map +1 -0
  49. package/server/dist/features/note/services/snapshot.js +94 -12
  50. package/server/dist/features/note/services/snapshot.js.map +1 -1
  51. package/server/dist/features/note/services/trash.js +70 -2
  52. package/server/dist/features/note/services/trash.js.map +1 -1
  53. package/server/dist/features/note/services/write.js +21 -7
  54. package/server/dist/features/note/services/write.js.map +1 -1
  55. package/server/dist/features/view/graphql/view.query.resolver.js +31 -1
  56. package/server/dist/features/view/graphql/view.query.resolver.js.map +1 -1
  57. package/server/dist/features/view/graphql/view.type-defs.js +57 -1
  58. package/server/dist/features/view/graphql/view.type-defs.js.map +1 -1
  59. package/server/dist/features/view/services/workspace.js +367 -43
  60. package/server/dist/features/view/services/workspace.js.map +1 -1
  61. package/server/dist/modules/blocknote.js +367 -7
  62. package/server/dist/modules/blocknote.js.map +1 -1
  63. package/server/dist/routes/mcp.js +21 -1
  64. package/server/dist/routes/mcp.js.map +1 -1
  65. package/server/prisma/migrations/20260527073000_0017_note_properties/migration.sql +105 -0
  66. package/server/prisma/migrations/20260531073000_0018_view_query/migration.sql +3 -0
  67. package/server/prisma/schema.prisma +226 -130
  68. package/server/client/dist/assets/Graph-Dond2hGK.js +0 -1
  69. package/server/client/dist/assets/Note-Cr6GtXUM.js +0 -21
  70. package/server/client/dist/assets/Reminders-BlLLT_x0.js +0 -1
  71. package/server/client/dist/assets/Search-CrZ0ap8x.js +0 -1
  72. package/server/client/dist/assets/Tag-vws5AfNW.js +0 -1
  73. package/server/client/dist/assets/TagNotes-Fmf1Kgim.js +0 -1
  74. package/server/client/dist/assets/ViewNotes-BALN7IfH.js +0 -1
  75. package/server/client/dist/assets/Views-Ccc_Ml5U.js +0 -1
  76. package/server/client/dist/assets/index-BSVE2cGd.js +0 -1
  77. package/server/client/dist/assets/index-BvKBTjQq.js +0 -246
  78. package/server/client/dist/assets/manage-image-IoTXaMBP.js +0 -1
  79. package/server/client/dist/assets/manage-image-detail-BRTXwOsh.js +0 -1
  80. package/server/client/dist/assets/trash-hiJIg6-c.js +0 -1
  81. package/server/client/dist/assets/view-dashboard-BuaZ4MvI.js +0 -90
@@ -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":[]}
@@ -52,7 +52,13 @@ const serializePayload = (note) => {
52
52
  content: note.content,
53
53
  pinned: note.pinned,
54
54
  order: note.order,
55
- 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
+ } : {}
56
62
  };
57
63
  return JSON.stringify(payload);
58
64
  };
@@ -280,7 +286,33 @@ const createNoteSnapshotService = (deps) => ({
280
286
  });
281
287
  const defaultNoteSnapshotService = createNoteSnapshotService({
282
288
  findNoteById: async (id) => {
283
- 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
+ };
284
316
  },
285
317
  findSnapshotByEditSessionId: async (noteId, editSessionId) => {
286
318
  return models.noteSnapshot.findFirst({
@@ -340,17 +372,67 @@ const defaultNoteSnapshotService = createNoteSnapshotService({
340
372
  return models.noteSnapshot.findUnique({ where: { id } });
341
373
  },
342
374
  updateNote: async (id, input) => {
343
- const { tagIds, ...noteInput } = input;
344
- return models.note.update({
345
- where: { id },
346
- data: {
347
- ...noteInput,
348
- ...buildNoteSearchProjection({
349
- title: noteInput.title,
350
- content: noteInput.content
351
- }),
352
- ...tagIds !== void 0 ? { tags: { set: tagIds.map((tagId) => ({ id: tagId })) } } : {}
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
+ }
353
434
  }
435
+ return note;
354
436
  });
355
437
  }
356
438
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/features/note/services/snapshot.ts"],"sourcesContent":["import { ensureTagByName } from '~/features/tag/services/organization.js';\nimport models, { type NoteLayout } 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}\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 },\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\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 };\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 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 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, ...noteInput } = input;\n\n return models.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});\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":[]}
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
  });