@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.
- package/dist/ai/chat/controllers/chat.post.js +19 -4
- package/dist/ai/chat/lib/create-ui-stream.d.ts +8 -7
- package/dist/ai/chat/lib/create-ui-stream.js +28 -25
- package/dist/ai/chat/middleware/load-settings.js +31 -7
- package/dist/ai/chat/models/chat-request.d.ts +135 -2
- package/dist/ai/chat/models/chat-request.js +56 -2
- package/dist/ai/chat/models/providers.d.ts +16 -2
- package/dist/ai/chat/models/providers.js +16 -2
- package/dist/ai/chat/utils/chat-request-tool-to-ai-sdk-tool.js +3 -4
- package/dist/ai/chat/utils/format-context.d.ts +5 -0
- package/dist/ai/chat/utils/format-context.js +122 -0
- package/dist/ai/mcp/server.d.ts +27 -1
- package/dist/ai/providers/index.d.ts +3 -0
- package/dist/ai/providers/index.js +3 -0
- package/dist/ai/providers/options.d.ts +14 -0
- package/dist/ai/providers/options.js +26 -0
- package/dist/ai/providers/registry.d.ts +6 -0
- package/dist/ai/providers/registry.js +65 -0
- package/dist/ai/providers/types.d.ts +34 -0
- package/dist/ai/providers/types.js +1 -0
- package/dist/ai/tools/assets/index.js +1 -1
- package/dist/ai/tools/collections/index.js +2 -2
- package/dist/ai/tools/fields/index.js +2 -2
- package/dist/ai/tools/files/index.js +1 -1
- package/dist/ai/tools/flows/index.js +1 -1
- package/dist/ai/tools/folders/index.js +1 -1
- package/dist/ai/tools/items/index.js +6 -3
- package/dist/ai/tools/items/prompt.md +7 -9
- package/dist/ai/tools/relations/index.js +1 -1
- package/dist/ai/tools/schema.js +1 -1
- package/dist/ai/tools/trigger-flow/index.js +1 -1
- package/dist/app.js +12 -8
- package/dist/auth/drivers/ldap.d.ts +1 -1
- package/dist/auth/drivers/ldap.js +144 -139
- package/dist/auth/drivers/local.js +1 -1
- package/dist/auth/drivers/oauth2.d.ts +1 -2
- package/dist/auth/drivers/oauth2.js +22 -17
- package/dist/auth/drivers/openid.d.ts +1 -2
- package/dist/auth/drivers/openid.js +18 -13
- package/dist/auth/drivers/saml.js +3 -3
- package/dist/auth/utils/generate-callback-url.d.ts +11 -0
- package/dist/auth/utils/generate-callback-url.js +40 -0
- package/dist/auth/utils/is-login-redirect-allowed.d.ts +7 -0
- package/dist/{utils → auth/utils}/is-login-redirect-allowed.js +12 -9
- package/dist/cache.d.ts +12 -0
- package/dist/cache.js +27 -3
- package/dist/cli/commands/bootstrap/index.js +2 -2
- package/dist/cli/commands/database/install.js +1 -1
- package/dist/cli/commands/database/migrate.js +1 -1
- package/dist/cli/commands/init/index.js +2 -2
- package/dist/cli/commands/roles/create.js +4 -4
- package/dist/cli/commands/schema/apply.js +3 -3
- package/dist/cli/commands/schema/snapshot.js +1 -1
- package/dist/cli/utils/create-db-connection.d.ts +1 -1
- package/dist/cli/utils/create-db-connection.js +1 -1
- package/dist/cli/utils/create-env/env-stub.liquid +3 -0
- package/dist/cli/utils/create-env/index.js +1 -1
- package/dist/constants.d.ts +7 -3
- package/dist/constants.js +7 -3
- package/dist/controllers/access.js +1 -1
- package/dist/controllers/assets.js +1 -1
- package/dist/controllers/deployment.js +481 -0
- package/dist/controllers/extensions.js +1 -1
- package/dist/controllers/fields.js +8 -6
- package/dist/controllers/files.js +1 -1
- package/dist/controllers/items.js +1 -1
- package/dist/controllers/not-found.js +1 -1
- package/dist/controllers/relations.js +1 -1
- package/dist/database/errors/dialects/mysql.d.ts +1 -1
- package/dist/database/errors/dialects/postgres.d.ts +1 -1
- package/dist/database/errors/dialects/sqlite.d.ts +1 -1
- package/dist/database/errors/translate.d.ts +1 -1
- package/dist/database/errors/translate.js +1 -1
- package/dist/database/get-ast-from-query/lib/parse-fields.js +2 -2
- package/dist/database/helpers/date/dialects/mssql.js +1 -1
- package/dist/database/helpers/date/dialects/mysql.js +1 -1
- package/dist/database/helpers/date/types.js +1 -1
- package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +1 -0
- package/dist/database/helpers/schema/dialects/cockroachdb.js +24 -1
- package/dist/database/helpers/schema/dialects/mssql.d.ts +1 -1
- package/dist/database/helpers/schema/dialects/mysql.d.ts +2 -1
- package/dist/database/helpers/schema/dialects/mysql.js +16 -3
- package/dist/database/helpers/schema/dialects/postgres.d.ts +1 -1
- package/dist/database/helpers/schema/types.d.ts +13 -0
- package/dist/database/helpers/schema/types.js +24 -0
- package/dist/database/index.js +4 -4
- package/dist/database/migrations/20220429A-add-flows.js +1 -1
- package/dist/database/migrations/20230526A-migrate-translation-strings.js +1 -1
- package/dist/database/migrations/20231009A-update-csv-fields-to-text.js +1 -1
- package/dist/database/migrations/20240204A-marketplace.js +9 -7
- package/dist/database/migrations/20240311A-deprecate-webhooks.d.ts +15 -0
- package/dist/database/migrations/20240311A-deprecate-webhooks.js +1 -1
- package/dist/database/migrations/20240806A-permissions-policies.js +2 -2
- package/dist/database/migrations/20240924A-migrate-legacy-comments.js +1 -1
- package/dist/database/migrations/20251014A-add-project-owner.js +1 -1
- package/dist/database/migrations/20251224A-remove-webhooks.d.ts +3 -0
- package/dist/database/migrations/20251224A-remove-webhooks.js +19 -0
- package/dist/database/migrations/20260110A-add-ai-provider-settings.d.ts +3 -0
- package/dist/database/migrations/20260110A-add-ai-provider-settings.js +35 -0
- package/dist/database/migrations/20260113A-add-revisions-index.d.ts +3 -0
- package/dist/database/migrations/20260113A-add-revisions-index.js +41 -0
- package/dist/database/migrations/20260128A-add-collaborative-editing.d.ts +3 -0
- package/dist/database/migrations/20260128A-add-collaborative-editing.js +10 -0
- package/dist/database/migrations/20260204A-add-deployment.d.ts +3 -0
- package/dist/database/migrations/20260204A-add-deployment.js +32 -0
- package/dist/database/migrations/run.js +3 -3
- package/dist/database/run-ast/lib/apply-query/add-join.js +1 -1
- package/dist/database/run-ast/lib/apply-query/filter/get-filter-type.d.ts +2 -2
- package/dist/database/run-ast/lib/apply-query/filter/get-filter-type.js +1 -1
- package/dist/database/run-ast/lib/apply-query/filter/index.js +1 -1
- package/dist/database/run-ast/lib/apply-query/filter/operator.js +1 -1
- package/dist/database/run-ast/lib/apply-query/sort.js +1 -1
- package/dist/database/run-ast/utils/get-column-pre-processor.js +2 -2
- package/dist/database/run-ast/utils/get-column.js +1 -1
- package/dist/database/seeds/run.js +3 -3
- package/dist/deployment/deployment.d.ts +94 -0
- package/dist/deployment/deployment.js +29 -0
- package/dist/deployment/drivers/index.d.ts +1 -0
- package/dist/deployment/drivers/index.js +1 -0
- package/dist/deployment/drivers/vercel.d.ts +32 -0
- package/dist/deployment/drivers/vercel.js +208 -0
- package/dist/deployment/index.d.ts +2 -0
- package/dist/deployment/index.js +2 -0
- package/dist/deployment.d.ts +24 -0
- package/dist/deployment.js +39 -0
- package/dist/extensions/lib/get-extensions-path.js +1 -1
- package/dist/extensions/lib/get-extensions-settings.js +1 -1
- package/dist/extensions/lib/get-extensions.js +1 -1
- package/dist/extensions/lib/get-shared-deps-mapping.js +3 -3
- package/dist/extensions/lib/installation/manager.js +3 -3
- package/dist/extensions/lib/sandbox/register/route.d.ts +1 -1
- package/dist/extensions/lib/sync/status.js +1 -1
- package/dist/extensions/lib/sync/sync.js +7 -7
- package/dist/extensions/lib/sync/utils.js +2 -2
- package/dist/extensions/manager.d.ts +1 -1
- package/dist/extensions/manager.js +8 -8
- package/dist/flows.d.ts +1 -1
- package/dist/logger/index.js +1 -1
- package/dist/logger/logs-stream.d.ts +1 -1
- package/dist/logger/logs-stream.js +1 -1
- package/dist/mailer.js +1 -1
- package/dist/metrics/lib/create-metrics.js +2 -2
- package/dist/middleware/authenticate.js +3 -3
- package/dist/middleware/collection-exists.js +1 -1
- package/dist/middleware/extract-token.js +1 -1
- package/dist/middleware/graphql.js +2 -2
- package/dist/middleware/respond.js +27 -14
- package/dist/middleware/validate-batch.js +1 -1
- package/dist/operations/exec/index.js +2 -1
- package/dist/operations/mail/index.js +1 -1
- package/dist/operations/mail/rate-limiter.js +2 -2
- package/dist/permissions/cache.js +5 -0
- package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.js +1 -1
- package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.js +1 -1
- package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.js +2 -2
- package/dist/permissions/modules/process-ast/lib/inject-cases.js +1 -1
- package/dist/permissions/modules/process-ast/process-ast.js +1 -1
- package/dist/permissions/modules/process-ast/utils/find-related-collection.js +1 -1
- package/dist/permissions/modules/process-payload/process-payload.js +1 -1
- package/dist/permissions/modules/validate-access/lib/validate-item-access.d.ts +14 -2
- package/dist/permissions/modules/validate-access/lib/validate-item-access.js +72 -13
- package/dist/permissions/modules/validate-access/validate-access.js +3 -2
- package/dist/rate-limiter.js +1 -1
- package/dist/request/is-denied-ip.js +1 -1
- package/dist/schedules/project.js +1 -1
- package/dist/schedules/telemetry.js +1 -1
- package/dist/schedules/tus.js +1 -1
- package/dist/server.js +6 -5
- package/dist/services/assets.d.ts +2 -1
- package/dist/services/assets.js +35 -8
- package/dist/services/authentication.js +2 -2
- package/dist/services/collections.js +1 -1
- package/dist/services/deployment-projects.d.ts +20 -0
- package/dist/services/deployment-projects.js +34 -0
- package/dist/services/deployment-runs.d.ts +13 -0
- package/dist/services/deployment-runs.js +6 -0
- package/dist/services/deployment.d.ts +40 -0
- package/dist/services/deployment.js +202 -0
- package/dist/services/extensions.d.ts +1 -1
- package/dist/services/files/utils/get-metadata.d.ts +1 -1
- package/dist/services/files/utils/get-metadata.js +1 -1
- package/dist/services/files.d.ts +1 -1
- package/dist/services/files.js +4 -4
- package/dist/services/graphql/index.d.ts +1 -1
- package/dist/services/graphql/index.js +1 -1
- package/dist/services/graphql/resolvers/mutation.js +1 -1
- package/dist/services/graphql/resolvers/system-admin.js +2 -3
- package/dist/services/graphql/schema/get-types.d.ts +1 -1
- package/dist/services/graphql/schema/read.js +1 -1
- package/dist/services/graphql/subscription.d.ts +1 -1
- package/dist/services/graphql/types/date.js +1 -1
- package/dist/services/graphql/types/hash.js +1 -1
- package/dist/services/graphql/utils/add-path-to-validation-error.js +1 -1
- package/dist/services/graphql/utils/filter-replace-m2a.js +3 -4
- package/dist/services/import-export.d.ts +1 -1
- package/dist/services/import-export.js +2 -2
- package/dist/services/index.d.ts +3 -1
- package/dist/services/index.js +3 -1
- package/dist/services/mail/index.js +2 -2
- package/dist/services/mail/rate-limiter.js +2 -2
- package/dist/services/payload.js +2 -2
- package/dist/services/schema.js +1 -1
- package/dist/services/server.js +13 -4
- package/dist/services/settings.js +2 -2
- package/dist/services/specifications.js +2 -2
- package/dist/services/tfa.js +1 -1
- package/dist/services/translations.js +1 -1
- package/dist/services/tus/data-store.d.ts +1 -3
- package/dist/services/tus/data-store.js +2 -5
- package/dist/services/tus/server.js +6 -6
- package/dist/services/users.js +4 -4
- package/dist/services/versions.js +1 -1
- package/dist/telemetry/lib/get-report.js +2 -0
- package/dist/telemetry/lib/send-report.d.ts +1 -1
- package/dist/telemetry/lib/send-report.js +1 -1
- package/dist/telemetry/lib/track.js +1 -1
- package/dist/telemetry/types/report.d.ts +8 -0
- package/dist/telemetry/utils/get-settings.d.ts +2 -0
- package/dist/telemetry/utils/get-settings.js +5 -0
- package/dist/test-utils/knex.js +1 -1
- package/dist/types/collection.d.ts +1 -1
- package/dist/utils/async-handler.d.ts +1 -1
- package/dist/utils/calculate-field-depth.js +1 -1
- package/dist/utils/compress.js +1 -1
- package/dist/utils/deep-map-response.d.ts +1 -1
- package/dist/utils/deep-map-response.js +2 -2
- package/dist/utils/get-cache-key.js +1 -1
- package/dist/utils/get-column-path.js +1 -1
- package/dist/utils/get-field-system-rows.js +1 -1
- package/dist/utils/get-ip-from-req.d.ts +1 -1
- package/dist/utils/get-ip-from-req.js +1 -1
- package/dist/utils/get-local-type.js +7 -3
- package/dist/utils/get-service.js +7 -3
- package/dist/utils/get-snapshot-diff.js +1 -1
- package/dist/utils/is-field-allowed.d.ts +4 -0
- package/dist/utils/is-field-allowed.js +9 -0
- package/dist/utils/is-url-allowed.js +1 -1
- package/dist/utils/jwt.js +1 -1
- package/dist/utils/sanitize-schema.d.ts +1 -1
- package/dist/utils/should-clear-cache.d.ts +1 -1
- package/dist/utils/should-skip-cache.js +2 -2
- package/dist/utils/validate-diff.js +1 -1
- package/dist/utils/validate-snapshot.js +3 -3
- package/dist/utils/validate-storage.js +2 -2
- package/dist/utils/verify-session-jwt.js +1 -1
- package/dist/utils/versioning/handle-version.js +1 -1
- package/dist/websocket/collab/calculate-cache-metadata.d.ts +9 -0
- package/dist/websocket/collab/calculate-cache-metadata.js +121 -0
- package/dist/websocket/collab/collab.d.ts +63 -0
- package/dist/websocket/collab/collab.js +481 -0
- package/dist/websocket/collab/constants.d.ts +1 -0
- package/dist/websocket/collab/constants.js +13 -0
- package/dist/websocket/collab/filter-to-fields.d.ts +2 -0
- package/dist/websocket/collab/filter-to-fields.js +11 -0
- package/dist/websocket/collab/messenger.d.ts +43 -0
- package/dist/websocket/collab/messenger.js +225 -0
- package/dist/websocket/collab/payload-permissions.d.ts +18 -0
- package/dist/websocket/collab/payload-permissions.js +158 -0
- package/dist/websocket/collab/permissions-cache.d.ts +52 -0
- package/dist/websocket/collab/permissions-cache.js +204 -0
- package/dist/websocket/collab/room.d.ts +125 -0
- package/dist/websocket/collab/room.js +593 -0
- package/dist/websocket/collab/store.d.ts +7 -0
- package/dist/websocket/collab/store.js +33 -0
- package/dist/websocket/collab/types.d.ts +21 -0
- package/dist/websocket/collab/types.js +1 -0
- package/dist/websocket/collab/verify-permissions.d.ts +11 -0
- package/dist/websocket/collab/verify-permissions.js +100 -0
- package/dist/websocket/controllers/base.d.ts +2 -2
- package/dist/websocket/controllers/base.js +3 -3
- package/dist/websocket/controllers/graphql.d.ts +1 -1
- package/dist/websocket/controllers/graphql.js +1 -1
- package/dist/websocket/controllers/logs.d.ts +1 -1
- package/dist/websocket/controllers/rest.d.ts +1 -1
- package/dist/websocket/controllers/rest.js +2 -2
- package/dist/websocket/handlers/heartbeat.js +1 -1
- package/dist/websocket/handlers/index.d.ts +2 -0
- package/dist/websocket/handlers/index.js +9 -0
- package/dist/websocket/handlers/items.js +2 -2
- package/dist/websocket/handlers/subscribe.js +1 -1
- package/dist/websocket/types.d.ts +1 -1
- package/dist/websocket/utils/items.d.ts +2 -2
- package/dist/websocket/utils/message.d.ts +1 -1
- package/dist/websocket/utils/message.js +2 -2
- package/dist/websocket/utils/wait-for-message.js +1 -1
- package/package.json +35 -33
- package/dist/controllers/webhooks.js +0 -74
- package/dist/services/webhooks.d.ts +0 -14
- package/dist/services/webhooks.js +0 -32
- package/dist/utils/get-relation-info.d.ts +0 -6
- package/dist/utils/get-relation-info.js +0 -43
- package/dist/utils/get-relation-type.d.ts +0 -6
- package/dist/utils/get-relation-type.js +0 -18
- package/dist/utils/is-login-redirect-allowed.d.ts +0 -4
- package/dist/utils/versioning/deep-map-with-schema.d.ts +0 -23
- package/dist/utils/versioning/deep-map-with-schema.js +0 -81
- /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 {};
|