@directus/api 28.0.3 → 29.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 (141) hide show
  1. package/dist/auth/drivers/openid.js +12 -6
  2. package/dist/cli/commands/init/index.js +2 -2
  3. package/dist/cli/commands/init/questions.d.ts +1 -1
  4. package/dist/cli/commands/schema/apply.js +1 -1
  5. package/dist/cli/utils/create-db-connection.d.ts +1 -1
  6. package/dist/cli/utils/create-env/index.d.ts +1 -1
  7. package/dist/cli/utils/drivers.d.ts +1 -1
  8. package/dist/constants.d.ts +1 -1
  9. package/dist/controllers/assets.js +1 -1
  10. package/dist/controllers/extensions.js +4 -4
  11. package/dist/database/get-ast-from-query/lib/parse-fields.js +3 -1
  12. package/dist/database/helpers/index.d.ts +4 -4
  13. package/dist/database/helpers/schema/types.d.ts +1 -2
  14. package/dist/database/index.d.ts +1 -1
  15. package/dist/database/migrations/20210225A-add-relations-sort-field.js +1 -1
  16. package/dist/database/migrations/20240924B-populate-versioning-deltas.js +1 -1
  17. package/dist/database/migrations/20250718A-add-direction.d.ts +3 -0
  18. package/dist/database/migrations/20250718A-add-direction.js +10 -0
  19. package/dist/database/run-ast/lib/apply-query/filter/get-filter-type.d.ts +2 -2
  20. package/dist/database/run-ast/utils/apply-parent-filters.js +2 -0
  21. package/dist/database/run-ast/utils/get-field-alias.js +2 -0
  22. package/dist/database/run-ast/utils/remove-temporary-fields.js +8 -2
  23. package/dist/extensions/lib/get-extensions-settings.d.ts +1 -1
  24. package/dist/extensions/lib/sandbox/generate-api-extensions-sandbox-entrypoint.d.ts +1 -1
  25. package/dist/extensions/manager.d.ts +5 -13
  26. package/dist/extensions/manager.js +39 -28
  27. package/dist/flows.d.ts +1 -2
  28. package/dist/operations/throw-error/index.d.ts +7 -0
  29. package/dist/operations/throw-error/index.js +11 -0
  30. package/dist/permissions/modules/validate-access/lib/validate-item-access.js +2 -1
  31. package/dist/permissions/utils/fetch-share-info.d.ts +1 -1
  32. package/dist/services/access.d.ts +1 -2
  33. package/dist/services/access.js +1 -1
  34. package/dist/services/activity.d.ts +1 -1
  35. package/dist/services/assets.d.ts +1 -3
  36. package/dist/services/authentication.d.ts +1 -2
  37. package/dist/services/collections.d.ts +3 -10
  38. package/dist/services/comments.d.ts +1 -2
  39. package/dist/services/dashboards.d.ts +1 -1
  40. package/dist/services/extensions.d.ts +2 -4
  41. package/dist/services/fields.d.ts +1 -2
  42. package/dist/services/fields.js +2 -8
  43. package/dist/services/files.d.ts +2 -3
  44. package/dist/services/flows.d.ts +1 -2
  45. package/dist/services/folders.d.ts +1 -1
  46. package/dist/services/graphql/errors/format.d.ts +1 -1
  47. package/dist/services/graphql/errors/format.js +0 -1
  48. package/dist/services/graphql/index.d.ts +1 -3
  49. package/dist/services/graphql/resolvers/system-admin.d.ts +1 -1
  50. package/dist/services/graphql/resolvers/system-global.d.ts +1 -1
  51. package/dist/services/graphql/resolvers/system.d.ts +1 -1
  52. package/dist/services/graphql/schema/get-types.d.ts +1 -1
  53. package/dist/services/graphql/schema/get-types.js +0 -1
  54. package/dist/services/import-export.d.ts +2 -4
  55. package/dist/services/import-export.js +5 -3
  56. package/dist/services/items.d.ts +2 -12
  57. package/dist/services/items.js +2 -1
  58. package/dist/services/mail/index.d.ts +2 -4
  59. package/dist/services/mail/index.js +11 -5
  60. package/dist/services/meta.d.ts +1 -2
  61. package/dist/services/notifications.d.ts +1 -2
  62. package/dist/services/operations.d.ts +1 -2
  63. package/dist/services/panels.d.ts +1 -1
  64. package/dist/services/payload.d.ts +9 -17
  65. package/dist/services/payload.js +1 -1
  66. package/dist/services/permissions.d.ts +1 -3
  67. package/dist/services/policies.d.ts +1 -2
  68. package/dist/services/policies.js +1 -1
  69. package/dist/services/presets.d.ts +1 -1
  70. package/dist/services/relations.d.ts +2 -3
  71. package/dist/services/revisions.d.ts +1 -2
  72. package/dist/services/roles.d.ts +1 -2
  73. package/dist/services/roles.js +1 -1
  74. package/dist/services/schema.d.ts +1 -2
  75. package/dist/services/server.d.ts +1 -2
  76. package/dist/services/settings.d.ts +1 -1
  77. package/dist/services/shares.d.ts +1 -2
  78. package/dist/services/specifications.d.ts +1 -2
  79. package/dist/services/tfa.d.ts +1 -2
  80. package/dist/services/translations.d.ts +1 -3
  81. package/dist/services/users.d.ts +1 -2
  82. package/dist/services/users.js +1 -1
  83. package/dist/services/utils.d.ts +1 -2
  84. package/dist/services/versions.d.ts +1 -2
  85. package/dist/services/webhooks.d.ts +1 -2
  86. package/dist/services/websocket.d.ts +1 -3
  87. package/dist/types/ast.d.ts +2 -0
  88. package/dist/types/auth.d.ts +0 -6
  89. package/dist/types/index.d.ts +0 -7
  90. package/dist/types/index.js +0 -7
  91. package/dist/utils/apply-diff.d.ts +1 -2
  92. package/dist/utils/apply-diff.js +1 -1
  93. package/dist/utils/apply-snapshot.d.ts +1 -2
  94. package/dist/utils/get-ip-from-req.js +1 -1
  95. package/dist/utils/get-service.d.ts +1 -1
  96. package/dist/utils/get-snapshot-diff.d.ts +1 -1
  97. package/dist/utils/get-snapshot-diff.js +1 -1
  98. package/dist/utils/get-snapshot.d.ts +1 -2
  99. package/dist/utils/get-snapshot.js +8 -3
  100. package/dist/utils/schedule.js +4 -1
  101. package/dist/utils/should-clear-cache.d.ts +1 -1
  102. package/dist/utils/transformations.d.ts +1 -2
  103. package/dist/utils/validate-diff.d.ts +1 -1
  104. package/dist/utils/validate-diff.js +1 -1
  105. package/dist/utils/validate-snapshot.d.ts +1 -1
  106. package/dist/utils/validate-snapshot.js +1 -1
  107. package/dist/utils/validate-user-count-integrity.d.ts +1 -8
  108. package/dist/utils/validate-user-count-integrity.js +1 -9
  109. package/dist/websocket/authenticate.d.ts +2 -1
  110. package/dist/websocket/authenticate.js +25 -4
  111. package/dist/websocket/controllers/base.d.ts +2 -1
  112. package/dist/websocket/controllers/base.js +12 -15
  113. package/dist/websocket/controllers/graphql.js +5 -3
  114. package/dist/websocket/controllers/logs.js +1 -1
  115. package/dist/websocket/controllers/rest.d.ts +1 -1
  116. package/dist/websocket/controllers/rest.js +1 -1
  117. package/dist/websocket/errors.d.ts +1 -1
  118. package/dist/websocket/handlers/heartbeat.d.ts +1 -1
  119. package/dist/websocket/handlers/heartbeat.js +1 -1
  120. package/dist/websocket/messages.d.ts +57 -308
  121. package/dist/websocket/messages.js +5 -10
  122. package/dist/websocket/utils/items.d.ts +1 -1
  123. package/dist/websocket/utils/wait-for-message.d.ts +1 -1
  124. package/dist/websocket/utils/wait-for-message.js +1 -1
  125. package/package.json +86 -85
  126. package/dist/extensions/types.d.ts +0 -19
  127. package/dist/extensions/types.js +0 -1
  128. package/dist/types/assets.d.ts +0 -22
  129. package/dist/types/assets.js +0 -51
  130. package/dist/types/database.d.ts +0 -3
  131. package/dist/types/database.js +0 -1
  132. package/dist/types/graphql.d.ts +0 -14
  133. package/dist/types/graphql.js +0 -1
  134. package/dist/types/items.d.ts +0 -52
  135. package/dist/types/items.js +0 -1
  136. package/dist/types/services.d.ts +0 -22
  137. package/dist/types/services.js +0 -1
  138. package/dist/types/snapshot.d.ts +0 -55
  139. package/dist/types/snapshot.js +0 -13
  140. package/dist/types/webhooks.d.ts +0 -15
  141. package/dist/types/webhooks.js +0 -1
@@ -32,8 +32,9 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
32
32
  super(options, config);
33
33
  const env = useEnv();
34
34
  const logger = useLogger();
35
- const { issuerUrl, clientId, clientSecret, provider, issuerDiscoveryMustSucceed } = config;
36
- if (!issuerUrl || !clientId || !clientSecret || !provider) {
35
+ const { issuerUrl, clientId, clientSecret, clientPrivateKeys, clientTokenEndpointAuthMethod, provider, issuerDiscoveryMustSucceed, } = config;
36
+ const isPrivateKeyJwtAuthMethod = clientTokenEndpointAuthMethod === 'private_key_jwt';
37
+ if (!issuerUrl || !clientId || !(clientSecret || (isPrivateKeyJwtAuthMethod && clientPrivateKeys)) || !provider) {
37
38
  logger.error('Invalid provider config');
38
39
  throw new InvalidProviderConfigError({ provider });
39
40
  }
@@ -66,7 +67,8 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
66
67
  if (this.client)
67
68
  return this.client;
68
69
  const logger = useLogger();
69
- const { issuerUrl, clientId, clientSecret, provider } = this.config;
70
+ const { issuerUrl, clientId, clientSecret, clientPrivateKeys, clientTokenEndpointAuthMethod, provider } = this.config;
71
+ const isPrivateKeyJwtAuthMethod = clientTokenEndpointAuthMethod === 'private_key_jwt';
70
72
  // extract client http overrides/options
71
73
  const clientHttpOptions = getConfigFromEnv(`AUTH_${provider.toUpperCase()}_CLIENT_HTTP_`);
72
74
  if (clientHttpOptions) {
@@ -87,17 +89,21 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
87
89
  }
88
90
  // extract client overrides/options excluding CLIENT_ID and CLIENT_SECRET as they are passed directly
89
91
  const clientOptionsOverrides = getConfigFromEnv(`AUTH_${provider.toUpperCase()}_CLIENT_`, {
90
- omitKey: [`AUTH_${provider.toUpperCase()}_CLIENT_ID`, `AUTH_${provider.toUpperCase()}_CLIENT_SECRET`],
92
+ omitKey: [
93
+ `AUTH_${provider.toUpperCase()}_CLIENT_ID`,
94
+ `AUTH_${provider.toUpperCase()}_CLIENT_SECRET`,
95
+ `AUTH_${provider.toUpperCase()}_CLIENT_PRIVATE_KEYS`,
96
+ ],
91
97
  omitPrefix: [`AUTH_${provider.toUpperCase()}_CLIENT_HTTP_`],
92
98
  type: 'underscore',
93
99
  });
94
100
  const client = new issuer.Client({
95
101
  client_id: clientId,
96
- client_secret: clientSecret,
102
+ ...(!isPrivateKeyJwtAuthMethod && { client_secret: clientSecret }),
97
103
  redirect_uris: [this.redirectUrl],
98
104
  response_types: ['code'],
99
105
  ...clientOptionsOverrides,
100
- });
106
+ }, isPrivateKeyJwtAuthMethod ? { keys: clientPrivateKeys } : undefined);
101
107
  if (clientHttpOptions) {
102
108
  client[custom.http_options] = (_, options) => {
103
109
  return {
@@ -60,7 +60,7 @@ export default async function init() {
60
60
  default: 'admin@example.com',
61
61
  validate: (input) => {
62
62
  const emailSchema = Joi.string().email().required();
63
- const { error } = emailSchema.validate(input);
63
+ const { error } = emailSchema.validate(input.trim());
64
64
  if (error)
65
65
  throw new Error('The email entered is not a valid email address!');
66
66
  return true;
@@ -87,7 +87,7 @@ export default async function init() {
87
87
  await db('directus_users').insert({
88
88
  ...defaultAdminUser,
89
89
  id: randomUUID(),
90
- email: firstUser.email,
90
+ email: firstUser.email.trim(),
91
91
  password: firstUser.password,
92
92
  role,
93
93
  });
@@ -1,4 +1,4 @@
1
- import type { Driver } from '../../../types/index.js';
1
+ import type { Driver } from '@directus/types';
2
2
  export declare const databaseQuestions: {
3
3
  sqlite3: (({ filepath }: {
4
4
  filepath: string;
@@ -1,4 +1,5 @@
1
1
  import { parseJSON } from '@directus/utils';
2
+ import { DiffKind } from '@directus/types';
2
3
  import chalk from 'chalk';
3
4
  import { promises as fs } from 'fs';
4
5
  import inquirer from 'inquirer';
@@ -6,7 +7,6 @@ import { load as loadYaml } from 'js-yaml';
6
7
  import path from 'path';
7
8
  import getDatabase, { isInstalled, validateDatabaseConnection } from '../../../database/index.js';
8
9
  import { useLogger } from '../../../logger/index.js';
9
- import { DiffKind } from '../../../types/index.js';
10
10
  import { isNestedMetaUpdate } from '../../../utils/apply-diff.js';
11
11
  import { applySnapshot } from '../../../utils/apply-snapshot.js';
12
12
  import { getSnapshotDiff } from '../../../utils/get-snapshot-diff.js';
@@ -1,5 +1,5 @@
1
1
  import type { Knex } from 'knex';
2
- import type { Driver } from '../../types/index.js';
2
+ import type { Driver } from '@directus/types';
3
3
  export type Credentials = {
4
4
  filename?: string;
5
5
  host?: string;
@@ -1,3 +1,3 @@
1
- import type { Driver } from '../../../types/index.js';
1
+ import type { Driver } from '@directus/types';
2
2
  import type { Credentials } from '../create-db-connection.js';
3
3
  export default function createEnv(client: Driver, credentials: Credentials, directory: string): Promise<void>;
@@ -1,3 +1,3 @@
1
- import type { Driver } from '../../types/index.js';
1
+ import type { Driver } from '@directus/types';
2
2
  export declare const drivers: Record<Driver, string>;
3
3
  export declare function getDriverForClient(client: string): Driver | null;
@@ -1,5 +1,5 @@
1
1
  import type { CookieOptions } from 'express';
2
- import type { TransformationParams } from './types/index.js';
2
+ import type { TransformationParams } from '@directus/types';
3
3
  export declare const SYSTEM_ASSET_ALLOW_LIST: TransformationParams[];
4
4
  export declare const ASSET_TRANSFORM_QUERY_KEYS: readonly ["key", "transforms", "width", "height", "format", "fit", "quality", "withoutEnlargement", "focal_point_x", "focal_point_y"];
5
5
  export declare const FILTER_VARIABLES: string[];
@@ -1,5 +1,6 @@
1
1
  import { useEnv } from '@directus/env';
2
2
  import { InvalidQueryError, RangeNotSatisfiableError } from '@directus/errors';
3
+ import { TransformationMethods } from '@directus/types';
3
4
  import { parseJSON } from '@directus/utils';
4
5
  import contentDisposition from 'content-disposition';
5
6
  import { Router } from 'express';
@@ -10,7 +11,6 @@ import { useLogger } from '../logger/index.js';
10
11
  import useCollection from '../middleware/use-collection.js';
11
12
  import { AssetsService } from '../services/assets.js';
12
13
  import { PayloadService } from '../services/payload.js';
13
- import { TransformationMethods } from '../types/assets.js';
14
14
  import asyncHandler from '../utils/async-handler.js';
15
15
  import { getCacheControlHeader } from '../utils/get-cache-headers.js';
16
16
  import { getConfigFromEnv } from '../utils/get-config-from-env.js';
@@ -1,6 +1,6 @@
1
1
  import { useEnv } from '@directus/env';
2
2
  import { ErrorCode, ForbiddenError, isDirectusError, RouteNotFoundError } from '@directus/errors';
3
- import { EXTENSION_TYPES } from '@directus/extensions';
3
+ import { EXTENSION_TYPES } from '@directus/constants';
4
4
  import { account, describe, list, } from '@directus/extensions-registry';
5
5
  import { isIn } from '@directus/utils';
6
6
  import express from 'express';
@@ -189,10 +189,10 @@ router.get('/sources/:chunk', asyncHandler(async (req, res) => {
189
189
  const extensionManager = getExtensionManager();
190
190
  let source;
191
191
  if (chunk === 'index.js') {
192
- source = extensionManager.getAppExtensionsBundle();
192
+ source = await extensionManager.getAppExtensionChunk();
193
193
  }
194
194
  else {
195
- source = extensionManager.getAppExtensionChunk(chunk);
195
+ source = await extensionManager.getAppExtensionChunk(chunk);
196
196
  }
197
197
  if (source === null) {
198
198
  throw new RouteNotFoundError({ path: req.path });
@@ -200,6 +200,6 @@ router.get('/sources/:chunk', asyncHandler(async (req, res) => {
200
200
  res.setHeader('Content-Type', 'application/javascript; charset=UTF-8');
201
201
  res.setHeader('Cache-Control', getCacheControlHeader(req, getMilliseconds(env['EXTENSIONS_CACHE_TTL']), false, false));
202
202
  res.setHeader('Vary', 'Origin, Cache-Control');
203
- res.end(source);
203
+ source.pipe(res);
204
204
  }));
205
205
  export default router;
@@ -27,10 +27,12 @@ export async function parseFields(options, context) {
27
27
  : null;
28
28
  const relationalStructure = Object.create(null);
29
29
  for (const fieldKey of fields) {
30
+ let alias = false;
30
31
  let name = fieldKey;
31
32
  if (options.query.alias) {
32
33
  // check for field alias (is one of the key)
33
34
  if (name in options.query.alias) {
35
+ alias = true;
34
36
  name = options.query.alias[fieldKey];
35
37
  }
36
38
  }
@@ -100,7 +102,7 @@ export async function parseFields(options, context) {
100
102
  }
101
103
  continue;
102
104
  }
103
- children.push({ type: 'field', name, fieldKey, whenCase: [] });
105
+ children.push({ type: 'field', name, fieldKey, whenCase: [], alias });
104
106
  }
105
107
  }
106
108
  for (const [fieldKey, nestedFields] of Object.entries(relationalStructure)) {
@@ -9,11 +9,11 @@ import * as schemaHelpers from './schema/index.js';
9
9
  import * as sequenceHelpers from './sequence/index.js';
10
10
  export declare function getHelpers(database: Knex): {
11
11
  date: dateHelpers.postgres | dateHelpers.oracle | dateHelpers.mysql | dateHelpers.mssql | dateHelpers.sqlite;
12
- st: geometryHelpers.postgres | geometryHelpers.mssql | geometryHelpers.mysql | geometryHelpers.sqlite | geometryHelpers.oracle | geometryHelpers.redshift;
13
- schema: schemaHelpers.cockroachdb | schemaHelpers.mssql | schemaHelpers.mysql | schemaHelpers.postgres | schemaHelpers.sqlite | schemaHelpers.oracle | schemaHelpers.redshift;
12
+ st: geometryHelpers.mysql | geometryHelpers.postgres | geometryHelpers.sqlite | geometryHelpers.oracle | geometryHelpers.mssql | geometryHelpers.redshift;
13
+ schema: schemaHelpers.mysql | schemaHelpers.postgres | schemaHelpers.cockroachdb | schemaHelpers.sqlite | schemaHelpers.oracle | schemaHelpers.mssql | schemaHelpers.redshift;
14
14
  sequence: sequenceHelpers.mysql | sequenceHelpers.postgres;
15
- number: numberHelpers.cockroachdb | numberHelpers.mssql | numberHelpers.postgres | numberHelpers.sqlite | numberHelpers.oracle;
15
+ number: numberHelpers.cockroachdb | numberHelpers.postgres | numberHelpers.sqlite | numberHelpers.oracle | numberHelpers.mssql;
16
16
  capabilities: capabilitiesHelpers.postgres | capabilitiesHelpers.oracle | capabilitiesHelpers.mysql;
17
17
  };
18
- export declare function getFunctions(database: Knex, schema: SchemaOverview): fnHelpers.postgres | fnHelpers.mssql | fnHelpers.mysql | fnHelpers.sqlite | fnHelpers.oracle;
18
+ export declare function getFunctions(database: Knex, schema: SchemaOverview): fnHelpers.mysql | fnHelpers.postgres | fnHelpers.sqlite | fnHelpers.oracle | fnHelpers.mssql;
19
19
  export type Helpers = ReturnType<typeof getHelpers>;
@@ -1,8 +1,7 @@
1
1
  import type { KNEX_TYPES } from '@directus/constants';
2
2
  import type { Column } from '@directus/schema';
3
- import type { Field, RawField, Relation, Type } from '@directus/types';
3
+ import type { DatabaseClient, Field, RawField, Relation, Type } from '@directus/types';
4
4
  import type { Knex } from 'knex';
5
- import type { DatabaseClient } from '../../../types/index.js';
6
5
  import { DatabaseHelper } from '../types.js';
7
6
  export type Options = {
8
7
  nullable?: boolean;
@@ -1,6 +1,6 @@
1
1
  import type { SchemaInspector } from '@directus/schema';
2
+ import type { DatabaseClient } from '@directus/types';
2
3
  import type { Knex } from 'knex';
3
- import type { DatabaseClient } from '../types/index.js';
4
4
  export default getDatabase;
5
5
  export declare function getDatabase(): Knex;
6
6
  export declare function getSchemaInspector(database?: Knex): SchemaInspector;
@@ -10,7 +10,7 @@ export async function up(knex) {
10
10
  .from('directus_fields')
11
11
  .whereIn('interface', ['one-to-many', 'm2a-builder', 'many-to-many']);
12
12
  for (const field of fieldsWithSort) {
13
- const options = typeof field.options === 'string' ? parseJSON(field.options) : field.options ?? {};
13
+ const options = typeof field.options === 'string' ? parseJSON(field.options) : (field.options ?? {});
14
14
  if ('sortField' in options) {
15
15
  await knex('directus_relations')
16
16
  .update({
@@ -16,7 +16,7 @@ export async function up(knex) {
16
16
  .from('directus_revisions')
17
17
  .where('version', '=', missingDeltaVersion.id)
18
18
  .orderBy('id');
19
- const deltas = revisions.map((revision) => typeof revision.delta === 'string' ? parseJSON(revision.delta) : revision.delta ?? {});
19
+ const deltas = revisions.map((revision) => typeof revision.delta === 'string' ? parseJSON(revision.delta) : (revision.delta ?? {}));
20
20
  const consolidatedDelta = assign({}, ...deltas);
21
21
  await trx('directus_versions')
22
22
  .update({
@@ -0,0 +1,3 @@
1
+ import type { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
@@ -0,0 +1,10 @@
1
+ export async function up(knex) {
2
+ await knex.schema.alterTable('directus_users', (table) => {
3
+ table.string('text_direction').defaultTo('auto').notNullable();
4
+ });
5
+ }
6
+ export async function down(knex) {
7
+ await knex.schema.alterTable('directus_users', (table) => {
8
+ table.dropColumn('text_direction');
9
+ });
10
+ }
@@ -1,8 +1,8 @@
1
1
  import type { FieldOverview } from '@directus/types';
2
2
  export declare function getFilterType(fields: Record<string, FieldOverview>, key: string, collection?: string): {
3
- type: "string" | "boolean" | "json" | "binary" | "time" | "text" | "integer" | "float" | "alias" | "uuid" | "dateTime" | "timestamp" | "bigInteger" | "date" | "decimal" | "hash" | "csv" | "geometry" | "geometry.Point" | "geometry.LineString" | "geometry.Polygon" | "geometry.MultiPoint" | "geometry.MultiLineString" | "geometry.MultiPolygon" | "unknown";
3
+ type: "string" | "boolean" | "json" | "text" | "integer" | "float" | "alias" | "uuid" | "binary" | "time" | "dateTime" | "timestamp" | "bigInteger" | "date" | "decimal" | "hash" | "csv" | "geometry" | "geometry.Point" | "geometry.LineString" | "geometry.Polygon" | "geometry.MultiPoint" | "geometry.MultiLineString" | "geometry.MultiPolygon" | "unknown";
4
4
  special?: never;
5
5
  } | {
6
- type: "string" | "boolean" | "json" | "binary" | "time" | "text" | "integer" | "float" | "alias" | "uuid" | "dateTime" | "timestamp" | "bigInteger" | "date" | "decimal" | "hash" | "csv" | "geometry" | "geometry.Point" | "geometry.LineString" | "geometry.Polygon" | "geometry.MultiPoint" | "geometry.MultiLineString" | "geometry.MultiPolygon" | "unknown";
6
+ type: "string" | "boolean" | "json" | "text" | "integer" | "float" | "alias" | "uuid" | "binary" | "time" | "dateTime" | "timestamp" | "bigInteger" | "date" | "decimal" | "hash" | "csv" | "geometry" | "geometry.Point" | "geometry.LineString" | "geometry.Polygon" | "geometry.MultiPoint" | "geometry.MultiLineString" | "geometry.MultiPolygon" | "unknown";
7
7
  special: string[];
8
8
  };
@@ -20,6 +20,7 @@ export function applyParentFilters(schema, nestedCollectionNodes, parentItem) {
20
20
  name: nestedNode.relation.field,
21
21
  fieldKey: nestedNode.relation.field,
22
22
  whenCase: [],
23
+ alias: false,
23
24
  });
24
25
  }
25
26
  if (nestedNode.relation.meta?.sort_field) {
@@ -28,6 +29,7 @@ export function applyParentFilters(schema, nestedCollectionNodes, parentItem) {
28
29
  name: nestedNode.relation.meta.sort_field,
29
30
  fieldKey: nestedNode.relation.meta.sort_field,
30
31
  whenCase: [],
32
+ alias: false,
31
33
  });
32
34
  }
33
35
  const foreignField = nestedNode.relation.field;
@@ -1,4 +1,6 @@
1
1
  import { applyFunctionToColumnName } from './apply-function-to-column-name.js';
2
2
  export function getNodeAlias(node) {
3
+ if ('alias' in node && node.alias === true)
4
+ return node.fieldKey;
3
5
  return applyFunctionToColumnName(node.fieldKey);
4
6
  }
@@ -38,9 +38,15 @@ export function removeTemporaryFields(schema, rawItem, ast, primaryKeyField, par
38
38
  }
39
39
  else {
40
40
  const fields = [];
41
+ const aliasFields = [];
41
42
  const nestedCollectionNodes = [];
42
43
  for (const child of ast.children) {
43
- fields.push(child.fieldKey);
44
+ if ('alias' in child && child.alias === true) {
45
+ aliasFields.push(child.fieldKey);
46
+ }
47
+ else {
48
+ fields.push(child.fieldKey);
49
+ }
44
50
  if (child.type !== 'field' && child.type !== 'functionField') {
45
51
  nestedCollectionNodes.push(child);
46
52
  }
@@ -65,7 +71,7 @@ export function removeTemporaryFields(schema, rawItem, ast, primaryKeyField, par
65
71
  : schema.collections[nestedNode.relation.collection].primary, item);
66
72
  }
67
73
  const fieldsWithFunctionsApplied = fields.map((field) => applyFunctionToColumnName(field));
68
- item = fields.length > 0 ? pick(rawItem, fieldsWithFunctionsApplied) : rawItem[primaryKeyField];
74
+ item = fields.length > 0 ? pick(rawItem, fieldsWithFunctionsApplied, aliasFields) : rawItem[primaryKeyField];
69
75
  items.push(item);
70
76
  }
71
77
  }
@@ -1,4 +1,4 @@
1
- import type { Extension, ExtensionSettings } from '@directus/extensions';
1
+ import type { Extension, ExtensionSettings } from '@directus/types';
2
2
  /**
3
3
  * Loads stored settings for all extensions. Creates empty new rows in extensions tables for
4
4
  * extensions that don't have settings yet, and remove any settings for extensions that are no
@@ -1,4 +1,4 @@
1
- import type { ApiExtensionType, HybridExtensionType } from '@directus/extensions';
1
+ import type { ApiExtensionType, HybridExtensionType } from '@directus/types';
2
2
  import type { Router } from 'express';
3
3
  /**
4
4
  * Generate the JS to run in the isolate to create the extension's entrypoint to the host
@@ -1,6 +1,6 @@
1
- import type { Extension } from '@directus/extensions';
1
+ import type { Extension, ExtensionManagerOptions } from '@directus/types';
2
2
  import { Router } from 'express';
3
- import type { ExtensionManagerOptions } from './types.js';
3
+ import type { ReadStream } from 'node:fs';
4
4
  export declare class ExtensionManager {
5
5
  private options;
6
6
  /**
@@ -14,11 +14,6 @@ export declare class ExtensionManager {
14
14
  * Settings for the extensions that are loaded within the current process
15
15
  */
16
16
  private extensionsSettings;
17
- /**
18
- * App extensions rolled up into a single bundle. Any chunks from the bundle will be available
19
- * under appExtensionChunks
20
- */
21
- private appExtensionsBundle;
22
17
  /**
23
18
  * Individual filename chunks from the rollup bundle. Used to improve the performance by allowing
24
19
  * extensions to split up their bundle into multiple smaller chunks
@@ -97,13 +92,10 @@ export declare class ExtensionManager {
97
92
  forceSync: boolean;
98
93
  }): Promise<unknown>;
99
94
  /**
100
- * Return the previously generated app extensions bundle
101
- */
102
- getAppExtensionsBundle(): string | null;
103
- /**
104
- * Return the previously generated app extension bundle chunk by name
95
+ * Return the previously generated app extension bundle chunk by name.
96
+ * Providing no name will return the entry bundle.
105
97
  */
106
- getAppExtensionChunk(name: string): string | null;
98
+ getAppExtensionChunk(name?: string): Promise<ReadStream | null>;
107
99
  /**
108
100
  * Return the scoped router for custom endpoints
109
101
  */
@@ -1,5 +1,6 @@
1
1
  import { useEnv } from '@directus/env';
2
- import { APP_SHARED_DEPS, HYBRID_EXTENSION_TYPES } from '@directus/extensions';
2
+ import { APP_SHARED_DEPS } from '@directus/extensions';
3
+ import { HYBRID_EXTENSION_TYPES } from '@directus/constants';
3
4
  import { generateExtensionsEntrypoint } from '@directus/extensions/node';
4
5
  import { isTypeIn, toBoolean } from '@directus/utils';
5
6
  import { pathToRelativeUrl, processId } from '@directus/utils/node';
@@ -12,9 +13,10 @@ import ivm from 'isolated-vm';
12
13
  import { clone, debounce, isPlainObject } from 'lodash-es';
13
14
  import { readFile, readdir } from 'node:fs/promises';
14
15
  import os from 'node:os';
15
- import { dirname } from 'node:path';
16
+ import { dirname, join } from 'node:path';
16
17
  import { fileURLToPath } from 'node:url';
17
18
  import path from 'path';
19
+ import { rolldown } from 'rolldown';
18
20
  import { rollup } from 'rollup';
19
21
  import { useBus } from '../bus/index.js';
20
22
  import getDatabase from '../database/index.js';
@@ -37,6 +39,7 @@ import { generateApiExtensionsSandboxEntrypoint } from './lib/sandbox/generate-a
37
39
  import { instantiateSandboxSdk } from './lib/sandbox/sdk/instantiate.js';
38
40
  import { syncExtensions } from './lib/sync-extensions.js';
39
41
  import { wrapEmbeds } from './lib/wrap-embeds.js';
42
+ import DriverLocal from '@directus/storage-driver-local';
40
43
  // Workaround for https://github.com/rollup/plugins/issues/1329
41
44
  const virtual = virtualDefault;
42
45
  const alias = aliasDefault;
@@ -63,16 +66,11 @@ export class ExtensionManager {
63
66
  * Settings for the extensions that are loaded within the current process
64
67
  */
65
68
  extensionsSettings = [];
66
- /**
67
- * App extensions rolled up into a single bundle. Any chunks from the bundle will be available
68
- * under appExtensionChunks
69
- */
70
- appExtensionsBundle = null;
71
69
  /**
72
70
  * Individual filename chunks from the rollup bundle. Used to improve the performance by allowing
73
71
  * extensions to split up their bundle into multiple smaller chunks
74
72
  */
75
- appExtensionChunks = new Map();
73
+ appExtensionChunks = [];
76
74
  /**
77
75
  * Callbacks to be able to unregister extensions
78
76
  */
@@ -220,7 +218,7 @@ export class ExtensionManager {
220
218
  }
221
219
  await Promise.all([this.registerInternalOperations(), this.registerApiExtensions()]);
222
220
  if (env['SERVE_APP']) {
223
- this.appExtensionsBundle = await this.generateExtensionBundle();
221
+ await this.generateExtensionBundle();
224
222
  }
225
223
  this.isLoaded = true;
226
224
  emitter.emitAction('extensions.load', {
@@ -234,7 +232,6 @@ export class ExtensionManager {
234
232
  async unload() {
235
233
  await this.unregisterApiExtensions();
236
234
  this.localEmitter.offAll();
237
- this.appExtensionsBundle = null;
238
235
  this.isLoaded = false;
239
236
  emitter.emitAction('extensions.unload', {
240
237
  extensions: this.extensions,
@@ -289,16 +286,24 @@ export class ExtensionManager {
289
286
  return promise;
290
287
  }
291
288
  /**
292
- * Return the previously generated app extensions bundle
293
- */
294
- getAppExtensionsBundle() {
295
- return this.appExtensionsBundle;
296
- }
297
- /**
298
- * Return the previously generated app extension bundle chunk by name
289
+ * Return the previously generated app extension bundle chunk by name.
290
+ * Providing no name will return the entry bundle.
299
291
  */
300
- getAppExtensionChunk(name) {
301
- return this.appExtensionChunks.get(name) ?? null;
292
+ async getAppExtensionChunk(name) {
293
+ let file;
294
+ if (!name) {
295
+ file = this.appExtensionChunks[0];
296
+ }
297
+ else if (this.appExtensionChunks.includes(name)) {
298
+ file = name;
299
+ }
300
+ if (!file)
301
+ return null;
302
+ const tempDir = join(env['TEMP_PATH'], 'app-extensions');
303
+ const tmpStorage = new DriverLocal({ root: tempDir });
304
+ if ((await tmpStorage.exists(file)) === false)
305
+ return null;
306
+ return await tmpStorage.read(file);
302
307
  }
303
308
  /**
304
309
  * Return the scoped router for custom endpoints
@@ -369,6 +374,7 @@ export class ExtensionManager {
369
374
  */
370
375
  async generateExtensionBundle() {
371
376
  const logger = useLogger();
377
+ const env = useEnv();
372
378
  const sharedDepsMapping = await getSharedDepsMapping(APP_SHARED_DEPS);
373
379
  const internalImports = Object.entries(sharedDepsMapping).map(([name, path]) => ({
374
380
  find: name,
@@ -376,26 +382,30 @@ export class ExtensionManager {
376
382
  }));
377
383
  const entrypoint = generateExtensionsEntrypoint({ module: this.moduleExtensions, registry: this.registryExtensions, local: this.localExtensions }, this.extensionsSettings);
378
384
  try {
379
- const bundle = await rollup({
385
+ /** Opt In for now. Should be @deprecated later to always use rolldown! */
386
+ const rollDirection = (env['EXTENSIONS_ROLLDOWN'] ?? false) ? rolldown : rollup;
387
+ const bundle = await rollDirection({
380
388
  input: 'entry',
381
389
  external: Object.values(sharedDepsMapping),
382
390
  makeAbsoluteExternalsRelative: false,
383
391
  plugins: [virtual({ entry: entrypoint }), alias({ entries: internalImports }), nodeResolve({ browser: true })],
384
392
  });
385
- const { output } = await bundle.generate({ format: 'es', compact: true });
386
- for (const out of output) {
387
- if (out.type === 'chunk') {
388
- this.appExtensionChunks.set(out.fileName, out.code);
389
- }
390
- }
393
+ const tempDir = join(env['TEMP_PATH'], 'app-extensions');
394
+ const { output } = await bundle.write({
395
+ format: 'es',
396
+ dir: tempDir,
397
+ });
398
+ this.appExtensionChunks = output.reduce((acc, chunk) => {
399
+ if (chunk.type === 'chunk')
400
+ acc.push(chunk.fileName);
401
+ return acc;
402
+ }, []);
391
403
  await bundle.close();
392
- return output[0].code;
393
404
  }
394
405
  catch (error) {
395
406
  logger.warn(`Couldn't bundle App extensions`);
396
407
  logger.warn(error);
397
408
  }
398
- return null;
399
409
  }
400
410
  async registerSandboxedApiExtension(extension) {
401
411
  const logger = useLogger();
@@ -412,6 +422,7 @@ export class ExtensionManager {
412
422
  },
413
423
  });
414
424
  const context = await isolate.createContext();
425
+ context.global.setSync('process', { env: { NODE_ENV: process.env['NODE_ENV'] ?? 'production' } }, { copy: true });
415
426
  const module = await isolate.compileModule(extensionCode, { filename: `file://${entrypointPath}` });
416
427
  const sdkModule = await instantiateSandboxSdk(isolate, extension.sandbox?.requestedScopes ?? {});
417
428
  await module.instantiate(context, (specifier) => {
package/dist/flows.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import type { OperationHandler } from '@directus/extensions';
2
- import type { Accountability, SchemaOverview } from '@directus/types';
1
+ import type { Accountability, SchemaOverview, OperationHandler } from '@directus/types';
3
2
  export declare function getFlowManager(): FlowManager;
4
3
  declare class FlowManager {
5
4
  private isLoaded;
@@ -0,0 +1,7 @@
1
+ type Options = {
2
+ code: string;
3
+ status: string;
4
+ message: string;
5
+ };
6
+ declare const _default: import("@directus/extensions").OperationApiConfig<Options>;
7
+ export default _default;
@@ -0,0 +1,11 @@
1
+ import { createError, InternalServerError } from '@directus/errors';
2
+ import { defineOperationApi } from '@directus/extensions';
3
+ const FALLBACK_ERROR = new InternalServerError();
4
+ export default defineOperationApi({
5
+ id: 'throw-error',
6
+ handler: ({ code, status, message }) => {
7
+ const statusCode = parseInt(status);
8
+ const error = createError(code ?? FALLBACK_ERROR.code, message ?? FALLBACK_ERROR.message, isNaN(statusCode) ? FALLBACK_ERROR.status : statusCode);
9
+ throw new error();
10
+ },
11
+ });
@@ -13,7 +13,8 @@ export async function validateItemAccess(options, context) {
13
13
  name: options.collection,
14
14
  query: { limit: options.primaryKeys.length },
15
15
  // Act as if every field was a "normal" field
16
- children: options.fields?.map((field) => ({ type: 'field', name: field, fieldKey: field, whenCase: [] })) ?? [],
16
+ children: options.fields?.map((field) => ({ type: 'field', name: field, fieldKey: field, whenCase: [], alias: false })) ??
17
+ [],
17
18
  cases: [],
18
19
  };
19
20
  await processAst({ ast, ...options }, context);
@@ -1,4 +1,4 @@
1
- import type { AbstractServiceOptions } from '../../types/services.js';
1
+ import type { AbstractServiceOptions } from '@directus/types';
2
2
  export interface ShareInfo {
3
3
  collection: string;
4
4
  item: string;
@@ -1,5 +1,4 @@
1
- import type { Item, PrimaryKey } from '@directus/types';
2
- import type { AbstractServiceOptions, MutationOptions } from '../types/index.js';
1
+ import type { AbstractServiceOptions, Item, MutationOptions, PrimaryKey } from '@directus/types';
3
2
  import { ItemsService } from './items.js';
4
3
  export declare class AccessService extends ItemsService {
5
4
  constructor(options: AbstractServiceOptions);
@@ -1,5 +1,5 @@
1
+ import { UserIntegrityCheckFlag } from '@directus/types';
1
2
  import { clearSystemCache } from '../cache.js';
2
- import { UserIntegrityCheckFlag } from '../utils/validate-user-count-integrity.js';
3
3
  import { ItemsService } from './items.js';
4
4
  export class AccessService extends ItemsService {
5
5
  constructor(options) {
@@ -1,4 +1,4 @@
1
- import type { AbstractServiceOptions } from '../types/index.js';
1
+ import type { AbstractServiceOptions } from '@directus/types';
2
2
  import { ItemsService } from './items.js';
3
3
  export declare class ActivityService extends ItemsService {
4
4
  constructor(options: AbstractServiceOptions);
@@ -1,8 +1,6 @@
1
- import type { Range, Stat } from '@directus/storage';
2
- import type { Accountability, SchemaOverview } from '@directus/types';
1
+ import type { AbstractServiceOptions, Accountability, Range, Stat, SchemaOverview, TransformationSet } from '@directus/types';
3
2
  import type { Knex } from 'knex';
4
3
  import type { Readable } from 'node:stream';
5
- import type { AbstractServiceOptions, TransformationSet } from '../types/index.js';
6
4
  import { FilesService } from './files.js';
7
5
  export declare class AssetsService {
8
6
  knex: Knex;
@@ -1,6 +1,5 @@
1
- import type { Accountability, SchemaOverview } from '@directus/types';
1
+ import type { AbstractServiceOptions, Accountability, LoginResult, SchemaOverview } from '@directus/types';
2
2
  import type { Knex } from 'knex';
3
- import type { AbstractServiceOptions, LoginResult } from '../types/index.js';
4
3
  import { ActivityService } from './activity.js';
5
4
  export declare class AuthenticationService {
6
5
  knex: Knex;