@directus/api 33.0.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 (116) hide show
  1. package/dist/ai/chat/controllers/chat.post.js +19 -4
  2. package/dist/ai/chat/lib/create-ui-stream.d.ts +7 -6
  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/items/index.js +4 -1
  22. package/dist/ai/tools/items/prompt.md +7 -9
  23. package/dist/ai/tools/schema.js +1 -1
  24. package/dist/app.js +4 -0
  25. package/dist/auth/drivers/ldap.d.ts +1 -1
  26. package/dist/auth/drivers/ldap.js +142 -137
  27. package/dist/cache.d.ts +12 -0
  28. package/dist/cache.js +25 -1
  29. package/dist/cli/utils/create-env/env-stub.liquid +3 -0
  30. package/dist/controllers/deployment.d.ts +2 -0
  31. package/dist/controllers/deployment.js +481 -0
  32. package/dist/controllers/fields.js +6 -4
  33. package/dist/database/get-ast-from-query/lib/parse-fields.js +2 -2
  34. package/dist/database/migrations/20260110A-add-ai-provider-settings.d.ts +3 -0
  35. package/dist/database/migrations/20260110A-add-ai-provider-settings.js +35 -0
  36. package/dist/database/migrations/20260128A-add-collaborative-editing.d.ts +3 -0
  37. package/dist/database/migrations/20260128A-add-collaborative-editing.js +10 -0
  38. package/dist/database/migrations/20260204A-add-deployment.d.ts +3 -0
  39. package/dist/database/migrations/20260204A-add-deployment.js +32 -0
  40. package/dist/database/run-ast/lib/apply-query/add-join.js +1 -1
  41. package/dist/database/run-ast/lib/apply-query/filter/get-filter-type.d.ts +2 -2
  42. package/dist/database/run-ast/lib/apply-query/filter/index.js +1 -1
  43. package/dist/database/run-ast/lib/apply-query/sort.js +1 -1
  44. package/dist/deployment/deployment.d.ts +94 -0
  45. package/dist/deployment/deployment.js +29 -0
  46. package/dist/deployment/drivers/index.d.ts +1 -0
  47. package/dist/deployment/drivers/index.js +1 -0
  48. package/dist/deployment/drivers/vercel.d.ts +32 -0
  49. package/dist/deployment/drivers/vercel.js +208 -0
  50. package/dist/deployment/index.d.ts +2 -0
  51. package/dist/deployment/index.js +2 -0
  52. package/dist/deployment.d.ts +24 -0
  53. package/dist/deployment.js +39 -0
  54. package/dist/middleware/respond.js +27 -14
  55. package/dist/permissions/modules/process-ast/utils/find-related-collection.js +1 -1
  56. package/dist/permissions/modules/validate-access/lib/validate-item-access.d.ts +1 -1
  57. package/dist/permissions/modules/validate-access/lib/validate-item-access.js +19 -8
  58. package/dist/server.js +2 -1
  59. package/dist/services/deployment-projects.d.ts +20 -0
  60. package/dist/services/deployment-projects.js +34 -0
  61. package/dist/services/deployment-runs.d.ts +13 -0
  62. package/dist/services/deployment-runs.js +6 -0
  63. package/dist/services/deployment.d.ts +40 -0
  64. package/dist/services/deployment.js +202 -0
  65. package/dist/services/graphql/resolvers/system-admin.js +2 -3
  66. package/dist/services/graphql/utils/filter-replace-m2a.js +3 -4
  67. package/dist/services/index.d.ts +3 -0
  68. package/dist/services/index.js +3 -0
  69. package/dist/services/server.js +1 -0
  70. package/dist/services/specifications.js +2 -2
  71. package/dist/services/versions.js +1 -1
  72. package/dist/telemetry/lib/get-report.js +2 -0
  73. package/dist/telemetry/types/report.d.ts +8 -0
  74. package/dist/telemetry/utils/get-settings.d.ts +2 -0
  75. package/dist/telemetry/utils/get-settings.js +5 -0
  76. package/dist/utils/deep-map-response.d.ts +1 -1
  77. package/dist/utils/deep-map-response.js +1 -1
  78. package/dist/utils/get-column-path.js +1 -1
  79. package/dist/utils/get-service.js +7 -1
  80. package/dist/utils/is-field-allowed.d.ts +4 -0
  81. package/dist/utils/is-field-allowed.js +9 -0
  82. package/dist/utils/versioning/handle-version.js +1 -1
  83. package/dist/websocket/collab/calculate-cache-metadata.d.ts +9 -0
  84. package/dist/websocket/collab/calculate-cache-metadata.js +121 -0
  85. package/dist/websocket/collab/collab.d.ts +63 -0
  86. package/dist/websocket/collab/collab.js +481 -0
  87. package/dist/websocket/collab/constants.d.ts +1 -0
  88. package/dist/websocket/collab/constants.js +13 -0
  89. package/dist/websocket/collab/filter-to-fields.d.ts +2 -0
  90. package/dist/websocket/collab/filter-to-fields.js +11 -0
  91. package/dist/websocket/collab/messenger.d.ts +43 -0
  92. package/dist/websocket/collab/messenger.js +225 -0
  93. package/dist/websocket/collab/payload-permissions.d.ts +18 -0
  94. package/dist/websocket/collab/payload-permissions.js +158 -0
  95. package/dist/websocket/collab/permissions-cache.d.ts +52 -0
  96. package/dist/websocket/collab/permissions-cache.js +204 -0
  97. package/dist/websocket/collab/room.d.ts +125 -0
  98. package/dist/websocket/collab/room.js +593 -0
  99. package/dist/websocket/collab/store.d.ts +7 -0
  100. package/dist/websocket/collab/store.js +33 -0
  101. package/dist/websocket/collab/types.d.ts +21 -0
  102. package/dist/websocket/collab/types.js +1 -0
  103. package/dist/websocket/collab/verify-permissions.d.ts +11 -0
  104. package/dist/websocket/collab/verify-permissions.js +100 -0
  105. package/dist/websocket/handlers/index.d.ts +2 -0
  106. package/dist/websocket/handlers/index.js +9 -0
  107. package/dist/websocket/utils/items.d.ts +2 -2
  108. package/dist/websocket/utils/message.d.ts +1 -1
  109. package/dist/websocket/utils/message.js +2 -2
  110. package/package.json +32 -30
  111. package/dist/utils/get-relation-info.d.ts +0 -6
  112. package/dist/utils/get-relation-info.js +0 -43
  113. package/dist/utils/get-relation-type.d.ts +0 -6
  114. package/dist/utils/get-relation-type.js +0 -18
  115. package/dist/utils/versioning/deep-map-with-schema.d.ts +0 -23
  116. package/dist/utils/versioning/deep-map-with-schema.js +0 -81
@@ -0,0 +1,100 @@
1
+ import { useLogger } from '../../logger/index.js';
2
+ import { fetchPermissions } from '../../permissions/lib/fetch-permissions.js';
3
+ import { fetchPolicies } from '../../permissions/lib/fetch-policies.js';
4
+ import { fetchAllowedFields } from '../../permissions/modules/fetch-allowed-fields/fetch-allowed-fields.js';
5
+ import { validateItemAccess } from '../../permissions/modules/validate-access/lib/validate-item-access.js';
6
+ import { extractRequiredDynamicVariableContextForPermissions } from '../../permissions/utils/extract-required-dynamic-variable-context.js';
7
+ import { fetchDynamicVariableData } from '../../permissions/utils/fetch-dynamic-variable-data.js';
8
+ import { processPermissions } from '../../permissions/utils/process-permissions.js';
9
+ import { getService } from '../../utils/get-service.js';
10
+ import { calculateCacheMetadata } from './calculate-cache-metadata.js';
11
+ import { filterToFields } from './filter-to-fields.js';
12
+ import { permissionCache } from './permissions-cache.js';
13
+ /**
14
+ * Verify if a client has permissions to perform an action on the item.
15
+ * - `string[]`: List of fields the client has access to, empty if item exists but access is restricted.
16
+ * - `null`: Indicates the item doesn't exist.
17
+ */
18
+ export async function verifyPermissions(accountability, collection, item, action = 'read', options) {
19
+ if (!accountability)
20
+ return [];
21
+ const { schema, knex } = options;
22
+ if (!schema.collections[collection])
23
+ return [];
24
+ if (accountability.admin)
25
+ return ['*'];
26
+ const cached = permissionCache.get(accountability, collection, String(item), action);
27
+ if (cached !== undefined)
28
+ return cached;
29
+ // Prevent caching stale permissions if invalidation occurs during async steps
30
+ const startInvalidationCount = permissionCache.getInvalidationCount();
31
+ let itemData = null;
32
+ try {
33
+ const adminService = getService(collection, { schema, knex });
34
+ const policies = await fetchPolicies(accountability, { knex, schema });
35
+ const rawPermissions = await fetchPermissions({ action, collections: [collection], policies, accountability, bypassDynamicVariableProcessing: true }, { knex, schema });
36
+ // Check for item-level rules to skip DB fetch
37
+ const hasItemRules = rawPermissions.some((p) => p.permissions && Object.keys(p.permissions).length > 0);
38
+ if (hasItemRules) {
39
+ // Resolve dynamic variables used in the permission filters
40
+ const dynamicVariableContext = extractRequiredDynamicVariableContextForPermissions(rawPermissions);
41
+ const permissionsContext = await fetchDynamicVariableData({ accountability, policies, dynamicVariableContext }, { knex, schema });
42
+ const processedPermissions = processPermissions({
43
+ permissions: rawPermissions,
44
+ accountability,
45
+ permissionsContext,
46
+ });
47
+ const fieldsToFetch = processedPermissions
48
+ .map((perm) => (perm.permissions ? filterToFields(perm.permissions, collection, schema) : []))
49
+ .flat();
50
+ // Fetch current item data to evaluate any conditional permission filters based on record state
51
+ if (item && action !== 'create') {
52
+ try {
53
+ itemData = await adminService.readOne(item, {
54
+ fields: fieldsToFetch,
55
+ });
56
+ }
57
+ catch {
58
+ // Item doesn't exist
59
+ permissionCache.set(accountability, collection, String(item), action, null, []);
60
+ return null;
61
+ }
62
+ }
63
+ else if (schema.collections[collection]?.singleton && action !== 'create') {
64
+ const pkField = schema.collections[collection].primary;
65
+ if (pkField) {
66
+ if (Array.from(fieldsToFetch).some((field) => field === '*' || field === pkField) === false) {
67
+ fieldsToFetch.push(pkField);
68
+ }
69
+ }
70
+ itemData = await adminService.readSingleton({ fields: fieldsToFetch });
71
+ }
72
+ }
73
+ let allowedFields = [];
74
+ if ((item || schema.collections[collection]?.singleton) && hasItemRules) {
75
+ const primaryKeys = item ? [item] : [];
76
+ const validationContext = {
77
+ collection,
78
+ accountability,
79
+ action,
80
+ primaryKeys,
81
+ returnAllowedRootFields: true,
82
+ };
83
+ allowedFields = (await validateItemAccess(validationContext, { knex, schema })).allowedRootFields || [];
84
+ }
85
+ else {
86
+ allowedFields = await fetchAllowedFields({ accountability, action, collection }, { knex, schema });
87
+ }
88
+ // Only cache if the state hasn't been invalidated by another operation in the meantime
89
+ if (permissionCache.getInvalidationCount() === startInvalidationCount) {
90
+ // Determine TTL and relational dependencies for cache invalidation
91
+ const { ttlMs, dependencies } = calculateCacheMetadata(collection, itemData, rawPermissions, schema, accountability);
92
+ permissionCache.set(accountability, collection, String(item), action, allowedFields, dependencies, ttlMs);
93
+ }
94
+ return allowedFields;
95
+ }
96
+ catch (err) {
97
+ useLogger().error(err, `[Collab] verifyPermissions failed for user "${accountability.user}", collection "${collection}", and item "${item}"`);
98
+ return [];
99
+ }
100
+ }
@@ -1,4 +1,6 @@
1
+ import { CollabHandler } from '../collab/collab.js';
1
2
  export declare function startWebSocketHandlers(): void;
3
+ export declare function getCollabHandler(): CollabHandler | undefined;
2
4
  export * from './heartbeat.js';
3
5
  export * from './items.js';
4
6
  export * from './logs.js';
@@ -1,15 +1,18 @@
1
1
  import { useEnv } from '@directus/env';
2
2
  import { toBoolean } from '@directus/utils';
3
+ import { CollabHandler } from '../collab/collab.js';
3
4
  import { HeartbeatHandler } from './heartbeat.js';
4
5
  import { ItemsHandler } from './items.js';
5
6
  import { LogsHandler } from './logs.js';
6
7
  import { SubscribeHandler } from './subscribe.js';
8
+ let collabHandler;
7
9
  export function startWebSocketHandlers() {
8
10
  const env = useEnv();
9
11
  const heartbeatEnabled = toBoolean(env['WEBSOCKETS_HEARTBEAT_ENABLED']);
10
12
  const restEnabled = toBoolean(env['WEBSOCKETS_REST_ENABLED']);
11
13
  const graphqlEnabled = toBoolean(env['WEBSOCKETS_GRAPHQL_ENABLED']);
12
14
  const logsEnabled = toBoolean(env['WEBSOCKETS_LOGS_ENABLED']);
15
+ const collabEnabled = toBoolean(env['WEBSOCKETS_COLLAB_ENABLED']);
13
16
  if (restEnabled && heartbeatEnabled) {
14
17
  new HeartbeatHandler();
15
18
  }
@@ -22,6 +25,12 @@ export function startWebSocketHandlers() {
22
25
  if (logsEnabled) {
23
26
  new LogsHandler();
24
27
  }
28
+ if (collabEnabled) {
29
+ collabHandler = new CollabHandler();
30
+ }
31
+ }
32
+ export function getCollabHandler() {
33
+ return collabHandler;
25
34
  }
26
35
  export * from './heartbeat.js';
27
36
  export * from './items.js';
@@ -20,7 +20,7 @@ export declare function getPayload(subscription: PSubscription, accountability:
20
20
  * @param event Event data
21
21
  * @returns the fetched collection data
22
22
  */
23
- export declare function getCollectionPayload(subscription: PSubscription, accountability: Accountability | null, schema: SchemaOverview, event?: WebSocketEvent): Promise<string | number | import("../../types/collection.js").Collection | (string | number)[] | import("../../types/collection.js").Collection[]>;
23
+ export declare function getCollectionPayload(subscription: PSubscription, accountability: Accountability | null, schema: SchemaOverview, event?: WebSocketEvent): Promise<string | number | (string | number)[] | import("../../types/collection.js").Collection | import("../../types/collection.js").Collection[]>;
24
24
  /**
25
25
  * Get fields items
26
26
  *
@@ -29,7 +29,7 @@ export declare function getCollectionPayload(subscription: PSubscription, accoun
29
29
  * @param event Event data
30
30
  * @returns the fetched field data
31
31
  */
32
- export declare function getFieldsPayload(subscription: PSubscription, accountability: Accountability | null, schema: SchemaOverview, event?: WebSocketEvent): Promise<string | number | Record<string, any> | import("@directus/types").Field[] | (string | number)[]>;
32
+ export declare function getFieldsPayload(subscription: PSubscription, accountability: Accountability | null, schema: SchemaOverview, event?: WebSocketEvent): Promise<string | number | Record<string, any> | (string | number)[] | import("@directus/types").Field[]>;
33
33
  /**
34
34
  * Get items from a collection using the appropriate service
35
35
  *
@@ -1,4 +1,4 @@
1
1
  import type { WebSocketClient } from '../types.js';
2
2
  export declare const fmtMessage: (type: string, data?: Record<string, any>, uid?: string | number) => string;
3
3
  export declare const safeSend: (client: WebSocketClient, data: string, delay?: number) => Promise<unknown>;
4
- export declare const getMessageType: (message: any) => string;
4
+ export declare function getMessageType(message: unknown): string;
@@ -22,6 +22,6 @@ export const safeSend = async (client, data, delay = 100) => {
22
22
  return true;
23
23
  };
24
24
  // an often used message type extractor function
25
- export const getMessageType = (message) => {
25
+ export function getMessageType(message) {
26
26
  return typeof message !== 'object' || Array.isArray(message) || message === null ? '' : String(message.type);
27
- };
27
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@directus/api",
3
- "version": "33.0.0",
3
+ "version": "33.1.0",
4
4
  "description": "Directus is a real-time API and App dashboard for managing SQL database content",
5
5
  "keywords": [
6
6
  "directus",
@@ -58,19 +58,21 @@
58
58
  "dist"
59
59
  ],
60
60
  "dependencies": {
61
- "@ai-sdk/anthropic": "3.0.0-beta.53",
62
- "@ai-sdk/openai": "3.0.0-beta.57",
61
+ "@ai-sdk/anthropic": "3.0.9",
62
+ "@ai-sdk/google": "3.0.6",
63
+ "@ai-sdk/openai": "3.0.7",
64
+ "@ai-sdk/openai-compatible": "2.0.4",
63
65
  "@authenio/samlify-node-xmllint": "2.0.0",
64
66
  "@aws-sdk/client-sesv2": "3.928.0",
65
67
  "@godaddy/terminus": "4.12.1",
66
- "@modelcontextprotocol/sdk": "1.24.0",
68
+ "@modelcontextprotocol/sdk": "1.26.0",
67
69
  "@rollup/plugin-alias": "5.1.1",
68
70
  "@rollup/plugin-node-resolve": "16.0.3",
69
71
  "@rollup/plugin-virtual": "3.0.2",
70
72
  "@tus/server": "2.3.0",
71
73
  "@tus/utils": "0.6.0",
74
+ "ai": "6.0.25",
72
75
  "archiver": "7.0.1",
73
- "ai": "6.0.0-beta.98",
74
76
  "argon2": "0.44.0",
75
77
  "async": "3.2.6",
76
78
  "async-mutex": "0.5.0",
@@ -114,7 +116,7 @@
114
116
  "jsonwebtoken": "9.0.3",
115
117
  "keyv": "5.5.3",
116
118
  "knex": "3.1.0",
117
- "ldapjs": "2.3.3",
119
+ "ldapts": "8.1.3",
118
120
  "liquidjs": "10.24.0",
119
121
  "lodash-es": "4.17.21",
120
122
  "marked": "16.4.1",
@@ -138,10 +140,10 @@
138
140
  "pino-http": "10.5.0",
139
141
  "pino-http-print": "3.1.0",
140
142
  "pino-pretty": "13.1.2",
141
- "pm2": "6.0.13",
143
+ "pm2": "6.0.14",
142
144
  "prom-client": "15.1.3",
143
145
  "proxy-addr": "2.0.7",
144
- "qs": "6.14.0",
146
+ "qs": "6.14.1",
145
147
  "rate-limiter-flexible": "7.2.0",
146
148
  "rolldown": "1.0.0-beta.31",
147
149
  "rollup": "4.52.5",
@@ -151,37 +153,38 @@
151
153
  "sharp": "0.34.5",
152
154
  "snappy": "7.3.3",
153
155
  "stream-json": "1.9.1",
154
- "tar": "7.5.2",
156
+ "tar": "7.5.4",
155
157
  "tsx": "4.20.6",
156
158
  "uuid": "11.1.0",
157
159
  "wellknown": "0.5.0",
158
160
  "ws": "8.18.3",
159
161
  "zod": "4.1.12",
160
162
  "zod-validation-error": "4.0.2",
161
- "@directus/app": "15.0.0",
163
+ "@directus/ai": "1.1.0",
164
+ "@directus/app": "15.1.0",
165
+ "@directus/env": "5.5.0",
166
+ "@directus/extensions": "3.0.17",
162
167
  "@directus/constants": "14.0.0",
163
- "@directus/env": "5.4.0",
164
- "@directus/extensions": "3.0.16",
165
- "@directus/extensions-registry": "3.0.16",
166
- "@directus/errors": "2.1.0",
167
- "@directus/extensions-sdk": "17.0.5",
168
- "@directus/memory": "3.0.14",
168
+ "@directus/errors": "2.2.0",
169
+ "@directus/extensions-registry": "3.0.17",
170
+ "@directus/memory": "3.1.0",
169
171
  "@directus/format-title": "12.1.1",
170
- "@directus/pressure": "3.0.14",
172
+ "@directus/pressure": "3.0.15",
171
173
  "@directus/schema": "13.0.5",
172
- "@directus/schema-builder": "0.0.11",
174
+ "@directus/extensions-sdk": "17.0.6",
175
+ "@directus/schema-builder": "0.0.12",
173
176
  "@directus/specs": "12.0.0",
174
177
  "@directus/storage": "12.0.3",
175
- "@directus/storage-driver-azure": "12.0.14",
176
- "@directus/storage-driver-gcs": "12.0.14",
178
+ "@directus/storage-driver-azure": "12.0.15",
179
+ "@directus/storage-driver-s3": "12.1.1",
180
+ "@directus/storage-driver-gcs": "12.0.15",
177
181
  "@directus/storage-driver-local": "12.0.3",
178
- "@directus/storage-driver-cloudinary": "12.0.14",
179
- "@directus/storage-driver-s3": "12.1.0",
180
- "@directus/system-data": "4.0.0",
181
- "@directus/utils": "13.1.1",
182
- "@directus/validation": "2.0.14",
183
- "directus": "11.14.1",
184
- "@directus/storage-driver-supabase": "3.0.14"
182
+ "@directus/storage-driver-supabase": "3.0.15",
183
+ "@directus/system-data": "4.1.0",
184
+ "@directus/utils": "13.2.0",
185
+ "directus": "11.15.0",
186
+ "@directus/validation": "2.0.15",
187
+ "@directus/storage-driver-cloudinary": "12.0.15"
185
188
  },
186
189
  "devDependencies": {
187
190
  "@directus/tsconfig": "3.0.0",
@@ -204,7 +207,6 @@
204
207
  "@types/js-yaml": "4.0.9",
205
208
  "@types/json2csv": "5.0.7",
206
209
  "@types/jsonwebtoken": "9.0.10",
207
- "@types/ldapjs": "2.2.5",
208
210
  "@types/lodash-es": "4.17.12",
209
211
  "@types/mime-types": "3.0.1",
210
212
  "@types/ms": "2.1.0",
@@ -225,8 +227,8 @@
225
227
  "knex-mock-client": "3.0.2",
226
228
  "typescript": "5.9.3",
227
229
  "vitest": "3.2.4",
228
- "@directus/schema-builder": "0.0.11",
229
- "@directus/types": "14.0.0"
230
+ "@directus/schema-builder": "0.0.12",
231
+ "@directus/types": "14.1.0"
230
232
  },
231
233
  "optionalDependencies": {
232
234
  "@keyv/redis": "3.0.1",
@@ -1,6 +0,0 @@
1
- import type { Relation } from '@directus/types';
2
- export type RelationInfo = {
3
- relation: Relation | null;
4
- relationType: 'o2m' | 'm2o' | 'a2o' | 'o2a' | null;
5
- };
6
- export declare function getRelationInfo(relations: Relation[], collection: string, field: string): RelationInfo;
@@ -1,43 +0,0 @@
1
- import { getRelation } from '@directus/utils';
2
- import { getRelationType } from './get-relation-type.js';
3
- function checkImplicitRelation(field) {
4
- if (field.startsWith('$FOLLOW(') && field.endsWith(')')) {
5
- return field.slice(8, -1).split(',');
6
- }
7
- return null;
8
- }
9
- export function getRelationInfo(relations, collection, field) {
10
- if (field.startsWith('$FOLLOW') && field.length > 500) {
11
- throw new Error(`Implicit $FOLLOW statement is too big to parse. Got: "${field.substring(500)}..."`);
12
- }
13
- const implicitRelation = checkImplicitRelation(field);
14
- if (implicitRelation) {
15
- if (implicitRelation[2] === undefined) {
16
- const [m2oCollection, m2oField] = implicitRelation;
17
- const relation = {
18
- collection: m2oCollection.trim(),
19
- field: m2oField.trim(),
20
- related_collection: collection,
21
- schema: null,
22
- meta: null,
23
- };
24
- return { relation, relationType: 'o2m' };
25
- }
26
- else {
27
- const [a2oCollection, a2oItemField, a2oCollectionField] = implicitRelation;
28
- const relation = {
29
- collection: a2oCollection.trim(),
30
- field: a2oItemField.trim(),
31
- related_collection: collection,
32
- schema: null,
33
- meta: {
34
- one_collection_field: a2oCollectionField.trim(),
35
- },
36
- };
37
- return { relation, relationType: 'o2a' };
38
- }
39
- }
40
- const relation = getRelation(relations, collection, field) ?? null;
41
- const relationType = relation ? getRelationType({ relation, collection, field }) : null;
42
- return { relation, relationType };
43
- }
@@ -1,6 +0,0 @@
1
- import type { Relation } from '@directus/types';
2
- export declare function getRelationType(getRelationOptions: {
3
- relation?: Relation | null;
4
- collection: string | null;
5
- field: string;
6
- }): 'm2o' | 'o2m' | 'a2o' | null;
@@ -1,18 +0,0 @@
1
- export function getRelationType(getRelationOptions) {
2
- const { relation, collection, field } = getRelationOptions;
3
- if (!relation)
4
- return null;
5
- if (relation.collection === collection &&
6
- relation.field === field &&
7
- relation.meta?.one_collection_field &&
8
- relation.meta?.one_allowed_collections) {
9
- return 'a2o';
10
- }
11
- if (relation.collection === collection && relation.field === field) {
12
- return 'm2o';
13
- }
14
- if (relation.related_collection === collection && relation.meta?.one_field === field) {
15
- return 'o2m';
16
- }
17
- return null;
18
- }
@@ -1,23 +0,0 @@
1
- import type { CollectionOverview, FieldOverview, Relation, SchemaOverview } from '@directus/types';
2
- import { type RelationInfo } from '../get-relation-info.js';
3
- /**
4
- * Allows to deep map the data like a response or delta changes with collection, field and relation context for each entry.
5
- * Bottom to Top depth first mapping of values.
6
- */
7
- export declare function deepMapWithSchema(object: Record<string, any>, callback: (entry: [key: string | number, value: unknown], context: {
8
- collection: CollectionOverview;
9
- field: FieldOverview;
10
- relation: Relation | null;
11
- leaf: boolean;
12
- relationType: RelationInfo['relationType'] | null;
13
- object: Record<string, any>;
14
- }) => [key: string | number, value: unknown] | undefined, context: {
15
- schema: SchemaOverview;
16
- collection: string;
17
- relationInfo?: RelationInfo;
18
- }, options?: {
19
- /** If set to true, non-existent fields will be included in the mapping and will have a value of undefined */
20
- mapNonExistentFields?: boolean;
21
- /** If set to true, it will map the "create", "update" and "delete" syntax for o2m relations found in deltas */
22
- detailedUpdateSyntax?: boolean;
23
- }): any;
@@ -1,81 +0,0 @@
1
- import assert from 'node:assert';
2
- import { InvalidQueryError } from '@directus/errors';
3
- import { isPlainObject } from 'lodash-es';
4
- import { getRelationInfo } from '../get-relation-info.js';
5
- /**
6
- * Allows to deep map the data like a response or delta changes with collection, field and relation context for each entry.
7
- * Bottom to Top depth first mapping of values.
8
- */
9
- export function deepMapWithSchema(object, callback, context, options) {
10
- const collection = context.schema.collections[context.collection];
11
- assert(isPlainObject(object) && typeof object === 'object' && object !== null, `DeepMapResponse only works on objects, received ${JSON.stringify(object)}`);
12
- let fields;
13
- if (options?.mapNonExistentFields) {
14
- fields = Object.entries(collection.fields);
15
- }
16
- else {
17
- fields = Object.keys(object).map((key) => [key, collection.fields[key]]);
18
- }
19
- return Object.fromEntries(fields
20
- .map(([key, field]) => {
21
- let value = object[key];
22
- if (!field)
23
- return [key, value];
24
- const relationInfo = getRelationInfo(context.schema.relations, collection.collection, field.field);
25
- let leaf = true;
26
- if (relationInfo.relation && typeof value === 'object' && value !== null && isPlainObject(object)) {
27
- switch (relationInfo.relationType) {
28
- case 'm2o':
29
- value = deepMapWithSchema(value, callback, {
30
- schema: context.schema,
31
- collection: relationInfo.relation.related_collection,
32
- relationInfo,
33
- }, options);
34
- leaf = false;
35
- break;
36
- case 'o2m': {
37
- function map(childValue) {
38
- if (isPlainObject(childValue) && typeof childValue === 'object' && childValue !== null) {
39
- leaf = false;
40
- return deepMapWithSchema(childValue, callback, {
41
- schema: context.schema,
42
- collection: relationInfo.relation.collection,
43
- relationInfo,
44
- }, options);
45
- }
46
- else
47
- return childValue;
48
- }
49
- if (Array.isArray(value)) {
50
- value = value.map(map);
51
- }
52
- else if (options?.detailedUpdateSyntax && isPlainObject(value)) {
53
- value = {
54
- create: value['create']?.map(map),
55
- update: value['update']?.map(map),
56
- delete: value['delete']?.map(map),
57
- };
58
- }
59
- break;
60
- }
61
- case 'a2o': {
62
- const related_collection = object[relationInfo.relation.meta.one_collection_field];
63
- if (!related_collection) {
64
- throw new InvalidQueryError({
65
- reason: `When selecting '${collection.collection}.${field.field}', the field '${collection.collection}.${relationInfo.relation.meta.one_collection_field}' has to be selected when using versioning and m2a relations `,
66
- });
67
- }
68
- value = deepMapWithSchema(value, callback, {
69
- schema: context.schema,
70
- collection: related_collection,
71
- relationInfo,
72
- }, options);
73
- leaf = false;
74
- break;
75
- }
76
- }
77
- }
78
- return callback([key, value], { collection, field, ...relationInfo, leaf, object });
79
- })
80
- .filter((f) => f));
81
- }