@directus/api 23.1.1 → 23.1.3

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.
@@ -75,7 +75,7 @@ const readHandler = asyncHandler(async (req, res, next) => {
75
75
  };
76
76
  return next();
77
77
  });
78
- router.search('/:collection', collectionExists, validateBatch('read'), readHandler, respond);
78
+ router.search('/:collection', collectionExists, validateBatch('read'), readHandler, mergeContentVersions, respond);
79
79
  router.get('/:collection', collectionExists, readHandler, mergeContentVersions, respond);
80
80
  router.get('/:collection/:pk', collectionExists, asyncHandler(async (req, res, next) => {
81
81
  if (isSystemCollection(req.params['collection']))
@@ -6,6 +6,6 @@ export declare class SchemaHelperCockroachDb extends SchemaHelper {
6
6
  changeToType(table: string, column: string, type: (typeof KNEX_TYPES)[number], options?: Options): Promise<void>;
7
7
  constraintName(existingName: string): string;
8
8
  getDatabaseSize(): Promise<number | null>;
9
- preprocessBindings(queryParams: Sql): Sql;
9
+ prepQueryParams(queryParams: Sql): Sql;
10
10
  addInnerSortFieldsToGroupBy(groupByFields: (string | Knex.Raw)[], sortRecords: SortRecord[], hasRelationalSort: boolean): void;
11
11
  }
@@ -1,7 +1,7 @@
1
1
  import {} from 'knex';
2
2
  import { SchemaHelper } from '../types.js';
3
3
  import { useEnv } from '@directus/env';
4
- import { preprocessBindings } from '../utils/preprocess-bindings.js';
4
+ import { prepQueryParams } from '../utils/prep-query-params.js';
5
5
  const env = useEnv();
6
6
  export class SchemaHelperCockroachDb extends SchemaHelper {
7
7
  async changeToType(table, column, type, options = {}) {
@@ -29,8 +29,8 @@ export class SchemaHelperCockroachDb extends SchemaHelper {
29
29
  return null;
30
30
  }
31
31
  }
32
- preprocessBindings(queryParams) {
33
- return preprocessBindings(queryParams, { format: (index) => `$${index + 1}` });
32
+ prepQueryParams(queryParams) {
33
+ return prepQueryParams(queryParams, { format: (index) => `$${index + 1}` });
34
34
  }
35
35
  addInnerSortFieldsToGroupBy(groupByFields, sortRecords, hasRelationalSort) {
36
36
  if (hasRelationalSort) {
@@ -5,6 +5,6 @@ export declare class SchemaHelperMSSQL extends SchemaHelper {
5
5
  applyOffset(rootQuery: Knex.QueryBuilder, offset: number): void;
6
6
  formatUUID(uuid: string): string;
7
7
  getDatabaseSize(): Promise<number | null>;
8
- preprocessBindings(queryParams: Sql): Sql;
8
+ prepQueryParams(queryParams: Sql): Sql;
9
9
  addInnerSortFieldsToGroupBy(groupByFields: (string | Knex.Raw)[], sortRecords: SortRecord[], _hasRelationalSort: boolean): void;
10
10
  }
@@ -1,5 +1,5 @@
1
1
  import { SchemaHelper } from '../types.js';
2
- import { preprocessBindings } from '../utils/preprocess-bindings.js';
2
+ import { prepQueryParams } from '../utils/prep-query-params.js';
3
3
  export class SchemaHelperMSSQL extends SchemaHelper {
4
4
  applyLimit(rootQuery, limit) {
5
5
  // The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries,
@@ -27,8 +27,8 @@ export class SchemaHelperMSSQL extends SchemaHelper {
27
27
  return null;
28
28
  }
29
29
  }
30
- preprocessBindings(queryParams) {
31
- return preprocessBindings(queryParams, { format: (index) => `@p${index}` });
30
+ prepQueryParams(queryParams) {
31
+ return prepQueryParams(queryParams, { format: (index) => `@p${index}` });
32
32
  }
33
33
  addInnerSortFieldsToGroupBy(groupByFields, sortRecords, _hasRelationalSort) {
34
34
  /*
@@ -9,6 +9,7 @@ export declare class SchemaHelperOracle extends SchemaHelper {
9
9
  preRelationChange(relation: Partial<Relation>): void;
10
10
  processFieldType(field: Field): Type;
11
11
  getDatabaseSize(): Promise<number | null>;
12
- preprocessBindings(queryParams: Sql): Sql;
12
+ prepQueryParams(queryParams: Sql): Sql;
13
+ prepBindings(bindings: Knex.Value[]): any;
13
14
  addInnerSortFieldsToGroupBy(groupByFields: (string | Knex.Raw)[], sortRecords: SortRecord[], _hasRelationalSort: boolean): void;
14
15
  }
@@ -1,5 +1,5 @@
1
1
  import { SchemaHelper } from '../types.js';
2
- import { preprocessBindings } from '../utils/preprocess-bindings.js';
2
+ import { prepQueryParams } from '../utils/prep-query-params.js';
3
3
  export class SchemaHelperOracle extends SchemaHelper {
4
4
  async changeToType(table, column, type, options = {}) {
5
5
  await this.changeToTypeByCopy(table, column, type, options);
@@ -39,8 +39,13 @@ export class SchemaHelperOracle extends SchemaHelper {
39
39
  return null;
40
40
  }
41
41
  }
42
- preprocessBindings(queryParams) {
43
- return preprocessBindings(queryParams, { format: (index) => `:${index + 1}` });
42
+ prepQueryParams(queryParams) {
43
+ return prepQueryParams(queryParams, { format: (index) => `:${index + 1}` });
44
+ }
45
+ prepBindings(bindings) {
46
+ // Create an object with keys 1, 2, 3, ... and the bindings as values
47
+ // This will use the "named" binding syntax in the oracledb driver instead of the positional binding
48
+ return Object.fromEntries(bindings.map((binding, index) => [index + 1, binding]));
44
49
  }
45
50
  addInnerSortFieldsToGroupBy(groupByFields, sortRecords, _hasRelationalSort) {
46
51
  /*
@@ -2,6 +2,6 @@ import type { Knex } from 'knex';
2
2
  import { SchemaHelper, type SortRecord, type Sql } from '../types.js';
3
3
  export declare class SchemaHelperPostgres extends SchemaHelper {
4
4
  getDatabaseSize(): Promise<number | null>;
5
- preprocessBindings(queryParams: Sql): Sql;
5
+ prepQueryParams(queryParams: Sql): Sql;
6
6
  addInnerSortFieldsToGroupBy(groupByFields: (string | Knex.Raw)[], sortRecords: SortRecord[], hasRelationalSort: boolean): void;
7
7
  }
@@ -1,6 +1,6 @@
1
1
  import { useEnv } from '@directus/env';
2
2
  import { SchemaHelper } from '../types.js';
3
- import { preprocessBindings } from '../utils/preprocess-bindings.js';
3
+ import { prepQueryParams } from '../utils/prep-query-params.js';
4
4
  const env = useEnv();
5
5
  export class SchemaHelperPostgres extends SchemaHelper {
6
6
  async getDatabaseSize() {
@@ -12,8 +12,8 @@ export class SchemaHelperPostgres extends SchemaHelper {
12
12
  return null;
13
13
  }
14
14
  }
15
- preprocessBindings(queryParams) {
16
- return preprocessBindings(queryParams, { format: (index) => `$${index + 1}` });
15
+ prepQueryParams(queryParams) {
16
+ return prepQueryParams(queryParams, { format: (index) => `$${index + 1}` });
17
17
  }
18
18
  addInnerSortFieldsToGroupBy(groupByFields, sortRecords, hasRelationalSort) {
19
19
  if (hasRelationalSort) {
@@ -35,6 +35,7 @@ export declare abstract class SchemaHelper extends DatabaseHelper {
35
35
  * @returns Size of the database in bytes
36
36
  */
37
37
  getDatabaseSize(): Promise<number | null>;
38
- preprocessBindings(queryParams: Sql): Sql;
38
+ prepQueryParams(queryParams: Sql): Sql;
39
+ prepBindings(bindings: Knex.Value[]): any;
39
40
  addInnerSortFieldsToGroupBy(_groupByFields: (string | Knex.Raw)[], _sortRecords: SortRecord[], _hasRelationalSort: boolean): void;
40
41
  }
@@ -94,9 +94,12 @@ export class SchemaHelper extends DatabaseHelper {
94
94
  async getDatabaseSize() {
95
95
  return null;
96
96
  }
97
- preprocessBindings(queryParams) {
97
+ prepQueryParams(queryParams) {
98
98
  return queryParams;
99
99
  }
100
+ prepBindings(bindings) {
101
+ return bindings;
102
+ }
100
103
  addInnerSortFieldsToGroupBy(_groupByFields, _sortRecords, _hasRelationalSort) {
101
104
  // no-op by default
102
105
  }
@@ -1,12 +1,12 @@
1
1
  import type { Knex } from 'knex';
2
2
  import type { Sql } from '../types.js';
3
- export type PreprocessBindingsOptions = {
3
+ export type PrepQueryParamsOptions = {
4
4
  format(index: number): string;
5
5
  };
6
6
  /**
7
7
  * Preprocess a SQL query, such that repeated binding values are bound to the same binding index.
8
8
  **/
9
- export declare function preprocessBindings(queryParams: (Partial<Sql> & Pick<Sql, 'sql'>) | string, options: PreprocessBindingsOptions): {
9
+ export declare function prepQueryParams(queryParams: (Partial<Sql> & Pick<Sql, 'sql'>) | string, options: PrepQueryParamsOptions): {
10
10
  sql: string;
11
11
  bindings: Knex.Value[];
12
12
  };
@@ -2,7 +2,7 @@ import { isString } from 'lodash-es';
2
2
  /**
3
3
  * Preprocess a SQL query, such that repeated binding values are bound to the same binding index.
4
4
  **/
5
- export function preprocessBindings(queryParams, options) {
5
+ export function prepQueryParams(queryParams, options) {
6
6
  const query = { bindings: [], ...(isString(queryParams) ? { sql: queryParams } : queryParams) };
7
7
  // bindingIndices[i] is the index of the first occurrence of query.bindings[i]
8
8
  const bindingIndices = new Map();
@@ -3,7 +3,7 @@ import { createInspector } from '@directus/schema';
3
3
  import { isObject } from '@directus/utils';
4
4
  import fse from 'fs-extra';
5
5
  import knex from 'knex';
6
- import { merge } from 'lodash-es';
6
+ import { isArray, merge } from 'lodash-es';
7
7
  import { dirname } from 'node:path';
8
8
  import { fileURLToPath } from 'node:url';
9
9
  import path from 'path';
@@ -139,7 +139,13 @@ export function getDatabase() {
139
139
  delta = performance.now() - time;
140
140
  times.delete(queryInfo.__knexUid);
141
141
  }
142
- logger.trace(`[${delta ? delta.toFixed(3) : '?'}ms] ${queryInfo.sql} [${(queryInfo.bindings ?? []).join(', ')}]`);
142
+ // eslint-disable-next-line no-nested-ternary
143
+ const bindings = queryInfo.bindings
144
+ ? isArray(queryInfo.bindings)
145
+ ? queryInfo.bindings
146
+ : Object.values(queryInfo.bindings)
147
+ : [];
148
+ logger.trace(`[${delta ? delta.toFixed(3) : '?'}ms] ${queryInfo.sql} [${bindings.join(', ')}]`);
143
149
  })
144
150
  .on('query-error', (_, queryInfo) => {
145
151
  times.delete(queryInfo.__knexUid);
@@ -4,9 +4,10 @@ export function withPreprocessBindings(knex, dbQuery) {
4
4
  dbQuery.client = new Proxy(dbQuery.client, {
5
5
  get(target, prop, receiver) {
6
6
  if (prop === 'query') {
7
- return (connection, queryParam) => {
8
- return Reflect.get(target, prop, receiver).bind(target)(connection, schemaHelper.preprocessBindings(queryParam));
9
- };
7
+ return (connection, queryParams) => Reflect.get(target, prop, receiver).bind(dbQuery.client)(connection, schemaHelper.prepQueryParams(queryParams));
8
+ }
9
+ if (prop === 'prepBindings') {
10
+ return (bindings) => schemaHelper.prepBindings(Reflect.get(target, prop, receiver).bind(dbQuery.client)(bindings));
10
11
  }
11
12
  return Reflect.get(target, prop, receiver);
12
13
  },
@@ -1,3 +1,4 @@
1
+ import { toBoolean } from '@directus/utils';
1
2
  import { fetchPermittedAstRootFields } from '../../../../database/run-ast/modules/fetch-permitted-ast-root-fields.js';
2
3
  import { processAst } from '../../process-ast/process-ast.js';
3
4
  export async function validateItemAccess(options, context) {
@@ -31,7 +32,7 @@ export async function validateItemAccess(options, context) {
31
32
  if (items && items.length === options.primaryKeys.length) {
32
33
  const { fields } = options;
33
34
  if (fields) {
34
- return items.every((item) => fields.every((field) => item[field] === 1));
35
+ return items.every((item) => fields.every((field) => toBoolean(item[field])));
35
36
  }
36
37
  return true;
37
38
  }
@@ -6,6 +6,7 @@ export interface ValidateAccessOptions {
6
6
  collection: string;
7
7
  primaryKeys?: PrimaryKey[];
8
8
  fields?: string[];
9
+ skipCollectionExistsCheck?: boolean;
9
10
  }
10
11
  /**
11
12
  * Validate if the current user has access to perform action against the given collection and
@@ -8,7 +8,7 @@ import { validateItemAccess } from './lib/validate-item-access.js';
8
8
  */
9
9
  export async function validateAccess(options, context) {
10
10
  // Skip further validation if the collection does not exist
11
- if (options.collection in context.schema.collections === false) {
11
+ if (!options.skipCollectionExistsCheck && options.collection in context.schema.collections === false) {
12
12
  throw new ForbiddenError({
13
13
  reason: `You don't have permission to "${options.action}" from collection "${options.collection}" or it does not exist.`,
14
14
  });
@@ -155,11 +155,10 @@ export class CollectionsService {
155
155
  if (opts?.autoPurgeSystemCache !== false) {
156
156
  await clearSystemCache({ autoPurgeCache: opts?.autoPurgeCache });
157
157
  }
158
- // Refresh the schema for subsequent reads
159
- this.schema = await getSchema();
160
158
  if (opts?.emitEvents !== false && nestedActionEvents.length > 0) {
159
+ const updatedSchema = await getSchema();
161
160
  for (const nestedActionEvent of nestedActionEvents) {
162
- nestedActionEvent.context.schema = this.schema;
161
+ nestedActionEvent.context.schema = updatedSchema;
163
162
  emitter.emitAction(nestedActionEvent.event, nestedActionEvent.meta, nestedActionEvent.context);
164
163
  }
165
164
  }
@@ -197,11 +196,10 @@ export class CollectionsService {
197
196
  if (opts?.autoPurgeSystemCache !== false) {
198
197
  await clearSystemCache({ autoPurgeCache: opts?.autoPurgeCache });
199
198
  }
200
- // Refresh the schema for subsequent reads
201
- this.schema = await getSchema();
202
199
  if (opts?.emitEvents !== false && nestedActionEvents.length > 0) {
200
+ const updatedSchema = await getSchema();
203
201
  for (const nestedActionEvent of nestedActionEvents) {
204
- nestedActionEvent.context.schema = this.schema;
202
+ nestedActionEvent.context.schema = updatedSchema;
205
203
  emitter.emitAction(nestedActionEvent.event, nestedActionEvent.meta, nestedActionEvent.context);
206
204
  }
207
205
  }
@@ -290,6 +288,7 @@ export class CollectionsService {
290
288
  accountability: this.accountability,
291
289
  action: 'read',
292
290
  collection,
291
+ skipCollectionExistsCheck: true,
293
292
  }, {
294
293
  schema: this.schema,
295
294
  knex: this.knex,
@@ -6,6 +6,7 @@ export const registerLocations = async (storage) => {
6
6
  const env = useEnv();
7
7
  const locations = toArray(env['STORAGE_LOCATIONS']);
8
8
  const tus = {
9
+ enabled: RESUMABLE_UPLOADS.ENABLED,
9
10
  chunkSize: RESUMABLE_UPLOADS.CHUNK_SIZE,
10
11
  };
11
12
  locations.forEach((location) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@directus/api",
3
- "version": "23.1.1",
3
+ "version": "23.1.3",
4
4
  "description": "Directus is a real-time API and App dashboard for managing SQL database content",
5
5
  "keywords": [
6
6
  "directus",
@@ -149,29 +149,29 @@
149
149
  "ws": "8.18.0",
150
150
  "zod": "3.23.8",
151
151
  "zod-validation-error": "3.4.0",
152
- "@directus/app": "13.3.1",
153
- "@directus/constants": "12.0.0",
154
- "@directus/env": "3.1.3",
152
+ "@directus/app": "13.3.3",
153
+ "@directus/constants": "12.0.1",
154
+ "@directus/env": "4.0.0",
155
155
  "@directus/errors": "1.0.1",
156
- "@directus/extensions": "2.0.4",
157
- "@directus/extensions-registry": "2.0.4",
158
- "@directus/extensions-sdk": "12.1.2",
156
+ "@directus/extensions": "2.0.5",
157
+ "@directus/extensions-registry": "2.0.5",
158
+ "@directus/extensions-sdk": "12.1.3",
159
159
  "@directus/format-title": "11.0.0",
160
- "@directus/pressure": "2.0.3",
161
- "@directus/memory": "2.0.4",
160
+ "@directus/memory": "2.0.5",
161
+ "@directus/pressure": "2.0.4",
162
162
  "@directus/schema": "12.1.1",
163
163
  "@directus/specs": "11.1.0",
164
- "@directus/storage-driver-azure": "11.1.0",
165
- "@directus/storage-driver-cloudinary": "11.1.0",
166
164
  "@directus/storage": "11.0.1",
167
- "@directus/storage-driver-gcs": "11.1.0",
165
+ "@directus/storage-driver-azure": "11.1.1",
166
+ "@directus/storage-driver-cloudinary": "11.1.1",
167
+ "@directus/storage-driver-gcs": "11.1.1",
168
168
  "@directus/storage-driver-local": "11.0.1",
169
- "@directus/storage-driver-s3": "11.0.3",
170
- "@directus/storage-driver-supabase": "2.1.0",
171
- "@directus/utils": "12.0.3",
169
+ "@directus/storage-driver-s3": "11.0.4",
170
+ "@directus/storage-driver-supabase": "2.1.1",
172
171
  "@directus/system-data": "2.1.1",
173
- "@directus/validation": "1.0.3",
174
- "directus": "11.2.0"
172
+ "@directus/utils": "12.0.4",
173
+ "@directus/validation": "1.0.4",
174
+ "directus": "11.2.2"
175
175
  },
176
176
  "devDependencies": {
177
177
  "@ngneat/falso": "7.2.0",
@@ -215,7 +215,7 @@
215
215
  "vitest": "2.1.2",
216
216
  "@directus/random": "1.0.0",
217
217
  "@directus/tsconfig": "2.0.0",
218
- "@directus/types": "12.2.1"
218
+ "@directus/types": "12.2.2"
219
219
  },
220
220
  "optionalDependencies": {
221
221
  "@keyv/redis": "3.0.1",