comisai 1.0.11 → 1.0.13

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 (255) hide show
  1. package/node_modules/@comis/agent/dist/model/operation-model-defaults.js +1 -0
  2. package/node_modules/@comis/agent/dist/session/comis-session-manager.js +9 -9
  3. package/node_modules/@comis/agent/package.json +1 -1
  4. package/node_modules/@comis/channels/package.json +1 -1
  5. package/node_modules/@comis/cli/package.json +1 -1
  6. package/node_modules/@comis/core/dist/event-bus/events-infra.d.ts +2 -0
  7. package/node_modules/@comis/core/package.json +1 -1
  8. package/node_modules/@comis/daemon/dist/wiring/setup-channels.js +12 -1
  9. package/node_modules/@comis/daemon/dist/wiring/setup-gateway.js +1 -0
  10. package/node_modules/@comis/daemon/dist/wiring/setup-schedulers.js +1 -0
  11. package/node_modules/@comis/daemon/package.json +1 -1
  12. package/node_modules/@comis/gateway/package.json +1 -1
  13. package/node_modules/@comis/infra/package.json +1 -1
  14. package/node_modules/@comis/memory/package.json +1 -1
  15. package/node_modules/@comis/scheduler/dist/cron/cron-types.d.ts +6 -0
  16. package/node_modules/@comis/scheduler/dist/cron/cron-types.js +2 -0
  17. package/node_modules/@comis/scheduler/package.json +1 -1
  18. package/node_modules/@comis/shared/package.json +1 -1
  19. package/node_modules/@comis/skills/dist/builtin/platform/gateway-tool.d.ts +1 -1
  20. package/node_modules/@comis/skills/package.json +1 -1
  21. package/package.json +18 -18
  22. package/node_modules/@comis/core/dist/approval/approval-gate.test.d.ts +0 -1
  23. package/node_modules/@comis/core/dist/approval/approval-gate.test.js +0 -1003
  24. package/node_modules/@comis/core/dist/bootstrap.test.d.ts +0 -1
  25. package/node_modules/@comis/core/dist/bootstrap.test.js +0 -150
  26. package/node_modules/@comis/core/dist/config/backup.test.d.ts +0 -1
  27. package/node_modules/@comis/core/dist/config/backup.test.js +0 -160
  28. package/node_modules/@comis/core/dist/config/env-substitution.test.d.ts +0 -1
  29. package/node_modules/@comis/core/dist/config/env-substitution.test.js +0 -259
  30. package/node_modules/@comis/core/dist/config/field-metadata.test.d.ts +0 -1
  31. package/node_modules/@comis/core/dist/config/field-metadata.test.js +0 -72
  32. package/node_modules/@comis/core/dist/config/git-manager.test.d.ts +0 -1
  33. package/node_modules/@comis/core/dist/config/git-manager.test.js +0 -1042
  34. package/node_modules/@comis/core/dist/config/immutable-keys.test.d.ts +0 -1
  35. package/node_modules/@comis/core/dist/config/immutable-keys.test.js +0 -283
  36. package/node_modules/@comis/core/dist/config/include-resolver.test.d.ts +0 -1
  37. package/node_modules/@comis/core/dist/config/include-resolver.test.js +0 -292
  38. package/node_modules/@comis/core/dist/config/layered.test.d.ts +0 -1
  39. package/node_modules/@comis/core/dist/config/layered.test.js +0 -211
  40. package/node_modules/@comis/core/dist/config/loader.test.d.ts +0 -1
  41. package/node_modules/@comis/core/dist/config/loader.test.js +0 -300
  42. package/node_modules/@comis/core/dist/config/migrate.test.d.ts +0 -1
  43. package/node_modules/@comis/core/dist/config/migrate.test.js +0 -244
  44. package/node_modules/@comis/core/dist/config/partial-validator.test.d.ts +0 -1
  45. package/node_modules/@comis/core/dist/config/partial-validator.test.js +0 -98
  46. package/node_modules/@comis/core/dist/config/schema-agent-model.test.d.ts +0 -1
  47. package/node_modules/@comis/core/dist/config/schema-agent-model.test.js +0 -279
  48. package/node_modules/@comis/core/dist/config/schema-agent-session.test.d.ts +0 -1
  49. package/node_modules/@comis/core/dist/config/schema-agent-session.test.js +0 -242
  50. package/node_modules/@comis/core/dist/config/schema-agent.test.d.ts +0 -1
  51. package/node_modules/@comis/core/dist/config/schema-agent.test.js +0 -645
  52. package/node_modules/@comis/core/dist/config/schema-channel.test.d.ts +0 -1
  53. package/node_modules/@comis/core/dist/config/schema-channel.test.js +0 -341
  54. package/node_modules/@comis/core/dist/config/schema-coalescer.test.d.ts +0 -1
  55. package/node_modules/@comis/core/dist/config/schema-coalescer.test.js +0 -51
  56. package/node_modules/@comis/core/dist/config/schema-context-engine.test.d.ts +0 -1
  57. package/node_modules/@comis/core/dist/config/schema-context-engine.test.js +0 -797
  58. package/node_modules/@comis/core/dist/config/schema-context-guard.test.d.ts +0 -1
  59. package/node_modules/@comis/core/dist/config/schema-context-guard.test.js +0 -111
  60. package/node_modules/@comis/core/dist/config/schema-daemon.test.d.ts +0 -1
  61. package/node_modules/@comis/core/dist/config/schema-daemon.test.js +0 -107
  62. package/node_modules/@comis/core/dist/config/schema-delivery-timing.test.d.ts +0 -1
  63. package/node_modules/@comis/core/dist/config/schema-delivery-timing.test.js +0 -51
  64. package/node_modules/@comis/core/dist/config/schema-documentation.test.d.ts +0 -1
  65. package/node_modules/@comis/core/dist/config/schema-documentation.test.js +0 -63
  66. package/node_modules/@comis/core/dist/config/schema-gateway.test.d.ts +0 -1
  67. package/node_modules/@comis/core/dist/config/schema-gateway.test.js +0 -219
  68. package/node_modules/@comis/core/dist/config/schema-gemini-cache.test.d.ts +0 -1
  69. package/node_modules/@comis/core/dist/config/schema-gemini-cache.test.js +0 -41
  70. package/node_modules/@comis/core/dist/config/schema-integrations.test.d.ts +0 -1
  71. package/node_modules/@comis/core/dist/config/schema-integrations.test.js +0 -92
  72. package/node_modules/@comis/core/dist/config/schema-lifecycle-reactions.test.d.ts +0 -1
  73. package/node_modules/@comis/core/dist/config/schema-lifecycle-reactions.test.js +0 -61
  74. package/node_modules/@comis/core/dist/config/schema-misc.test.d.ts +0 -1
  75. package/node_modules/@comis/core/dist/config/schema-misc.test.js +0 -394
  76. package/node_modules/@comis/core/dist/config/schema-observability.test.d.ts +0 -1
  77. package/node_modules/@comis/core/dist/config/schema-observability.test.js +0 -76
  78. package/node_modules/@comis/core/dist/config/schema-providers.test.d.ts +0 -1
  79. package/node_modules/@comis/core/dist/config/schema-providers.test.js +0 -294
  80. package/node_modules/@comis/core/dist/config/schema-queue.test.d.ts +0 -1
  81. package/node_modules/@comis/core/dist/config/schema-queue.test.js +0 -234
  82. package/node_modules/@comis/core/dist/config/schema-response-prefix.test.d.ts +0 -1
  83. package/node_modules/@comis/core/dist/config/schema-response-prefix.test.js +0 -36
  84. package/node_modules/@comis/core/dist/config/schema-security.test.d.ts +0 -1
  85. package/node_modules/@comis/core/dist/config/schema-security.test.js +0 -54
  86. package/node_modules/@comis/core/dist/config/schema-sender-trust-display.test.d.ts +0 -1
  87. package/node_modules/@comis/core/dist/config/schema-sender-trust-display.test.js +0 -60
  88. package/node_modules/@comis/core/dist/config/schema-serializer.test.d.ts +0 -1
  89. package/node_modules/@comis/core/dist/config/schema-serializer.test.js +0 -73
  90. package/node_modules/@comis/core/dist/config/schema-telegram-file-guard.test.d.ts +0 -1
  91. package/node_modules/@comis/core/dist/config/schema-telegram-file-guard.test.js +0 -39
  92. package/node_modules/@comis/core/dist/context/context.test.d.ts +0 -1
  93. package/node_modules/@comis/core/dist/context/context.test.js +0 -276
  94. package/node_modules/@comis/core/dist/domain/agent-response.test.d.ts +0 -1
  95. package/node_modules/@comis/core/dist/domain/agent-response.test.js +0 -159
  96. package/node_modules/@comis/core/dist/domain/credential-mapping.test.d.ts +0 -1
  97. package/node_modules/@comis/core/dist/domain/credential-mapping.test.js +0 -135
  98. package/node_modules/@comis/core/dist/domain/delivery-origin.test.d.ts +0 -1
  99. package/node_modules/@comis/core/dist/domain/delivery-origin.test.js +0 -68
  100. package/node_modules/@comis/core/dist/domain/execution-graph.test.d.ts +0 -1
  101. package/node_modules/@comis/core/dist/domain/execution-graph.test.js +0 -441
  102. package/node_modules/@comis/core/dist/domain/memory-entry.test.d.ts +0 -1
  103. package/node_modules/@comis/core/dist/domain/memory-entry.test.js +0 -193
  104. package/node_modules/@comis/core/dist/domain/normalized-message.test.d.ts +0 -1
  105. package/node_modules/@comis/core/dist/domain/normalized-message.test.js +0 -255
  106. package/node_modules/@comis/core/dist/domain/session-key.test.d.ts +0 -1
  107. package/node_modules/@comis/core/dist/domain/session-key.test.js +0 -291
  108. package/node_modules/@comis/core/dist/domain/subagent-context-config.test.d.ts +0 -1
  109. package/node_modules/@comis/core/dist/domain/subagent-context-config.test.js +0 -131
  110. package/node_modules/@comis/core/dist/domain/subagent-context-types.test.d.ts +0 -1
  111. package/node_modules/@comis/core/dist/domain/subagent-context-types.test.js +0 -182
  112. package/node_modules/@comis/core/dist/event-bus/bus.test.d.ts +0 -1
  113. package/node_modules/@comis/core/dist/event-bus/bus.test.js +0 -152
  114. package/node_modules/@comis/core/dist/event-bus/events-agent.test.d.ts +0 -1
  115. package/node_modules/@comis/core/dist/event-bus/events-agent.test.js +0 -409
  116. package/node_modules/@comis/core/dist/event-bus/events-channel.test.d.ts +0 -1
  117. package/node_modules/@comis/core/dist/event-bus/events-channel.test.js +0 -240
  118. package/node_modules/@comis/core/dist/event-bus/events-infra.test.d.ts +0 -1
  119. package/node_modules/@comis/core/dist/event-bus/events-infra.test.js +0 -416
  120. package/node_modules/@comis/core/dist/event-bus/events-messaging.test.d.ts +0 -1
  121. package/node_modules/@comis/core/dist/event-bus/events-messaging.test.js +0 -433
  122. package/node_modules/@comis/core/dist/event-bus/events.test.d.ts +0 -1
  123. package/node_modules/@comis/core/dist/event-bus/events.test.js +0 -93
  124. package/node_modules/@comis/core/dist/hooks/hook-runner-global.test.d.ts +0 -1
  125. package/node_modules/@comis/core/dist/hooks/hook-runner-global.test.js +0 -29
  126. package/node_modules/@comis/core/dist/hooks/hook-runner.test.d.ts +0 -1
  127. package/node_modules/@comis/core/dist/hooks/hook-runner.test.js +0 -490
  128. package/node_modules/@comis/core/dist/hooks/hook-strategies.test.d.ts +0 -1
  129. package/node_modules/@comis/core/dist/hooks/hook-strategies.test.js +0 -209
  130. package/node_modules/@comis/core/dist/hooks/integration.test.d.ts +0 -1
  131. package/node_modules/@comis/core/dist/hooks/integration.test.js +0 -235
  132. package/node_modules/@comis/core/dist/hooks/plugin-registry.test.d.ts +0 -1
  133. package/node_modules/@comis/core/dist/hooks/plugin-registry.test.js +0 -470
  134. package/node_modules/@comis/core/dist/load-env.test.d.ts +0 -1
  135. package/node_modules/@comis/core/dist/load-env.test.js +0 -21
  136. package/node_modules/@comis/core/dist/security/action-classifier.test.d.ts +0 -1
  137. package/node_modules/@comis/core/dist/security/action-classifier.test.js +0 -298
  138. package/node_modules/@comis/core/dist/security/audit-aggregator.test.d.ts +0 -1
  139. package/node_modules/@comis/core/dist/security/audit-aggregator.test.js +0 -128
  140. package/node_modules/@comis/core/dist/security/audit.test.d.ts +0 -1
  141. package/node_modules/@comis/core/dist/security/audit.test.js +0 -154
  142. package/node_modules/@comis/core/dist/security/canary-token.test.d.ts +0 -1
  143. package/node_modules/@comis/core/dist/security/canary-token.test.js +0 -45
  144. package/node_modules/@comis/core/dist/security/config-redaction.test.d.ts +0 -1
  145. package/node_modules/@comis/core/dist/security/config-redaction.test.js +0 -107
  146. package/node_modules/@comis/core/dist/security/external-content.test.d.ts +0 -1
  147. package/node_modules/@comis/core/dist/security/external-content.test.js +0 -210
  148. package/node_modules/@comis/core/dist/security/injection-patterns.test.d.ts +0 -1
  149. package/node_modules/@comis/core/dist/security/injection-patterns.test.js +0 -213
  150. package/node_modules/@comis/core/dist/security/injection-rate-limiter.test.d.ts +0 -1
  151. package/node_modules/@comis/core/dist/security/injection-rate-limiter.test.js +0 -194
  152. package/node_modules/@comis/core/dist/security/input-guard.test.d.ts +0 -1
  153. package/node_modules/@comis/core/dist/security/input-guard.test.js +0 -215
  154. package/node_modules/@comis/core/dist/security/input-security-guard.test.d.ts +0 -1
  155. package/node_modules/@comis/core/dist/security/input-security-guard.test.js +0 -215
  156. package/node_modules/@comis/core/dist/security/input-validator.test.d.ts +0 -1
  157. package/node_modules/@comis/core/dist/security/input-validator.test.js +0 -133
  158. package/node_modules/@comis/core/dist/security/log-sanitizer.test.d.ts +0 -1
  159. package/node_modules/@comis/core/dist/security/log-sanitizer.test.js +0 -260
  160. package/node_modules/@comis/core/dist/security/memory-write-validator.test.d.ts +0 -1
  161. package/node_modules/@comis/core/dist/security/memory-write-validator.test.js +0 -62
  162. package/node_modules/@comis/core/dist/security/output-guard.test.d.ts +0 -1
  163. package/node_modules/@comis/core/dist/security/output-guard.test.js +0 -397
  164. package/node_modules/@comis/core/dist/security/safe-path.test.d.ts +0 -1
  165. package/node_modules/@comis/core/dist/security/safe-path.test.js +0 -95
  166. package/node_modules/@comis/core/dist/security/scoped-secret-manager.test.d.ts +0 -1
  167. package/node_modules/@comis/core/dist/security/scoped-secret-manager.test.js +0 -231
  168. package/node_modules/@comis/core/dist/security/secret-access.test.d.ts +0 -1
  169. package/node_modules/@comis/core/dist/security/secret-access.test.js +0 -41
  170. package/node_modules/@comis/core/dist/security/secret-crypto.test.d.ts +0 -1
  171. package/node_modules/@comis/core/dist/security/secret-crypto.test.js +0 -113
  172. package/node_modules/@comis/core/dist/security/secret-manager.test.d.ts +0 -1
  173. package/node_modules/@comis/core/dist/security/secret-manager.test.js +0 -394
  174. package/node_modules/@comis/core/dist/security/secret-ref-resolver.test.d.ts +0 -1
  175. package/node_modules/@comis/core/dist/security/secret-ref-resolver.test.js +0 -268
  176. package/node_modules/@comis/core/dist/security/secrets-audit.test.d.ts +0 -10
  177. package/node_modules/@comis/core/dist/security/secrets-audit.test.js +0 -295
  178. package/node_modules/@comis/core/dist/security/ssrf-guard.test.d.ts +0 -1
  179. package/node_modules/@comis/core/dist/security/ssrf-guard.test.js +0 -127
  180. package/node_modules/@comis/core/dist/security/token-generator.test.d.ts +0 -1
  181. package/node_modules/@comis/core/dist/security/token-generator.test.js +0 -35
  182. package/node_modules/@comis/core/dist/tool-metadata.test.d.ts +0 -1
  183. package/node_modules/@comis/core/dist/tool-metadata.test.js +0 -112
  184. package/node_modules/@comis/memory/dist/compaction.test.d.ts +0 -1
  185. package/node_modules/@comis/memory/dist/compaction.test.js +0 -298
  186. package/node_modules/@comis/memory/dist/context-schema.test.d.ts +0 -1
  187. package/node_modules/@comis/memory/dist/context-schema.test.js +0 -246
  188. package/node_modules/@comis/memory/dist/context-store.test.d.ts +0 -1
  189. package/node_modules/@comis/memory/dist/context-store.test.js +0 -708
  190. package/node_modules/@comis/memory/dist/credential-mapping-schema.test.d.ts +0 -7
  191. package/node_modules/@comis/memory/dist/credential-mapping-schema.test.js +0 -73
  192. package/node_modules/@comis/memory/dist/credential-mapping-store.test.d.ts +0 -8
  193. package/node_modules/@comis/memory/dist/credential-mapping-store.test.js +0 -340
  194. package/node_modules/@comis/memory/dist/delivery-mirror-adapter.test.d.ts +0 -1
  195. package/node_modules/@comis/memory/dist/delivery-mirror-adapter.test.js +0 -165
  196. package/node_modules/@comis/memory/dist/delivery-queue-adapter.test.d.ts +0 -1
  197. package/node_modules/@comis/memory/dist/delivery-queue-adapter.test.js +0 -263
  198. package/node_modules/@comis/memory/dist/embedding-batch-indexer.test.d.ts +0 -1
  199. package/node_modules/@comis/memory/dist/embedding-batch-indexer.test.js +0 -202
  200. package/node_modules/@comis/memory/dist/embedding-cache-lru.test.d.ts +0 -1
  201. package/node_modules/@comis/memory/dist/embedding-cache-lru.test.js +0 -241
  202. package/node_modules/@comis/memory/dist/embedding-cache-sqlite.test.d.ts +0 -8
  203. package/node_modules/@comis/memory/dist/embedding-cache-sqlite.test.js +0 -678
  204. package/node_modules/@comis/memory/dist/embedding-cache.test.d.ts +0 -1
  205. package/node_modules/@comis/memory/dist/embedding-cache.test.js +0 -213
  206. package/node_modules/@comis/memory/dist/embedding-fingerprint.test.d.ts +0 -1
  207. package/node_modules/@comis/memory/dist/embedding-fingerprint.test.js +0 -87
  208. package/node_modules/@comis/memory/dist/embedding-hash.test.d.ts +0 -1
  209. package/node_modules/@comis/memory/dist/embedding-hash.test.js +0 -31
  210. package/node_modules/@comis/memory/dist/embedding-integration.test.d.ts +0 -8
  211. package/node_modules/@comis/memory/dist/embedding-integration.test.js +0 -232
  212. package/node_modules/@comis/memory/dist/embedding-provider-factory.test.d.ts +0 -1
  213. package/node_modules/@comis/memory/dist/embedding-provider-factory.test.js +0 -176
  214. package/node_modules/@comis/memory/dist/embedding-provider-local.test.d.ts +0 -8
  215. package/node_modules/@comis/memory/dist/embedding-provider-local.test.js +0 -76
  216. package/node_modules/@comis/memory/dist/embedding-provider-openai.test.d.ts +0 -8
  217. package/node_modules/@comis/memory/dist/embedding-provider-openai.test.js +0 -100
  218. package/node_modules/@comis/memory/dist/embedding-queue.test.d.ts +0 -1
  219. package/node_modules/@comis/memory/dist/embedding-queue.test.js +0 -236
  220. package/node_modules/@comis/memory/dist/hybrid-search.test.d.ts +0 -1
  221. package/node_modules/@comis/memory/dist/hybrid-search.test.js +0 -476
  222. package/node_modules/@comis/memory/dist/identity-link-store.test.d.ts +0 -1
  223. package/node_modules/@comis/memory/dist/identity-link-store.test.js +0 -88
  224. package/node_modules/@comis/memory/dist/memory-api.test.d.ts +0 -1
  225. package/node_modules/@comis/memory/dist/memory-api.test.js +0 -495
  226. package/node_modules/@comis/memory/dist/memory-concurrency.test.d.ts +0 -20
  227. package/node_modules/@comis/memory/dist/memory-concurrency.test.js +0 -222
  228. package/node_modules/@comis/memory/dist/named-graph-store.test.d.ts +0 -1
  229. package/node_modules/@comis/memory/dist/named-graph-store.test.js +0 -227
  230. package/node_modules/@comis/memory/dist/observability-store.test.d.ts +0 -1
  231. package/node_modules/@comis/memory/dist/observability-store.test.js +0 -704
  232. package/node_modules/@comis/memory/dist/row-mapper.test.d.ts +0 -1
  233. package/node_modules/@comis/memory/dist/row-mapper.test.js +0 -409
  234. package/node_modules/@comis/memory/dist/schema.test.d.ts +0 -1
  235. package/node_modules/@comis/memory/dist/schema.test.js +0 -240
  236. package/node_modules/@comis/memory/dist/secret-store-schema.test.d.ts +0 -7
  237. package/node_modules/@comis/memory/dist/secret-store-schema.test.js +0 -90
  238. package/node_modules/@comis/memory/dist/session-store.test.d.ts +0 -1
  239. package/node_modules/@comis/memory/dist/session-store.test.js +0 -262
  240. package/node_modules/@comis/memory/dist/setup-secrets.test.d.ts +0 -1
  241. package/node_modules/@comis/memory/dist/setup-secrets.test.js +0 -140
  242. package/node_modules/@comis/memory/dist/sqlite-memory-adapter.test.d.ts +0 -1
  243. package/node_modules/@comis/memory/dist/sqlite-memory-adapter.test.js +0 -888
  244. package/node_modules/@comis/memory/dist/sqlite-secret-store.test.d.ts +0 -8
  245. package/node_modules/@comis/memory/dist/sqlite-secret-store.test.js +0 -245
  246. package/node_modules/@comis/shared/dist/abort.test.d.ts +0 -1
  247. package/node_modules/@comis/shared/dist/abort.test.js +0 -71
  248. package/node_modules/@comis/shared/dist/result.test.d.ts +0 -1
  249. package/node_modules/@comis/shared/dist/result.test.js +0 -178
  250. package/node_modules/@comis/shared/dist/suppress-error.test.d.ts +0 -1
  251. package/node_modules/@comis/shared/dist/suppress-error.test.js +0 -50
  252. package/node_modules/@comis/shared/dist/timeout.test.d.ts +0 -1
  253. package/node_modules/@comis/shared/dist/timeout.test.js +0 -75
  254. package/node_modules/@comis/shared/dist/ttl-cache.test.d.ts +0 -1
  255. package/node_modules/@comis/shared/dist/ttl-cache.test.js +0 -81
@@ -1,476 +0,0 @@
1
- import Database from "better-sqlite3";
2
- import { describe, it, expect, beforeEach } from "vitest";
3
- import { buildFtsQuery, searchByText, searchByVector, computeRRF, hybridSearch, } from "./hybrid-search.js";
4
- import { initSchema, isVecAvailable } from "./schema.js";
5
- /** Helper to insert a memory row with minimal boilerplate. */
6
- function insertMemory(db, id, content, opts) {
7
- db.prepare(`INSERT INTO memories (id, tenant_id, user_id, content, trust_level, memory_type, source_who, tags, created_at)
8
- VALUES (?, ?, ?, ?, ?, ?, 'agent', '[]', ?)`).run(id, opts?.tenantId ?? "default", opts?.userId ?? "u1", content, opts?.trustLevel ?? "learned", opts?.memoryType ?? "semantic", Date.now());
9
- }
10
- /** Helper to insert a vector embedding for a memory. */
11
- function insertEmbedding(db, memoryId, embedding) {
12
- const float32 = new Float32Array(embedding);
13
- db.prepare("INSERT INTO vec_memories(memory_id, embedding) VALUES (?, ?)").run(memoryId, float32);
14
- db.prepare("UPDATE memories SET has_embedding = 1 WHERE id = ?").run(memoryId);
15
- }
16
- // ── buildFtsQuery ────────────────────────────────────────────────────
17
- describe("buildFtsQuery", () => {
18
- it("produces quoted OR-joined query from normal text", () => {
19
- const result = buildFtsQuery("dentist appointment tomorrow");
20
- expect(result).toBe('"dentist" OR "appointment" OR "tomorrow"');
21
- });
22
- it("returns null for empty string", () => {
23
- expect(buildFtsQuery("")).toBeNull();
24
- });
25
- it("returns null for whitespace-only input", () => {
26
- expect(buildFtsQuery(" ")).toBeNull();
27
- });
28
- it("strips special characters, keeps only alphanumeric tokens", () => {
29
- const result = buildFtsQuery("hello! @world #2024");
30
- expect(result).toBe('"hello" OR "world" OR "2024"');
31
- });
32
- it("removes double quotes to prevent FTS5 injection", () => {
33
- const result = buildFtsQuery('test "injected phrase" query');
34
- // Double quotes removed, words tokenized individually
35
- expect(result).toBe('"test" OR "injected" OR "phrase" OR "query"');
36
- });
37
- it("returns null for input with only special characters", () => {
38
- expect(buildFtsQuery("!@#$%^&*()")).toBeNull();
39
- });
40
- it("handles single word input", () => {
41
- expect(buildFtsQuery("hello")).toBe('"hello"');
42
- });
43
- it("filters out English stop words", () => {
44
- const result = buildFtsQuery("what have we built together");
45
- expect(result).toBe('"built" OR "together"');
46
- });
47
- it("returns null when all tokens are stop words", () => {
48
- expect(buildFtsQuery("what is the")).toBeNull();
49
- });
50
- it("stop word filtering is case-insensitive", () => {
51
- const result = buildFtsQuery("The Quick Fox");
52
- expect(result).toBe('"Quick" OR "Fox"');
53
- });
54
- it("preserves non-Latin tokens even if they match stop words", () => {
55
- // Cyrillic "и" means "and" but should NOT be filtered as English stop word
56
- const result = buildFtsQuery("Привет и мир");
57
- expect(result).toBe('"Привет" OR "и" OR "мир"');
58
- });
59
- it("filters Latin stop words while preserving CJK in mixed query", () => {
60
- const result = buildFtsQuery("the 日本語 is great");
61
- expect(result).toBe('"日本語" OR "great"');
62
- });
63
- it("preserves Hebrew tokens (non-Latin script)", () => {
64
- const result = buildFtsQuery("מה בנינו ביחד");
65
- expect(result).toBe('"מה" OR "בנינו" OR "ביחד"');
66
- });
67
- it("preserves Arabic tokens (non-Latin script)", () => {
68
- const result = buildFtsQuery("ماذا بنينا معا");
69
- expect(result).toBe('"ماذا" OR "بنينا" OR "معا"');
70
- });
71
- it("filters Latin stop words while preserving Hebrew in mixed query", () => {
72
- const result = buildFtsQuery("the משחק is built");
73
- expect(result).toBe('"משחק" OR "built"');
74
- });
75
- // ── DATA-04: Unicode word character support ──────────────────────
76
- it("preserves Cyrillic characters", () => {
77
- const result = buildFtsQuery("\u041F\u0440\u0438\u0432\u0435\u0442 \u043C\u0438\u0440");
78
- expect(result).toBe('"\u041F\u0440\u0438\u0432\u0435\u0442" OR "\u043C\u0438\u0440"');
79
- });
80
- it("preserves CJK characters", () => {
81
- const result = buildFtsQuery("\u65E5\u672C\u8A9E");
82
- expect(result).toBe('"\u65E5\u672C\u8A9E"');
83
- });
84
- it("handles mixed Latin and CJK", () => {
85
- const result = buildFtsQuery("cafe \u65E5\u672C\u8A9E");
86
- expect(result).toBe('"cafe" OR "\u65E5\u672C\u8A9E"');
87
- });
88
- it("preserves Arabic characters", () => {
89
- const result = buildFtsQuery("\u0645\u0631\u062D\u0628\u0627");
90
- expect(result).toBe('"\u0645\u0631\u062D\u0628\u0627"');
91
- });
92
- it("still returns null for only symbols (no Unicode letters or numbers)", () => {
93
- expect(buildFtsQuery("***")).toBeNull();
94
- });
95
- it("handles mixed alpha-numeric as before", () => {
96
- const result = buildFtsQuery("test 123");
97
- expect(result).toBe('"test" OR "123"');
98
- });
99
- });
100
- // ── searchByText ─────────────────────────────────────────────────────
101
- describe("searchByText", () => {
102
- let db;
103
- beforeEach(() => {
104
- db = new Database(":memory:");
105
- initSchema(db, 4); // Small dimension for tests
106
- });
107
- it("finds exact keyword match", () => {
108
- insertMemory(db, "m1", "the quick brown fox jumps over the lazy dog");
109
- insertMemory(db, "m2", "a cat sleeps on the couch");
110
- const results = searchByText(db, "fox", 10);
111
- expect(results).toHaveLength(1);
112
- expect(results[0].id).toBe("m1");
113
- });
114
- it("FTS5 porter stemmer matches 'running' for query 'run'", () => {
115
- insertMemory(db, "m1", "she is running through the park");
116
- insertMemory(db, "m2", "he enjoys swimming");
117
- const results = searchByText(db, "run", 10);
118
- expect(results).toHaveLength(1);
119
- expect(results[0].id).toBe("m1");
120
- });
121
- it("returns results ordered by BM25 rank", () => {
122
- // Memory with more keyword density should rank higher
123
- insertMemory(db, "m1", "the cat sits");
124
- insertMemory(db, "m2", "the cat chases the cat toy and the cat plays");
125
- const results = searchByText(db, "cat", 10);
126
- expect(results.length).toBeGreaterThanOrEqual(2);
127
- // BM25 rank: lower is better match. m2 should have better (lower) rank
128
- expect(results[0].id).toBe("m2");
129
- });
130
- it("returns empty for no-match query", () => {
131
- insertMemory(db, "m1", "the quick brown fox");
132
- const results = searchByText(db, "elephant", 10);
133
- expect(results).toHaveLength(0);
134
- });
135
- it("limit parameter is respected", () => {
136
- insertMemory(db, "m1", "the cat sits on the mat");
137
- insertMemory(db, "m2", "the cat plays with yarn");
138
- insertMemory(db, "m3", "the cat sleeps in the sun");
139
- const results = searchByText(db, "cat", 2);
140
- expect(results).toHaveLength(2);
141
- });
142
- it("returns empty when query has no valid tokens", () => {
143
- insertMemory(db, "m1", "the quick brown fox");
144
- const results = searchByText(db, "!@#$%", 10);
145
- expect(results).toHaveLength(0);
146
- });
147
- });
148
- // ── searchByVector ───────────────────────────────────────────────────
149
- describe("searchByVector", () => {
150
- let db;
151
- beforeEach(() => {
152
- db = new Database(":memory:");
153
- initSchema(db, 4); // 4-dimensional vectors for testing
154
- });
155
- it("finds nearest neighbor by cosine distance", () => {
156
- if (!isVecAvailable())
157
- return;
158
- insertMemory(db, "m1", "entry one");
159
- insertMemory(db, "m2", "entry two");
160
- insertEmbedding(db, "m1", [1, 0, 0, 0]);
161
- insertEmbedding(db, "m2", [0, 1, 0, 0]);
162
- // Query embedding close to m1
163
- const results = searchByVector(db, [0.9, 0.1, 0, 0], 10);
164
- expect(results.length).toBeGreaterThanOrEqual(1);
165
- expect(results[0].id).toBe("m1");
166
- });
167
- it("k parameter controls result count", () => {
168
- if (!isVecAvailable())
169
- return;
170
- insertMemory(db, "m1", "entry one");
171
- insertMemory(db, "m2", "entry two");
172
- insertMemory(db, "m3", "entry three");
173
- insertEmbedding(db, "m1", [1, 0, 0, 0]);
174
- insertEmbedding(db, "m2", [0, 1, 0, 0]);
175
- insertEmbedding(db, "m3", [0, 0, 1, 0]);
176
- const results = searchByVector(db, [1, 0, 0, 0], 2);
177
- expect(results).toHaveLength(2);
178
- });
179
- it("Float32Array conversion works (same embedding returns distance near 0)", () => {
180
- if (!isVecAvailable())
181
- return;
182
- insertMemory(db, "m1", "exact match");
183
- insertEmbedding(db, "m1", [0.5, 0.5, 0.5, 0.5]);
184
- const results = searchByVector(db, [0.5, 0.5, 0.5, 0.5], 1);
185
- expect(results).toHaveLength(1);
186
- expect(results[0].id).toBe("m1");
187
- // Cosine distance for identical vectors should be ~0
188
- expect(results[0].distance).toBeCloseTo(0, 3);
189
- });
190
- it("returns empty when no embeddings are stored", () => {
191
- if (!isVecAvailable())
192
- return;
193
- // No memories inserted at all
194
- const results = searchByVector(db, [1, 0, 0, 0], 10);
195
- expect(results).toHaveLength(0);
196
- });
197
- });
198
- // ── computeRRF ───────────────────────────────────────────────────────
199
- describe("computeRRF", () => {
200
- it("result from both FTS and vec gets higher score than single-source", () => {
201
- const fts = [
202
- { id: "a", rank: 1 },
203
- { id: "b", rank: 2 },
204
- ];
205
- const vec = [
206
- { id: "a", rank: 1 },
207
- { id: "c", rank: 2 },
208
- ];
209
- const results = computeRRF(fts, vec);
210
- // "a" appears in both sources, should have highest score
211
- expect(results[0].id).toBe("a");
212
- // "a" score > "b" score and "a" score > "c" score
213
- const aScore = results.find((r) => r.id === "a").rrfScore;
214
- const bScore = results.find((r) => r.id === "b").rrfScore;
215
- const cScore = results.find((r) => r.id === "c").rrfScore;
216
- expect(aScore).toBeGreaterThan(bScore);
217
- expect(aScore).toBeGreaterThan(cScore);
218
- });
219
- it("empty FTS results -- vec results still ranked", () => {
220
- const fts = [];
221
- const vec = [
222
- { id: "a", rank: 1 },
223
- { id: "b", rank: 2 },
224
- ];
225
- const results = computeRRF(fts, vec);
226
- expect(results).toHaveLength(2);
227
- expect(results[0].id).toBe("a");
228
- expect(results[0].ftsRank).toBeNull();
229
- expect(results[0].vecRank).toBe(1);
230
- });
231
- it("empty vec results -- FTS results still ranked", () => {
232
- const fts = [
233
- { id: "a", rank: 1 },
234
- { id: "b", rank: 2 },
235
- ];
236
- const vec = [];
237
- const results = computeRRF(fts, vec);
238
- expect(results).toHaveLength(2);
239
- expect(results[0].id).toBe("a");
240
- expect(results[0].vecRank).toBeNull();
241
- expect(results[0].ftsRank).toBe(1);
242
- });
243
- it("both empty -- returns empty", () => {
244
- const results = computeRRF([], []);
245
- expect(results).toHaveLength(0);
246
- });
247
- it("weight parameters affect ranking", () => {
248
- // "a" is FTS-only, "b" is vec-only, both at rank 1
249
- const fts = [{ id: "a", rank: 1 }];
250
- const vec = [{ id: "b", rank: 1 }];
251
- // With higher vec weight, "b" should score higher
252
- const results = computeRRF(fts, vec, 1.0, 3.0);
253
- expect(results[0].id).toBe("b");
254
- // With higher FTS weight, "a" should score higher
255
- const results2 = computeRRF(fts, vec, 3.0, 1.0);
256
- expect(results2[0].id).toBe("a");
257
- });
258
- it("tracks source ranks correctly for dual-source results", () => {
259
- const fts = [{ id: "x", rank: 2 }];
260
- const vec = [{ id: "x", rank: 3 }];
261
- const results = computeRRF(fts, vec);
262
- expect(results).toHaveLength(1);
263
- expect(results[0].id).toBe("x");
264
- expect(results[0].ftsRank).toBe(2);
265
- expect(results[0].vecRank).toBe(3);
266
- });
267
- });
268
- // ── hybridSearch (integration) ───────────────────────────────────────
269
- describe("hybridSearch", () => {
270
- let db;
271
- beforeEach(() => {
272
- db = new Database(":memory:");
273
- initSchema(db, 4);
274
- });
275
- it("combines FTS and vector results for same query", () => {
276
- if (!isVecAvailable())
277
- return;
278
- // m1: matches text "dentist" and has embedding close to query
279
- insertMemory(db, "m1", "visit the dentist for a checkup");
280
- insertEmbedding(db, "m1", [1, 0, 0, 0]);
281
- // m2: matches text "dentist" but different embedding
282
- insertMemory(db, "m2", "the dentist said everything is fine");
283
- insertEmbedding(db, "m2", [0, 1, 0, 0]);
284
- // m3: no text match but embedding is close to query
285
- insertMemory(db, "m3", "oral health appointment scheduled");
286
- insertEmbedding(db, "m3", [0.9, 0.1, 0, 0]);
287
- const results = hybridSearch(db, "dentist", [0.95, 0.05, 0, 0], {
288
- limit: 10,
289
- });
290
- expect(results.length).toBeGreaterThanOrEqual(2);
291
- // m1 should rank highest: matches both text AND vector
292
- expect(results[0].id).toBe("m1");
293
- });
294
- it("falls back to FTS-only when no embedding provided", () => {
295
- insertMemory(db, "m1", "the quick brown fox");
296
- insertMemory(db, "m2", "lazy dog sleeps");
297
- const results = hybridSearch(db, "fox", undefined, { limit: 10 });
298
- expect(results).toHaveLength(1);
299
- expect(results[0].id).toBe("m1");
300
- });
301
- it("falls back to FTS-only when zero-length embedding provided (EMBED-01)", () => {
302
- insertMemory(db, "m1", "the quick brown fox");
303
- insertMemory(db, "m2", "lazy dog sleeps");
304
- // Empty array simulates embedding provider returning [] for short/emoji input
305
- const results = hybridSearch(db, "fox", [], { limit: 10 });
306
- // Should NOT crash -- zero-length guard skips vector search path
307
- expect(results).toHaveLength(1);
308
- expect(results[0].id).toBe("m1");
309
- });
310
- it("zero-length embedding produces same results as undefined embedding", () => {
311
- insertMemory(db, "m1", "cat plays with yarn ball");
312
- insertMemory(db, "m2", "cat naps in the sun");
313
- insertMemory(db, "m3", "dog barks at the mailman");
314
- const undefinedResults = hybridSearch(db, "cat", undefined, { limit: 10 });
315
- const emptyResults = hybridSearch(db, "cat", [], { limit: 10 });
316
- // Both should produce identical FTS-only results
317
- expect(emptyResults).toHaveLength(undefinedResults.length);
318
- expect(emptyResults.map((r) => r.id)).toEqual(undefinedResults.map((r) => r.id));
319
- });
320
- it("respects limit parameter on final output", () => {
321
- insertMemory(db, "m1", "cat plays with yarn");
322
- insertMemory(db, "m2", "cat naps in the sun");
323
- insertMemory(db, "m3", "cat chases a mouse");
324
- const results = hybridSearch(db, "cat", undefined, { limit: 2 });
325
- expect(results).toHaveLength(2);
326
- });
327
- it("filters by trustLevel", () => {
328
- insertMemory(db, "m1", "trusted cat memory", { trustLevel: "system" });
329
- insertMemory(db, "m2", "external cat data", { trustLevel: "external" });
330
- const results = hybridSearch(db, "cat", undefined, {
331
- limit: 10,
332
- trustLevel: "system",
333
- });
334
- expect(results).toHaveLength(1);
335
- expect(results[0].id).toBe("m1");
336
- });
337
- it("filters by memoryType", () => {
338
- insertMemory(db, "m1", "episodic cat event", { memoryType: "episodic" });
339
- insertMemory(db, "m2", "semantic cat fact", { memoryType: "semantic" });
340
- const results = hybridSearch(db, "cat", undefined, {
341
- limit: 10,
342
- memoryType: "episodic",
343
- });
344
- expect(results).toHaveLength(1);
345
- expect(results[0].id).toBe("m1");
346
- });
347
- it("filters by tenantId", () => {
348
- insertMemory(db, "m1", "tenant A cat", { tenantId: "tenantA" });
349
- insertMemory(db, "m2", "tenant B cat", { tenantId: "tenantB" });
350
- const results = hybridSearch(db, "cat", undefined, {
351
- limit: 10,
352
- tenantId: "tenantA",
353
- });
354
- expect(results).toHaveLength(1);
355
- expect(results[0].id).toBe("m1");
356
- });
357
- it("returns empty when no memories match query", () => {
358
- insertMemory(db, "m1", "the quick brown fox");
359
- const results = hybridSearch(db, "elephant", undefined, { limit: 10 });
360
- expect(results).toHaveLength(0);
361
- });
362
- it("returns empty when query produces no valid tokens", () => {
363
- insertMemory(db, "m1", "the quick brown fox");
364
- const results = hybridSearch(db, "!@#$%", undefined, { limit: 10 });
365
- expect(results).toHaveLength(0);
366
- });
367
- it("returns vector-only results when FTS5 has no keyword matches", () => {
368
- if (!isVecAvailable())
369
- return;
370
- insertMemory(db, "m1", "space shooter with asteroids");
371
- insertEmbedding(db, "m1", [1, 0, 0, 0]);
372
- insertMemory(db, "m2", "underwater exploration game");
373
- insertEmbedding(db, "m2", [0, 1, 0, 0]);
374
- // Query has no keyword overlap but embedding is close to m1
375
- const results = hybridSearch(db, "cosmic blaster", [0.9, 0.1, 0, 0], { limit: 10 });
376
- expect(results.length).toBeGreaterThanOrEqual(1);
377
- expect(results[0].id).toBe("m1");
378
- });
379
- it("vector weight boost ranks semantic matches higher", () => {
380
- if (!isVecAvailable())
381
- return;
382
- // m1: weak FTS match (rank 2), strong vector match (rank 1)
383
- insertMemory(db, "m1", "space shooter void breaker game with depth");
384
- insertEmbedding(db, "m1", [1, 0, 0, 0]);
385
- // m2: strong FTS match (rank 1), weak vector match (rank 2)
386
- insertMemory(db, "m2", "built a game together recently");
387
- insertEmbedding(db, "m2", [0, 1, 0, 0]);
388
- // Query embedding close to m1, text matches "game" in both
389
- const results = hybridSearch(db, "game", [0.95, 0.05, 0, 0], { limit: 10 });
390
- expect(results.length).toBeGreaterThanOrEqual(2);
391
- // m1 should rank higher due to vector weight boost (1.5x)
392
- expect(results[0].id).toBe("m1");
393
- });
394
- });
395
- // ── RRF score normalization ─────────────────────────────────────────
396
- describe("RRF score normalization", () => {
397
- let db;
398
- beforeEach(() => {
399
- db = new Database(":memory:");
400
- initSchema(db, 4);
401
- });
402
- it("hybridSearch scores are normalized to 0-1 range", () => {
403
- if (!isVecAvailable())
404
- return;
405
- insertMemory(db, "m1", "the dentist appointment next week");
406
- insertEmbedding(db, "m1", [1, 0, 0, 0]);
407
- insertMemory(db, "m2", "dentist cleaning scheduled for monday");
408
- insertEmbedding(db, "m2", [0.8, 0.2, 0, 0]);
409
- insertMemory(db, "m3", "visit dentist for cavity filling");
410
- insertEmbedding(db, "m3", [0.6, 0.4, 0, 0]);
411
- const results = hybridSearch(db, "dentist", [0.95, 0.05, 0, 0], {
412
- limit: 10,
413
- });
414
- expect(results.length).toBeGreaterThanOrEqual(1);
415
- for (const r of results) {
416
- expect(r.score).toBeGreaterThanOrEqual(0);
417
- expect(r.score).toBeLessThanOrEqual(1);
418
- }
419
- });
420
- it("highest-ranked result has score near 1.0", () => {
421
- if (!isVecAvailable())
422
- return;
423
- // m1 matches both FTS and vector at rank 1
424
- insertMemory(db, "m1", "dentist checkup tomorrow morning");
425
- insertEmbedding(db, "m1", [1, 0, 0, 0]);
426
- insertMemory(db, "m2", "dentist said teeth are healthy");
427
- insertEmbedding(db, "m2", [0, 1, 0, 0]);
428
- const results = hybridSearch(db, "dentist", [0.99, 0.01, 0, 0], {
429
- limit: 10,
430
- });
431
- expect(results.length).toBeGreaterThanOrEqual(1);
432
- // After normalization, top result should score well above 0.5
433
- // (raw RRF would be ~0.041, proving normalization happened)
434
- expect(results[0].score).toBeGreaterThan(0.5);
435
- });
436
- it("FTS-only fallback scores are normalized to 0-1 range", () => {
437
- insertMemory(db, "m1", "cat plays with yarn ball");
438
- insertMemory(db, "m2", "cat naps in the sunny window");
439
- insertMemory(db, "m3", "cat chases a laser pointer");
440
- // No embedding provided -- FTS-only fallback
441
- const results = hybridSearch(db, "cat", undefined, { limit: 10 });
442
- expect(results.length).toBeGreaterThanOrEqual(1);
443
- for (const r of results) {
444
- expect(r.score).toBeGreaterThanOrEqual(0);
445
- expect(r.score).toBeLessThanOrEqual(1);
446
- }
447
- });
448
- it("score ordering preserved after normalization", () => {
449
- if (!isVecAvailable())
450
- return;
451
- // Varied relevance: m1 matches both strongly, m2 matches FTS only, m3 matches vec weakly
452
- insertMemory(db, "m1", "cat plays with yarn ball daily");
453
- insertEmbedding(db, "m1", [1, 0, 0, 0]);
454
- insertMemory(db, "m2", "cat naps in the sunny window all day");
455
- insertEmbedding(db, "m2", [0, 0, 1, 0]);
456
- insertMemory(db, "m3", "dog barks at the mailman");
457
- insertEmbedding(db, "m3", [0.9, 0.1, 0, 0]);
458
- const results = hybridSearch(db, "cat", [0.95, 0.05, 0, 0], {
459
- limit: 10,
460
- });
461
- // Scores must be in strictly descending order
462
- for (let i = 1; i < results.length; i++) {
463
- expect(results[i - 1].score).toBeGreaterThanOrEqual(results[i].score);
464
- }
465
- });
466
- it("computeRRF raw scores are NOT normalized", () => {
467
- // computeRRF is the raw engine -- normalization happens in hybridSearch
468
- const fts = [{ id: "a", rank: 1 }];
469
- const vec = [{ id: "a", rank: 1 }];
470
- const results = computeRRF(fts, vec, 1.0, 1.5);
471
- expect(results).toHaveLength(1);
472
- // Raw RRF score: (1.0 / 61) + (1.5 / 61) = 2.5 / 61 = ~0.04098
473
- expect(results[0].rrfScore).toBeLessThan(0.05);
474
- expect(results[0].rrfScore).toBeGreaterThan(0);
475
- });
476
- });
@@ -1,88 +0,0 @@
1
- import Database from "better-sqlite3";
2
- import { describe, it, expect, beforeEach } from "vitest";
3
- import { initSchema } from "./schema.js";
4
- import { createIdentityLinkStore, } from "./identity-link-store.js";
5
- describe("createIdentityLinkStore", () => {
6
- let db;
7
- let store;
8
- beforeEach(() => {
9
- db = new Database(":memory:");
10
- initSchema(db, 128);
11
- store = createIdentityLinkStore(db);
12
- });
13
- it("link creates a new identity link, resolve returns the canonical ID", () => {
14
- store.link("canonical-1", "discord", "discord-user-42");
15
- const result = store.resolve("discord", "discord-user-42");
16
- expect(result).toBe("canonical-1");
17
- });
18
- it("link with same provider+providerUserId but different canonicalId updates (upsert)", () => {
19
- store.link("canonical-1", "telegram", "tg-user-7");
20
- store.link("canonical-2", "telegram", "tg-user-7");
21
- const result = store.resolve("telegram", "tg-user-7");
22
- expect(result).toBe("canonical-2");
23
- });
24
- it("unlink removes existing link and returns true", () => {
25
- store.link("canonical-1", "discord", "discord-user-42");
26
- const removed = store.unlink("discord", "discord-user-42");
27
- expect(removed).toBe(true);
28
- const result = store.resolve("discord", "discord-user-42");
29
- expect(result).toBeUndefined();
30
- });
31
- it("unlink returns false for non-existent link", () => {
32
- const removed = store.unlink("discord", "nonexistent-user");
33
- expect(removed).toBe(false);
34
- });
35
- it("resolve returns undefined for unlinked provider identity", () => {
36
- const result = store.resolve("slack", "unknown-user");
37
- expect(result).toBeUndefined();
38
- });
39
- it("listByCanonical returns all links for a canonical ID", () => {
40
- store.link("canonical-1", "discord", "discord-user-42");
41
- store.link("canonical-1", "telegram", "tg-user-7");
42
- store.link("canonical-2", "slack", "slack-user-99");
43
- const links = store.listByCanonical("canonical-1");
44
- expect(links).toHaveLength(2);
45
- expect(links.map((l) => l.provider).sort()).toEqual(["discord", "telegram"]);
46
- expect(links.every((l) => l.canonicalId === "canonical-1")).toBe(true);
47
- });
48
- it("listAll returns all links sorted by canonical_id, provider", () => {
49
- store.link("canonical-2", "slack", "slack-user-99");
50
- store.link("canonical-1", "telegram", "tg-user-7");
51
- store.link("canonical-1", "discord", "discord-user-42");
52
- const links = store.listAll();
53
- expect(links).toHaveLength(3);
54
- // Sorted by canonical_id then provider
55
- expect(links[0].canonicalId).toBe("canonical-1");
56
- expect(links[0].provider).toBe("discord");
57
- expect(links[1].canonicalId).toBe("canonical-1");
58
- expect(links[1].provider).toBe("telegram");
59
- expect(links[2].canonicalId).toBe("canonical-2");
60
- expect(links[2].provider).toBe("slack");
61
- });
62
- it("link with displayName stores and retrieves it", () => {
63
- store.link("canonical-1", "discord", "discord-user-42", "CoolUser#1234");
64
- const links = store.listByCanonical("canonical-1");
65
- expect(links).toHaveLength(1);
66
- expect(links[0].displayName).toBe("CoolUser#1234");
67
- });
68
- it("link without displayName stores undefined", () => {
69
- store.link("canonical-1", "discord", "discord-user-42");
70
- const links = store.listByCanonical("canonical-1");
71
- expect(links).toHaveLength(1);
72
- expect(links[0].displayName).toBeUndefined();
73
- });
74
- it("listByCanonical returns links ordered by linked_at DESC", () => {
75
- // Insert with explicit timestamps for deterministic ordering
76
- db.prepare(`INSERT INTO identity_links (canonical_id, provider, provider_user_id, display_name, linked_at)
77
- VALUES ('c1', 'discord', 'u1', NULL, 1000)`).run();
78
- db.prepare(`INSERT INTO identity_links (canonical_id, provider, provider_user_id, display_name, linked_at)
79
- VALUES ('c1', 'telegram', 'u2', NULL, 3000)`).run();
80
- db.prepare(`INSERT INTO identity_links (canonical_id, provider, provider_user_id, display_name, linked_at)
81
- VALUES ('c1', 'slack', 'u3', NULL, 2000)`).run();
82
- const links = store.listByCanonical("c1");
83
- expect(links).toHaveLength(3);
84
- expect(links[0].provider).toBe("telegram"); // linked_at 3000
85
- expect(links[1].provider).toBe("slack"); // linked_at 2000
86
- expect(links[2].provider).toBe("discord"); // linked_at 1000
87
- });
88
- });
@@ -1 +0,0 @@
1
- export {};