@directus/api 9.25.1 → 9.25.2

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.
package/dist/app.js CHANGED
@@ -6,7 +6,6 @@ import { createRequire } from 'node:module';
6
6
  import path from 'path';
7
7
  import qs from 'qs';
8
8
  import { registerAuthProviders } from './auth.js';
9
- import { flushCaches } from './cache.js';
10
9
  import activityRouter from './controllers/activity.js';
11
10
  import assetsRouter from './controllers/assets.js';
12
11
  import authRouter from './controllers/auth.js';
@@ -76,7 +75,6 @@ export default async function createApp() {
76
75
  if ((await validateMigrations()) === false) {
77
76
  logger.warn(`Database migrations have not all been run`);
78
77
  }
79
- await flushCaches();
80
78
  await registerAuthProviders();
81
79
  const extensionManager = getExtensionManager();
82
80
  const flowManager = getFlowManager();
@@ -105,18 +105,23 @@ asyncHandler(async (req, res) => {
105
105
  accountability: req.accountability,
106
106
  schema: req.schema,
107
107
  });
108
+ const vary = ['Origin', 'Cache-Control'];
108
109
  const transformation = res.locals['transformation'].key
109
110
  ? res.locals['shortcuts'].find((transformation) => transformation['key'] === res.locals['transformation'].key)
110
111
  : res.locals['transformation'];
111
- if (transformation.format === 'auto' && req.headers.accept) {
112
- let format = 'jpg';
113
- if (req.headers.accept.includes('image/webp')) {
112
+ if (transformation.format === 'auto') {
113
+ let format;
114
+ if (req.headers.accept?.includes('image/avif')) {
115
+ format = 'avif';
116
+ }
117
+ else if (req.headers.accept?.includes('image/webp')) {
114
118
  format = 'webp';
115
119
  }
116
- else if (req.headers.accept.includes('image/avif')) {
117
- format = 'avif';
120
+ else {
121
+ format = 'jpg';
118
122
  }
119
123
  transformation.format = format;
124
+ vary.push('Accept');
120
125
  }
121
126
  let range = undefined;
122
127
  if (req.headers.range) {
@@ -141,6 +146,7 @@ asyncHandler(async (req, res) => {
141
146
  res.setHeader('Content-Type', file.type);
142
147
  res.setHeader('Accept-Ranges', 'bytes');
143
148
  res.setHeader('Cache-Control', getCacheControlHeader(req, getMilliseconds(env['ASSETS_CACHE_TTL']), false, true));
149
+ res.setHeader('Vary', vary.join(', '));
144
150
  const unixTime = Date.parse(file.modified_on);
145
151
  if (!Number.isNaN(unixTime)) {
146
152
  const lastModifiedDate = new Date(unixTime);
@@ -4,6 +4,7 @@ import { orderBy } from 'lodash-es';
4
4
  import { dirname } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import path from 'path';
7
+ import { flushCaches } from '../../cache.js';
7
8
  import env from '../../env.js';
8
9
  import logger from '../../logger.js';
9
10
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -59,6 +60,7 @@ export default async function run(database, direction, log = true) {
59
60
  }
60
61
  await up(database);
61
62
  await database.insert({ version: nextVersion.version, name: nextVersion.name }).into('directus_migrations');
63
+ await flushCaches(true);
62
64
  }
63
65
  async function down() {
64
66
  const lastAppliedMigration = orderBy(completedMigrations, ['timestamp', 'version'], ['desc', 'desc'])[0];
@@ -75,10 +77,13 @@ export default async function run(database, direction, log = true) {
75
77
  }
76
78
  await down(database);
77
79
  await database('directus_migrations').delete().where({ version: migration.version });
80
+ await flushCaches(true);
78
81
  }
79
82
  async function latest() {
83
+ let needsCacheFlush = false;
80
84
  for (const migration of migrations) {
81
85
  if (migration.completed === false) {
86
+ needsCacheFlush = true;
82
87
  const { up } = await import(`file://${migration.file}`);
83
88
  if (log) {
84
89
  logger.info(`Applying ${migration.name}...`);
@@ -87,5 +92,8 @@ export default async function run(database, direction, log = true) {
87
92
  await database.insert({ version: migration.version, name: migration.name }).into('directus_migrations');
88
93
  }
89
94
  }
95
+ if (needsCacheFlush) {
96
+ await flushCaches(true);
97
+ }
90
98
  }
91
99
  }
@@ -7,7 +7,6 @@ import nodeResolveDefault from '@rollup/plugin-node-resolve';
7
7
  import virtualDefault from '@rollup/plugin-virtual';
8
8
  import chokidar from 'chokidar';
9
9
  import express, { Router } from 'express';
10
- import globby from 'globby';
11
10
  import { clone, escapeRegExp } from 'lodash-es';
12
11
  import { schedule, validate } from 'node-cron';
13
12
  import { readdir } from 'node:fs/promises';
@@ -67,15 +66,15 @@ class ExtensionManager {
67
66
  this.appExtensionChunks = new Map();
68
67
  }
69
68
  async initialize(options = {}) {
70
- const prevOptions = this.options;
71
69
  this.options = {
72
70
  ...defaultOptions,
73
71
  ...options,
74
72
  };
75
- if (!prevOptions.watch && this.options.watch) {
73
+ const wasWatcherInitialized = this.watcher !== null;
74
+ if (this.options.watch && !wasWatcherInitialized) {
76
75
  this.initializeWatcher();
77
76
  }
78
- else if (prevOptions.watch && !this.options.watch) {
77
+ else if (!this.options.watch && wasWatcherInitialized) {
79
78
  await this.closeWatcher();
80
79
  }
81
80
  if (!this.isLoaded) {
@@ -85,7 +84,7 @@ class ExtensionManager {
85
84
  logger.info(`Loaded extensions: ${loadedExtensions.map((ext) => ext.name).join(', ')}`);
86
85
  }
87
86
  }
88
- if (!prevOptions.watch && this.options.watch) {
87
+ if (this.options.watch && !wasWatcherInitialized) {
89
88
  this.updateWatchedExtensions(this.extensions);
90
89
  }
91
90
  }
@@ -198,29 +197,32 @@ class ExtensionManager {
198
197
  this.isLoaded = false;
199
198
  }
200
199
  initializeWatcher() {
201
- if (!this.watcher) {
202
- logger.info('Watching extensions for changes...');
203
- const localExtensionPaths = NESTED_EXTENSION_TYPES.flatMap((type) => {
204
- const typeDir = path.posix.join(pathToRelativeUrl(env['EXTENSIONS_PATH']), pluralize(type));
205
- if (isIn(type, HYBRID_EXTENSION_TYPES)) {
206
- return [path.posix.join(typeDir, '*', 'app.js'), path.posix.join(typeDir, '*', 'api.js')];
207
- }
208
- else {
209
- return path.posix.join(typeDir, '*', 'index.js');
210
- }
211
- });
212
- this.watcher = chokidar.watch([path.resolve('package.json'), ...localExtensionPaths], {
213
- ignoreInitial: true,
214
- });
215
- this.watcher
216
- .on('add', () => this.reload())
217
- .on('change', () => this.reload())
218
- .on('unlink', () => this.reload());
219
- }
200
+ logger.info('Watching extensions for changes...');
201
+ const localExtensionPaths = NESTED_EXTENSION_TYPES.flatMap((type) => {
202
+ const typeDir = path.posix.join(pathToRelativeUrl(env['EXTENSIONS_PATH']), pluralize(type));
203
+ const fileExts = ['js', 'mjs', 'cjs'];
204
+ if (isIn(type, HYBRID_EXTENSION_TYPES)) {
205
+ return [
206
+ path.posix.join(typeDir, '*', `app.{${fileExts.join()}}`),
207
+ path.posix.join(typeDir, '*', `api.{${fileExts.join()}}`),
208
+ ];
209
+ }
210
+ else {
211
+ return path.posix.join(typeDir, '*', `index.{${fileExts.join()}}`);
212
+ }
213
+ });
214
+ this.watcher = chokidar.watch([path.resolve('package.json'), ...localExtensionPaths], {
215
+ ignoreInitial: true,
216
+ });
217
+ this.watcher
218
+ .on('add', () => this.reload())
219
+ .on('change', () => this.reload())
220
+ .on('unlink', () => this.reload());
220
221
  }
221
222
  async closeWatcher() {
222
223
  if (this.watcher) {
223
224
  await this.watcher.close();
225
+ this.watcher = null;
224
226
  }
225
227
  }
226
228
  updateWatchedExtensions(added, removed = []) {
@@ -295,7 +297,7 @@ class ExtensionManager {
295
297
  for (const hook of hooks) {
296
298
  try {
297
299
  const hookPath = path.resolve(hook.path, hook.entrypoint);
298
- const hookInstance = await import(`file://${hookPath}`);
300
+ const hookInstance = await import(`./${pathToRelativeUrl(hookPath, __dirname)}?t=${Date.now()}`);
299
301
  const config = getModuleDefault(hookInstance);
300
302
  this.registerHook(config);
301
303
  this.apiExtensions.push({ path: hookPath });
@@ -311,7 +313,7 @@ class ExtensionManager {
311
313
  for (const endpoint of endpoints) {
312
314
  try {
313
315
  const endpointPath = path.resolve(endpoint.path, endpoint.entrypoint);
314
- const endpointInstance = await import(`file://${endpointPath}`);
316
+ const endpointInstance = await import(`./${pathToRelativeUrl(endpointPath, __dirname)}?t=${Date.now()}`);
315
317
  const config = getModuleDefault(endpointInstance);
316
318
  this.registerEndpoint(config, endpoint.name);
317
319
  this.apiExtensions.push({ path: endpointPath });
@@ -323,20 +325,17 @@ class ExtensionManager {
323
325
  }
324
326
  }
325
327
  async registerOperations() {
326
- const internalPaths = await globby(path.posix.join(pathToRelativeUrl(__dirname), 'operations/*/index.(js|ts)'));
327
- const internalOperations = internalPaths.map((internalPath) => {
328
- const dirs = internalPath.split(path.sep);
329
- return {
330
- name: dirs[dirs.length - 2],
331
- path: dirs.slice(0, -1).join(path.sep),
332
- entrypoint: { api: dirs[dirs.length - 1] },
333
- };
334
- });
328
+ const internalOperations = await readdir(path.join(__dirname, 'operations'));
329
+ for (const operation of internalOperations) {
330
+ const operationInstance = await import(`./operations/${operation}/index.js`);
331
+ const config = getModuleDefault(operationInstance);
332
+ this.registerOperation(config);
333
+ }
335
334
  const operations = this.extensions.filter((extension) => extension.type === 'operation');
336
- for (const operation of [...internalOperations, ...operations]) {
335
+ for (const operation of operations) {
337
336
  try {
338
337
  const operationPath = path.resolve(operation.path, operation.entrypoint.api);
339
- const operationInstance = await import(`file://${operationPath}`);
338
+ const operationInstance = await import(`./${pathToRelativeUrl(operationPath, __dirname)}?t=${Date.now()}`);
340
339
  const config = getModuleDefault(operationInstance);
341
340
  this.registerOperation(config);
342
341
  this.apiExtensions.push({ path: operationPath });
@@ -352,7 +351,7 @@ class ExtensionManager {
352
351
  for (const bundle of bundles) {
353
352
  try {
354
353
  const bundlePath = path.resolve(bundle.path, bundle.entrypoint.api);
355
- const bundleInstances = await import(`file://${bundlePath}`);
354
+ const bundleInstances = await import(`./${pathToRelativeUrl(bundlePath, __dirname)}?t=${Date.now()}`);
356
355
  const configs = getModuleDefault(bundleInstances);
357
356
  for (const { config } of configs.hooks) {
358
357
  this.registerHook(config);
@@ -1,10 +1,12 @@
1
1
  import hash from 'object-hash';
2
2
  import url from 'url';
3
3
  import { getGraphqlQueryAndVariables } from './get-graphql-query-and-variables.js';
4
+ import { version } from './package.js';
4
5
  export function getCacheKey(req) {
5
6
  const path = url.parse(req.originalUrl).pathname;
6
7
  const isGraphQl = path?.startsWith('/graphql');
7
8
  const info = {
9
+ version,
8
10
  user: req.accountability?.user || null,
9
11
  path,
10
12
  query: isGraphQl ? getGraphqlQueryAndVariables(req) : req.sanitizedQuery,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@directus/api",
3
- "version": "9.25.1",
3
+ "version": "9.25.2",
4
4
  "description": "Directus is a real-time API and App dashboard for managing SQL database content",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -95,7 +95,6 @@
95
95
  "fast-redact": "3.1.2",
96
96
  "flat": "5.0.2",
97
97
  "fs-extra": "11.1.0",
98
- "globby": "11.1.0",
99
98
  "graphql": "16.6.0",
100
99
  "graphql-compose": "9.0.10",
101
100
  "helmet": "6.0.1",
@@ -144,20 +143,20 @@
144
143
  "uuid-validate": "0.0.3",
145
144
  "vm2": "3.9.16",
146
145
  "wellknown": "0.5.0",
147
- "@directus/app": "9.25.1",
148
- "@directus/constants": "9.25.1",
149
- "@directus/exceptions": "9.25.1",
150
- "@directus/extensions-sdk": "9.25.1",
151
- "@directus/schema": "9.25.1",
152
- "@directus/specs": "9.25.1",
153
- "@directus/storage": "9.25.1",
154
- "@directus/storage-driver-azure": "9.25.1",
155
- "@directus/storage-driver-cloudinary": "9.25.1",
156
- "@directus/storage-driver-gcs": "9.25.1",
157
- "@directus/storage-driver-local": "9.25.1",
158
- "@directus/storage-driver-s3": "9.25.1",
159
- "@directus/update-check": "9.25.1",
160
- "@directus/utils": "9.25.1"
146
+ "@directus/app": "9.25.2",
147
+ "@directus/constants": "9.25.2",
148
+ "@directus/exceptions": "9.25.2",
149
+ "@directus/extensions-sdk": "9.25.2",
150
+ "@directus/schema": "9.25.2",
151
+ "@directus/specs": "9.25.2",
152
+ "@directus/storage": "9.25.2",
153
+ "@directus/storage-driver-azure": "9.25.2",
154
+ "@directus/storage-driver-cloudinary": "9.25.2",
155
+ "@directus/storage-driver-gcs": "9.25.2",
156
+ "@directus/storage-driver-local": "9.25.2",
157
+ "@directus/storage-driver-s3": "9.25.2",
158
+ "@directus/update-check": "9.25.2",
159
+ "@directus/utils": "9.25.2"
161
160
  },
162
161
  "devDependencies": {
163
162
  "@directus/tsconfig": "0.0.6",
@@ -206,7 +205,7 @@
206
205
  "supertest": "6.3.3",
207
206
  "typescript": "4.9.5",
208
207
  "vitest": "0.29.3",
209
- "@directus/types": "9.25.1"
208
+ "@directus/types": "9.25.2"
210
209
  },
211
210
  "optionalDependencies": {
212
211
  "@keyv/redis": "2.5.7",