@directus/api 13.1.1 → 14.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__utils__/snapshots.js +9 -0
- package/dist/app.js +6 -4
- package/dist/auth/drivers/ldap.js +3 -2
- package/dist/auth/drivers/local.js +1 -1
- package/dist/auth/drivers/oauth2.js +1 -1
- package/dist/auth/drivers/openid.js +1 -1
- package/dist/auth/drivers/saml.js +1 -1
- package/dist/auth.js +1 -1
- package/dist/cli/index.js +7 -4
- package/dist/controllers/activity.js +1 -1
- package/dist/controllers/assets.js +2 -2
- package/dist/controllers/auth.js +1 -1
- package/dist/controllers/collections.js +1 -1
- package/dist/controllers/dashboards.js +1 -1
- package/dist/controllers/extensions.js +29 -16
- package/dist/controllers/fields.js +1 -1
- package/dist/controllers/files.js +1 -1
- package/dist/controllers/flows.js +1 -1
- package/dist/controllers/folders.js +1 -1
- package/dist/controllers/items.js +1 -1
- package/dist/controllers/not-found.js +1 -1
- package/dist/controllers/notifications.js +1 -1
- package/dist/controllers/operations.js +1 -1
- package/dist/controllers/panels.js +1 -1
- package/dist/controllers/permissions.js +1 -1
- package/dist/controllers/presets.js +1 -1
- package/dist/controllers/relations.js +1 -1
- package/dist/controllers/roles.js +1 -1
- package/dist/controllers/schema.js +1 -1
- package/dist/controllers/server.js +1 -1
- package/dist/controllers/settings.js +1 -1
- package/dist/controllers/shares.js +1 -1
- package/dist/controllers/translations.js +1 -1
- package/dist/controllers/users.js +1 -1
- package/dist/controllers/utils.js +37 -18
- package/dist/controllers/versions.d.ts +2 -0
- package/dist/controllers/versions.js +188 -0
- package/dist/controllers/webhooks.js +1 -1
- package/dist/database/errors/dialects/mssql.js +1 -1
- package/dist/database/errors/dialects/mysql.js +1 -1
- package/dist/database/errors/dialects/oracle.js +1 -1
- package/dist/database/errors/dialects/postgres.js +1 -1
- package/dist/database/errors/dialects/sqlite.js +1 -1
- package/dist/database/helpers/schema/dialects/mysql.js +1 -1
- package/dist/database/helpers/sequence/dialects/postgres.d.ts +5 -2
- package/dist/database/helpers/sequence/dialects/postgres.js +6 -3
- package/dist/database/migrations/20230823A-add-content-versioning.d.ts +3 -0
- package/dist/database/migrations/20230823A-add-content-versioning.js +36 -0
- package/dist/database/migrations/20230927A-themes.d.ts +3 -0
- package/dist/database/migrations/20230927A-themes.js +49 -0
- package/dist/database/migrations/20231009A-update-csv-fields-to-text.d.ts +3 -0
- package/dist/database/migrations/20231009A-update-csv-fields-to-text.js +44 -0
- package/dist/database/migrations/20231009B-update-panel-options.d.ts +3 -0
- package/dist/database/migrations/20231009B-update-panel-options.js +77 -0
- package/dist/database/migrations/20231010A-add-extensions.d.ts +3 -0
- package/dist/database/migrations/20231010A-add-extensions.js +9 -0
- package/dist/database/run-ast.js +2 -2
- package/dist/database/seeds/run.js +1 -1
- package/dist/database/system-data/collections/collections.yaml +6 -0
- package/dist/database/system-data/fields/activity.yaml +4 -4
- package/dist/database/system-data/fields/collections.yaml +19 -0
- package/dist/database/system-data/fields/extensions.yaml +10 -0
- package/dist/database/system-data/fields/revisions.yaml +3 -0
- package/dist/database/system-data/fields/settings.yaml +73 -17
- package/dist/database/system-data/fields/users.yaml +48 -12
- package/dist/database/system-data/fields/versions.yaml +38 -0
- package/dist/database/system-data/fields/webhooks.yaml +9 -9
- package/dist/database/system-data/relations/relations.yaml +88 -20
- package/dist/env.js +4 -0
- package/dist/extensions/index.d.ts +2 -0
- package/dist/extensions/index.js +9 -0
- package/dist/extensions/lib/get-extensions-settings.d.ts +7 -0
- package/dist/extensions/lib/get-extensions-settings.js +39 -0
- package/dist/extensions/lib/get-extensions.d.ts +1 -0
- package/dist/extensions/lib/get-extensions.js +11 -0
- package/dist/extensions/lib/get-shared-deps-mapping.d.ts +1 -0
- package/dist/extensions/lib/get-shared-deps-mapping.js +26 -0
- package/dist/extensions/lib/sandbox/generate-api-extensions-sandbox-entrypoint.d.ts +31 -0
- package/dist/extensions/lib/sandbox/generate-api-extensions-sandbox-entrypoint.js +80 -0
- package/dist/extensions/lib/sandbox/generate-host-function-reference.d.ts +11 -0
- package/dist/extensions/lib/sandbox/generate-host-function-reference.js +28 -0
- package/dist/extensions/lib/sandbox/register/action.d.ts +6 -0
- package/dist/extensions/lib/sandbox/register/action.js +18 -0
- package/dist/extensions/lib/sandbox/register/call-reference.d.ts +5 -0
- package/dist/extensions/lib/sandbox/register/call-reference.js +20 -0
- package/dist/extensions/lib/sandbox/register/filter.d.ts +6 -0
- package/dist/extensions/lib/sandbox/register/filter.js +21 -0
- package/dist/extensions/lib/sandbox/register/index.d.ts +5 -0
- package/dist/extensions/lib/sandbox/register/index.js +5 -0
- package/dist/extensions/lib/sandbox/register/operation.d.ts +6 -0
- package/dist/extensions/lib/sandbox/register/operation.js +19 -0
- package/dist/extensions/lib/sandbox/register/route.d.ts +17 -0
- package/dist/extensions/lib/sandbox/register/route.js +44 -0
- package/dist/extensions/lib/sandbox/sdk/generators/index.d.ts +3 -0
- package/dist/extensions/lib/sandbox/sdk/generators/index.js +3 -0
- package/dist/extensions/lib/sandbox/sdk/generators/log.d.ts +3 -0
- package/dist/extensions/lib/sandbox/sdk/generators/log.js +11 -0
- package/dist/extensions/lib/sandbox/sdk/generators/request.d.ts +12 -0
- package/dist/extensions/lib/sandbox/sdk/generators/request.js +49 -0
- package/dist/extensions/lib/sandbox/sdk/generators/sleep.d.ts +3 -0
- package/dist/extensions/lib/sandbox/sdk/generators/sleep.js +11 -0
- package/dist/extensions/lib/sandbox/sdk/index.d.ts +2 -0
- package/dist/extensions/lib/sandbox/sdk/index.js +2 -0
- package/dist/extensions/lib/sandbox/sdk/instantiate.d.ts +11 -0
- package/dist/extensions/lib/sandbox/sdk/instantiate.js +28 -0
- package/dist/extensions/lib/sandbox/sdk/sdk.d.ts +20 -0
- package/dist/extensions/lib/sandbox/sdk/sdk.js +11 -0
- package/dist/extensions/lib/sandbox/sdk/utils/index.d.ts +1 -0
- package/dist/extensions/lib/sandbox/sdk/utils/index.js +1 -0
- package/dist/extensions/lib/sandbox/sdk/utils/wrap.d.ts +11 -0
- package/dist/extensions/lib/sandbox/sdk/utils/wrap.js +17 -0
- package/dist/extensions/lib/wrap-embeds.d.ts +4 -0
- package/dist/extensions/lib/wrap-embeds.js +8 -0
- package/dist/extensions/manager.d.ts +158 -0
- package/dist/extensions/manager.js +604 -0
- package/dist/extensions/types.d.ts +19 -0
- package/dist/flows.d.ts +2 -2
- package/dist/flows.js +7 -7
- package/dist/middleware/check-ip.js +1 -1
- package/dist/middleware/collection-exists.js +1 -1
- package/dist/middleware/error-handler.js +1 -1
- package/dist/middleware/graphql.js +1 -1
- package/dist/middleware/rate-limiter-global.js +1 -1
- package/dist/middleware/rate-limiter-ip.js +1 -1
- package/dist/middleware/respond.js +13 -1
- package/dist/middleware/validate-batch.js +1 -1
- package/dist/operations/condition/index.d.ts +1 -1
- package/dist/operations/condition/index.js +2 -1
- package/dist/operations/exec/index.d.ts +1 -1
- package/dist/operations/exec/index.js +1 -1
- package/dist/operations/item-create/index.d.ts +1 -1
- package/dist/operations/item-create/index.js +2 -1
- package/dist/operations/item-delete/index.d.ts +1 -1
- package/dist/operations/item-delete/index.js +2 -1
- package/dist/operations/item-read/index.d.ts +1 -1
- package/dist/operations/item-read/index.js +2 -1
- package/dist/operations/item-update/index.d.ts +1 -1
- package/dist/operations/item-update/index.js +2 -1
- package/dist/operations/json-web-token/index.d.ts +1 -1
- package/dist/operations/json-web-token/index.js +2 -1
- package/dist/operations/log/index.d.ts +1 -1
- package/dist/operations/log/index.js +2 -1
- package/dist/operations/mail/index.d.ts +1 -1
- package/dist/operations/mail/index.js +1 -1
- package/dist/operations/notification/index.d.ts +1 -1
- package/dist/operations/notification/index.js +2 -1
- package/dist/operations/request/index.d.ts +1 -1
- package/dist/operations/request/index.js +3 -2
- package/dist/operations/sleep/index.d.ts +1 -1
- package/dist/operations/sleep/index.js +1 -1
- package/dist/operations/transform/index.d.ts +1 -1
- package/dist/operations/transform/index.js +2 -1
- package/dist/operations/trigger/index.d.ts +1 -1
- package/dist/operations/trigger/index.js +2 -1
- package/dist/services/activity.js +1 -1
- package/dist/services/assets.d.ts +1 -1
- package/dist/services/assets.js +3 -3
- package/dist/services/authentication.js +2 -2
- package/dist/services/authorization.js +1 -1
- package/dist/services/collections.js +3 -3
- package/dist/services/extensions.d.ts +31 -0
- package/dist/services/extensions.js +121 -0
- package/dist/services/fields.d.ts +2 -2
- package/dist/services/fields.js +4 -4
- package/dist/services/files.d.ts +4 -1
- package/dist/services/files.js +5 -5
- package/dist/services/graphql/index.d.ts +1 -1
- package/dist/services/graphql/index.js +87 -24
- package/dist/services/graphql/subscription.js +3 -3
- package/dist/services/import-export/import-worker.d.ts +9 -0
- package/dist/services/import-export/import-worker.js +9 -0
- package/dist/services/{import-export.d.ts → import-export/index.d.ts} +2 -2
- package/dist/services/{import-export.js → import-export/index.js} +51 -42
- package/dist/services/index.d.ts +3 -1
- package/dist/services/index.js +3 -1
- package/dist/services/items.js +2 -2
- package/dist/services/mail/index.js +1 -1
- package/dist/services/meta.js +1 -1
- package/dist/services/payload.js +1 -1
- package/dist/services/permissions.d.ts +2 -2
- package/dist/services/permissions.js +1 -1
- package/dist/services/relations.js +1 -1
- package/dist/services/revisions.js +1 -1
- package/dist/services/roles.js +1 -1
- package/dist/services/schema.js +1 -1
- package/dist/services/server.js +3 -1
- package/dist/services/shares.js +1 -1
- package/dist/services/tfa.js +1 -1
- package/dist/services/translations.js +1 -1
- package/dist/services/users.js +4 -2
- package/dist/services/utils.d.ts +1 -0
- package/dist/services/utils.js +8 -2
- package/dist/services/versions.d.ts +21 -0
- package/dist/services/versions.js +232 -0
- package/dist/services/websocket.js +11 -1
- package/dist/types/collection.d.ts +1 -0
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.js +0 -1
- package/dist/utils/apply-query.d.ts +1 -1
- package/dist/utils/apply-query.js +31 -3
- package/dist/utils/delete-from-require-cache.d.ts +1 -0
- package/dist/utils/delete-from-require-cache.js +5 -0
- package/dist/utils/get-accountability-for-token.js +1 -1
- package/dist/utils/get-ast-from-query.js +1 -1
- package/dist/utils/get-column-path.js +1 -1
- package/dist/utils/get-column.js +1 -1
- package/dist/utils/get-default-value.d.ts +1 -2
- package/dist/utils/get-permissions.js +1 -1
- package/dist/utils/get-service.js +3 -1
- package/dist/utils/import-file-url.d.ts +5 -0
- package/dist/utils/import-file-url.js +6 -0
- package/dist/utils/job-queue.d.ts +2 -3
- package/dist/utils/jwt.js +1 -1
- package/dist/utils/redact-object.js +9 -3
- package/dist/utils/sanitize-query.js +3 -0
- package/dist/utils/transformations.d.ts +2 -1
- package/dist/utils/validate-diff.js +1 -1
- package/dist/utils/validate-keys.js +1 -1
- package/dist/utils/validate-query.js +2 -1
- package/dist/utils/validate-snapshot.js +1 -1
- package/dist/websocket/controllers/base.js +1 -1
- package/dist/websocket/controllers/index.d.ts +1 -1
- package/dist/websocket/controllers/index.js +0 -7
- package/dist/websocket/handlers/heartbeat.js +6 -1
- package/dist/websocket/handlers/subscribe.js +11 -16
- package/dist/websocket/utils/items.d.ts +4 -14
- package/dist/websocket/utils/items.js +59 -64
- package/dist/worker-pool.d.ts +2 -0
- package/dist/worker-pool.js +11 -0
- package/package.json +34 -31
- package/dist/errors/codes.d.ts +0 -29
- package/dist/errors/codes.js +0 -30
- package/dist/errors/contains-null-values.d.ts +0 -7
- package/dist/errors/contains-null-values.js +0 -4
- package/dist/errors/content-too-large.d.ts +0 -1
- package/dist/errors/content-too-large.js +0 -3
- package/dist/errors/forbidden.d.ts +0 -1
- package/dist/errors/forbidden.js +0 -3
- package/dist/errors/hit-rate-limit.d.ts +0 -6
- package/dist/errors/hit-rate-limit.js +0 -8
- package/dist/errors/illegal-asset-transformation.d.ts +0 -4
- package/dist/errors/illegal-asset-transformation.js +0 -3
- package/dist/errors/index.d.ts +0 -28
- package/dist/errors/index.js +0 -28
- package/dist/errors/invalid-credentials.d.ts +0 -1
- package/dist/errors/invalid-credentials.js +0 -3
- package/dist/errors/invalid-foreign-key.d.ts +0 -6
- package/dist/errors/invalid-foreign-key.js +0 -14
- package/dist/errors/invalid-ip.d.ts +0 -1
- package/dist/errors/invalid-ip.js +0 -3
- package/dist/errors/invalid-otp.d.ts +0 -1
- package/dist/errors/invalid-otp.js +0 -3
- package/dist/errors/invalid-payload.d.ts +0 -5
- package/dist/errors/invalid-payload.js +0 -4
- package/dist/errors/invalid-provider-config.d.ts +0 -5
- package/dist/errors/invalid-provider-config.js +0 -3
- package/dist/errors/invalid-provider.d.ts +0 -1
- package/dist/errors/invalid-provider.js +0 -3
- package/dist/errors/invalid-query.d.ts +0 -5
- package/dist/errors/invalid-query.js +0 -4
- package/dist/errors/invalid-token.d.ts +0 -1
- package/dist/errors/invalid-token.js +0 -3
- package/dist/errors/method-not-allowed.d.ts +0 -6
- package/dist/errors/method-not-allowed.js +0 -6
- package/dist/errors/not-null-violation.d.ts +0 -6
- package/dist/errors/not-null-violation.js +0 -14
- package/dist/errors/range-not-satisfiable.d.ts +0 -7
- package/dist/errors/range-not-satisfiable.js +0 -7
- package/dist/errors/record-not-unique.d.ts +0 -6
- package/dist/errors/record-not-unique.js +0 -14
- package/dist/errors/route-not-found.d.ts +0 -5
- package/dist/errors/route-not-found.js +0 -4
- package/dist/errors/service-unavailable.d.ts +0 -7
- package/dist/errors/service-unavailable.js +0 -4
- package/dist/errors/token-expired.d.ts +0 -1
- package/dist/errors/token-expired.js +0 -3
- package/dist/errors/unexpected-response.d.ts +0 -1
- package/dist/errors/unexpected-response.js +0 -3
- package/dist/errors/unprocessable-content.d.ts +0 -5
- package/dist/errors/unprocessable-content.js +0 -4
- package/dist/errors/unsupported-media-type.d.ts +0 -6
- package/dist/errors/unsupported-media-type.js +0 -4
- package/dist/errors/user-suspended.d.ts +0 -1
- package/dist/errors/user-suspended.js +0 -3
- package/dist/errors/value-out-of-range.d.ts +0 -6
- package/dist/errors/value-out-of-range.js +0 -14
- package/dist/errors/value-too-long.d.ts +0 -6
- package/dist/errors/value-too-long.js +0 -14
- package/dist/extensions.d.ts +0 -51
- package/dist/extensions.js +0 -487
- package/dist/types/files.d.ts +0 -29
- /package/dist/{types/files.js → extensions/types.js} +0 -0
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
import { JAVASCRIPT_FILE_EXTS } from '@directus/constants';
|
|
2
|
+
import { APP_SHARED_DEPS, HYBRID_EXTENSION_TYPES, NESTED_EXTENSION_TYPES } from '@directus/extensions';
|
|
3
|
+
import { ensureExtensionDirs, generateExtensionsEntrypoint } from '@directus/extensions/node';
|
|
4
|
+
import { isIn, isTypeIn, pluralize } from '@directus/utils';
|
|
5
|
+
import { pathToRelativeUrl } from '@directus/utils/node';
|
|
6
|
+
import aliasDefault from '@rollup/plugin-alias';
|
|
7
|
+
import nodeResolveDefault from '@rollup/plugin-node-resolve';
|
|
8
|
+
import virtualDefault from '@rollup/plugin-virtual';
|
|
9
|
+
import chokidar, { FSWatcher } from 'chokidar';
|
|
10
|
+
import express, { Router } from 'express';
|
|
11
|
+
import ivm from 'isolated-vm';
|
|
12
|
+
import { clone } from 'lodash-es';
|
|
13
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
14
|
+
import { dirname } from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import { rollup } from 'rollup';
|
|
18
|
+
import getDatabase from '../database/index.js';
|
|
19
|
+
import emitter, { Emitter } from '../emitter.js';
|
|
20
|
+
import env from '../env.js';
|
|
21
|
+
import { getFlowManager } from '../flows.js';
|
|
22
|
+
import logger from '../logger.js';
|
|
23
|
+
import * as services from '../services/index.js';
|
|
24
|
+
import { deleteFromRequireCache } from '../utils/delete-from-require-cache.js';
|
|
25
|
+
import getModuleDefault from '../utils/get-module-default.js';
|
|
26
|
+
import { getSchema } from '../utils/get-schema.js';
|
|
27
|
+
import { importFileUrl } from '../utils/import-file-url.js';
|
|
28
|
+
import { JobQueue } from '../utils/job-queue.js';
|
|
29
|
+
import { scheduleSynchronizedJob, validateCron } from '../utils/schedule.js';
|
|
30
|
+
import { getExtensionsSettings } from './lib/get-extensions-settings.js';
|
|
31
|
+
import { getExtensions } from './lib/get-extensions.js';
|
|
32
|
+
import { getSharedDepsMapping } from './lib/get-shared-deps-mapping.js';
|
|
33
|
+
import { generateApiExtensionsSandboxEntrypoint } from './lib/sandbox/generate-api-extensions-sandbox-entrypoint.js';
|
|
34
|
+
import { instantiateSandboxSdk } from './lib/sandbox/sdk/instantiate.js';
|
|
35
|
+
import { wrapEmbeds } from './lib/wrap-embeds.js';
|
|
36
|
+
// Workaround for https://github.com/rollup/plugins/issues/1329
|
|
37
|
+
const virtual = virtualDefault;
|
|
38
|
+
const alias = aliasDefault;
|
|
39
|
+
const nodeResolve = nodeResolveDefault;
|
|
40
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
41
|
+
export class ExtensionManager {
|
|
42
|
+
options = {
|
|
43
|
+
schedule: true,
|
|
44
|
+
watch: env['EXTENSIONS_AUTO_RELOAD'] && env['NODE_ENV'] !== 'development',
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Whether or not the extensions have been read from disk and registered into the system
|
|
48
|
+
*/
|
|
49
|
+
isLoaded = false;
|
|
50
|
+
/**
|
|
51
|
+
* All extensions that are loaded within the current process
|
|
52
|
+
*/
|
|
53
|
+
extensions = [];
|
|
54
|
+
/**
|
|
55
|
+
* Settings for the extensions that are loaded within the current process
|
|
56
|
+
*/
|
|
57
|
+
extensionsSettings = [];
|
|
58
|
+
/**
|
|
59
|
+
* App extensions rolled up into a single bundle. Any chunks from the bundle will be available
|
|
60
|
+
* under appExtensionChunks
|
|
61
|
+
*/
|
|
62
|
+
appExtensionsBundle = null;
|
|
63
|
+
/**
|
|
64
|
+
* Individual filename chunks from the rollup bundle. Used to improve the performance by allowing
|
|
65
|
+
* extensions to split up their bundle into multiple smaller chunks
|
|
66
|
+
*/
|
|
67
|
+
appExtensionChunks = new Map();
|
|
68
|
+
/**
|
|
69
|
+
* Callbacks to be able to unregister extensions
|
|
70
|
+
*/
|
|
71
|
+
unregisterFunctionMap = new Map();
|
|
72
|
+
/**
|
|
73
|
+
* A local-to-extensions scoped emitter that can be used to fire and listen to custom events
|
|
74
|
+
* between extensions. These events are completely isolated from the core events that trigger
|
|
75
|
+
* hooks etc
|
|
76
|
+
*/
|
|
77
|
+
localEmitter = new Emitter();
|
|
78
|
+
/**
|
|
79
|
+
* Locally scoped express router used for custom endpoints. Allows extensions to dynamically
|
|
80
|
+
* register and de-register endpoints without affecting the regular global router
|
|
81
|
+
*/
|
|
82
|
+
endpointRouter = Router();
|
|
83
|
+
/**
|
|
84
|
+
* Custom HTML to be injected at the end of the `<head>` tag of the app's index.html
|
|
85
|
+
*/
|
|
86
|
+
hookEmbedsHead = [];
|
|
87
|
+
/**
|
|
88
|
+
* Custom HTML to be injected at the end of the `<body>` tag of the app's index.html
|
|
89
|
+
*/
|
|
90
|
+
hookEmbedsBody = [];
|
|
91
|
+
/**
|
|
92
|
+
* Used to prevent race conditions when reloading extensions. Forces each reload to happen in
|
|
93
|
+
* sequence.
|
|
94
|
+
*/
|
|
95
|
+
reloadQueue = new JobQueue();
|
|
96
|
+
/**
|
|
97
|
+
* Optional file system watcher to auto-reload extensions when the local file system changes
|
|
98
|
+
*/
|
|
99
|
+
watcher = null;
|
|
100
|
+
/**
|
|
101
|
+
* Load and register all extensions
|
|
102
|
+
*
|
|
103
|
+
* @param {ExtensionManagerOptions} options - Extension manager configuration options
|
|
104
|
+
* @param {boolean} options.schedule - Whether or not to allow for scheduled (CRON) hook extensions
|
|
105
|
+
* @param {boolean} options.watch - Whether or not to watch the local extensions folder for changes
|
|
106
|
+
*/
|
|
107
|
+
async initialize(options = {}) {
|
|
108
|
+
if (options.schedule !== undefined) {
|
|
109
|
+
this.options.schedule = options.schedule;
|
|
110
|
+
}
|
|
111
|
+
if (options.watch !== undefined) {
|
|
112
|
+
this.options.watch = options.watch;
|
|
113
|
+
}
|
|
114
|
+
const wasWatcherInitialized = this.watcher !== null;
|
|
115
|
+
if (this.options.watch && !wasWatcherInitialized) {
|
|
116
|
+
this.initializeWatcher();
|
|
117
|
+
}
|
|
118
|
+
else if (!this.options.watch && wasWatcherInitialized) {
|
|
119
|
+
await this.closeWatcher();
|
|
120
|
+
}
|
|
121
|
+
if (!this.isLoaded) {
|
|
122
|
+
await this.load();
|
|
123
|
+
if (this.extensions.length > 0) {
|
|
124
|
+
logger.info(`Loaded extensions: ${this.extensions.map((ext) => ext.name).join(', ')}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (this.options.watch && !wasWatcherInitialized) {
|
|
128
|
+
this.updateWatchedExtensions(this.extensions);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Load all extensions from disk and register them in their respective places
|
|
133
|
+
*/
|
|
134
|
+
async load() {
|
|
135
|
+
try {
|
|
136
|
+
await ensureExtensionDirs(env['EXTENSIONS_PATH'], NESTED_EXTENSION_TYPES);
|
|
137
|
+
this.extensions = await getExtensions();
|
|
138
|
+
this.extensionsSettings = await getExtensionsSettings(this.extensions);
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
logger.warn(`Couldn't load extensions`);
|
|
142
|
+
logger.warn(err);
|
|
143
|
+
}
|
|
144
|
+
await this.registerHooks();
|
|
145
|
+
await this.registerEndpoints();
|
|
146
|
+
await this.registerOperations();
|
|
147
|
+
await this.registerBundles();
|
|
148
|
+
if (env['SERVE_APP']) {
|
|
149
|
+
this.appExtensionsBundle = await this.generateExtensionBundle();
|
|
150
|
+
}
|
|
151
|
+
this.isLoaded = true;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Unregister all extensions from the current process
|
|
155
|
+
*/
|
|
156
|
+
async unload() {
|
|
157
|
+
await this.unregisterApiExtensions();
|
|
158
|
+
this.localEmitter.offAll();
|
|
159
|
+
this.appExtensionsBundle = null;
|
|
160
|
+
this.isLoaded = false;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Reload all the extensions. Will unload if extensions have already been loaded
|
|
164
|
+
*/
|
|
165
|
+
reload() {
|
|
166
|
+
this.reloadQueue.enqueue(async () => {
|
|
167
|
+
if (this.isLoaded) {
|
|
168
|
+
logger.info('Reloading extensions');
|
|
169
|
+
const prevExtensions = clone(this.extensions);
|
|
170
|
+
await this.unload();
|
|
171
|
+
await this.load();
|
|
172
|
+
const added = this.extensions.filter((extension) => !prevExtensions.some((prevExtension) => extension.path === prevExtension.path));
|
|
173
|
+
const removed = prevExtensions.filter((prevExtension) => !this.extensions.some((extension) => prevExtension.path === extension.path));
|
|
174
|
+
this.updateWatchedExtensions(added, removed);
|
|
175
|
+
const addedExtensions = added.map((extension) => extension.name);
|
|
176
|
+
const removedExtensions = removed.map((extension) => extension.name);
|
|
177
|
+
if (addedExtensions.length > 0) {
|
|
178
|
+
logger.info(`Added extensions: ${addedExtensions.join(', ')}`);
|
|
179
|
+
}
|
|
180
|
+
if (removedExtensions.length > 0) {
|
|
181
|
+
logger.info(`Removed extensions: ${removedExtensions.join(', ')}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
logger.warn('Extensions have to be loaded before they can be reloaded');
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Return the previously generated app extensions bundle
|
|
191
|
+
*/
|
|
192
|
+
getAppExtensionsBundle() {
|
|
193
|
+
return this.appExtensionsBundle;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Return the previously generated app extension bundle chunk by name
|
|
197
|
+
*/
|
|
198
|
+
getAppExtensionChunk(name) {
|
|
199
|
+
return this.appExtensionChunks.get(name) ?? null;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Return the scoped router for custom endpoints
|
|
203
|
+
*/
|
|
204
|
+
getEndpointRouter() {
|
|
205
|
+
return this.endpointRouter;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Return the custom HTML head and body embeds wrapped in a marker comment
|
|
209
|
+
*/
|
|
210
|
+
getEmbeds() {
|
|
211
|
+
return {
|
|
212
|
+
head: wrapEmbeds('Custom Embed Head', this.hookEmbedsHead),
|
|
213
|
+
body: wrapEmbeds('Custom Embed Body', this.hookEmbedsBody),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Allow reading the installed extensions
|
|
218
|
+
*/
|
|
219
|
+
getExtensions() {
|
|
220
|
+
return this.extensions;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Start the chokidar watcher for extensions on the local filesystem
|
|
224
|
+
*/
|
|
225
|
+
initializeWatcher() {
|
|
226
|
+
logger.info('Watching extensions for changes...');
|
|
227
|
+
const extensionDirUrl = pathToRelativeUrl(env['EXTENSIONS_PATH']);
|
|
228
|
+
const localExtensionUrls = NESTED_EXTENSION_TYPES.flatMap((type) => {
|
|
229
|
+
const typeDir = path.posix.join(extensionDirUrl, pluralize(type));
|
|
230
|
+
if (isIn(type, HYBRID_EXTENSION_TYPES)) {
|
|
231
|
+
return [
|
|
232
|
+
path.posix.join(typeDir, '*', `app.{${JAVASCRIPT_FILE_EXTS.join()}}`),
|
|
233
|
+
path.posix.join(typeDir, '*', `api.{${JAVASCRIPT_FILE_EXTS.join()}}`),
|
|
234
|
+
];
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
return path.posix.join(typeDir, '*', `index.{${JAVASCRIPT_FILE_EXTS.join()}}`);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
this.watcher = chokidar.watch([path.resolve('package.json'), path.posix.join(extensionDirUrl, '*', 'package.json'), ...localExtensionUrls], {
|
|
241
|
+
ignoreInitial: true,
|
|
242
|
+
});
|
|
243
|
+
this.watcher
|
|
244
|
+
.on('add', () => this.reload())
|
|
245
|
+
.on('change', () => this.reload())
|
|
246
|
+
.on('unlink', () => this.reload());
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Close and destroy the local filesystem watcher if enabled
|
|
250
|
+
*/
|
|
251
|
+
async closeWatcher() {
|
|
252
|
+
if (this.watcher) {
|
|
253
|
+
await this.watcher.close();
|
|
254
|
+
this.watcher = null;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Update the chokidar watcher configuration when new extensions are added or existing ones
|
|
259
|
+
* removed
|
|
260
|
+
*/
|
|
261
|
+
updateWatchedExtensions(added, removed = []) {
|
|
262
|
+
if (this.watcher) {
|
|
263
|
+
const toPackageExtensionPaths = (extensions) => extensions
|
|
264
|
+
.filter((extension) => !extension.local || extension.type === 'bundle')
|
|
265
|
+
.flatMap((extension) => isTypeIn(extension, HYBRID_EXTENSION_TYPES) || extension.type === 'bundle'
|
|
266
|
+
? [
|
|
267
|
+
path.resolve(extension.path, extension.entrypoint.app),
|
|
268
|
+
path.resolve(extension.path, extension.entrypoint.api),
|
|
269
|
+
]
|
|
270
|
+
: path.resolve(extension.path, extension.entrypoint));
|
|
271
|
+
const addedPackageExtensionPaths = toPackageExtensionPaths(added);
|
|
272
|
+
const removedPackageExtensionPaths = toPackageExtensionPaths(removed);
|
|
273
|
+
this.watcher.add(addedPackageExtensionPaths);
|
|
274
|
+
this.watcher.unwatch(removedPackageExtensionPaths);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Uses rollup to bundle the app extensions together into a single file the app can download and
|
|
279
|
+
* run.
|
|
280
|
+
*/
|
|
281
|
+
async generateExtensionBundle() {
|
|
282
|
+
const sharedDepsMapping = await getSharedDepsMapping(APP_SHARED_DEPS);
|
|
283
|
+
const internalImports = Object.entries(sharedDepsMapping).map(([name, path]) => ({
|
|
284
|
+
find: name,
|
|
285
|
+
replacement: path,
|
|
286
|
+
}));
|
|
287
|
+
const entrypoint = generateExtensionsEntrypoint(this.extensions);
|
|
288
|
+
try {
|
|
289
|
+
const bundle = await rollup({
|
|
290
|
+
input: 'entry',
|
|
291
|
+
external: Object.values(sharedDepsMapping),
|
|
292
|
+
makeAbsoluteExternalsRelative: false,
|
|
293
|
+
plugins: [virtual({ entry: entrypoint }), alias({ entries: internalImports }), nodeResolve({ browser: true })],
|
|
294
|
+
});
|
|
295
|
+
const { output } = await bundle.generate({ format: 'es', compact: true });
|
|
296
|
+
for (const out of output) {
|
|
297
|
+
if (out.type === 'chunk') {
|
|
298
|
+
this.appExtensionChunks.set(out.fileName, out.code);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
await bundle.close();
|
|
302
|
+
return output[0].code;
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
logger.warn(`Couldn't bundle App extensions`);
|
|
306
|
+
logger.warn(error);
|
|
307
|
+
}
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
async registerSandboxedApiExtension(extension) {
|
|
311
|
+
const sandboxMemory = Number(env['EXTENSIONS_SANDBOX_MEMORY']);
|
|
312
|
+
const sandboxTimeout = Number(env['EXTENSIONS_SANDBOX_TIMEOUT']);
|
|
313
|
+
const entrypointPath = path.resolve(extension.path, isTypeIn(extension, HYBRID_EXTENSION_TYPES) ? extension.entrypoint.api : extension.entrypoint);
|
|
314
|
+
const extensionCode = await readFile(entrypointPath, 'utf-8');
|
|
315
|
+
const isolate = new ivm.Isolate({
|
|
316
|
+
memoryLimit: sandboxMemory,
|
|
317
|
+
onCatastrophicError: (e) => {
|
|
318
|
+
logger.error(`Error in API extension sandbox of ${extension.type} "${extension.name}"`);
|
|
319
|
+
logger.error(e);
|
|
320
|
+
process.abort();
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
const context = await isolate.createContext();
|
|
324
|
+
const module = await isolate.compileModule(extensionCode, { filename: `file://${entrypointPath}` });
|
|
325
|
+
const sdkModule = await instantiateSandboxSdk(isolate, extension.sandbox?.requestedScopes ?? {});
|
|
326
|
+
await module.instantiate(context, (specifier) => {
|
|
327
|
+
if (specifier !== 'directus:api') {
|
|
328
|
+
throw new Error('Imports other than "directus:api" are prohibited in API extension sandboxes');
|
|
329
|
+
}
|
|
330
|
+
return sdkModule;
|
|
331
|
+
});
|
|
332
|
+
await module.evaluate({ timeout: sandboxTimeout });
|
|
333
|
+
const cb = await module.namespace.get('default', { reference: true });
|
|
334
|
+
const { code, hostFunctions, unregisterFunction } = generateApiExtensionsSandboxEntrypoint(extension.type, extension.name, this.endpointRouter);
|
|
335
|
+
await context.evalClosure(code, [cb, ...hostFunctions.map((fn) => new ivm.Reference(fn))], {
|
|
336
|
+
timeout: sandboxTimeout,
|
|
337
|
+
filename: '<extensions-sandbox>',
|
|
338
|
+
});
|
|
339
|
+
this.unregisterFunctionMap.set(extension.name, async () => {
|
|
340
|
+
await unregisterFunction();
|
|
341
|
+
isolate.dispose();
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Import the hook module code for all hook extensions, and register them individually through
|
|
346
|
+
* registerHook
|
|
347
|
+
*/
|
|
348
|
+
async registerHooks() {
|
|
349
|
+
const hooks = this.extensions.filter((extension) => extension.type === 'hook');
|
|
350
|
+
for (const hook of hooks) {
|
|
351
|
+
const { enabled } = this.extensionsSettings.find(({ name }) => name === hook.name) ?? { enabled: false };
|
|
352
|
+
if (!enabled)
|
|
353
|
+
continue;
|
|
354
|
+
try {
|
|
355
|
+
if (hook.sandbox?.enabled) {
|
|
356
|
+
await this.registerSandboxedApiExtension(hook);
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
const hookPath = path.resolve(hook.path, hook.entrypoint);
|
|
360
|
+
const hookInstance = await importFileUrl(hookPath, import.meta.url, {
|
|
361
|
+
fresh: true,
|
|
362
|
+
});
|
|
363
|
+
const config = getModuleDefault(hookInstance);
|
|
364
|
+
const unregisterFunctions = this.registerHook(config, hook.name);
|
|
365
|
+
this.unregisterFunctionMap.set(hook.name, async () => {
|
|
366
|
+
await Promise.all(unregisterFunctions.map((fn) => fn()));
|
|
367
|
+
deleteFromRequireCache(hookPath);
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
catch (error) {
|
|
372
|
+
logger.warn(`Couldn't register hook "${hook.name}"`);
|
|
373
|
+
logger.warn(error);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Import the endpoint module code for all endpoint extensions, and register them individually through
|
|
379
|
+
* registerEndpoint
|
|
380
|
+
*/
|
|
381
|
+
async registerEndpoints() {
|
|
382
|
+
const endpoints = this.extensions.filter((extension) => extension.type === 'endpoint');
|
|
383
|
+
for (const endpoint of endpoints) {
|
|
384
|
+
const { enabled } = this.extensionsSettings.find(({ name }) => name === endpoint.name) ?? { enabled: false };
|
|
385
|
+
if (!enabled)
|
|
386
|
+
continue;
|
|
387
|
+
try {
|
|
388
|
+
if (endpoint.sandbox?.enabled) {
|
|
389
|
+
await this.registerSandboxedApiExtension(endpoint);
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
const endpointPath = path.resolve(endpoint.path, endpoint.entrypoint);
|
|
393
|
+
const endpointInstance = await importFileUrl(endpointPath, import.meta.url, {
|
|
394
|
+
fresh: true,
|
|
395
|
+
});
|
|
396
|
+
const config = getModuleDefault(endpointInstance);
|
|
397
|
+
const unregister = this.registerEndpoint(config, endpoint.name);
|
|
398
|
+
this.unregisterFunctionMap.set(endpoint.name, async () => {
|
|
399
|
+
await unregister();
|
|
400
|
+
deleteFromRequireCache(endpointPath);
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
logger.warn(`Couldn't register endpoint "${endpoint.name}"`);
|
|
406
|
+
logger.warn(error);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Import the operation module code for all operation extensions, and register them individually through
|
|
412
|
+
* registerOperation
|
|
413
|
+
*/
|
|
414
|
+
async registerOperations() {
|
|
415
|
+
const internalOperations = await readdir(path.join(__dirname, '..', 'operations'));
|
|
416
|
+
for (const operation of internalOperations) {
|
|
417
|
+
const operationInstance = await import(`../operations/${operation}/index.js`);
|
|
418
|
+
const config = getModuleDefault(operationInstance);
|
|
419
|
+
this.registerOperation(config);
|
|
420
|
+
}
|
|
421
|
+
const operations = this.extensions.filter((extension) => extension.type === 'operation');
|
|
422
|
+
for (const operation of operations) {
|
|
423
|
+
const { enabled } = this.extensionsSettings.find(({ name }) => name === operation.name) ?? { enabled: false };
|
|
424
|
+
if (!enabled)
|
|
425
|
+
continue;
|
|
426
|
+
try {
|
|
427
|
+
if (operation.sandbox?.enabled) {
|
|
428
|
+
await this.registerSandboxedApiExtension(operation);
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
const operationPath = path.resolve(operation.path, operation.entrypoint.api);
|
|
432
|
+
const operationInstance = await importFileUrl(operationPath, import.meta.url, {
|
|
433
|
+
fresh: true,
|
|
434
|
+
});
|
|
435
|
+
const config = getModuleDefault(operationInstance);
|
|
436
|
+
const unregister = this.registerOperation(config);
|
|
437
|
+
this.unregisterFunctionMap.set(operation.name, async () => {
|
|
438
|
+
await unregister();
|
|
439
|
+
deleteFromRequireCache(operationPath);
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
catch (error) {
|
|
444
|
+
logger.warn(`Couldn't register operation "${operation.name}"`);
|
|
445
|
+
logger.warn(error);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Import the module code for all hook, endpoint, and operation extensions registered within a
|
|
451
|
+
* bundle, and register them with their respective registration function
|
|
452
|
+
*/
|
|
453
|
+
async registerBundles() {
|
|
454
|
+
const bundles = this.extensions.filter((extension) => extension.type === 'bundle');
|
|
455
|
+
for (const bundle of bundles) {
|
|
456
|
+
try {
|
|
457
|
+
const bundlePath = path.resolve(bundle.path, bundle.entrypoint.api);
|
|
458
|
+
const bundleInstances = await importFileUrl(bundlePath, import.meta.url, {
|
|
459
|
+
fresh: true,
|
|
460
|
+
});
|
|
461
|
+
const configs = getModuleDefault(bundleInstances);
|
|
462
|
+
const unregisterFunctions = [];
|
|
463
|
+
for (const { config, name } of configs.hooks) {
|
|
464
|
+
const unregisters = this.registerHook(config, name);
|
|
465
|
+
unregisterFunctions.push(...unregisters);
|
|
466
|
+
}
|
|
467
|
+
for (const { config, name } of configs.endpoints) {
|
|
468
|
+
const unregister = this.registerEndpoint(config, name);
|
|
469
|
+
unregisterFunctions.push(unregister);
|
|
470
|
+
}
|
|
471
|
+
for (const { config } of configs.operations) {
|
|
472
|
+
const unregister = this.registerOperation(config);
|
|
473
|
+
unregisterFunctions.push(unregister);
|
|
474
|
+
}
|
|
475
|
+
this.unregisterFunctionMap.set(bundle.name, async () => {
|
|
476
|
+
await Promise.all(unregisterFunctions.map((fn) => fn()));
|
|
477
|
+
deleteFromRequireCache(bundlePath);
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
catch (error) {
|
|
481
|
+
logger.warn(`Couldn't register bundle "${bundle.name}"`);
|
|
482
|
+
logger.warn(error);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Register a single hook
|
|
488
|
+
*/
|
|
489
|
+
registerHook(hookRegistrationCallback, name) {
|
|
490
|
+
let scheduleIndex = 0;
|
|
491
|
+
const unregisterFunctions = [];
|
|
492
|
+
const hookRegistrationContext = {
|
|
493
|
+
filter: (event, handler) => {
|
|
494
|
+
emitter.onFilter(event, handler);
|
|
495
|
+
unregisterFunctions.push(() => {
|
|
496
|
+
emitter.offFilter(event, handler);
|
|
497
|
+
});
|
|
498
|
+
},
|
|
499
|
+
action: (event, handler) => {
|
|
500
|
+
emitter.onAction(event, handler);
|
|
501
|
+
unregisterFunctions.push(() => {
|
|
502
|
+
emitter.offAction(event, handler);
|
|
503
|
+
});
|
|
504
|
+
},
|
|
505
|
+
init: (event, handler) => {
|
|
506
|
+
emitter.onInit(event, handler);
|
|
507
|
+
unregisterFunctions.push(() => {
|
|
508
|
+
emitter.offInit(name, handler);
|
|
509
|
+
});
|
|
510
|
+
},
|
|
511
|
+
schedule: (cron, handler) => {
|
|
512
|
+
if (validateCron(cron)) {
|
|
513
|
+
const job = scheduleSynchronizedJob(`${name}:${scheduleIndex}`, cron, async () => {
|
|
514
|
+
if (this.options.schedule) {
|
|
515
|
+
try {
|
|
516
|
+
await handler();
|
|
517
|
+
}
|
|
518
|
+
catch (error) {
|
|
519
|
+
logger.error(error);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
scheduleIndex++;
|
|
524
|
+
unregisterFunctions.push(async () => {
|
|
525
|
+
await job.stop();
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
logger.warn(`Couldn't register cron hook. Provided cron is invalid: ${cron}`);
|
|
530
|
+
}
|
|
531
|
+
},
|
|
532
|
+
embed: (position, code) => {
|
|
533
|
+
const content = typeof code === 'function' ? code() : code;
|
|
534
|
+
if (content.trim().length !== 0) {
|
|
535
|
+
if (position === 'head') {
|
|
536
|
+
const index = this.hookEmbedsHead.length;
|
|
537
|
+
this.hookEmbedsHead.push(content);
|
|
538
|
+
unregisterFunctions.push(() => {
|
|
539
|
+
this.hookEmbedsHead.splice(index, 1);
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
const index = this.hookEmbedsBody.length;
|
|
544
|
+
this.hookEmbedsBody.push(content);
|
|
545
|
+
unregisterFunctions.push(() => {
|
|
546
|
+
this.hookEmbedsBody.splice(index, 1);
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
logger.warn(`Couldn't register embed hook. Provided code is empty!`);
|
|
552
|
+
}
|
|
553
|
+
},
|
|
554
|
+
};
|
|
555
|
+
hookRegistrationCallback(hookRegistrationContext, {
|
|
556
|
+
services,
|
|
557
|
+
env,
|
|
558
|
+
database: getDatabase(),
|
|
559
|
+
emitter: this.localEmitter,
|
|
560
|
+
logger,
|
|
561
|
+
getSchema,
|
|
562
|
+
});
|
|
563
|
+
return unregisterFunctions;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Register an individual endpoint
|
|
567
|
+
*/
|
|
568
|
+
registerEndpoint(config, name) {
|
|
569
|
+
const endpointRegistrationCallback = typeof config === 'function' ? config : config.handler;
|
|
570
|
+
const routeName = typeof config === 'function' ? name : config.id;
|
|
571
|
+
const scopedRouter = express.Router();
|
|
572
|
+
this.endpointRouter.use(`/${routeName}`, scopedRouter);
|
|
573
|
+
endpointRegistrationCallback(scopedRouter, {
|
|
574
|
+
services,
|
|
575
|
+
env,
|
|
576
|
+
database: getDatabase(),
|
|
577
|
+
emitter: this.localEmitter,
|
|
578
|
+
logger,
|
|
579
|
+
getSchema,
|
|
580
|
+
});
|
|
581
|
+
const unregisterFunction = () => {
|
|
582
|
+
this.endpointRouter.stack = this.endpointRouter.stack.filter((layer) => scopedRouter !== layer.handle);
|
|
583
|
+
};
|
|
584
|
+
return unregisterFunction;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Register an individual operation
|
|
588
|
+
*/
|
|
589
|
+
registerOperation(config) {
|
|
590
|
+
const flowManager = getFlowManager();
|
|
591
|
+
flowManager.addOperation(config.id, config.handler);
|
|
592
|
+
const unregisterFunction = () => {
|
|
593
|
+
flowManager.removeOperation(config.id);
|
|
594
|
+
};
|
|
595
|
+
return unregisterFunction;
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Remove the registration for all API extensions
|
|
599
|
+
*/
|
|
600
|
+
async unregisterApiExtensions() {
|
|
601
|
+
const unregisterFunctions = Array.from(this.unregisterFunctionMap.values());
|
|
602
|
+
await Promise.all(unregisterFunctions.map((fn) => fn()));
|
|
603
|
+
}
|
|
604
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { EndpointConfig, HookConfig, OperationApiConfig } from '@directus/extensions';
|
|
2
|
+
export type BundleConfig = {
|
|
3
|
+
endpoints: {
|
|
4
|
+
name: string;
|
|
5
|
+
config: EndpointConfig;
|
|
6
|
+
}[];
|
|
7
|
+
hooks: {
|
|
8
|
+
name: string;
|
|
9
|
+
config: HookConfig;
|
|
10
|
+
}[];
|
|
11
|
+
operations: {
|
|
12
|
+
name: string;
|
|
13
|
+
config: OperationApiConfig;
|
|
14
|
+
}[];
|
|
15
|
+
};
|
|
16
|
+
export interface ExtensionManagerOptions {
|
|
17
|
+
schedule: boolean;
|
|
18
|
+
watch: boolean;
|
|
19
|
+
}
|
package/dist/flows.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { OperationHandler } from '@directus/
|
|
1
|
+
import type { OperationHandler } from '@directus/extensions';
|
|
2
2
|
export declare function getFlowManager(): FlowManager;
|
|
3
3
|
declare class FlowManager {
|
|
4
4
|
private isLoaded;
|
|
@@ -12,7 +12,7 @@ declare class FlowManager {
|
|
|
12
12
|
initialize(): Promise<void>;
|
|
13
13
|
reload(): Promise<void>;
|
|
14
14
|
addOperation(id: string, operation: OperationHandler): void;
|
|
15
|
-
|
|
15
|
+
removeOperation(id: string): void;
|
|
16
16
|
runOperationFlow(id: string, data: unknown, context: Record<string, unknown>): Promise<unknown>;
|
|
17
17
|
runWebhookFlow(id: string, data: unknown, context: Record<string, unknown>): Promise<{
|
|
18
18
|
result: unknown;
|
package/dist/flows.js
CHANGED
|
@@ -5,7 +5,7 @@ import { get } from 'micromustache';
|
|
|
5
5
|
import getDatabase from './database/index.js';
|
|
6
6
|
import emitter from './emitter.js';
|
|
7
7
|
import env from './env.js';
|
|
8
|
-
import { ForbiddenError } from '
|
|
8
|
+
import { ForbiddenError } from '@directus/errors';
|
|
9
9
|
import logger from './logger.js';
|
|
10
10
|
import { getMessenger } from './messenger.js';
|
|
11
11
|
import { ActivityService } from './services/activity.js';
|
|
@@ -33,7 +33,7 @@ const LAST_KEY = '$last';
|
|
|
33
33
|
const ENV_KEY = '$env';
|
|
34
34
|
class FlowManager {
|
|
35
35
|
isLoaded = false;
|
|
36
|
-
operations =
|
|
36
|
+
operations = new Map();
|
|
37
37
|
triggerHandlers = [];
|
|
38
38
|
operationFlowHandlers = {};
|
|
39
39
|
webhookFlowHandlers = {};
|
|
@@ -67,10 +67,10 @@ class FlowManager {
|
|
|
67
67
|
messenger.publish('flows', { type: 'reload' });
|
|
68
68
|
}
|
|
69
69
|
addOperation(id, operation) {
|
|
70
|
-
this.operations
|
|
70
|
+
this.operations.set(id, operation);
|
|
71
71
|
}
|
|
72
|
-
|
|
73
|
-
this.operations
|
|
72
|
+
removeOperation(id) {
|
|
73
|
+
this.operations.delete(id);
|
|
74
74
|
}
|
|
75
75
|
async runOperationFlow(id, data, context) {
|
|
76
76
|
if (!(id in this.operationFlowHandlers)) {
|
|
@@ -305,11 +305,11 @@ class FlowManager {
|
|
|
305
305
|
return undefined;
|
|
306
306
|
}
|
|
307
307
|
async executeOperation(operation, keyedData, context = {}) {
|
|
308
|
-
if (!(operation.type
|
|
308
|
+
if (!this.operations.has(operation.type)) {
|
|
309
309
|
logger.warn(`Couldn't find operation ${operation.type}`);
|
|
310
310
|
return { successor: null, status: 'unknown', data: null, options: null };
|
|
311
311
|
}
|
|
312
|
-
const handler = this.operations
|
|
312
|
+
const handler = this.operations.get(operation.type);
|
|
313
313
|
const options = applyOptionsData(operation.options, keyedData);
|
|
314
314
|
try {
|
|
315
315
|
let result = await handler(options, {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import getDatabase from '../database/index.js';
|
|
2
|
-
import { InvalidIpError } from '
|
|
2
|
+
import { InvalidIpError } from '@directus/errors';
|
|
3
3
|
import asyncHandler from '../utils/async-handler.js';
|
|
4
4
|
export const checkIP = asyncHandler(async (req, _res, next) => {
|
|
5
5
|
const database = getDatabase();
|