@directus/api 27.0.2 → 28.0.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 (63) hide show
  1. package/dist/auth/drivers/oauth2.js +4 -4
  2. package/dist/auth/drivers/openid.d.ts +2 -1
  3. package/dist/auth/drivers/openid.js +78 -43
  4. package/dist/database/errors/dialects/mssql.d.ts +2 -1
  5. package/dist/database/errors/dialects/mssql.js +124 -120
  6. package/dist/database/errors/dialects/mysql.d.ts +2 -1
  7. package/dist/database/errors/dialects/mysql.js +112 -108
  8. package/dist/database/errors/dialects/postgres.d.ts +2 -1
  9. package/dist/database/errors/dialects/postgres.js +75 -71
  10. package/dist/database/errors/dialects/sqlite.d.ts +2 -1
  11. package/dist/database/errors/dialects/sqlite.js +6 -5
  12. package/dist/database/errors/translate.d.ts +2 -1
  13. package/dist/database/errors/translate.js +5 -5
  14. package/dist/database/get-ast-from-query/lib/convert-wildcards.d.ts +5 -3
  15. package/dist/database/get-ast-from-query/lib/convert-wildcards.js +26 -16
  16. package/dist/database/get-ast-from-query/lib/parse-fields.d.ts +2 -1
  17. package/dist/database/get-ast-from-query/lib/parse-fields.js +5 -4
  18. package/dist/database/migrations/20250609A-license-banner.d.ts +3 -0
  19. package/dist/database/migrations/20250609A-license-banner.js +14 -0
  20. package/dist/database/migrations/20250613A-add-project-id.d.ts +3 -0
  21. package/dist/database/migrations/20250613A-add-project-id.js +26 -0
  22. package/dist/extensions/lib/get-extensions-settings.js +14 -8
  23. package/dist/extensions/manager.js +26 -0
  24. package/dist/flows.d.ts +5 -1
  25. package/dist/flows.js +61 -4
  26. package/dist/operations/condition/index.js +1 -1
  27. package/dist/permissions/utils/get-permissions-for-share.js +2 -0
  28. package/dist/permissions/utils/merge-fields.d.ts +1 -0
  29. package/dist/permissions/utils/merge-fields.js +29 -0
  30. package/dist/permissions/utils/merge-permissions.js +3 -14
  31. package/dist/services/fields.js +3 -3
  32. package/dist/services/graphql/resolvers/mutation.js +1 -1
  33. package/dist/services/graphql/resolvers/query.js +1 -1
  34. package/dist/services/graphql/resolvers/system.js +4 -4
  35. package/dist/services/graphql/schema/parse-query.d.ts +1 -1
  36. package/dist/services/graphql/schema/parse-query.js +8 -1
  37. package/dist/services/graphql/schema/read.js +4 -4
  38. package/dist/services/graphql/subscription.js +1 -1
  39. package/dist/services/graphql/utils/filter-replace-m2a.d.ts +3 -0
  40. package/dist/services/graphql/utils/filter-replace-m2a.js +59 -0
  41. package/dist/services/items.js +2 -2
  42. package/dist/services/payload.js +2 -2
  43. package/dist/services/relations.d.ts +1 -1
  44. package/dist/services/relations.js +5 -2
  45. package/dist/services/specifications.js +6 -2
  46. package/dist/services/users.js +3 -0
  47. package/dist/telemetry/lib/get-report.js +4 -1
  48. package/dist/telemetry/types/report.d.ts +4 -0
  49. package/dist/telemetry/utils/get-project-id.d.ts +2 -0
  50. package/dist/telemetry/utils/get-project-id.js +4 -0
  51. package/dist/utils/get-ip-from-req.d.ts +2 -1
  52. package/dist/utils/get-ip-from-req.js +29 -2
  53. package/dist/utils/get-schema.js +1 -1
  54. package/dist/utils/is-url-allowed.js +1 -1
  55. package/dist/utils/sanitize-query.js +6 -0
  56. package/dist/utils/validate-query.js +1 -0
  57. package/dist/websocket/controllers/base.d.ts +2 -2
  58. package/dist/websocket/controllers/base.js +33 -5
  59. package/dist/websocket/types.d.ts +1 -0
  60. package/dist/websocket/utils/items.d.ts +1 -1
  61. package/package.json +27 -24
  62. package/dist/utils/map-values-deep.d.ts +0 -1
  63. package/dist/utils/map-values-deep.js +0 -25
@@ -58,7 +58,7 @@ export class RelationsService {
58
58
  }
59
59
  return foreignKeys;
60
60
  }
61
- async readAll(collection, opts) {
61
+ async readAll(collection, opts, bypassCache) {
62
62
  if (this.accountability) {
63
63
  await validateAccess({
64
64
  accountability: this.accountability,
@@ -87,7 +87,10 @@ export class RelationsService {
87
87
  return true;
88
88
  return metaRow.many_collection === collection;
89
89
  });
90
- const schemaRows = await this.foreignKeys(collection);
90
+ let schemaRows = bypassCache ? await this.schemaInspector.foreignKeys() : await this.foreignKeys(collection);
91
+ if (collection && bypassCache) {
92
+ schemaRows = schemaRows.filter((row) => row.table === collection);
93
+ }
91
94
  const results = this.stitchRelations(metaRows, schemaRows);
92
95
  return await this.filterForbidden(results);
93
96
  }
@@ -3,8 +3,8 @@ import formatTitle from '@directus/format-title';
3
3
  import { spec } from '@directus/specs';
4
4
  import { isSystemCollection } from '@directus/system-data';
5
5
  import { getRelation } from '@directus/utils';
6
- import { version } from 'directus/version';
7
6
  import { cloneDeep, mergeWith } from 'lodash-es';
7
+ import hash from 'object-hash';
8
8
  import { OAS_REQUIRED_SCHEMAS } from '../constants.js';
9
9
  import getDatabase from '../database/index.js';
10
10
  import { fetchPermissions } from '../permissions/lib/fetch-permissions.js';
@@ -54,12 +54,16 @@ class OASSpecsService {
54
54
  const components = await this.generateComponents(schemaForSpec, tags);
55
55
  const isDefaultPublicUrl = env['PUBLIC_URL'] === '/';
56
56
  const url = isDefaultPublicUrl && host ? host : env['PUBLIC_URL'];
57
+ const hashedVersion = hash({
58
+ now: new Date().toISOString(),
59
+ user: this.accountability?.user,
60
+ });
57
61
  const spec = {
58
62
  openapi: '3.0.1',
59
63
  info: {
60
64
  title: 'Dynamic API Specification',
61
65
  description: 'This is a dynamically generated API specification for all endpoints existing on the current project.',
62
- version: version,
66
+ version: hashedVersion,
63
67
  },
64
68
  servers: [
65
69
  {
@@ -40,6 +40,7 @@ export class UsersService extends ItemsService {
40
40
  throw new RecordNotUniqueError({
41
41
  collection: 'directus_users',
42
42
  field: 'email',
43
+ value: '[' + String(duplicates) + ']',
43
44
  });
44
45
  }
45
46
  const query = this.knex
@@ -54,6 +55,7 @@ export class UsersService extends ItemsService {
54
55
  throw new RecordNotUniqueError({
55
56
  collection: 'directus_users',
56
57
  field: 'email',
58
+ value: '[' + String(emails) + ']',
57
59
  });
58
60
  }
59
61
  }
@@ -210,6 +212,7 @@ export class UsersService extends ItemsService {
210
212
  throw new RecordNotUniqueError({
211
213
  collection: 'directus_users',
212
214
  field: 'email',
215
+ value: data['email'],
213
216
  });
214
217
  }
215
218
  this.validateEmail(data['email']);
@@ -8,6 +8,7 @@ import { getFieldCount } from '../utils/get-field-count.js';
8
8
  import { getFilesizeSum } from '../utils/get-filesize-sum.js';
9
9
  import { getItemCount } from '../utils/get-item-count.js';
10
10
  import { getUserItemCount } from '../utils/get-user-item-count.js';
11
+ import { getProjectId } from '../utils/get-project-id.js';
11
12
  const basicCountTasks = [
12
13
  { collection: 'directus_dashboards' },
13
14
  { collection: 'directus_files' },
@@ -25,7 +26,7 @@ export const getReport = async () => {
25
26
  const db = getDatabase();
26
27
  const env = useEnv();
27
28
  const helpers = getHelpers(db);
28
- const [basicCounts, userCounts, userItemCount, fieldsCounts, extensionsCounts, databaseSize, filesizes] = await Promise.all([
29
+ const [basicCounts, userCounts, userItemCount, fieldsCounts, extensionsCounts, databaseSize, filesizes, projectId] = await Promise.all([
29
30
  getItemCount(db, basicCountTasks),
30
31
  fetchUserCount({ knex: db }),
31
32
  getUserItemCount(db),
@@ -33,6 +34,7 @@ export const getReport = async () => {
33
34
  getExtensionCount(db),
34
35
  helpers.schema.getDatabaseSize(),
35
36
  getFilesizeSum(db),
37
+ getProjectId(db),
36
38
  ]);
37
39
  return {
38
40
  url: env['PUBLIC_URL'],
@@ -53,5 +55,6 @@ export const getReport = async () => {
53
55
  extensions: extensionsCounts.totalEnabled,
54
56
  database_size: databaseSize ?? 0,
55
57
  files_size_total: filesizes.total,
58
+ project_id: projectId,
56
59
  };
57
60
  };
@@ -71,4 +71,8 @@ export interface TelemetryReport {
71
71
  * Total size of the files in bytes
72
72
  */
73
73
  files_size_total: number;
74
+ /**
75
+ * Unique project identifier
76
+ */
77
+ project_id?: string | null;
74
78
  }
@@ -0,0 +1,2 @@
1
+ import type { Knex } from 'knex';
2
+ export declare const getProjectId: (db: Knex) => Promise<string>;
@@ -0,0 +1,4 @@
1
+ export const getProjectId = async (db) => {
2
+ const projectId = await db.select('project_id').from('directus_settings').first();
3
+ return projectId?.project_id || null;
4
+ };
@@ -1,2 +1,3 @@
1
1
  import type { Request } from 'express';
2
- export declare function getIPFromReq(req: Request): string | null;
2
+ import type { IncomingMessage } from 'http';
3
+ export declare function getIPFromReq(req: IncomingMessage | Request): string | null;
@@ -1,12 +1,39 @@
1
1
  import { useEnv } from '@directus/env';
2
2
  import { isIP } from 'net';
3
+ import proxyAddr from 'proxy-addr';
3
4
  import { useLogger } from '../logger/index.js';
5
+ /**
6
+ * Generate the trusted ip list
7
+ *
8
+ * Adapted to have feature parity with the express equivalent https://github.com/expressjs/express/blob/9f4dbe3a1332cd883069ba9b73a9eed99234cfc7/lib/utils.js#L192
9
+ */
10
+ function getTrustValue(trust) {
11
+ if (typeof trust === 'boolean') {
12
+ // Support plain true/false
13
+ return (_addr, _i) => trust;
14
+ }
15
+ else if (typeof trust === 'number') {
16
+ // Support trusting hop count
17
+ return (_addr, i) => i < trust;
18
+ }
19
+ else if (typeof trust === 'string') {
20
+ // Support comma-separated values
21
+ trust = trust.split(',').map((v) => v.trim());
22
+ }
23
+ return proxyAddr.compile(trust || []);
24
+ }
4
25
  export function getIPFromReq(req) {
5
26
  const env = useEnv();
6
27
  const logger = useLogger();
7
- let ip = req.ip;
28
+ let ip = 'ip' in req ? req.ip : proxyAddr(req, getTrustValue(env['IP_TRUST_PROXY']));
8
29
  if (env['IP_CUSTOM_HEADER']) {
9
- const customIPHeaderValue = req.get(env['IP_CUSTOM_HEADER']);
30
+ const customIPHeaderName = env['IP_CUSTOM_HEADER'].toLowerCase();
31
+ // All req.headers are auto lower-cased
32
+ let customIPHeaderValue = req.headers[customIPHeaderName];
33
+ // // Done to have feature parity with https://github.com/expressjs/express/blob/9f4dbe3a1332cd883069ba9b73a9eed99234cfc7/lib/request.js#L63
34
+ if (customIPHeaderName === 'referer' || customIPHeaderName === 'referrer') {
35
+ customIPHeaderValue = req.headers['referrer'] || req.headers['referer'];
36
+ }
10
37
  if (typeof customIPHeaderValue === 'string' && isIP(customIPHeaderValue) !== 0) {
11
38
  ip = customIPHeaderValue;
12
39
  }
@@ -162,6 +162,6 @@ async function getDatabaseSchema(database, schemaInspector) {
162
162
  };
163
163
  }
164
164
  const relationsService = new RelationsService({ knex: database, schema: result });
165
- result.relations = await relationsService.readAll();
165
+ result.relations = await relationsService.readAll(undefined, undefined, true);
166
166
  return result;
167
167
  }
@@ -16,7 +16,7 @@ export default function isUrlAllowed(url, allowList) {
16
16
  return origin + pathname;
17
17
  }
18
18
  catch {
19
- logger.warn(`Invalid URL used "${url}"`);
19
+ logger.warn(`Invalid URL used "${allowedURL}"`);
20
20
  }
21
21
  return null;
22
22
  })
@@ -71,6 +71,9 @@ export async function sanitizeQuery(rawQuery, schema, accountability) {
71
71
  if (rawQuery['alias']) {
72
72
  query.alias = sanitizeAlias(rawQuery['alias']);
73
73
  }
74
+ if ('backlink' in rawQuery) {
75
+ query.backlink = sanitizeBacklink(rawQuery['backlink']);
76
+ }
74
77
  return query;
75
78
  }
76
79
  function sanitizeFields(rawFields) {
@@ -171,6 +174,9 @@ function sanitizeMeta(rawMeta) {
171
174
  }
172
175
  return [rawMeta];
173
176
  }
177
+ function sanitizeBacklink(rawBacklink) {
178
+ return rawBacklink !== false && rawBacklink !== 'false';
179
+ }
174
180
  async function sanitizeDeep(deep, schema, accountability) {
175
181
  const logger = useLogger();
176
182
  const result = {};
@@ -26,6 +26,7 @@ const querySchema = Joi.object({
26
26
  aggregate: Joi.object(),
27
27
  deep: Joi.object(),
28
28
  alias: Joi.object(),
29
+ backlink: Joi.boolean(),
29
30
  }).id('query');
30
31
  export function validateQuery(query) {
31
32
  const { error } = querySchema.validate(query);
@@ -22,8 +22,8 @@ export default abstract class SocketController {
22
22
  protected getRateLimiter(): RateLimiterAbstract | null;
23
23
  private catchInvalidMessages;
24
24
  protected handleUpgrade(request: IncomingMessage, socket: internal.Duplex, head: Buffer): Promise<void>;
25
- protected handleTokenUpgrade({ request, socket, head }: UpgradeContext, token: string | null): Promise<void>;
26
- protected handleHandshakeUpgrade({ request, socket, head }: UpgradeContext): Promise<void>;
25
+ protected handleTokenUpgrade({ request, socket, head, accountabilityOverrides }: UpgradeContext, token: string | null): Promise<void>;
26
+ protected handleHandshakeUpgrade({ request, socket, head, accountabilityOverrides }: UpgradeContext): Promise<void>;
27
27
  createClient(ws: WebSocket, { accountability, expires_at }: AuthenticationState): WebSocketClient;
28
28
  protected parseMessage(data: string): WebSocketMessage;
29
29
  protected handleAuthRequest(client: WebSocketClient, message: WebSocketAuthMessage): Promise<void>;
@@ -8,15 +8,16 @@ import WebSocket, { WebSocketServer } from 'ws';
8
8
  import { fromZodError } from 'zod-validation-error';
9
9
  import emitter from '../../emitter.js';
10
10
  import { useLogger } from '../../logger/index.js';
11
+ import { createDefaultAccountability } from '../../permissions/utils/create-default-accountability.js';
11
12
  import { createRateLimiter } from '../../rate-limiter.js';
12
13
  import { getAccountabilityForToken } from '../../utils/get-accountability-for-token.js';
14
+ import { getIPFromReq } from '../../utils/get-ip-from-req.js';
13
15
  import { authenticateConnection, authenticationSuccess } from '../authenticate.js';
14
16
  import { WebSocketError, handleWebSocketError } from '../errors.js';
15
17
  import { AuthMode, WebSocketAuthMessage, WebSocketMessage } from '../messages.js';
16
18
  import { getExpiresAtForToken } from '../utils/get-expires-at-for-token.js';
17
19
  import { getMessageType } from '../utils/message.js';
18
20
  import { waitForAnyMessage, waitForMessageType } from '../utils/wait-for-message.js';
19
- import { createDefaultAccountability } from '../../permissions/utils/create-default-accountability.js';
20
21
  const TOKEN_CHECK_INTERVAL = 15 * 60 * 1000; // 15 minutes
21
22
  const logger = useLogger();
22
23
  export default class SocketController {
@@ -98,8 +99,17 @@ export default class SocketController {
98
99
  }
99
100
  const env = useEnv();
100
101
  const cookies = request.headers.cookie ? cookie.parse(request.headers.cookie) : {};
101
- const context = { request, socket, head };
102
102
  const sessionCookieName = env['SESSION_COOKIE_NAME'];
103
+ const accountabilityOverrides = {
104
+ ip: getIPFromReq(request) ?? null,
105
+ };
106
+ const userAgent = request.headers['user-agent']?.substring(0, 1024);
107
+ if (userAgent)
108
+ accountabilityOverrides.userAgent = userAgent;
109
+ const origin = request.headers['origin'];
110
+ if (origin)
111
+ accountabilityOverrides.origin = origin;
112
+ const context = { request, socket, head, accountabilityOverrides };
103
113
  if (this.authentication.mode === 'strict' || query['access_token'] || cookies[sessionCookieName]) {
104
114
  let token = null;
105
115
  if (typeof query['access_token'] === 'string') {
@@ -117,11 +127,14 @@ export default class SocketController {
117
127
  }
118
128
  this.server.handleUpgrade(request, socket, head, async (ws) => {
119
129
  this.catchInvalidMessages(ws);
120
- const state = { accountability: createDefaultAccountability(), expires_at: null };
130
+ const state = {
131
+ accountability: createDefaultAccountability(accountabilityOverrides),
132
+ expires_at: null,
133
+ };
121
134
  this.server.emit('connection', ws, state);
122
135
  });
123
136
  }
124
- async handleTokenUpgrade({ request, socket, head }, token) {
137
+ async handleTokenUpgrade({ request, socket, head, accountabilityOverrides }, token) {
125
138
  let accountability = null;
126
139
  let expires_at = null;
127
140
  if (token) {
@@ -149,13 +162,14 @@ export default class SocketController {
149
162
  socket.destroy();
150
163
  return;
151
164
  }
165
+ Object.assign(accountability, accountabilityOverrides);
152
166
  this.server.handleUpgrade(request, socket, head, async (ws) => {
153
167
  this.catchInvalidMessages(ws);
154
168
  const state = { accountability, expires_at };
155
169
  this.server.emit('connection', ws, state);
156
170
  });
157
171
  }
158
- async handleHandshakeUpgrade({ request, socket, head }) {
172
+ async handleHandshakeUpgrade({ request, socket, head, accountabilityOverrides }) {
159
173
  this.server.handleUpgrade(request, socket, head, async (ws) => {
160
174
  this.catchInvalidMessages(ws);
161
175
  try {
@@ -163,6 +177,9 @@ export default class SocketController {
163
177
  if (getMessageType(payload) !== 'auth')
164
178
  throw new Error();
165
179
  const state = await authenticateConnection(WebSocketAuthMessage.parse(payload));
180
+ if (state.accountability) {
181
+ Object.assign(state.accountability, accountabilityOverrides);
182
+ }
166
183
  this.checkUserRequirements(state.accountability);
167
184
  ws.send(authenticationSuccess(payload['uid'], state.refresh_token));
168
185
  this.server.emit('connection', ws, state);
@@ -253,6 +270,17 @@ export default class SocketController {
253
270
  try {
254
271
  const { accountability, expires_at, refresh_token } = await authenticateConnection(message);
255
272
  this.checkUserRequirements(accountability);
273
+ /**
274
+ * Re-use the existing ip, userAgent and origin accountability properties.
275
+ * They are only sent in the original connection request
276
+ */
277
+ if (accountability && client.accountability) {
278
+ Object.assign(accountability, {
279
+ ip: client.accountability.ip,
280
+ userAgent: client.accountability.userAgent,
281
+ origin: client.accountability.origin,
282
+ });
283
+ }
256
284
  client.accountability = accountability;
257
285
  client.expires_at = expires_at;
258
286
  this.setTokenExpireTimer(client);
@@ -30,6 +30,7 @@ export type UpgradeContext = {
30
30
  request: IncomingMessage;
31
31
  socket: internal.Duplex;
32
32
  head: Buffer;
33
+ accountabilityOverrides: Pick<Accountability, 'ip' | 'userAgent' | 'origin'>;
33
34
  };
34
35
  export type GraphQLSocket = {
35
36
  client: WebSocketClient;
@@ -39,5 +39,5 @@ export declare function getFieldsPayload(subscription: PSubscription, accountabi
39
39
  * @param event Event data
40
40
  * @returns the fetched data
41
41
  */
42
- export declare function getItemsPayload(subscription: PSubscription, accountability: Accountability | null, schema: SchemaOverview, event?: WebSocketEvent): Promise<string | number | (string | number)[] | import("@directus/types").Item | import("@directus/types").Item[]>;
42
+ export declare function getItemsPayload(subscription: PSubscription, accountability: Accountability | null, schema: SchemaOverview, event?: WebSocketEvent): Promise<string | number | import("@directus/types").Item | (string | number)[] | import("@directus/types").Item[]>;
43
43
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@directus/api",
3
- "version": "27.0.2",
3
+ "version": "28.0.0",
4
4
  "description": "Directus is a real-time API and App dashboard for managing SQL database content",
5
5
  "keywords": [
6
6
  "directus",
@@ -136,44 +136,46 @@
136
136
  "pino-pretty": "13.0.0",
137
137
  "pm2": "5.4.3",
138
138
  "prom-client": "15.1.3",
139
+ "proxy-addr": "2.0.7",
139
140
  "qs": "6.14.0",
140
141
  "rate-limiter-flexible": "5.0.5",
141
142
  "rollup": "4.34.9",
142
- "samlify": "2.9.1",
143
+ "samlify": "2.10.0",
143
144
  "sanitize-html": "2.14.0",
144
145
  "sharp": "0.33.5",
145
146
  "snappy": "7.2.2",
146
147
  "stream-json": "1.9.1",
147
148
  "tar": "7.4.3",
148
149
  "tsx": "4.19.3",
150
+ "uuid": "11.1.0",
149
151
  "wellknown": "0.5.0",
150
152
  "ws": "8.18.1",
151
153
  "zod": "3.24.2",
152
154
  "zod-validation-error": "3.4.0",
153
- "@directus/app": "13.9.2",
155
+ "@directus/app": "13.11.0",
154
156
  "@directus/constants": "13.0.1",
155
- "@directus/errors": "2.0.1",
156
- "@directus/env": "5.0.4",
157
- "@directus/extensions": "3.0.5",
158
- "@directus/extensions-registry": "3.0.5",
159
- "@directus/extensions-sdk": "13.1.0",
157
+ "@directus/env": "5.1.0",
158
+ "@directus/errors": "2.0.2",
159
+ "@directus/extensions": "3.0.7",
160
+ "@directus/extensions-registry": "3.0.7",
161
+ "@directus/extensions-sdk": "14.0.0",
160
162
  "@directus/format-title": "12.0.1",
163
+ "@directus/pressure": "3.0.6",
161
164
  "@directus/schema": "13.0.1",
162
- "@directus/memory": "3.0.4",
163
- "@directus/schema-builder": "0.0.1",
165
+ "@directus/memory": "3.0.6",
164
166
  "@directus/specs": "11.1.0",
167
+ "@directus/schema-builder": "0.0.3",
165
168
  "@directus/storage": "12.0.0",
166
- "@directus/pressure": "3.0.4",
167
- "@directus/storage-driver-azure": "12.0.4",
168
- "@directus/storage-driver-cloudinary": "12.0.4",
169
- "@directus/storage-driver-gcs": "12.0.4",
170
- "@directus/storage-driver-s3": "12.0.4",
171
- "@directus/storage-driver-supabase": "3.0.4",
172
- "@directus/utils": "13.0.5",
173
- "@directus/validation": "2.0.4",
174
- "@directus/system-data": "3.1.0",
175
- "directus": "11.7.2",
176
- "@directus/storage-driver-local": "12.0.0"
169
+ "@directus/storage-driver-azure": "12.0.6",
170
+ "@directus/storage-driver-cloudinary": "12.0.6",
171
+ "@directus/storage-driver-local": "12.0.0",
172
+ "@directus/storage-driver-gcs": "12.0.6",
173
+ "@directus/storage-driver-supabase": "3.0.6",
174
+ "@directus/storage-driver-s3": "12.0.6",
175
+ "@directus/utils": "13.0.7",
176
+ "@directus/validation": "2.0.6",
177
+ "@directus/system-data": "3.1.1",
178
+ "directus": "11.9.0"
177
179
  },
178
180
  "devDependencies": {
179
181
  "@directus/tsconfig": "3.0.0",
@@ -204,6 +206,7 @@
204
206
  "@types/nodemailer": "6.4.17",
205
207
  "@types/object-hash": "3.0.6",
206
208
  "@types/papaparse": "5.3.15",
209
+ "@types/proxy-addr": "2.0.3",
207
210
  "@types/qs": "6.9.18",
208
211
  "@types/sanitize-html": "2.13.0",
209
212
  "@types/stream-json": "1.7.8",
@@ -216,9 +219,9 @@
216
219
  "knex-mock-client": "3.0.2",
217
220
  "typescript": "5.8.2",
218
221
  "vitest": "2.1.9",
222
+ "@directus/schema-builder": "0.0.3",
219
223
  "@directus/random": "2.0.1",
220
- "@directus/schema-builder": "0.0.1",
221
- "@directus/types": "13.1.1"
224
+ "@directus/types": "13.2.0"
222
225
  },
223
226
  "optionalDependencies": {
224
227
  "@keyv/redis": "3.0.1",
@@ -233,7 +236,7 @@
233
236
  "node": ">=22"
234
237
  },
235
238
  "scripts": {
236
- "build": "tsc --project tsconfig.prod.json && copyfiles \"src/**/*.{yaml,liquid}\" -u 1 dist",
239
+ "build": "rimraf ./dist && tsc --project tsconfig.prod.json && copyfiles \"src/**/*.{yaml,liquid}\" -u 1 dist",
237
240
  "cli": "NODE_ENV=development SERVE_APP=false tsx src/cli/run.ts",
238
241
  "dev": "NODE_ENV=development SERVE_APP=true tsx watch --ignore extensions --clear-screen=false src/start.ts",
239
242
  "test": "vitest run",
@@ -1 +0,0 @@
1
- export declare function mapValuesDeep(obj: Record<string, any>, fn: (key: string, value: any) => any): Record<string, any>;
@@ -1,25 +0,0 @@
1
- export function mapValuesDeep(obj, fn) {
2
- return recurse(obj);
3
- function recurse(obj, prefix = '') {
4
- if (Array.isArray(obj)) {
5
- return obj.map((value, index) => {
6
- if (typeof value === 'object' && value !== null) {
7
- return recurse(value, prefix + `[${index}]`);
8
- }
9
- else {
10
- return fn(prefix + `[${index}]`, value);
11
- }
12
- });
13
- }
14
- else {
15
- return Object.fromEntries(Object.entries(obj).map(([key, value]) => {
16
- if (typeof value === 'object' && value !== null) {
17
- return [key, recurse(value, prefix + (prefix ? '.' : '') + key)];
18
- }
19
- else {
20
- return [key, fn(prefix + (prefix ? '.' : '') + key, value)];
21
- }
22
- }));
23
- }
24
- }
25
- }