@directus/api 13.1.1 → 13.2.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 (219) hide show
  1. package/dist/app.js +4 -4
  2. package/dist/auth/drivers/ldap.js +3 -2
  3. package/dist/auth/drivers/local.js +1 -1
  4. package/dist/auth/drivers/oauth2.js +1 -1
  5. package/dist/auth/drivers/openid.js +1 -1
  6. package/dist/auth/drivers/saml.js +1 -1
  7. package/dist/auth.js +1 -1
  8. package/dist/cli/index.js +1 -1
  9. package/dist/controllers/activity.js +1 -1
  10. package/dist/controllers/assets.js +2 -2
  11. package/dist/controllers/auth.js +1 -1
  12. package/dist/controllers/collections.js +1 -1
  13. package/dist/controllers/dashboards.js +1 -1
  14. package/dist/controllers/extensions.js +11 -7
  15. package/dist/controllers/fields.js +1 -1
  16. package/dist/controllers/files.js +1 -1
  17. package/dist/controllers/flows.js +1 -1
  18. package/dist/controllers/folders.js +1 -1
  19. package/dist/controllers/items.js +1 -1
  20. package/dist/controllers/not-found.js +1 -1
  21. package/dist/controllers/notifications.js +1 -1
  22. package/dist/controllers/operations.js +1 -1
  23. package/dist/controllers/panels.js +1 -1
  24. package/dist/controllers/permissions.js +1 -1
  25. package/dist/controllers/presets.js +1 -1
  26. package/dist/controllers/relations.js +1 -1
  27. package/dist/controllers/roles.js +1 -1
  28. package/dist/controllers/schema.js +1 -1
  29. package/dist/controllers/server.js +1 -1
  30. package/dist/controllers/settings.js +1 -1
  31. package/dist/controllers/shares.js +1 -1
  32. package/dist/controllers/translations.js +1 -1
  33. package/dist/controllers/users.js +1 -1
  34. package/dist/controllers/utils.js +37 -18
  35. package/dist/controllers/webhooks.js +1 -1
  36. package/dist/database/errors/dialects/mssql.js +1 -1
  37. package/dist/database/errors/dialects/mysql.js +1 -1
  38. package/dist/database/errors/dialects/oracle.js +1 -1
  39. package/dist/database/errors/dialects/postgres.js +1 -1
  40. package/dist/database/errors/dialects/sqlite.js +1 -1
  41. package/dist/database/helpers/schema/dialects/mysql.js +1 -1
  42. package/dist/database/helpers/sequence/dialects/postgres.d.ts +5 -2
  43. package/dist/database/helpers/sequence/dialects/postgres.js +6 -3
  44. package/dist/database/migrations/20231009A-update-csv-fields-to-text.d.ts +3 -0
  45. package/dist/database/migrations/20231009A-update-csv-fields-to-text.js +44 -0
  46. package/dist/database/run-ast.js +1 -1
  47. package/dist/database/seeds/run.js +1 -1
  48. package/dist/extensions/get-extensions.d.ts +47 -0
  49. package/dist/extensions/get-extensions.js +9 -0
  50. package/dist/extensions/get-shared-deps-mapping.d.ts +1 -0
  51. package/dist/extensions/get-shared-deps-mapping.js +26 -0
  52. package/dist/extensions/index.d.ts +2 -0
  53. package/dist/extensions/index.js +9 -0
  54. package/dist/{extensions.d.ts → extensions/manager.d.ts} +4 -11
  55. package/dist/{extensions.js → extensions/manager.js} +28 -86
  56. package/dist/extensions/normalize-extension-info.d.ts +5 -0
  57. package/dist/extensions/normalize-extension-info.js +30 -0
  58. package/dist/extensions/types.d.ts +23 -0
  59. package/dist/extensions/wrap-embeds.d.ts +4 -0
  60. package/dist/extensions/wrap-embeds.js +8 -0
  61. package/dist/flows.d.ts +1 -1
  62. package/dist/flows.js +1 -1
  63. package/dist/middleware/check-ip.js +1 -1
  64. package/dist/middleware/collection-exists.js +1 -1
  65. package/dist/middleware/error-handler.js +1 -1
  66. package/dist/middleware/graphql.js +1 -1
  67. package/dist/middleware/rate-limiter-global.js +1 -1
  68. package/dist/middleware/rate-limiter-ip.js +1 -1
  69. package/dist/middleware/respond.js +1 -1
  70. package/dist/middleware/validate-batch.js +1 -1
  71. package/dist/operations/condition/index.d.ts +1 -1
  72. package/dist/operations/condition/index.js +2 -1
  73. package/dist/operations/exec/index.d.ts +1 -1
  74. package/dist/operations/exec/index.js +1 -1
  75. package/dist/operations/item-create/index.d.ts +1 -1
  76. package/dist/operations/item-create/index.js +2 -1
  77. package/dist/operations/item-delete/index.d.ts +1 -1
  78. package/dist/operations/item-delete/index.js +2 -1
  79. package/dist/operations/item-read/index.d.ts +1 -1
  80. package/dist/operations/item-read/index.js +2 -1
  81. package/dist/operations/item-update/index.d.ts +1 -1
  82. package/dist/operations/item-update/index.js +2 -1
  83. package/dist/operations/json-web-token/index.d.ts +1 -1
  84. package/dist/operations/json-web-token/index.js +2 -1
  85. package/dist/operations/log/index.d.ts +1 -1
  86. package/dist/operations/log/index.js +2 -1
  87. package/dist/operations/mail/index.d.ts +1 -1
  88. package/dist/operations/mail/index.js +1 -1
  89. package/dist/operations/notification/index.d.ts +1 -1
  90. package/dist/operations/notification/index.js +2 -1
  91. package/dist/operations/request/index.d.ts +1 -1
  92. package/dist/operations/request/index.js +3 -2
  93. package/dist/operations/sleep/index.d.ts +1 -1
  94. package/dist/operations/sleep/index.js +1 -1
  95. package/dist/operations/transform/index.d.ts +1 -1
  96. package/dist/operations/transform/index.js +2 -1
  97. package/dist/operations/trigger/index.d.ts +1 -1
  98. package/dist/operations/trigger/index.js +2 -1
  99. package/dist/services/activity.js +1 -1
  100. package/dist/services/assets.d.ts +1 -1
  101. package/dist/services/assets.js +2 -2
  102. package/dist/services/authentication.js +2 -2
  103. package/dist/services/authorization.js +1 -1
  104. package/dist/services/collections.js +3 -3
  105. package/dist/services/fields.d.ts +2 -2
  106. package/dist/services/fields.js +4 -4
  107. package/dist/services/files.d.ts +4 -1
  108. package/dist/services/files.js +5 -5
  109. package/dist/services/graphql/index.js +2 -2
  110. package/dist/services/graphql/subscription.js +3 -3
  111. package/dist/services/import-export/import-worker.d.ts +9 -0
  112. package/dist/services/import-export/import-worker.js +9 -0
  113. package/dist/services/{import-export.d.ts → import-export/index.d.ts} +2 -2
  114. package/dist/services/{import-export.js → import-export/index.js} +51 -42
  115. package/dist/services/index.d.ts +1 -1
  116. package/dist/services/index.js +1 -1
  117. package/dist/services/items.js +2 -2
  118. package/dist/services/mail/index.js +1 -1
  119. package/dist/services/meta.js +1 -1
  120. package/dist/services/payload.js +1 -1
  121. package/dist/services/permissions.d.ts +2 -2
  122. package/dist/services/permissions.js +1 -1
  123. package/dist/services/relations.js +1 -1
  124. package/dist/services/revisions.js +1 -1
  125. package/dist/services/roles.js +1 -1
  126. package/dist/services/schema.js +1 -1
  127. package/dist/services/shares.js +1 -1
  128. package/dist/services/tfa.js +1 -1
  129. package/dist/services/translations.js +1 -1
  130. package/dist/services/users.js +2 -2
  131. package/dist/services/utils.d.ts +1 -0
  132. package/dist/services/utils.js +8 -2
  133. package/dist/services/websocket.js +11 -1
  134. package/dist/types/index.d.ts +0 -1
  135. package/dist/types/index.js +0 -1
  136. package/dist/utils/apply-query.js +1 -1
  137. package/dist/utils/get-accountability-for-token.js +1 -1
  138. package/dist/utils/get-ast-from-query.js +1 -1
  139. package/dist/utils/get-column-path.js +1 -1
  140. package/dist/utils/get-column.js +1 -1
  141. package/dist/utils/get-default-value.d.ts +1 -2
  142. package/dist/utils/get-permissions.js +1 -1
  143. package/dist/utils/jwt.js +1 -1
  144. package/dist/utils/redact-object.js +9 -3
  145. package/dist/utils/transformations.d.ts +2 -1
  146. package/dist/utils/validate-diff.js +1 -1
  147. package/dist/utils/validate-keys.js +1 -1
  148. package/dist/utils/validate-query.js +1 -1
  149. package/dist/utils/validate-snapshot.js +1 -1
  150. package/dist/websocket/controllers/base.js +1 -1
  151. package/dist/websocket/controllers/index.d.ts +1 -1
  152. package/dist/websocket/controllers/index.js +0 -7
  153. package/dist/websocket/handlers/heartbeat.js +6 -1
  154. package/dist/websocket/handlers/subscribe.js +11 -16
  155. package/dist/websocket/utils/items.d.ts +4 -14
  156. package/dist/websocket/utils/items.js +59 -64
  157. package/dist/worker-pool.d.ts +2 -0
  158. package/dist/worker-pool.js +11 -0
  159. package/package.json +24 -22
  160. package/dist/errors/codes.d.ts +0 -29
  161. package/dist/errors/codes.js +0 -30
  162. package/dist/errors/contains-null-values.d.ts +0 -7
  163. package/dist/errors/contains-null-values.js +0 -4
  164. package/dist/errors/content-too-large.d.ts +0 -1
  165. package/dist/errors/content-too-large.js +0 -3
  166. package/dist/errors/forbidden.d.ts +0 -1
  167. package/dist/errors/forbidden.js +0 -3
  168. package/dist/errors/hit-rate-limit.d.ts +0 -6
  169. package/dist/errors/hit-rate-limit.js +0 -8
  170. package/dist/errors/illegal-asset-transformation.d.ts +0 -4
  171. package/dist/errors/illegal-asset-transformation.js +0 -3
  172. package/dist/errors/index.d.ts +0 -28
  173. package/dist/errors/index.js +0 -28
  174. package/dist/errors/invalid-credentials.d.ts +0 -1
  175. package/dist/errors/invalid-credentials.js +0 -3
  176. package/dist/errors/invalid-foreign-key.d.ts +0 -6
  177. package/dist/errors/invalid-foreign-key.js +0 -14
  178. package/dist/errors/invalid-ip.d.ts +0 -1
  179. package/dist/errors/invalid-ip.js +0 -3
  180. package/dist/errors/invalid-otp.d.ts +0 -1
  181. package/dist/errors/invalid-otp.js +0 -3
  182. package/dist/errors/invalid-payload.d.ts +0 -5
  183. package/dist/errors/invalid-payload.js +0 -4
  184. package/dist/errors/invalid-provider-config.d.ts +0 -5
  185. package/dist/errors/invalid-provider-config.js +0 -3
  186. package/dist/errors/invalid-provider.d.ts +0 -1
  187. package/dist/errors/invalid-provider.js +0 -3
  188. package/dist/errors/invalid-query.d.ts +0 -5
  189. package/dist/errors/invalid-query.js +0 -4
  190. package/dist/errors/invalid-token.d.ts +0 -1
  191. package/dist/errors/invalid-token.js +0 -3
  192. package/dist/errors/method-not-allowed.d.ts +0 -6
  193. package/dist/errors/method-not-allowed.js +0 -6
  194. package/dist/errors/not-null-violation.d.ts +0 -6
  195. package/dist/errors/not-null-violation.js +0 -14
  196. package/dist/errors/range-not-satisfiable.d.ts +0 -7
  197. package/dist/errors/range-not-satisfiable.js +0 -7
  198. package/dist/errors/record-not-unique.d.ts +0 -6
  199. package/dist/errors/record-not-unique.js +0 -14
  200. package/dist/errors/route-not-found.d.ts +0 -5
  201. package/dist/errors/route-not-found.js +0 -4
  202. package/dist/errors/service-unavailable.d.ts +0 -7
  203. package/dist/errors/service-unavailable.js +0 -4
  204. package/dist/errors/token-expired.d.ts +0 -1
  205. package/dist/errors/token-expired.js +0 -3
  206. package/dist/errors/unexpected-response.d.ts +0 -1
  207. package/dist/errors/unexpected-response.js +0 -3
  208. package/dist/errors/unprocessable-content.d.ts +0 -5
  209. package/dist/errors/unprocessable-content.js +0 -4
  210. package/dist/errors/unsupported-media-type.d.ts +0 -6
  211. package/dist/errors/unsupported-media-type.js +0 -4
  212. package/dist/errors/user-suspended.d.ts +0 -1
  213. package/dist/errors/user-suspended.js +0 -3
  214. package/dist/errors/value-out-of-range.d.ts +0 -6
  215. package/dist/errors/value-out-of-range.js +0 -14
  216. package/dist/errors/value-too-long.d.ts +0 -6
  217. package/dist/errors/value-too-long.js +0 -14
  218. package/dist/types/files.d.ts +0 -29
  219. /package/dist/{types/files.js → extensions/types.js} +0 -0
@@ -1,7 +1,7 @@
1
1
  import argon2 from 'argon2';
2
2
  import jwt from 'jsonwebtoken';
3
3
  import env from '../env.js';
4
- import { ForbiddenError, InvalidCredentialsError } from '../errors/index.js';
4
+ import { ForbiddenError, InvalidCredentialsError } from '@directus/errors';
5
5
  import { getMilliseconds } from '../utils/get-milliseconds.js';
6
6
  import { md } from '../utils/md.js';
7
7
  import { Url } from '../utils/url.js';
@@ -1,6 +1,6 @@
1
1
  import { authenticator } from 'otplib';
2
2
  import getDatabase from '../database/index.js';
3
- import { InvalidPayloadError } from '../errors/index.js';
3
+ import { InvalidPayloadError } from '@directus/errors';
4
4
  import { ItemsService } from './items.js';
5
5
  export class TFAService {
6
6
  knex;
@@ -1,5 +1,5 @@
1
1
  import getDatabase from '../database/index.js';
2
- import { InvalidPayloadError } from '../errors/index.js';
2
+ import { InvalidPayloadError } from '@directus/errors';
3
3
  import { ItemsService } from './items.js';
4
4
  export class TranslationsService extends ItemsService {
5
5
  constructor(options) {
@@ -6,8 +6,8 @@ import { cloneDeep, isEmpty } from 'lodash-es';
6
6
  import { performance } from 'perf_hooks';
7
7
  import getDatabase from '../database/index.js';
8
8
  import env from '../env.js';
9
- import { ForbiddenError } from '../errors/forbidden.js';
10
- import { InvalidPayloadError, RecordNotUniqueError, UnprocessableContentError } from '../errors/index.js';
9
+ import { ForbiddenError } from '@directus/errors';
10
+ import { InvalidPayloadError, RecordNotUniqueError, UnprocessableContentError } from '@directus/errors';
11
11
  import isUrlAllowed from '../utils/is-url-allowed.js';
12
12
  import { verifyJWT } from '../utils/jwt.js';
13
13
  import { stall } from '../utils/stall.js';
@@ -10,4 +10,5 @@ export declare class UtilsService {
10
10
  item: PrimaryKey;
11
11
  to: PrimaryKey;
12
12
  }): Promise<void>;
13
+ clearCache(): Promise<void>;
13
14
  }
@@ -1,8 +1,8 @@
1
+ import { flushCaches, getCache } from '../cache.js';
1
2
  import getDatabase from '../database/index.js';
2
3
  import { systemCollectionRows } from '../database/system-data/collections/index.js';
3
4
  import emitter from '../emitter.js';
4
- import { ForbiddenError, InvalidPayloadError } from '../errors/index.js';
5
- import { getCache } from '../cache.js';
5
+ import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
6
6
  import { shouldClearCache } from '../utils/should-clear-cache.js';
7
7
  export class UtilsService {
8
8
  knex;
@@ -112,4 +112,10 @@ export class UtilsService {
112
112
  accountability: this.accountability,
113
113
  });
114
114
  }
115
+ async clearCache() {
116
+ if (this.accountability?.admin !== true) {
117
+ throw new ForbiddenError();
118
+ }
119
+ return flushCaches(true);
120
+ }
115
121
  }
@@ -1,9 +1,19 @@
1
1
  import { getWebSocketController } from '../websocket/controllers/index.js';
2
+ import { ServiceUnavailableError } from '@directus/errors';
3
+ import { toBoolean } from '../utils/to-boolean.js';
2
4
  import emitter from '../emitter.js';
5
+ import env from '../env.js';
3
6
  export class WebSocketService {
4
7
  controller;
5
8
  constructor() {
6
- this.controller = getWebSocketController();
9
+ if (!toBoolean(env['WEBSOCKETS_ENABLED']) || !toBoolean(env['WEBSOCKETS_REST_ENABLED'])) {
10
+ throw new ServiceUnavailableError({ service: 'ws', reason: 'WebSocket server is disabled' });
11
+ }
12
+ const controller = getWebSocketController();
13
+ if (!controller) {
14
+ throw new ServiceUnavailableError({ service: 'ws', reason: 'WebSocket server is not initialized' });
15
+ }
16
+ this.controller = controller;
7
17
  }
8
18
  on(event, callback) {
9
19
  emitter.onAction('websocket.' + event, callback);
@@ -4,7 +4,6 @@ export * from './auth.js';
4
4
  export * from './collection.js';
5
5
  export * from './database.js';
6
6
  export * from './events.js';
7
- export * from './files.js';
8
7
  export * from './graphql.js';
9
8
  export * from './items.js';
10
9
  export * from './meta.js';
@@ -4,7 +4,6 @@ export * from './auth.js';
4
4
  export * from './collection.js';
5
5
  export * from './database.js';
6
6
  export * from './events.js';
7
- export * from './files.js';
8
7
  export * from './graphql.js';
9
8
  export * from './items.js';
10
9
  export * from './meta.js';
@@ -3,7 +3,7 @@ import { clone, isPlainObject } from 'lodash-es';
3
3
  import { customAlphabet } from 'nanoid/non-secure';
4
4
  import validate from 'uuid-validate';
5
5
  import { getHelpers } from '../database/helpers/index.js';
6
- import { InvalidQueryError } from '../errors/index.js';
6
+ import { InvalidQueryError } from '@directus/errors';
7
7
  import { getColumnPath } from './get-column-path.js';
8
8
  import { getColumn } from './get-column.js';
9
9
  import { getRelationInfo } from './get-relation-info.js';
@@ -1,6 +1,6 @@
1
1
  import getDatabase from '../database/index.js';
2
2
  import env from '../env.js';
3
- import { InvalidCredentialsError } from '../errors/index.js';
3
+ import { InvalidCredentialsError } from '@directus/errors';
4
4
  import isDirectusJWT from './is-directus-jwt.js';
5
5
  import { verifyAccessJWT } from './jwt.js';
6
6
  export async function getAccountabilityForToken(token, accountability) {
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { REGEX_BETWEEN_PARENS } from '@directus/constants';
5
5
  import { cloneDeep, isEmpty, mapKeys, omitBy, uniq } from 'lodash-es';
6
- import { getRelationType } from '../utils/get-relation-type.js';
6
+ import { getRelationType } from './get-relation-type.js';
7
7
  export default async function getASTFromQuery(collection, query, schema, options) {
8
8
  query = cloneDeep(query);
9
9
  const accountability = options?.accountability;
@@ -1,4 +1,4 @@
1
- import { InvalidQueryError } from '../errors/index.js';
1
+ import { InvalidQueryError } from '@directus/errors';
2
2
  import { getRelationInfo } from './get-relation-info.js';
3
3
  /**
4
4
  * Converts a Directus field list path to the correct SQL names based on the constructed alias map.
@@ -1,7 +1,7 @@
1
1
  import { REGEX_BETWEEN_PARENS } from '@directus/constants';
2
2
  import { getFunctionsForType } from '@directus/utils';
3
3
  import { getFunctions } from '../database/helpers/index.js';
4
- import { InvalidQueryError } from '../errors/index.js';
4
+ import { InvalidQueryError } from '@directus/errors';
5
5
  import { applyFunctionToColumnName } from './apply-function-to-column-name.js';
6
6
  /**
7
7
  * Return column prefixed by table. If column includes functions (like `year(date_created)`), the
@@ -1,5 +1,4 @@
1
- import type { SchemaOverview } from '@directus/schema/types/overview';
2
- import type { Column } from '@directus/schema';
1
+ import type { Column, SchemaOverview } from '@directus/schema';
3
2
  import type { FieldMeta } from '@directus/types';
4
3
  export default function getDefaultValue(column: SchemaOverview[string]['columns'][string] | Column, field?: {
5
4
  special?: FieldMeta['special'];
@@ -8,7 +8,7 @@ import env from '../env.js';
8
8
  import logger from '../logger.js';
9
9
  import { RolesService } from '../services/roles.js';
10
10
  import { UsersService } from '../services/users.js';
11
- import { mergePermissions } from '../utils/merge-permissions.js';
11
+ import { mergePermissions } from './merge-permissions.js';
12
12
  import { mergePermissionsForShare } from './merge-permissions-for-share.js';
13
13
  export async function getPermissions(accountability, schema) {
14
14
  const database = getDatabase();
package/dist/utils/jwt.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import jwt from 'jsonwebtoken';
2
- import { InvalidTokenError, ServiceUnavailableError, TokenExpiredError } from '../errors/index.js';
2
+ import { InvalidTokenError, ServiceUnavailableError, TokenExpiredError } from '@directus/errors';
3
3
  export function verifyJWT(token, secret) {
4
4
  let payload;
5
5
  try {
@@ -12,7 +12,6 @@ import { isObject } from '@directus/utils';
12
12
  export function redactObject(input, redact, replacement) {
13
13
  const wildcardChars = ['*', '**'];
14
14
  const clone = JSON.parse(JSON.stringify(input, getReplacer(replacement, redact.values)));
15
- const visited = new WeakSet();
16
15
  if (redact.keys) {
17
16
  traverse(clone, redact.keys);
18
17
  }
@@ -21,7 +20,6 @@ export function redactObject(input, redact, replacement) {
21
20
  if (checkKeyPaths.length === 0) {
22
21
  return;
23
22
  }
24
- visited.add(object);
25
23
  const REDACTED_TEXT = replacement();
26
24
  const globalCheckPaths = [];
27
25
  for (const key of Object.keys(object)) {
@@ -73,7 +71,7 @@ export function redactObject(input, redact, replacement) {
73
71
  }
74
72
  }
75
73
  const value = object[key];
76
- if (isObject(value) && !visited.has(value)) {
74
+ if (isObject(value)) {
77
75
  traverse(value, [...globalCheckPaths, ...localCheckPaths]);
78
76
  }
79
77
  }
@@ -86,7 +84,15 @@ export function getReplacer(replacement, values) {
86
84
  const filteredValues = values
87
85
  ? Object.entries(values).filter(([_k, v]) => typeof v === 'string' && v.length > 0)
88
86
  : [];
87
+ const seen = new WeakSet();
89
88
  return (_key, value) => {
89
+ // Skip circular values
90
+ if (isObject(value)) {
91
+ if (seen.has(value)) {
92
+ return;
93
+ }
94
+ seen.add(value);
95
+ }
90
96
  if (value instanceof Error) {
91
97
  return {
92
98
  name: value.name,
@@ -1,4 +1,5 @@
1
- import type { File, Transformation, TransformationSet } from '../types/index.js';
1
+ import type { File } from '@directus/types';
2
+ import type { Transformation, TransformationSet } from '../types/index.js';
2
3
  export declare function resolvePreset({ transformationParams, acceptFormat }: TransformationSet, file: File): Transformation[];
3
4
  /**
4
5
  * Try to extract a file format from an array of `Transformation`'s.
@@ -1,5 +1,5 @@
1
1
  import Joi from 'joi';
2
- import { InvalidPayloadError } from '../errors/index.js';
2
+ import { InvalidPayloadError } from '@directus/errors';
3
3
  import { DiffKind } from '../types/snapshot.js';
4
4
  const deepDiffSchema = Joi.object({
5
5
  kind: Joi.string()
@@ -1,4 +1,4 @@
1
- import { ForbiddenError } from '../errors/index.js';
1
+ import { ForbiddenError } from '@directus/errors';
2
2
  import validateUUID from 'uuid-validate';
3
3
  /**
4
4
  * Validate keys based on its type
@@ -2,7 +2,7 @@ import Joi from 'joi';
2
2
  import { isPlainObject, uniq } from 'lodash-es';
3
3
  import { stringify } from 'wellknown';
4
4
  import env from '../env.js';
5
- import { InvalidQueryError } from '../errors/index.js';
5
+ import { InvalidQueryError } from '@directus/errors';
6
6
  import { calculateFieldDepth } from './calculate-field-depth.js';
7
7
  const querySchema = Joi.object({
8
8
  fields: Joi.array().items(Joi.string()),
@@ -2,7 +2,7 @@ import { TYPES } from '@directus/constants';
2
2
  import Joi from 'joi';
3
3
  import { ALIAS_TYPES } from '../constants.js';
4
4
  import { getDatabaseClient } from '../database/index.js';
5
- import { InvalidPayloadError } from '../errors/index.js';
5
+ import { InvalidPayloadError } from '@directus/errors';
6
6
  import { DatabaseClients } from '../types/index.js';
7
7
  import { version as currentDirectusVersion } from './package.js';
8
8
  const snapshotJoiSchema = Joi.object({
@@ -5,7 +5,7 @@ import WebSocket, { WebSocketServer } from 'ws';
5
5
  import { fromZodError } from 'zod-validation-error';
6
6
  import emitter from '../../emitter.js';
7
7
  import env from '../../env.js';
8
- import { InvalidProviderConfigError, TokenExpiredError } from '../../errors/index.js';
8
+ import { InvalidProviderConfigError, TokenExpiredError } from '@directus/errors';
9
9
  import logger from '../../logger.js';
10
10
  import { createRateLimiter } from '../../rate-limiter.js';
11
11
  import { getAccountabilityForToken } from '../../utils/get-accountability-for-token.js';
@@ -3,7 +3,7 @@ import type { Server as httpServer } from 'http';
3
3
  import { GraphQLSubscriptionController } from './graphql.js';
4
4
  import { WebSocketController } from './rest.js';
5
5
  export declare function createWebSocketController(server: httpServer): void;
6
- export declare function getWebSocketController(): WebSocketController;
6
+ export declare function getWebSocketController(): WebSocketController | undefined;
7
7
  export declare function createSubscriptionController(server: httpServer): void;
8
8
  export declare function getSubscriptionController(): GraphQLSubscriptionController | undefined;
9
9
  export * from './graphql.js';
@@ -1,5 +1,4 @@
1
1
  import env from '../../env.js';
2
- import { ServiceUnavailableError } from '../../errors/index.js';
3
2
  import { toBoolean } from '../../utils/to-boolean.js';
4
3
  import { GraphQLSubscriptionController } from './graphql.js';
5
4
  import { WebSocketController } from './rest.js';
@@ -11,12 +10,6 @@ export function createWebSocketController(server) {
11
10
  }
12
11
  }
13
12
  export function getWebSocketController() {
14
- if (!toBoolean(env['WEBSOCKETS_ENABLED']) || !toBoolean(env['WEBSOCKETS_REST_ENABLED'])) {
15
- throw new ServiceUnavailableError({ service: 'ws', reason: 'WebSocket server is disabled' });
16
- }
17
- if (!websocketController) {
18
- throw new ServiceUnavailableError({ service: 'ws', reason: 'WebSocket server is not initialized' });
19
- }
20
13
  return websocketController;
21
14
  }
22
15
  export function createSubscriptionController(server) {
@@ -4,12 +4,17 @@ import { toBoolean } from '../../utils/to-boolean.js';
4
4
  import { WebSocketController, getWebSocketController } from '../controllers/index.js';
5
5
  import { WebSocketMessage } from '../messages.js';
6
6
  import { fmtMessage, getMessageType } from '../utils/message.js';
7
+ import { ServiceUnavailableError } from '@directus/errors';
7
8
  const HEARTBEAT_FREQUENCY = Number(env['WEBSOCKETS_HEARTBEAT_PERIOD']) * 1000;
8
9
  export class HeartbeatHandler {
9
10
  pulse;
10
11
  controller;
11
12
  constructor(controller) {
12
- this.controller = controller ?? getWebSocketController();
13
+ controller = controller ?? getWebSocketController();
14
+ if (!controller) {
15
+ throw new ServiceUnavailableError({ service: 'ws', reason: 'WebSocket server is not initialized' });
16
+ }
17
+ this.controller = controller;
13
18
  emitter.onAction('websocket.message', ({ client, message }) => {
14
19
  try {
15
20
  this.onMessage(client, WebSocketMessage.parse(message));
@@ -1,5 +1,5 @@
1
1
  import emitter from '../../emitter.js';
2
- import { InvalidPayloadError } from '../../errors/index.js';
2
+ import { InvalidPayloadError } from '@directus/errors';
3
3
  import { getMessenger } from '../../messenger.js';
4
4
  import { getSchema } from '../../utils/get-schema.js';
5
5
  import { sanitizeQuery } from '../../utils/sanitize-query.js';
@@ -7,7 +7,7 @@ import { refreshAccountability } from '../authenticate.js';
7
7
  import { WebSocketError, handleWebSocketError } from '../errors.js';
8
8
  import { WebSocketSubscribeMessage } from '../messages.js';
9
9
  import { fmtMessage, getMessageType } from '../utils/message.js';
10
- import { getMultiPayload, getSinglePayload } from '../utils/items.js';
10
+ import { getPayload } from '../utils/items.js';
11
11
  /**
12
12
  * Handler responsible for subscriptions
13
13
  */
@@ -104,13 +104,17 @@ export class SubscribeHandler {
104
104
  if (subscription.event !== undefined && event.action !== subscription.event) {
105
105
  continue; // skip filtered events
106
106
  }
107
+ if ('item' in subscription) {
108
+ if ('keys' in event && !event.keys.includes(subscription.item))
109
+ continue;
110
+ if ('key' in event && event.key !== subscription.item)
111
+ continue;
112
+ }
107
113
  try {
108
114
  client.accountability = await refreshAccountability(client.accountability);
109
- const result = 'item' in subscription
110
- ? await getSinglePayload(subscription, client.accountability, schema, event)
111
- : await getMultiPayload(subscription, client.accountability, schema, event);
115
+ const result = await getPayload(subscription, client.accountability, schema, event);
112
116
  if (Array.isArray(result?.['data']) && result?.['data']?.length === 0)
113
- return;
117
+ continue;
114
118
  client.send(fmtMessage('subscription', result, subscription.uid));
115
119
  }
116
120
  catch (err) {
@@ -147,16 +151,7 @@ export class SubscribeHandler {
147
151
  // remove the subscription if it already exists
148
152
  this.unsubscribe(client, subscription.uid);
149
153
  }
150
- let data;
151
- if (subscription.event === undefined) {
152
- data =
153
- 'item' in subscription
154
- ? await getSinglePayload(subscription, accountability, schema)
155
- : await getMultiPayload(subscription, accountability, schema);
156
- }
157
- else {
158
- data = { event: 'init' };
159
- }
154
+ const data = subscription.event === undefined ? await getPayload(subscription, accountability, schema) : { event: 'init' };
160
155
  // if no errors were thrown register the subscription
161
156
  this.subscribe(subscription);
162
157
  // send an initial response
@@ -2,16 +2,6 @@ import type { Accountability, SchemaOverview } from '@directus/types';
2
2
  import type { WebSocketEvent } from '../messages.js';
3
3
  import type { Subscription } from '../types.js';
4
4
  type PSubscription = Omit<Subscription, 'client'>;
5
- /**
6
- * Get a single item from a collection using the appropriate service
7
- *
8
- * @param subscription Subscription object
9
- * @param accountability Accountability object
10
- * @param schema Schema object
11
- * @param event Event data
12
- * @returns the fetched item
13
- */
14
- export declare function getSinglePayload(subscription: PSubscription, accountability: Accountability | null, schema: SchemaOverview, event?: WebSocketEvent): Promise<Record<string, any>>;
15
5
  /**
16
6
  * Get items from a collection using the appropriate service
17
7
  *
@@ -21,7 +11,7 @@ export declare function getSinglePayload(subscription: PSubscription, accountabi
21
11
  * @param event Event data
22
12
  * @returns the fetched items
23
13
  */
24
- export declare function getMultiPayload(subscription: PSubscription, accountability: Accountability | null, schema: SchemaOverview, event?: WebSocketEvent): Promise<Record<string, any>>;
14
+ export declare function getPayload(subscription: PSubscription, accountability: Accountability | null, schema: SchemaOverview, event?: WebSocketEvent): Promise<Record<string, any>>;
25
15
  /**
26
16
  * Get collection items
27
17
  *
@@ -30,7 +20,7 @@ export declare function getMultiPayload(subscription: PSubscription, accountabil
30
20
  * @param event Event data
31
21
  * @returns the fetched collection data
32
22
  */
33
- export declare function getCollectionPayload(accountability: Accountability | null, schema: SchemaOverview, event?: WebSocketEvent): Promise<(string | number)[] | import("../../types/collection.js").Collection[]>;
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[]>;
34
24
  /**
35
25
  * Get fields items
36
26
  *
@@ -39,7 +29,7 @@ export declare function getCollectionPayload(accountability: Accountability | nu
39
29
  * @param event Event data
40
30
  * @returns the fetched field data
41
31
  */
42
- export declare function getFieldsPayload(accountability: Accountability | null, schema: SchemaOverview, event?: WebSocketEvent): Promise<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> | import("@directus/types").Field[] | (string | number)[]>;
43
33
  /**
44
34
  * Get items from a collection using the appropriate service
45
35
  *
@@ -49,5 +39,5 @@ export declare function getFieldsPayload(accountability: Accountability | null,
49
39
  * @param event Event data
50
40
  * @returns the fetched data
51
41
  */
52
- export declare function getItemsPayload(subscription: PSubscription, accountability: Accountability | null, schema: SchemaOverview, event?: WebSocketEvent): Promise<(string | number)[] | import("../../types/items.js").Item[]>;
42
+ export declare function getItemsPayload(subscription: PSubscription, accountability: Accountability | null, schema: SchemaOverview, event?: WebSocketEvent): Promise<string | number | import("../../types/items.js").Item | (string | number)[] | import("../../types/items.js").Item[]>;
53
43
  export {};
@@ -1,34 +1,5 @@
1
1
  import { getService } from '../../utils/get-service.js';
2
2
  import { CollectionsService, FieldsService, MetaService } from '../../services/index.js';
3
- /**
4
- * Get a single item from a collection using the appropriate service
5
- *
6
- * @param subscription Subscription object
7
- * @param accountability Accountability object
8
- * @param schema Schema object
9
- * @param event Event data
10
- * @returns the fetched item
11
- */
12
- export async function getSinglePayload(subscription, accountability, schema, event) {
13
- const metaService = new MetaService({ schema, accountability });
14
- const query = subscription.query ?? {};
15
- const id = subscription.item;
16
- const result = {
17
- event: event?.action ?? 'init',
18
- };
19
- if (subscription.collection === 'directus_collections') {
20
- const service = new CollectionsService({ schema, accountability });
21
- result['data'] = await service.readOne(String(id));
22
- }
23
- else {
24
- const service = getService(subscription.collection, { schema, accountability });
25
- result['data'] = await service.readOne(id, query);
26
- }
27
- if ('meta' in query) {
28
- result['meta'] = await metaService.getMetaForQuery(subscription.collection, query);
29
- }
30
- return result;
31
- }
32
3
  /**
33
4
  * Get items from a collection using the appropriate service
34
5
  *
@@ -38,17 +9,17 @@ export async function getSinglePayload(subscription, accountability, schema, eve
38
9
  * @param event Event data
39
10
  * @returns the fetched items
40
11
  */
41
- export async function getMultiPayload(subscription, accountability, schema, event) {
12
+ export async function getPayload(subscription, accountability, schema, event) {
42
13
  const metaService = new MetaService({ schema, accountability });
43
14
  const result = {
44
15
  event: event?.action ?? 'init',
45
16
  };
46
17
  switch (subscription.collection) {
47
18
  case 'directus_collections':
48
- result['data'] = await getCollectionPayload(accountability, schema, event);
19
+ result['data'] = await getCollectionPayload(subscription, accountability, schema, event);
49
20
  break;
50
21
  case 'directus_fields':
51
- result['data'] = await getFieldsPayload(accountability, schema, event);
22
+ result['data'] = await getFieldsPayload(subscription, accountability, schema, event);
52
23
  break;
53
24
  case 'directus_relations':
54
25
  result['data'] = event?.payload;
@@ -71,19 +42,27 @@ export async function getMultiPayload(subscription, accountability, schema, even
71
42
  * @param event Event data
72
43
  * @returns the fetched collection data
73
44
  */
74
- export async function getCollectionPayload(accountability, schema, event) {
45
+ export async function getCollectionPayload(subscription, accountability, schema, event) {
75
46
  const service = new CollectionsService({ schema, accountability });
76
- if (!event?.action) {
77
- return await service.readByQuery();
78
- }
79
- else if (event.action === 'create') {
80
- return await service.readMany([String(event.key)]);
81
- }
82
- else if (event.action === 'delete') {
83
- return event.keys;
84
- }
85
- else {
86
- return await service.readMany(event.keys.map((key) => String(key)));
47
+ if ('item' in subscription) {
48
+ if (event?.action === 'delete') {
49
+ // return only the subscribed id in case a bulk delete was done
50
+ return subscription.item;
51
+ }
52
+ else {
53
+ return await service.readOne(String(subscription.item));
54
+ }
55
+ }
56
+ switch (event?.action) {
57
+ case 'create':
58
+ return await service.readMany([String(event.key)]);
59
+ case 'update':
60
+ return await service.readMany(event.keys.map((key) => String(key)));
61
+ case 'delete':
62
+ return event.keys;
63
+ case undefined:
64
+ default:
65
+ return await service.readByQuery();
87
66
  }
88
67
  }
89
68
  /**
@@ -94,16 +73,24 @@ export async function getCollectionPayload(accountability, schema, event) {
94
73
  * @param event Event data
95
74
  * @returns the fetched field data
96
75
  */
97
- export async function getFieldsPayload(accountability, schema, event) {
76
+ export async function getFieldsPayload(subscription, accountability, schema, event) {
98
77
  const service = new FieldsService({ schema, accountability });
99
- if (!event?.action) {
100
- return await service.readAll();
101
- }
102
- else if (event.action === 'delete') {
103
- return event.keys;
104
- }
105
- else {
106
- return await service.readOne(event.payload?.['collection'], event.payload?.['field']);
78
+ if ('item' in subscription) {
79
+ if (event?.action === 'delete') {
80
+ // return only the subscribed id in case a bluk delete was done
81
+ return subscription.item;
82
+ }
83
+ else {
84
+ return await service.readOne(subscription.collection, String(subscription.item));
85
+ }
86
+ }
87
+ switch (event?.action) {
88
+ case undefined:
89
+ return await service.readAll();
90
+ case 'delete':
91
+ return event.keys;
92
+ default:
93
+ return await service.readOne(event?.payload?.['collection'], event?.payload?.['field']);
107
94
  }
108
95
  }
109
96
  /**
@@ -118,16 +105,24 @@ export async function getFieldsPayload(accountability, schema, event) {
118
105
  export async function getItemsPayload(subscription, accountability, schema, event) {
119
106
  const query = subscription.query ?? {};
120
107
  const service = getService(subscription.collection, { schema, accountability });
121
- if (!event?.action) {
122
- return await service.readByQuery(query);
123
- }
124
- else if (event.action === 'create') {
125
- return await service.readMany([event.key], query);
126
- }
127
- else if (event.action === 'delete') {
128
- return event.keys;
129
- }
130
- else {
131
- return await service.readMany(event.keys, query);
108
+ if ('item' in subscription) {
109
+ if (event?.action === 'delete') {
110
+ // return only the subscribed id in case a bluk delete was done
111
+ return subscription.item;
112
+ }
113
+ else {
114
+ return await service.readOne(subscription.item, query);
115
+ }
116
+ }
117
+ switch (event?.action) {
118
+ case 'create':
119
+ return await service.readMany([event.key], query);
120
+ case 'update':
121
+ return await service.readMany(event.keys, query);
122
+ case 'delete':
123
+ return event.keys;
124
+ case undefined:
125
+ default:
126
+ return await service.readByQuery(query);
132
127
  }
133
128
  }
@@ -0,0 +1,2 @@
1
+ import Tinypool from 'tinypool';
2
+ export declare function getWorkerPool(): Tinypool;
@@ -0,0 +1,11 @@
1
+ import Tinypool from 'tinypool';
2
+ let workerPool;
3
+ export function getWorkerPool() {
4
+ if (!workerPool) {
5
+ workerPool = new Tinypool({
6
+ minThreads: 0,
7
+ maxQueue: 'auto',
8
+ });
9
+ }
10
+ return workerPool;
11
+ }