@directus/api 32.2.0 → 33.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. package/dist/ai/chat/controllers/chat.post.js +19 -4
  2. package/dist/ai/chat/lib/create-ui-stream.d.ts +8 -7
  3. package/dist/ai/chat/lib/create-ui-stream.js +28 -25
  4. package/dist/ai/chat/middleware/load-settings.js +31 -7
  5. package/dist/ai/chat/models/chat-request.d.ts +135 -2
  6. package/dist/ai/chat/models/chat-request.js +56 -2
  7. package/dist/ai/chat/models/providers.d.ts +16 -2
  8. package/dist/ai/chat/models/providers.js +16 -2
  9. package/dist/ai/chat/utils/chat-request-tool-to-ai-sdk-tool.js +3 -4
  10. package/dist/ai/chat/utils/format-context.d.ts +5 -0
  11. package/dist/ai/chat/utils/format-context.js +122 -0
  12. package/dist/ai/mcp/server.d.ts +27 -1
  13. package/dist/ai/providers/index.d.ts +3 -0
  14. package/dist/ai/providers/index.js +3 -0
  15. package/dist/ai/providers/options.d.ts +14 -0
  16. package/dist/ai/providers/options.js +26 -0
  17. package/dist/ai/providers/registry.d.ts +6 -0
  18. package/dist/ai/providers/registry.js +65 -0
  19. package/dist/ai/providers/types.d.ts +34 -0
  20. package/dist/ai/providers/types.js +1 -0
  21. package/dist/ai/tools/assets/index.js +1 -1
  22. package/dist/ai/tools/collections/index.js +2 -2
  23. package/dist/ai/tools/fields/index.js +2 -2
  24. package/dist/ai/tools/files/index.js +1 -1
  25. package/dist/ai/tools/flows/index.js +1 -1
  26. package/dist/ai/tools/folders/index.js +1 -1
  27. package/dist/ai/tools/items/index.js +6 -3
  28. package/dist/ai/tools/items/prompt.md +7 -9
  29. package/dist/ai/tools/relations/index.js +1 -1
  30. package/dist/ai/tools/schema.js +1 -1
  31. package/dist/ai/tools/trigger-flow/index.js +1 -1
  32. package/dist/app.js +12 -8
  33. package/dist/auth/drivers/ldap.d.ts +1 -1
  34. package/dist/auth/drivers/ldap.js +144 -139
  35. package/dist/auth/drivers/local.js +1 -1
  36. package/dist/auth/drivers/oauth2.d.ts +1 -2
  37. package/dist/auth/drivers/oauth2.js +22 -17
  38. package/dist/auth/drivers/openid.d.ts +1 -2
  39. package/dist/auth/drivers/openid.js +18 -13
  40. package/dist/auth/drivers/saml.js +3 -3
  41. package/dist/auth/utils/generate-callback-url.d.ts +11 -0
  42. package/dist/auth/utils/generate-callback-url.js +40 -0
  43. package/dist/auth/utils/is-login-redirect-allowed.d.ts +7 -0
  44. package/dist/{utils → auth/utils}/is-login-redirect-allowed.js +12 -9
  45. package/dist/cache.d.ts +12 -0
  46. package/dist/cache.js +27 -3
  47. package/dist/cli/commands/bootstrap/index.js +2 -2
  48. package/dist/cli/commands/database/install.js +1 -1
  49. package/dist/cli/commands/database/migrate.js +1 -1
  50. package/dist/cli/commands/init/index.js +2 -2
  51. package/dist/cli/commands/roles/create.js +4 -4
  52. package/dist/cli/commands/schema/apply.js +3 -3
  53. package/dist/cli/commands/schema/snapshot.js +1 -1
  54. package/dist/cli/utils/create-db-connection.d.ts +1 -1
  55. package/dist/cli/utils/create-db-connection.js +1 -1
  56. package/dist/cli/utils/create-env/env-stub.liquid +3 -0
  57. package/dist/cli/utils/create-env/index.js +1 -1
  58. package/dist/constants.d.ts +7 -3
  59. package/dist/constants.js +7 -3
  60. package/dist/controllers/access.js +1 -1
  61. package/dist/controllers/assets.js +1 -1
  62. package/dist/controllers/deployment.js +481 -0
  63. package/dist/controllers/extensions.js +1 -1
  64. package/dist/controllers/fields.js +8 -6
  65. package/dist/controllers/files.js +1 -1
  66. package/dist/controllers/items.js +1 -1
  67. package/dist/controllers/not-found.js +1 -1
  68. package/dist/controllers/relations.js +1 -1
  69. package/dist/database/errors/dialects/mysql.d.ts +1 -1
  70. package/dist/database/errors/dialects/postgres.d.ts +1 -1
  71. package/dist/database/errors/dialects/sqlite.d.ts +1 -1
  72. package/dist/database/errors/translate.d.ts +1 -1
  73. package/dist/database/errors/translate.js +1 -1
  74. package/dist/database/get-ast-from-query/lib/parse-fields.js +2 -2
  75. package/dist/database/helpers/date/dialects/mssql.js +1 -1
  76. package/dist/database/helpers/date/dialects/mysql.js +1 -1
  77. package/dist/database/helpers/date/types.js +1 -1
  78. package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +1 -0
  79. package/dist/database/helpers/schema/dialects/cockroachdb.js +24 -1
  80. package/dist/database/helpers/schema/dialects/mssql.d.ts +1 -1
  81. package/dist/database/helpers/schema/dialects/mysql.d.ts +2 -1
  82. package/dist/database/helpers/schema/dialects/mysql.js +16 -3
  83. package/dist/database/helpers/schema/dialects/postgres.d.ts +1 -1
  84. package/dist/database/helpers/schema/types.d.ts +13 -0
  85. package/dist/database/helpers/schema/types.js +24 -0
  86. package/dist/database/index.js +4 -4
  87. package/dist/database/migrations/20220429A-add-flows.js +1 -1
  88. package/dist/database/migrations/20230526A-migrate-translation-strings.js +1 -1
  89. package/dist/database/migrations/20231009A-update-csv-fields-to-text.js +1 -1
  90. package/dist/database/migrations/20240204A-marketplace.js +9 -7
  91. package/dist/database/migrations/20240311A-deprecate-webhooks.d.ts +15 -0
  92. package/dist/database/migrations/20240311A-deprecate-webhooks.js +1 -1
  93. package/dist/database/migrations/20240806A-permissions-policies.js +2 -2
  94. package/dist/database/migrations/20240924A-migrate-legacy-comments.js +1 -1
  95. package/dist/database/migrations/20251014A-add-project-owner.js +1 -1
  96. package/dist/database/migrations/20251224A-remove-webhooks.d.ts +3 -0
  97. package/dist/database/migrations/20251224A-remove-webhooks.js +19 -0
  98. package/dist/database/migrations/20260110A-add-ai-provider-settings.d.ts +3 -0
  99. package/dist/database/migrations/20260110A-add-ai-provider-settings.js +35 -0
  100. package/dist/database/migrations/20260113A-add-revisions-index.d.ts +3 -0
  101. package/dist/database/migrations/20260113A-add-revisions-index.js +41 -0
  102. package/dist/database/migrations/20260128A-add-collaborative-editing.d.ts +3 -0
  103. package/dist/database/migrations/20260128A-add-collaborative-editing.js +10 -0
  104. package/dist/database/migrations/20260204A-add-deployment.d.ts +3 -0
  105. package/dist/database/migrations/20260204A-add-deployment.js +32 -0
  106. package/dist/database/migrations/run.js +3 -3
  107. package/dist/database/run-ast/lib/apply-query/add-join.js +1 -1
  108. package/dist/database/run-ast/lib/apply-query/filter/get-filter-type.d.ts +2 -2
  109. package/dist/database/run-ast/lib/apply-query/filter/get-filter-type.js +1 -1
  110. package/dist/database/run-ast/lib/apply-query/filter/index.js +1 -1
  111. package/dist/database/run-ast/lib/apply-query/filter/operator.js +1 -1
  112. package/dist/database/run-ast/lib/apply-query/sort.js +1 -1
  113. package/dist/database/run-ast/utils/get-column-pre-processor.js +2 -2
  114. package/dist/database/run-ast/utils/get-column.js +1 -1
  115. package/dist/database/seeds/run.js +3 -3
  116. package/dist/deployment/deployment.d.ts +94 -0
  117. package/dist/deployment/deployment.js +29 -0
  118. package/dist/deployment/drivers/index.d.ts +1 -0
  119. package/dist/deployment/drivers/index.js +1 -0
  120. package/dist/deployment/drivers/vercel.d.ts +32 -0
  121. package/dist/deployment/drivers/vercel.js +208 -0
  122. package/dist/deployment/index.d.ts +2 -0
  123. package/dist/deployment/index.js +2 -0
  124. package/dist/deployment.d.ts +24 -0
  125. package/dist/deployment.js +39 -0
  126. package/dist/extensions/lib/get-extensions-path.js +1 -1
  127. package/dist/extensions/lib/get-extensions-settings.js +1 -1
  128. package/dist/extensions/lib/get-extensions.js +1 -1
  129. package/dist/extensions/lib/get-shared-deps-mapping.js +3 -3
  130. package/dist/extensions/lib/installation/manager.js +3 -3
  131. package/dist/extensions/lib/sandbox/register/route.d.ts +1 -1
  132. package/dist/extensions/lib/sync/status.js +1 -1
  133. package/dist/extensions/lib/sync/sync.js +7 -7
  134. package/dist/extensions/lib/sync/utils.js +2 -2
  135. package/dist/extensions/manager.d.ts +1 -1
  136. package/dist/extensions/manager.js +8 -8
  137. package/dist/flows.d.ts +1 -1
  138. package/dist/logger/index.js +1 -1
  139. package/dist/logger/logs-stream.d.ts +1 -1
  140. package/dist/logger/logs-stream.js +1 -1
  141. package/dist/mailer.js +1 -1
  142. package/dist/metrics/lib/create-metrics.js +2 -2
  143. package/dist/middleware/authenticate.js +3 -3
  144. package/dist/middleware/collection-exists.js +1 -1
  145. package/dist/middleware/extract-token.js +1 -1
  146. package/dist/middleware/graphql.js +2 -2
  147. package/dist/middleware/respond.js +27 -14
  148. package/dist/middleware/validate-batch.js +1 -1
  149. package/dist/operations/exec/index.js +2 -1
  150. package/dist/operations/mail/index.js +1 -1
  151. package/dist/operations/mail/rate-limiter.js +2 -2
  152. package/dist/permissions/cache.js +5 -0
  153. package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.js +1 -1
  154. package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.js +1 -1
  155. package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.js +2 -2
  156. package/dist/permissions/modules/process-ast/lib/inject-cases.js +1 -1
  157. package/dist/permissions/modules/process-ast/process-ast.js +1 -1
  158. package/dist/permissions/modules/process-ast/utils/find-related-collection.js +1 -1
  159. package/dist/permissions/modules/process-payload/process-payload.js +1 -1
  160. package/dist/permissions/modules/validate-access/lib/validate-item-access.d.ts +14 -2
  161. package/dist/permissions/modules/validate-access/lib/validate-item-access.js +72 -13
  162. package/dist/permissions/modules/validate-access/validate-access.js +3 -2
  163. package/dist/rate-limiter.js +1 -1
  164. package/dist/request/is-denied-ip.js +1 -1
  165. package/dist/schedules/project.js +1 -1
  166. package/dist/schedules/telemetry.js +1 -1
  167. package/dist/schedules/tus.js +1 -1
  168. package/dist/server.js +6 -5
  169. package/dist/services/assets.d.ts +2 -1
  170. package/dist/services/assets.js +35 -8
  171. package/dist/services/authentication.js +2 -2
  172. package/dist/services/collections.js +1 -1
  173. package/dist/services/deployment-projects.d.ts +20 -0
  174. package/dist/services/deployment-projects.js +34 -0
  175. package/dist/services/deployment-runs.d.ts +13 -0
  176. package/dist/services/deployment-runs.js +6 -0
  177. package/dist/services/deployment.d.ts +40 -0
  178. package/dist/services/deployment.js +202 -0
  179. package/dist/services/extensions.d.ts +1 -1
  180. package/dist/services/files/utils/get-metadata.d.ts +1 -1
  181. package/dist/services/files/utils/get-metadata.js +1 -1
  182. package/dist/services/files.d.ts +1 -1
  183. package/dist/services/files.js +4 -4
  184. package/dist/services/graphql/index.d.ts +1 -1
  185. package/dist/services/graphql/index.js +1 -1
  186. package/dist/services/graphql/resolvers/mutation.js +1 -1
  187. package/dist/services/graphql/resolvers/system-admin.js +2 -3
  188. package/dist/services/graphql/schema/get-types.d.ts +1 -1
  189. package/dist/services/graphql/schema/read.js +1 -1
  190. package/dist/services/graphql/subscription.d.ts +1 -1
  191. package/dist/services/graphql/types/date.js +1 -1
  192. package/dist/services/graphql/types/hash.js +1 -1
  193. package/dist/services/graphql/utils/add-path-to-validation-error.js +1 -1
  194. package/dist/services/graphql/utils/filter-replace-m2a.js +3 -4
  195. package/dist/services/import-export.d.ts +1 -1
  196. package/dist/services/import-export.js +2 -2
  197. package/dist/services/index.d.ts +3 -1
  198. package/dist/services/index.js +3 -1
  199. package/dist/services/mail/index.js +2 -2
  200. package/dist/services/mail/rate-limiter.js +2 -2
  201. package/dist/services/payload.js +2 -2
  202. package/dist/services/schema.js +1 -1
  203. package/dist/services/server.js +13 -4
  204. package/dist/services/settings.js +2 -2
  205. package/dist/services/specifications.js +2 -2
  206. package/dist/services/tfa.js +1 -1
  207. package/dist/services/translations.js +1 -1
  208. package/dist/services/tus/data-store.d.ts +1 -3
  209. package/dist/services/tus/data-store.js +2 -5
  210. package/dist/services/tus/server.js +6 -6
  211. package/dist/services/users.js +4 -4
  212. package/dist/services/versions.js +1 -1
  213. package/dist/telemetry/lib/get-report.js +2 -0
  214. package/dist/telemetry/lib/send-report.d.ts +1 -1
  215. package/dist/telemetry/lib/send-report.js +1 -1
  216. package/dist/telemetry/lib/track.js +1 -1
  217. package/dist/telemetry/types/report.d.ts +8 -0
  218. package/dist/telemetry/utils/get-settings.d.ts +2 -0
  219. package/dist/telemetry/utils/get-settings.js +5 -0
  220. package/dist/test-utils/knex.js +1 -1
  221. package/dist/types/collection.d.ts +1 -1
  222. package/dist/utils/async-handler.d.ts +1 -1
  223. package/dist/utils/calculate-field-depth.js +1 -1
  224. package/dist/utils/compress.js +1 -1
  225. package/dist/utils/deep-map-response.d.ts +1 -1
  226. package/dist/utils/deep-map-response.js +2 -2
  227. package/dist/utils/get-cache-key.js +1 -1
  228. package/dist/utils/get-column-path.js +1 -1
  229. package/dist/utils/get-field-system-rows.js +1 -1
  230. package/dist/utils/get-ip-from-req.d.ts +1 -1
  231. package/dist/utils/get-ip-from-req.js +1 -1
  232. package/dist/utils/get-local-type.js +7 -3
  233. package/dist/utils/get-service.js +7 -3
  234. package/dist/utils/get-snapshot-diff.js +1 -1
  235. package/dist/utils/is-field-allowed.d.ts +4 -0
  236. package/dist/utils/is-field-allowed.js +9 -0
  237. package/dist/utils/is-url-allowed.js +1 -1
  238. package/dist/utils/jwt.js +1 -1
  239. package/dist/utils/sanitize-schema.d.ts +1 -1
  240. package/dist/utils/should-clear-cache.d.ts +1 -1
  241. package/dist/utils/should-skip-cache.js +2 -2
  242. package/dist/utils/validate-diff.js +1 -1
  243. package/dist/utils/validate-snapshot.js +3 -3
  244. package/dist/utils/validate-storage.js +2 -2
  245. package/dist/utils/verify-session-jwt.js +1 -1
  246. package/dist/utils/versioning/handle-version.js +1 -1
  247. package/dist/websocket/collab/calculate-cache-metadata.d.ts +9 -0
  248. package/dist/websocket/collab/calculate-cache-metadata.js +121 -0
  249. package/dist/websocket/collab/collab.d.ts +63 -0
  250. package/dist/websocket/collab/collab.js +481 -0
  251. package/dist/websocket/collab/constants.d.ts +1 -0
  252. package/dist/websocket/collab/constants.js +13 -0
  253. package/dist/websocket/collab/filter-to-fields.d.ts +2 -0
  254. package/dist/websocket/collab/filter-to-fields.js +11 -0
  255. package/dist/websocket/collab/messenger.d.ts +43 -0
  256. package/dist/websocket/collab/messenger.js +225 -0
  257. package/dist/websocket/collab/payload-permissions.d.ts +18 -0
  258. package/dist/websocket/collab/payload-permissions.js +158 -0
  259. package/dist/websocket/collab/permissions-cache.d.ts +52 -0
  260. package/dist/websocket/collab/permissions-cache.js +204 -0
  261. package/dist/websocket/collab/room.d.ts +125 -0
  262. package/dist/websocket/collab/room.js +593 -0
  263. package/dist/websocket/collab/store.d.ts +7 -0
  264. package/dist/websocket/collab/store.js +33 -0
  265. package/dist/websocket/collab/types.d.ts +21 -0
  266. package/dist/websocket/collab/types.js +1 -0
  267. package/dist/websocket/collab/verify-permissions.d.ts +11 -0
  268. package/dist/websocket/collab/verify-permissions.js +100 -0
  269. package/dist/websocket/controllers/base.d.ts +2 -2
  270. package/dist/websocket/controllers/base.js +3 -3
  271. package/dist/websocket/controllers/graphql.d.ts +1 -1
  272. package/dist/websocket/controllers/graphql.js +1 -1
  273. package/dist/websocket/controllers/logs.d.ts +1 -1
  274. package/dist/websocket/controllers/rest.d.ts +1 -1
  275. package/dist/websocket/controllers/rest.js +2 -2
  276. package/dist/websocket/handlers/heartbeat.js +1 -1
  277. package/dist/websocket/handlers/index.d.ts +2 -0
  278. package/dist/websocket/handlers/index.js +9 -0
  279. package/dist/websocket/handlers/items.js +2 -2
  280. package/dist/websocket/handlers/subscribe.js +1 -1
  281. package/dist/websocket/types.d.ts +1 -1
  282. package/dist/websocket/utils/items.d.ts +2 -2
  283. package/dist/websocket/utils/message.d.ts +1 -1
  284. package/dist/websocket/utils/message.js +2 -2
  285. package/dist/websocket/utils/wait-for-message.js +1 -1
  286. package/package.json +35 -33
  287. package/dist/controllers/webhooks.js +0 -74
  288. package/dist/services/webhooks.d.ts +0 -14
  289. package/dist/services/webhooks.js +0 -32
  290. package/dist/utils/get-relation-info.d.ts +0 -6
  291. package/dist/utils/get-relation-info.js +0 -43
  292. package/dist/utils/get-relation-type.d.ts +0 -6
  293. package/dist/utils/get-relation-type.js +0 -18
  294. package/dist/utils/is-login-redirect-allowed.d.ts +0 -4
  295. package/dist/utils/versioning/deep-map-with-schema.d.ts +0 -23
  296. package/dist/utils/versioning/deep-map-with-schema.js +0 -81
  297. /package/dist/controllers/{webhooks.d.ts → deployment.d.ts} +0 -0
@@ -0,0 +1,204 @@
1
+ import { useEnv } from '@directus/env';
2
+ import { LRUMapWithDelete } from 'mnemonist';
3
+ import { useBus } from '../../bus/index.js';
4
+ import { IRRELEVANT_COLLECTIONS } from './constants.js';
5
+ const env = useEnv();
6
+ /**
7
+ * Caches permission check results for collaborative editing clients.
8
+ * Supports granular invalidation based on collection, item, and relational dependencies.
9
+ */
10
+ export class PermissionCache {
11
+ cache;
12
+ tags = new Map();
13
+ keyTags = new Map();
14
+ timers = new Map();
15
+ bus = useBus();
16
+ invalidationCount = 0;
17
+ constructor(maxSize) {
18
+ this.cache = new LRUMapWithDelete(maxSize);
19
+ this.bus.subscribe('websocket.event', (event) => {
20
+ this.handleInvalidation(event);
21
+ });
22
+ }
23
+ /**
24
+ * Used for race condition protection during async permission fetches.
25
+ */
26
+ getInvalidationCount() {
27
+ return this.invalidationCount;
28
+ }
29
+ /**
30
+ * Clears entire cache for system collections, or performs granular invalidation for user data.
31
+ */
32
+ handleInvalidation(event) {
33
+ const { collection, keys, key } = event;
34
+ const items = keys || (key ? [key] : []);
35
+ const affectedKeys = new Set();
36
+ // System Invalidation (Roles, Permissions, Policies, Schema)
37
+ if ([
38
+ 'directus_roles',
39
+ 'directus_permissions',
40
+ 'directus_policies',
41
+ 'directus_access',
42
+ 'directus_fields',
43
+ 'directus_relations',
44
+ 'directus_collections',
45
+ ].includes(collection)) {
46
+ this.clear();
47
+ return;
48
+ }
49
+ // Skip known high-traffic collections
50
+ if (IRRELEVANT_COLLECTIONS.includes(collection)) {
51
+ return;
52
+ }
53
+ this.invalidationCount++;
54
+ // Prevent overflow issues in long-running processes
55
+ if (this.invalidationCount >= Number.MAX_SAFE_INTEGER) {
56
+ this.invalidationCount = 1;
57
+ }
58
+ // Skip if no keys are watching
59
+ if (!this.tags.has(`collection:${collection}`) &&
60
+ !this.tags.has(`dependency:${collection}`) &&
61
+ !this.tags.has(`collection-dependency:${collection}`)) {
62
+ return;
63
+ }
64
+ // Collection-level Invalidation
65
+ if (items.length === 0 && this.tags.has(`collection:${collection}`)) {
66
+ for (const k of this.tags.get(`collection:${collection}`))
67
+ affectedKeys.add(k);
68
+ }
69
+ // Item-level Invalidation
70
+ for (const id of items) {
71
+ const tag = `item:${collection}:${id}`;
72
+ if (this.tags.has(tag)) {
73
+ for (const k of this.tags.get(tag))
74
+ affectedKeys.add(k);
75
+ }
76
+ }
77
+ // Dependency Invalidation (Items + Relational)
78
+ const depTags = [`dependency:${collection}`];
79
+ if (items.length > 0) {
80
+ for (const id of items) {
81
+ depTags.push(`dependency:${collection}:${id}`);
82
+ }
83
+ }
84
+ else {
85
+ depTags.push(`collection-dependency:${collection}`);
86
+ }
87
+ for (const tag of depTags) {
88
+ if (this.tags.has(tag)) {
89
+ for (const k of this.tags.get(tag))
90
+ affectedKeys.add(k);
91
+ }
92
+ }
93
+ for (const k of affectedKeys) {
94
+ this.invalidateKey(k);
95
+ }
96
+ }
97
+ /**
98
+ * Get cached allowed fields for a given accountability and collection/item.
99
+ * LRUMap automatically updates access order on get().
100
+ */
101
+ get(accountability, collection, item, action) {
102
+ const key = this.getCacheKey(accountability, collection, item, action);
103
+ return this.cache.get(key);
104
+ }
105
+ /**
106
+ * Store allowed fields in the cache with optional TTL and dependencies.
107
+ */
108
+ set(accountability, collection, item, action, fields, dependencies = [], ttlMs) {
109
+ const key = this.getCacheKey(accountability, collection, item, action);
110
+ // Clear existing timer if any
111
+ if (this.timers.has(key)) {
112
+ clearTimeout(this.timers.get(key));
113
+ this.timers.delete(key);
114
+ }
115
+ // Clean up metadata for LRU eviction if at capacity
116
+ // LRUMapWithDelete auto-evicts, but we need to clean up our tag mappings
117
+ if (!this.cache.has(key) && this.cache.size >= this.cache.capacity) {
118
+ const lruKey = this.cache.keys().next().value;
119
+ if (lruKey) {
120
+ this.cleanupKeyMetadata(lruKey);
121
+ }
122
+ }
123
+ this.cache.set(key, fields);
124
+ if (ttlMs) {
125
+ const timer = setTimeout(() => {
126
+ this.invalidateKey(key);
127
+ }, ttlMs);
128
+ this.timers.set(key, timer);
129
+ }
130
+ // Always tag the specific item
131
+ this.addTag(key, `item:${collection}:${item}`);
132
+ // Always tag the collection to cover batch updates
133
+ this.addTag(key, `collection:${collection}`);
134
+ // Add custom dependencies such as relational collections
135
+ for (const dep of dependencies) {
136
+ this.addTag(key, `dependency:${dep}`);
137
+ if (dep.includes(':')) {
138
+ const [dependencyCollection] = dep.split(':');
139
+ this.addTag(key, `collection-dependency:${dependencyCollection}`);
140
+ }
141
+ }
142
+ }
143
+ /**
144
+ * Called before LRU eviction or explicit invalidation to prevent orphaned metadata.
145
+ */
146
+ cleanupKeyMetadata(key) {
147
+ if (this.timers.has(key)) {
148
+ clearTimeout(this.timers.get(key));
149
+ this.timers.delete(key);
150
+ }
151
+ const tags = this.keyTags.get(key);
152
+ if (tags) {
153
+ for (const tag of tags) {
154
+ const keys = this.tags.get(tag);
155
+ if (keys) {
156
+ keys.delete(key);
157
+ if (keys.size === 0)
158
+ this.tags.delete(tag);
159
+ }
160
+ }
161
+ this.keyTags.delete(key);
162
+ }
163
+ }
164
+ /**
165
+ * Maintains bidirectional mappings: tag → keys and key → tags.
166
+ */
167
+ addTag(key, tag) {
168
+ if (!this.tags.has(tag)) {
169
+ this.tags.set(tag, new Set());
170
+ }
171
+ this.tags.get(tag).add(key);
172
+ if (!this.keyTags.has(key)) {
173
+ this.keyTags.set(key, new Set());
174
+ }
175
+ this.keyTags.get(key).add(tag);
176
+ }
177
+ /**
178
+ * Cleans up metadata first, then removes from cache.
179
+ */
180
+ invalidateKey(key) {
181
+ this.cleanupKeyMetadata(key);
182
+ this.cache.delete(key);
183
+ }
184
+ /**
185
+ * Cache key format: user:collection:item:action
186
+ */
187
+ getCacheKey(accountability, collection, item, action) {
188
+ return `${accountability.user || 'public'}:${collection}:${item || 'singleton'}:${action}`;
189
+ }
190
+ /**
191
+ * Clear the entire cache.
192
+ */
193
+ clear() {
194
+ for (const timer of this.timers.values()) {
195
+ clearTimeout(timer);
196
+ }
197
+ this.timers.clear();
198
+ this.cache.clear();
199
+ this.tags.clear();
200
+ this.keyTags.clear();
201
+ this.invalidationCount++;
202
+ }
203
+ }
204
+ export const permissionCache = new PermissionCache(Number(env['WEBSOCKETS_COLLAB_PERMISSIONS_CACHE_CAPACITY'] ?? 2000));
@@ -0,0 +1,125 @@
1
+ import { type Accountability, type Item, type PrimaryKey, type WebSocketClient } from '@directus/types';
2
+ import { type BaseServerMessage, type ClientID, type Color, type ServerError } from '@directus/types/collab';
3
+ import { Messenger } from './messenger.js';
4
+ import type { JoinMessage, PermissionClient } from './types.js';
5
+ /**
6
+ * Store and manage all active collaborative editing rooms
7
+ */
8
+ export declare class RoomManager {
9
+ rooms: Record<string, Room>;
10
+ messenger: Messenger;
11
+ constructor(messenger?: Messenger);
12
+ /**
13
+ * Create a new collaborative editing room or return an existing one matching collection, item and version.
14
+ */
15
+ createRoom(collection: string, item: PrimaryKey | null, version: string | null, initialChanges?: Item): Promise<Room>;
16
+ /**
17
+ * Remove a room from local memory
18
+ */
19
+ removeRoom(uid: string): void;
20
+ /**
21
+ * Get an existing room by UID.
22
+ * If the room is not part of the local rooms, it will be loaded from shared memory.
23
+ * The room will not be persisted in local memory.
24
+ */
25
+ getRoom(uid: string): Promise<Room | undefined>;
26
+ /**
27
+ * Get all rooms a client is currently in from local memory
28
+ */
29
+ getClientRooms(uid: ClientID): Promise<Room[]>;
30
+ /**
31
+ * Returns all clients that are part of a room in the local memory
32
+ */
33
+ getLocalRoomClients(): Promise<RoomClient[]>;
34
+ /**
35
+ * Remove empty rooms from local memory
36
+ */
37
+ cleanupRooms(uids?: string[]): Promise<void>;
38
+ /**
39
+ * Forcefully close all local rooms and notify clients.
40
+ */
41
+ terminateAll(): Promise<void>;
42
+ }
43
+ type RoomClient = {
44
+ uid: ClientID;
45
+ accountability: Accountability;
46
+ color: Color;
47
+ };
48
+ type RoomData = {
49
+ uid: string;
50
+ collection: string;
51
+ item: PrimaryKey | null;
52
+ version: string | null;
53
+ changes: Item;
54
+ clients: RoomClient[];
55
+ focuses: Record<ClientID, string>;
56
+ };
57
+ /**
58
+ * Represents a single collaborative editing room for a specific item
59
+ */
60
+ export declare class Room {
61
+ uid: string;
62
+ collection: string;
63
+ item: PrimaryKey | null;
64
+ version: string | null;
65
+ initialChanges: Item | undefined;
66
+ messenger: Messenger;
67
+ store: <T>(callback: (store: import("./store.js").RedisStore<RoomData>) => Promise<T>) => Promise<T>;
68
+ onUpdateHandler: (meta: Record<string, any>, context?: any) => Promise<void>;
69
+ onDeleteHandler: (meta: Record<string, any>, context?: any) => Promise<void>;
70
+ constructor(uid: string, collection: string, item: PrimaryKey | null, version: string | null, initialChanges?: Item, messenger?: Messenger);
71
+ /**
72
+ * Ensures that foundational room state (metadata) exists in shared memory even after restarts
73
+ */
74
+ ensureInitialized(): Promise<void>;
75
+ getDisplayName(): string;
76
+ getClients(): Promise<RoomClient[]>;
77
+ getFocuses(): Promise<Record<ClientID, string>>;
78
+ getChanges(): Promise<Item>;
79
+ hasClient(id: ClientID): Promise<boolean>;
80
+ getFocusByUser(id: ClientID): Promise<string | undefined>;
81
+ getFocusByField(field: string): Promise<string | undefined>;
82
+ /**
83
+ * Client requesting to join a room. If the client hasn't entered the room already, add a new client.
84
+ * Otherwise all users just will be informed again that the user has joined.
85
+ */
86
+ join(client: WebSocketClient, color?: JoinMessage['color']): Promise<void>;
87
+ /**
88
+ * Leave the room
89
+ */
90
+ leave(uid: ClientID): Promise<void>;
91
+ /**
92
+ * Propagate an update to other clients
93
+ */
94
+ update(sender: WebSocketClient, changes: Record<string, unknown>): Promise<void>;
95
+ /**
96
+ * Propagate an unset to other clients
97
+ */
98
+ unset(sender: PermissionClient, field: string): Promise<void>;
99
+ /**
100
+ * Discard specified changes in the room and propagate to other clients
101
+ */
102
+ discard(fields: string[]): Promise<void>;
103
+ /**
104
+ * Atomically acquire or release focus and propagate focus state to other clients
105
+ */
106
+ focus(sender: PermissionClient, field: string | null): Promise<boolean>;
107
+ sendAll(message: BaseServerMessage): Promise<void>;
108
+ sendExcluding(message: BaseServerMessage, exclude: ClientID): Promise<void>;
109
+ send(client: ClientID, message: BaseServerMessage): void;
110
+ /**
111
+ * Close the room and clean up shared state
112
+ *
113
+ * @param options.force If true, close the room even if active clients are present
114
+ * @param options.reason Optional reason to be sent to clients
115
+ * @param options.terminate If true, forcefully terminate the client connection after closing
116
+ */
117
+ close(options?: {
118
+ force?: boolean;
119
+ reason?: ServerError;
120
+ terminate?: boolean;
121
+ }): Promise<boolean>;
122
+ dispose(): void;
123
+ }
124
+ export declare function getRoomHash(collection: string, item: PrimaryKey | null, version: string | null): string;
125
+ export {};