@directus/api 32.2.0 → 33.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 (297) hide show
  1. package/dist/ai/chat/controllers/chat.post.js +19 -4
  2. package/dist/ai/chat/lib/create-ui-stream.d.ts +8 -7
  3. package/dist/ai/chat/lib/create-ui-stream.js +28 -25
  4. package/dist/ai/chat/middleware/load-settings.js +31 -7
  5. package/dist/ai/chat/models/chat-request.d.ts +135 -2
  6. package/dist/ai/chat/models/chat-request.js +56 -2
  7. package/dist/ai/chat/models/providers.d.ts +16 -2
  8. package/dist/ai/chat/models/providers.js +16 -2
  9. package/dist/ai/chat/utils/chat-request-tool-to-ai-sdk-tool.js +3 -4
  10. package/dist/ai/chat/utils/format-context.d.ts +5 -0
  11. package/dist/ai/chat/utils/format-context.js +122 -0
  12. package/dist/ai/mcp/server.d.ts +27 -1
  13. package/dist/ai/providers/index.d.ts +3 -0
  14. package/dist/ai/providers/index.js +3 -0
  15. package/dist/ai/providers/options.d.ts +14 -0
  16. package/dist/ai/providers/options.js +26 -0
  17. package/dist/ai/providers/registry.d.ts +6 -0
  18. package/dist/ai/providers/registry.js +65 -0
  19. package/dist/ai/providers/types.d.ts +34 -0
  20. package/dist/ai/providers/types.js +1 -0
  21. package/dist/ai/tools/assets/index.js +1 -1
  22. package/dist/ai/tools/collections/index.js +2 -2
  23. package/dist/ai/tools/fields/index.js +2 -2
  24. package/dist/ai/tools/files/index.js +1 -1
  25. package/dist/ai/tools/flows/index.js +1 -1
  26. package/dist/ai/tools/folders/index.js +1 -1
  27. package/dist/ai/tools/items/index.js +6 -3
  28. package/dist/ai/tools/items/prompt.md +7 -9
  29. package/dist/ai/tools/relations/index.js +1 -1
  30. package/dist/ai/tools/schema.js +1 -1
  31. package/dist/ai/tools/trigger-flow/index.js +1 -1
  32. package/dist/app.js +12 -8
  33. package/dist/auth/drivers/ldap.d.ts +1 -1
  34. package/dist/auth/drivers/ldap.js +144 -139
  35. package/dist/auth/drivers/local.js +1 -1
  36. package/dist/auth/drivers/oauth2.d.ts +1 -2
  37. package/dist/auth/drivers/oauth2.js +22 -17
  38. package/dist/auth/drivers/openid.d.ts +1 -2
  39. package/dist/auth/drivers/openid.js +18 -13
  40. package/dist/auth/drivers/saml.js +3 -3
  41. package/dist/auth/utils/generate-callback-url.d.ts +11 -0
  42. package/dist/auth/utils/generate-callback-url.js +40 -0
  43. package/dist/auth/utils/is-login-redirect-allowed.d.ts +7 -0
  44. package/dist/{utils → auth/utils}/is-login-redirect-allowed.js +12 -9
  45. package/dist/cache.d.ts +12 -0
  46. package/dist/cache.js +27 -3
  47. package/dist/cli/commands/bootstrap/index.js +2 -2
  48. package/dist/cli/commands/database/install.js +1 -1
  49. package/dist/cli/commands/database/migrate.js +1 -1
  50. package/dist/cli/commands/init/index.js +2 -2
  51. package/dist/cli/commands/roles/create.js +4 -4
  52. package/dist/cli/commands/schema/apply.js +3 -3
  53. package/dist/cli/commands/schema/snapshot.js +1 -1
  54. package/dist/cli/utils/create-db-connection.d.ts +1 -1
  55. package/dist/cli/utils/create-db-connection.js +1 -1
  56. package/dist/cli/utils/create-env/env-stub.liquid +3 -0
  57. package/dist/cli/utils/create-env/index.js +1 -1
  58. package/dist/constants.d.ts +7 -3
  59. package/dist/constants.js +7 -3
  60. package/dist/controllers/access.js +1 -1
  61. package/dist/controllers/assets.js +1 -1
  62. package/dist/controllers/deployment.js +481 -0
  63. package/dist/controllers/extensions.js +1 -1
  64. package/dist/controllers/fields.js +8 -6
  65. package/dist/controllers/files.js +1 -1
  66. package/dist/controllers/items.js +1 -1
  67. package/dist/controllers/not-found.js +1 -1
  68. package/dist/controllers/relations.js +1 -1
  69. package/dist/database/errors/dialects/mysql.d.ts +1 -1
  70. package/dist/database/errors/dialects/postgres.d.ts +1 -1
  71. package/dist/database/errors/dialects/sqlite.d.ts +1 -1
  72. package/dist/database/errors/translate.d.ts +1 -1
  73. package/dist/database/errors/translate.js +1 -1
  74. package/dist/database/get-ast-from-query/lib/parse-fields.js +2 -2
  75. package/dist/database/helpers/date/dialects/mssql.js +1 -1
  76. package/dist/database/helpers/date/dialects/mysql.js +1 -1
  77. package/dist/database/helpers/date/types.js +1 -1
  78. package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +1 -0
  79. package/dist/database/helpers/schema/dialects/cockroachdb.js +24 -1
  80. package/dist/database/helpers/schema/dialects/mssql.d.ts +1 -1
  81. package/dist/database/helpers/schema/dialects/mysql.d.ts +2 -1
  82. package/dist/database/helpers/schema/dialects/mysql.js +16 -3
  83. package/dist/database/helpers/schema/dialects/postgres.d.ts +1 -1
  84. package/dist/database/helpers/schema/types.d.ts +13 -0
  85. package/dist/database/helpers/schema/types.js +24 -0
  86. package/dist/database/index.js +4 -4
  87. package/dist/database/migrations/20220429A-add-flows.js +1 -1
  88. package/dist/database/migrations/20230526A-migrate-translation-strings.js +1 -1
  89. package/dist/database/migrations/20231009A-update-csv-fields-to-text.js +1 -1
  90. package/dist/database/migrations/20240204A-marketplace.js +9 -7
  91. package/dist/database/migrations/20240311A-deprecate-webhooks.d.ts +15 -0
  92. package/dist/database/migrations/20240311A-deprecate-webhooks.js +1 -1
  93. package/dist/database/migrations/20240806A-permissions-policies.js +2 -2
  94. package/dist/database/migrations/20240924A-migrate-legacy-comments.js +1 -1
  95. package/dist/database/migrations/20251014A-add-project-owner.js +1 -1
  96. package/dist/database/migrations/20251224A-remove-webhooks.d.ts +3 -0
  97. package/dist/database/migrations/20251224A-remove-webhooks.js +19 -0
  98. package/dist/database/migrations/20260110A-add-ai-provider-settings.d.ts +3 -0
  99. package/dist/database/migrations/20260110A-add-ai-provider-settings.js +35 -0
  100. package/dist/database/migrations/20260113A-add-revisions-index.d.ts +3 -0
  101. package/dist/database/migrations/20260113A-add-revisions-index.js +41 -0
  102. package/dist/database/migrations/20260128A-add-collaborative-editing.d.ts +3 -0
  103. package/dist/database/migrations/20260128A-add-collaborative-editing.js +10 -0
  104. package/dist/database/migrations/20260204A-add-deployment.d.ts +3 -0
  105. package/dist/database/migrations/20260204A-add-deployment.js +32 -0
  106. package/dist/database/migrations/run.js +3 -3
  107. package/dist/database/run-ast/lib/apply-query/add-join.js +1 -1
  108. package/dist/database/run-ast/lib/apply-query/filter/get-filter-type.d.ts +2 -2
  109. package/dist/database/run-ast/lib/apply-query/filter/get-filter-type.js +1 -1
  110. package/dist/database/run-ast/lib/apply-query/filter/index.js +1 -1
  111. package/dist/database/run-ast/lib/apply-query/filter/operator.js +1 -1
  112. package/dist/database/run-ast/lib/apply-query/sort.js +1 -1
  113. package/dist/database/run-ast/utils/get-column-pre-processor.js +2 -2
  114. package/dist/database/run-ast/utils/get-column.js +1 -1
  115. package/dist/database/seeds/run.js +3 -3
  116. package/dist/deployment/deployment.d.ts +94 -0
  117. package/dist/deployment/deployment.js +29 -0
  118. package/dist/deployment/drivers/index.d.ts +1 -0
  119. package/dist/deployment/drivers/index.js +1 -0
  120. package/dist/deployment/drivers/vercel.d.ts +32 -0
  121. package/dist/deployment/drivers/vercel.js +208 -0
  122. package/dist/deployment/index.d.ts +2 -0
  123. package/dist/deployment/index.js +2 -0
  124. package/dist/deployment.d.ts +24 -0
  125. package/dist/deployment.js +39 -0
  126. package/dist/extensions/lib/get-extensions-path.js +1 -1
  127. package/dist/extensions/lib/get-extensions-settings.js +1 -1
  128. package/dist/extensions/lib/get-extensions.js +1 -1
  129. package/dist/extensions/lib/get-shared-deps-mapping.js +3 -3
  130. package/dist/extensions/lib/installation/manager.js +3 -3
  131. package/dist/extensions/lib/sandbox/register/route.d.ts +1 -1
  132. package/dist/extensions/lib/sync/status.js +1 -1
  133. package/dist/extensions/lib/sync/sync.js +7 -7
  134. package/dist/extensions/lib/sync/utils.js +2 -2
  135. package/dist/extensions/manager.d.ts +1 -1
  136. package/dist/extensions/manager.js +8 -8
  137. package/dist/flows.d.ts +1 -1
  138. package/dist/logger/index.js +1 -1
  139. package/dist/logger/logs-stream.d.ts +1 -1
  140. package/dist/logger/logs-stream.js +1 -1
  141. package/dist/mailer.js +1 -1
  142. package/dist/metrics/lib/create-metrics.js +2 -2
  143. package/dist/middleware/authenticate.js +3 -3
  144. package/dist/middleware/collection-exists.js +1 -1
  145. package/dist/middleware/extract-token.js +1 -1
  146. package/dist/middleware/graphql.js +2 -2
  147. package/dist/middleware/respond.js +27 -14
  148. package/dist/middleware/validate-batch.js +1 -1
  149. package/dist/operations/exec/index.js +2 -1
  150. package/dist/operations/mail/index.js +1 -1
  151. package/dist/operations/mail/rate-limiter.js +2 -2
  152. package/dist/permissions/cache.js +5 -0
  153. package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.js +1 -1
  154. package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.js +1 -1
  155. package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.js +2 -2
  156. package/dist/permissions/modules/process-ast/lib/inject-cases.js +1 -1
  157. package/dist/permissions/modules/process-ast/process-ast.js +1 -1
  158. package/dist/permissions/modules/process-ast/utils/find-related-collection.js +1 -1
  159. package/dist/permissions/modules/process-payload/process-payload.js +1 -1
  160. package/dist/permissions/modules/validate-access/lib/validate-item-access.d.ts +14 -2
  161. package/dist/permissions/modules/validate-access/lib/validate-item-access.js +72 -13
  162. package/dist/permissions/modules/validate-access/validate-access.js +3 -2
  163. package/dist/rate-limiter.js +1 -1
  164. package/dist/request/is-denied-ip.js +1 -1
  165. package/dist/schedules/project.js +1 -1
  166. package/dist/schedules/telemetry.js +1 -1
  167. package/dist/schedules/tus.js +1 -1
  168. package/dist/server.js +6 -5
  169. package/dist/services/assets.d.ts +2 -1
  170. package/dist/services/assets.js +35 -8
  171. package/dist/services/authentication.js +2 -2
  172. package/dist/services/collections.js +1 -1
  173. package/dist/services/deployment-projects.d.ts +20 -0
  174. package/dist/services/deployment-projects.js +34 -0
  175. package/dist/services/deployment-runs.d.ts +13 -0
  176. package/dist/services/deployment-runs.js +6 -0
  177. package/dist/services/deployment.d.ts +40 -0
  178. package/dist/services/deployment.js +202 -0
  179. package/dist/services/extensions.d.ts +1 -1
  180. package/dist/services/files/utils/get-metadata.d.ts +1 -1
  181. package/dist/services/files/utils/get-metadata.js +1 -1
  182. package/dist/services/files.d.ts +1 -1
  183. package/dist/services/files.js +4 -4
  184. package/dist/services/graphql/index.d.ts +1 -1
  185. package/dist/services/graphql/index.js +1 -1
  186. package/dist/services/graphql/resolvers/mutation.js +1 -1
  187. package/dist/services/graphql/resolvers/system-admin.js +2 -3
  188. package/dist/services/graphql/schema/get-types.d.ts +1 -1
  189. package/dist/services/graphql/schema/read.js +1 -1
  190. package/dist/services/graphql/subscription.d.ts +1 -1
  191. package/dist/services/graphql/types/date.js +1 -1
  192. package/dist/services/graphql/types/hash.js +1 -1
  193. package/dist/services/graphql/utils/add-path-to-validation-error.js +1 -1
  194. package/dist/services/graphql/utils/filter-replace-m2a.js +3 -4
  195. package/dist/services/import-export.d.ts +1 -1
  196. package/dist/services/import-export.js +2 -2
  197. package/dist/services/index.d.ts +3 -1
  198. package/dist/services/index.js +3 -1
  199. package/dist/services/mail/index.js +2 -2
  200. package/dist/services/mail/rate-limiter.js +2 -2
  201. package/dist/services/payload.js +2 -2
  202. package/dist/services/schema.js +1 -1
  203. package/dist/services/server.js +13 -4
  204. package/dist/services/settings.js +2 -2
  205. package/dist/services/specifications.js +2 -2
  206. package/dist/services/tfa.js +1 -1
  207. package/dist/services/translations.js +1 -1
  208. package/dist/services/tus/data-store.d.ts +1 -3
  209. package/dist/services/tus/data-store.js +2 -5
  210. package/dist/services/tus/server.js +6 -6
  211. package/dist/services/users.js +4 -4
  212. package/dist/services/versions.js +1 -1
  213. package/dist/telemetry/lib/get-report.js +2 -0
  214. package/dist/telemetry/lib/send-report.d.ts +1 -1
  215. package/dist/telemetry/lib/send-report.js +1 -1
  216. package/dist/telemetry/lib/track.js +1 -1
  217. package/dist/telemetry/types/report.d.ts +8 -0
  218. package/dist/telemetry/utils/get-settings.d.ts +2 -0
  219. package/dist/telemetry/utils/get-settings.js +5 -0
  220. package/dist/test-utils/knex.js +1 -1
  221. package/dist/types/collection.d.ts +1 -1
  222. package/dist/utils/async-handler.d.ts +1 -1
  223. package/dist/utils/calculate-field-depth.js +1 -1
  224. package/dist/utils/compress.js +1 -1
  225. package/dist/utils/deep-map-response.d.ts +1 -1
  226. package/dist/utils/deep-map-response.js +2 -2
  227. package/dist/utils/get-cache-key.js +1 -1
  228. package/dist/utils/get-column-path.js +1 -1
  229. package/dist/utils/get-field-system-rows.js +1 -1
  230. package/dist/utils/get-ip-from-req.d.ts +1 -1
  231. package/dist/utils/get-ip-from-req.js +1 -1
  232. package/dist/utils/get-local-type.js +7 -3
  233. package/dist/utils/get-service.js +7 -3
  234. package/dist/utils/get-snapshot-diff.js +1 -1
  235. package/dist/utils/is-field-allowed.d.ts +4 -0
  236. package/dist/utils/is-field-allowed.js +9 -0
  237. package/dist/utils/is-url-allowed.js +1 -1
  238. package/dist/utils/jwt.js +1 -1
  239. package/dist/utils/sanitize-schema.d.ts +1 -1
  240. package/dist/utils/should-clear-cache.d.ts +1 -1
  241. package/dist/utils/should-skip-cache.js +2 -2
  242. package/dist/utils/validate-diff.js +1 -1
  243. package/dist/utils/validate-snapshot.js +3 -3
  244. package/dist/utils/validate-storage.js +2 -2
  245. package/dist/utils/verify-session-jwt.js +1 -1
  246. package/dist/utils/versioning/handle-version.js +1 -1
  247. package/dist/websocket/collab/calculate-cache-metadata.d.ts +9 -0
  248. package/dist/websocket/collab/calculate-cache-metadata.js +121 -0
  249. package/dist/websocket/collab/collab.d.ts +63 -0
  250. package/dist/websocket/collab/collab.js +481 -0
  251. package/dist/websocket/collab/constants.d.ts +1 -0
  252. package/dist/websocket/collab/constants.js +13 -0
  253. package/dist/websocket/collab/filter-to-fields.d.ts +2 -0
  254. package/dist/websocket/collab/filter-to-fields.js +11 -0
  255. package/dist/websocket/collab/messenger.d.ts +43 -0
  256. package/dist/websocket/collab/messenger.js +225 -0
  257. package/dist/websocket/collab/payload-permissions.d.ts +18 -0
  258. package/dist/websocket/collab/payload-permissions.js +158 -0
  259. package/dist/websocket/collab/permissions-cache.d.ts +52 -0
  260. package/dist/websocket/collab/permissions-cache.js +204 -0
  261. package/dist/websocket/collab/room.d.ts +125 -0
  262. package/dist/websocket/collab/room.js +593 -0
  263. package/dist/websocket/collab/store.d.ts +7 -0
  264. package/dist/websocket/collab/store.js +33 -0
  265. package/dist/websocket/collab/types.d.ts +21 -0
  266. package/dist/websocket/collab/types.js +1 -0
  267. package/dist/websocket/collab/verify-permissions.d.ts +11 -0
  268. package/dist/websocket/collab/verify-permissions.js +100 -0
  269. package/dist/websocket/controllers/base.d.ts +2 -2
  270. package/dist/websocket/controllers/base.js +3 -3
  271. package/dist/websocket/controllers/graphql.d.ts +1 -1
  272. package/dist/websocket/controllers/graphql.js +1 -1
  273. package/dist/websocket/controllers/logs.d.ts +1 -1
  274. package/dist/websocket/controllers/rest.d.ts +1 -1
  275. package/dist/websocket/controllers/rest.js +2 -2
  276. package/dist/websocket/handlers/heartbeat.js +1 -1
  277. package/dist/websocket/handlers/index.d.ts +2 -0
  278. package/dist/websocket/handlers/index.js +9 -0
  279. package/dist/websocket/handlers/items.js +2 -2
  280. package/dist/websocket/handlers/subscribe.js +1 -1
  281. package/dist/websocket/types.d.ts +1 -1
  282. package/dist/websocket/utils/items.d.ts +2 -2
  283. package/dist/websocket/utils/message.d.ts +1 -1
  284. package/dist/websocket/utils/message.js +2 -2
  285. package/dist/websocket/utils/wait-for-message.js +1 -1
  286. package/package.json +35 -33
  287. package/dist/controllers/webhooks.js +0 -74
  288. package/dist/services/webhooks.d.ts +0 -14
  289. package/dist/services/webhooks.js +0 -32
  290. package/dist/utils/get-relation-info.d.ts +0 -6
  291. package/dist/utils/get-relation-info.js +0 -43
  292. package/dist/utils/get-relation-type.d.ts +0 -6
  293. package/dist/utils/get-relation-type.js +0 -18
  294. package/dist/utils/is-login-redirect-allowed.d.ts +0 -4
  295. package/dist/utils/versioning/deep-map-with-schema.d.ts +0 -23
  296. package/dist/utils/versioning/deep-map-with-schema.js +0 -81
  297. /package/dist/controllers/{webhooks.d.ts → deployment.d.ts} +0 -0
@@ -14,10 +14,10 @@ import { validateAccess } from '../permissions/modules/validate-access/validate-
14
14
  import { getSchema } from '../utils/get-schema.js';
15
15
  import { shouldClearCache } from '../utils/should-clear-cache.js';
16
16
  import { transaction } from '../utils/transaction.js';
17
- import { FieldsService } from './fields.js';
18
17
  import { buildCollectionAndFieldRelations } from './fields/build-collection-and-field-relations.js';
19
18
  import { getCollectionMetaUpdates } from './fields/get-collection-meta-updates.js';
20
19
  import { getCollectionRelationList } from './fields/get-collection-relation-list.js';
20
+ import { FieldsService } from './fields.js';
21
21
  import { ItemsService } from './items.js';
22
22
  export class CollectionsService {
23
23
  knex;
@@ -0,0 +1,20 @@
1
+ import type { AbstractServiceOptions, PrimaryKey } from '@directus/types';
2
+ import { ItemsService } from './items.js';
3
+ export interface DeploymentProject {
4
+ id: string;
5
+ deployment: string;
6
+ external_id: string;
7
+ name: string;
8
+ date_created: string;
9
+ user_created: string;
10
+ }
11
+ export declare class DeploymentProjectsService extends ItemsService<DeploymentProject> {
12
+ constructor(options: AbstractServiceOptions);
13
+ /**
14
+ * Update project selection (create/delete)
15
+ */
16
+ updateSelection(deploymentId: string, create: {
17
+ external_id: string;
18
+ name: string;
19
+ }[], deleteIds: PrimaryKey[]): Promise<DeploymentProject[]>;
20
+ }
@@ -0,0 +1,34 @@
1
+ import getDatabase from '../database/index.js';
2
+ import { transaction } from '../utils/transaction.js';
3
+ import { ItemsService } from './items.js';
4
+ export class DeploymentProjectsService extends ItemsService {
5
+ constructor(options) {
6
+ super('directus_deployment_projects', options);
7
+ }
8
+ /**
9
+ * Update project selection (create/delete)
10
+ */
11
+ async updateSelection(deploymentId, create, deleteIds) {
12
+ const db = getDatabase();
13
+ return transaction(db, async (trx) => {
14
+ const trxService = new DeploymentProjectsService({
15
+ accountability: this.accountability,
16
+ schema: this.schema,
17
+ knex: trx,
18
+ });
19
+ if (deleteIds.length > 0) {
20
+ await trxService.deleteMany(deleteIds);
21
+ }
22
+ if (create.length > 0) {
23
+ await trxService.createMany(create.map((p) => ({
24
+ deployment: deploymentId,
25
+ external_id: p.external_id,
26
+ name: p.name,
27
+ })));
28
+ }
29
+ return trxService.readByQuery({
30
+ filter: { deployment: { _eq: deploymentId } },
31
+ });
32
+ });
33
+ }
34
+ }
@@ -0,0 +1,13 @@
1
+ import type { AbstractServiceOptions } from '@directus/types';
2
+ import { ItemsService } from './items.js';
3
+ export interface DeploymentRun {
4
+ id: string;
5
+ project: string;
6
+ external_id: string;
7
+ target: string;
8
+ date_created: string;
9
+ user_created: string;
10
+ }
11
+ export declare class DeploymentRunsService extends ItemsService<DeploymentRun> {
12
+ constructor(options: AbstractServiceOptions);
13
+ }
@@ -0,0 +1,6 @@
1
+ import { ItemsService } from './items.js';
2
+ export class DeploymentRunsService extends ItemsService {
3
+ constructor(options) {
4
+ super('directus_deployment_runs', options);
5
+ }
6
+ }
@@ -0,0 +1,40 @@
1
+ import type { AbstractServiceOptions, CachedResult, DeploymentConfig, PrimaryKey, Project, ProviderType, Query } from '@directus/types';
2
+ import type { DeploymentDriver } from '../deployment/deployment.js';
3
+ import { ItemsService } from './items.js';
4
+ export declare class DeploymentService extends ItemsService<DeploymentConfig> {
5
+ constructor(options: AbstractServiceOptions);
6
+ createOne(data: Partial<DeploymentConfig>, opts?: any): Promise<PrimaryKey>;
7
+ updateOne(key: PrimaryKey, data: Partial<DeploymentConfig>, opts?: any): Promise<PrimaryKey>;
8
+ /**
9
+ * Read deployment config by provider
10
+ */
11
+ readByProvider(provider: ProviderType, query?: Query): Promise<DeploymentConfig>;
12
+ /**
13
+ * Update deployment config by provider
14
+ */
15
+ updateByProvider(provider: ProviderType, data: Partial<DeploymentConfig>): Promise<PrimaryKey>;
16
+ /**
17
+ * Delete deployment config by provider
18
+ */
19
+ deleteByProvider(provider: ProviderType): Promise<PrimaryKey>;
20
+ /**
21
+ * Read deployment config with decrypted credentials (internal use)
22
+ */
23
+ private readConfig;
24
+ /**
25
+ * Parse JSON string or return value as-is
26
+ */
27
+ private parseValue;
28
+ /**
29
+ * Get a deployment driver instance with decrypted credentials
30
+ */
31
+ getDriver(provider: ProviderType): Promise<DeploymentDriver>;
32
+ /**
33
+ * List projects from provider with caching
34
+ */
35
+ listProviderProjects(provider: ProviderType): Promise<CachedResult<Project[]>>;
36
+ /**
37
+ * Get project details from provider with caching
38
+ */
39
+ getProviderProject(provider: ProviderType, projectId: string): Promise<CachedResult<Project>>;
40
+ }
@@ -0,0 +1,202 @@
1
+ import { useEnv } from '@directus/env';
2
+ import { InvalidPayloadError, InvalidProviderConfigError } from '@directus/errors';
3
+ import { mergeFilters, parseJSON } from '@directus/utils';
4
+ import { has, isEmpty } from 'lodash-es';
5
+ import { getCache, getCacheValueWithTTL, setCacheValueWithExpiry } from '../cache.js';
6
+ import { getDeploymentDriver } from '../deployment.js';
7
+ import { getMilliseconds } from '../utils/get-milliseconds.js';
8
+ import { ItemsService } from './items.js';
9
+ const env = useEnv();
10
+ const DEPLOYMENT_CACHE_TTL = getMilliseconds(env['CACHE_DEPLOYMENT_TTL']) || 5000; // Default 5s
11
+ export class DeploymentService extends ItemsService {
12
+ constructor(options) {
13
+ super('directus_deployments', options);
14
+ }
15
+ async createOne(data, opts) {
16
+ const provider = data.provider;
17
+ if (!provider) {
18
+ throw new InvalidPayloadError({ reason: 'Provider is required' });
19
+ }
20
+ if (isEmpty(data.credentials)) {
21
+ throw new InvalidPayloadError({ reason: 'Credentials are required' });
22
+ }
23
+ let credentials;
24
+ try {
25
+ credentials = this.parseValue(data.credentials, {});
26
+ }
27
+ catch {
28
+ throw new InvalidPayloadError({ reason: 'Credentials must be valid JSON' });
29
+ }
30
+ let options;
31
+ try {
32
+ options = this.parseValue(data.options, undefined);
33
+ }
34
+ catch {
35
+ throw new InvalidPayloadError({ reason: 'Options must be valid JSON' });
36
+ }
37
+ // Test connection before persisting
38
+ const driver = getDeploymentDriver(provider, credentials, options);
39
+ try {
40
+ await driver.testConnection();
41
+ }
42
+ catch {
43
+ throw new InvalidProviderConfigError({ provider, reason: 'Invalid config connection' });
44
+ }
45
+ const payload = {
46
+ ...data,
47
+ // Persist as string so payload service encrypts the value
48
+ credentials: JSON.stringify(credentials),
49
+ };
50
+ if (!isEmpty(options)) {
51
+ payload.options = JSON.stringify(options);
52
+ }
53
+ return super.createOne(payload, opts);
54
+ }
55
+ async updateOne(key, data, opts) {
56
+ const hasCredentials = has(data, 'credentials');
57
+ const hasOptions = has(data, 'options');
58
+ if (!hasCredentials && !hasOptions) {
59
+ return super.updateOne(key, data, opts);
60
+ }
61
+ const existing = await this.readOne(key);
62
+ const provider = existing.provider;
63
+ const internal = await this.readConfig(provider);
64
+ let credentials = this.parseValue(internal.credentials, {});
65
+ if (hasCredentials) {
66
+ try {
67
+ const parsed = this.parseValue(data.credentials, {});
68
+ credentials = { ...credentials, ...parsed };
69
+ }
70
+ catch {
71
+ throw new InvalidPayloadError({ reason: 'Credentials must be valid JSON or object' });
72
+ }
73
+ }
74
+ let options = existing.options ?? undefined;
75
+ if (hasOptions) {
76
+ try {
77
+ options = this.parseValue(data.options, undefined);
78
+ }
79
+ catch {
80
+ throw new InvalidPayloadError({ reason: 'Options must be valid JSON' });
81
+ }
82
+ if (isEmpty(options)) {
83
+ throw new InvalidPayloadError({ reason: 'Options must not be empty' });
84
+ }
85
+ }
86
+ // Test connection before persisting
87
+ const driver = getDeploymentDriver(provider, credentials, options);
88
+ try {
89
+ await driver.testConnection();
90
+ }
91
+ catch {
92
+ throw new InvalidProviderConfigError({ provider, reason: 'Invalid config connection' });
93
+ }
94
+ return super.updateOne(key, {
95
+ credentials: JSON.stringify(credentials),
96
+ ...(!isEmpty(options) ? { options: JSON.stringify(options) } : {}),
97
+ }, opts);
98
+ }
99
+ /**
100
+ * Read deployment config by provider
101
+ */
102
+ async readByProvider(provider, query) {
103
+ const results = await this.readByQuery({
104
+ ...query,
105
+ filter: mergeFilters({ provider: { _eq: provider } }, query?.filter ?? null),
106
+ limit: 1,
107
+ });
108
+ if (!results || results.length === 0) {
109
+ throw new Error(`Deployment config for "${provider}" not found`);
110
+ }
111
+ return results[0];
112
+ }
113
+ /**
114
+ * Update deployment config by provider
115
+ */
116
+ async updateByProvider(provider, data) {
117
+ const deployment = await this.readByProvider(provider);
118
+ return this.updateOne(deployment.id, data);
119
+ }
120
+ /**
121
+ * Delete deployment config by provider
122
+ */
123
+ async deleteByProvider(provider) {
124
+ const deployment = await this.readByProvider(provider);
125
+ return this.deleteOne(deployment.id);
126
+ }
127
+ /**
128
+ * Read deployment config with decrypted credentials (internal use)
129
+ */
130
+ async readConfig(provider) {
131
+ const internalService = new ItemsService('directus_deployments', {
132
+ knex: this.knex,
133
+ schema: this.schema,
134
+ accountability: null,
135
+ });
136
+ const results = await internalService.readByQuery({
137
+ filter: { provider: { _eq: provider } },
138
+ limit: 1,
139
+ });
140
+ if (!results || results.length === 0) {
141
+ throw new Error(`Deployment config for "${provider}" not found`);
142
+ }
143
+ return results[0];
144
+ }
145
+ /**
146
+ * Parse JSON string or return value as-is
147
+ */
148
+ parseValue(value, fallback) {
149
+ if (!value)
150
+ return fallback;
151
+ if (typeof value === 'string')
152
+ return parseJSON(value);
153
+ return value;
154
+ }
155
+ /**
156
+ * Get a deployment driver instance with decrypted credentials
157
+ */
158
+ async getDriver(provider) {
159
+ const deployment = await this.readConfig(provider);
160
+ const credentials = this.parseValue(deployment.credentials, {});
161
+ const options = this.parseValue(deployment.options, {});
162
+ return getDeploymentDriver(deployment.provider, credentials, options);
163
+ }
164
+ /**
165
+ * List projects from provider with caching
166
+ */
167
+ async listProviderProjects(provider) {
168
+ const cacheKey = `${provider}:projects`;
169
+ const { deploymentCache } = getCache();
170
+ // Check cache first
171
+ const cached = await getCacheValueWithTTL(deploymentCache, cacheKey);
172
+ if (cached) {
173
+ return { data: cached.data, remainingTTL: cached.remainingTTL };
174
+ }
175
+ // Fetch from driver
176
+ const driver = await this.getDriver(provider);
177
+ const projects = await driver.listProjects();
178
+ // Store in cache
179
+ await setCacheValueWithExpiry(deploymentCache, cacheKey, projects, DEPLOYMENT_CACHE_TTL);
180
+ // Return with full TTL (just cached)
181
+ return { data: projects, remainingTTL: DEPLOYMENT_CACHE_TTL };
182
+ }
183
+ /**
184
+ * Get project details from provider with caching
185
+ */
186
+ async getProviderProject(provider, projectId) {
187
+ const cacheKey = `${provider}:project:${projectId}`;
188
+ const { deploymentCache } = getCache();
189
+ // Check cache first
190
+ const cached = await getCacheValueWithTTL(deploymentCache, cacheKey);
191
+ if (cached) {
192
+ return { data: cached.data, remainingTTL: cached.remainingTTL };
193
+ }
194
+ // Fetch from driver
195
+ const driver = await this.getDriver(provider);
196
+ const project = await driver.getProject(projectId);
197
+ // Store in cache
198
+ await setCacheValueWithExpiry(deploymentCache, cacheKey, project, DEPLOYMENT_CACHE_TTL);
199
+ // Return with full TTL (just cached)
200
+ return { data: project, remainingTTL: DEPLOYMENT_CACHE_TTL };
201
+ }
202
+ }
@@ -1,4 +1,4 @@
1
- import type { ApiOutput, ExtensionSettings, ExtensionManager } from '@directus/types';
1
+ import type { ApiOutput, ExtensionManager, ExtensionSettings } from '@directus/types';
2
2
  import type { AbstractServiceOptions, Accountability, DeepPartial, SchemaOverview } from '@directus/types';
3
3
  import type { Knex } from 'knex';
4
4
  import { ItemsService } from './items.js';
@@ -1,4 +1,4 @@
1
- import type { File } from '@directus/types';
2
1
  import type { Readable } from 'node:stream';
2
+ import type { File } from '@directus/types';
3
3
  export type Metadata = Partial<Pick<File, 'height' | 'width' | 'description' | 'title' | 'tags' | 'metadata'>>;
4
4
  export declare function getMetadata(stream: Readable, allowList?: string | string[]): Promise<Metadata>;
@@ -1,8 +1,8 @@
1
+ import { pipeline } from 'node:stream/promises';
1
2
  import { useEnv } from '@directus/env';
2
3
  import exif, {} from 'exif-reader';
3
4
  import { parse as parseIcc } from 'icc';
4
5
  import { pick } from 'lodash-es';
5
- import { pipeline } from 'node:stream/promises';
6
6
  import { useLogger } from '../../../logger/index.js';
7
7
  import { getSharpInstance } from '../lib/get-sharp-instance.js';
8
8
  import { parseIptc, parseXmp } from './parse-image-metadata.js';
@@ -1,5 +1,5 @@
1
- import type { AbstractServiceOptions, BusboyFileStream, File, MutationOptions, PrimaryKey, Query, QueryOptions } from '@directus/types';
2
1
  import type { Readable } from 'node:stream';
2
+ import type { AbstractServiceOptions, BusboyFileStream, File, MutationOptions, PrimaryKey, Query, QueryOptions } from '@directus/types';
3
3
  import { ItemsService } from './items.js';
4
4
  export declare class FilesService extends ItemsService<File> {
5
5
  constructor(options: AbstractServiceOptions);
@@ -1,3 +1,7 @@
1
+ import { PassThrough as PassThroughStream, Transform as TransformStream } from 'node:stream';
2
+ import zlib from 'node:zlib';
3
+ import path from 'path';
4
+ import url from 'url';
1
5
  import { useEnv } from '@directus/env';
2
6
  import { ContentTooLargeError, InvalidPayloadError, ServiceUnavailableError } from '@directus/errors';
3
7
  import formatTitle from '@directus/format-title';
@@ -5,10 +9,6 @@ import { toArray } from '@directus/utils';
5
9
  import encodeURL from 'encodeurl';
6
10
  import { clone, cloneDeep } from 'lodash-es';
7
11
  import { extension } from 'mime-types';
8
- import { PassThrough as PassThroughStream, Transform as TransformStream } from 'node:stream';
9
- import zlib from 'node:zlib';
10
- import path from 'path';
11
- import url from 'url';
12
12
  import { RESUMABLE_UPLOADS } from '../constants.js';
13
13
  import emitter from '../emitter.js';
14
14
  import { useLogger } from '../logger/index.js';
@@ -1,4 +1,4 @@
1
- import type { AbstractServiceOptions, Accountability, GraphQLParams, GQLScope, Item, Query, SchemaOverview, PrimaryKey } from '@directus/types';
1
+ import type { AbstractServiceOptions, Accountability, GQLScope, GraphQLParams, Item, PrimaryKey, Query, SchemaOverview } from '@directus/types';
2
2
  import type { FormattedExecutionResult, GraphQLSchema } from 'graphql';
3
3
  import type { Knex } from 'knex';
4
4
  export declare class GraphQLService {
@@ -1,5 +1,5 @@
1
1
  import { useEnv } from '@directus/env';
2
- import { NoSchemaIntrospectionCustomRule, execute, specifiedRules, validate } from 'graphql';
2
+ import { execute, NoSchemaIntrospectionCustomRule, specifiedRules, validate } from 'graphql';
3
3
  import getDatabase from '../../database/index.js';
4
4
  import { getService } from '../../utils/get-service.js';
5
5
  import { formatError } from './errors/format.js';
@@ -1,7 +1,7 @@
1
1
  import { getService } from '../../../utils/get-service.js';
2
2
  import { formatError } from '../errors/format.js';
3
- import { replaceFragmentsInSelections } from '../utils/replace-fragments.js';
4
3
  import { getQuery } from '../schema/parse-query.js';
4
+ import { replaceFragmentsInSelections } from '../utils/replace-fragments.js';
5
5
  export async function resolveMutation(gql, args, info) {
6
6
  const action = info.fieldName.split('_')[0];
7
7
  let collection = info.fieldName.substring(action.length + 1);
@@ -2,7 +2,6 @@ import { InvalidPayloadError } from '@directus/errors';
2
2
  import { isSystemField } from '@directus/system-data';
3
3
  import { GraphQLBoolean, GraphQLID, GraphQLList, GraphQLNonNull, GraphQLString } from 'graphql';
4
4
  import { SchemaComposer, toInputObjectType } from 'graphql-compose';
5
- import { fromZodError } from 'zod-validation-error';
6
5
  import { CollectionsService } from '../../collections.js';
7
6
  import { ExtensionsService } from '../../extensions.js';
8
7
  import { FieldsService, systemFieldUpdateSchema } from '../../fields.js';
@@ -112,7 +111,7 @@ export function resolveSystemAdmin(gql, schema, schemaComposer) {
112
111
  if (isSystemField(args['collection'], args['field'])) {
113
112
  const validationResult = systemFieldUpdateSchema.safeParse(args['data']);
114
113
  if (!validationResult.success) {
115
- throw new InvalidPayloadError({ reason: fromZodError(validationResult.error).message });
114
+ throw new InvalidPayloadError({ reason: 'Only "schema.is_indexed" may be modified for system fields' });
116
115
  }
117
116
  }
118
117
  await service.updateField(args['collection'], {
@@ -140,7 +139,7 @@ export function resolveSystemAdmin(gql, schema, schemaComposer) {
140
139
  if (isSystemField(args['collection'], fieldData['field'])) {
141
140
  const validationResult = systemFieldUpdateSchema.safeParse(fieldData);
142
141
  if (!validationResult.success) {
143
- throw new InvalidPayloadError({ reason: fromZodError(validationResult.error).message });
142
+ throw new InvalidPayloadError({ reason: 'Only "schema.is_indexed" may be modified for system fields' });
144
143
  }
145
144
  }
146
145
  }
@@ -1,6 +1,6 @@
1
+ import type { GQLScope } from '@directus/types';
1
2
  import type { SchemaComposer } from 'graphql-compose';
2
3
  import { ObjectTypeComposer } from 'graphql-compose';
3
- import type { GQLScope } from '@directus/types';
4
4
  import { type InconsistentFields, type Schema } from './index.js';
5
5
  /**
6
6
  * Construct an object of types for every collection, using the permitted fields per action type
@@ -9,8 +9,8 @@ import { GraphQLDate } from '../types/date.js';
9
9
  import { GraphQLGeoJSON } from '../types/geojson.js';
10
10
  import { GraphQLHash } from '../types/hash.js';
11
11
  import { GraphQLStringOrFloat } from '../types/string-or-float.js';
12
- import { SYSTEM_DENY_LIST } from './index.js';
13
12
  import { getTypes } from './get-types.js';
13
+ import { SYSTEM_DENY_LIST } from './index.js';
14
14
  /**
15
15
  * Create readable types and attach resolvers for each. Also prepares full filter argument structures
16
16
  */
@@ -1,5 +1,5 @@
1
- import type { GraphQLService } from './index.js';
2
1
  import type { GraphQLResolveInfo } from 'graphql';
2
+ import type { GraphQLService } from './index.js';
3
3
  export declare function bindPubSub(): void;
4
4
  export declare function createSubscriptionGenerator(gql: GraphQLService, event: string): (_x: unknown, _y: unknown, _z: unknown, request: GraphQLResolveInfo) => AsyncGenerator<{
5
5
  [event]: {
@@ -1,4 +1,4 @@
1
- import { GraphQLString, GraphQLScalarType } from 'graphql';
1
+ import { GraphQLScalarType, GraphQLString } from 'graphql';
2
2
  export const GraphQLDate = new GraphQLScalarType({
3
3
  ...GraphQLString,
4
4
  name: 'Date',
@@ -1,4 +1,4 @@
1
- import { GraphQLString, GraphQLScalarType } from 'graphql';
1
+ import { GraphQLScalarType, GraphQLString } from 'graphql';
2
2
  export const GraphQLHash = new GraphQLScalarType({
3
3
  ...GraphQLString,
4
4
  name: 'Hash',
@@ -1,4 +1,4 @@
1
- import { GraphQLError, Token, locatedError } from 'graphql';
1
+ import { GraphQLError, locatedError, Token } from 'graphql';
2
2
  export function addPathToValidationError(validationError) {
3
3
  const token = validationError.nodes?.[0]?.loc?.startToken;
4
4
  if (!token)
@@ -1,5 +1,4 @@
1
- import { getRelation } from '@directus/utils';
2
- import { getRelationType } from '../../../utils/get-relation-type.js';
1
+ import { getRelation, getRelationType } from '@directus/utils';
3
2
  export function filterReplaceM2A(filter_arg, collection, schema, options) {
4
3
  const filter = filter_arg;
5
4
  for (const key in filter) {
@@ -10,7 +9,7 @@ export function filterReplaceM2A(filter_arg, collection, schema, options) {
10
9
  continue;
11
10
  field = options?.aliasMap?.[field] ?? field;
12
11
  const relation = getRelation(schema.relations, collection, field);
13
- const type = relation ? getRelationType({ relation, collection, field }) : null;
12
+ const type = relation ? getRelationType({ relation, collection, field, useA2O: true }) : null;
14
13
  if (type === 'o2m' && relation) {
15
14
  filter[key] = filterReplaceM2A(filter[key], relation.collection, schema, options);
16
15
  }
@@ -46,7 +45,7 @@ export function filterReplaceM2ADeep(deep_arg, collection, schema, options) {
46
45
  const relation = getRelation(schema.relations, collection, field);
47
46
  if (!relation)
48
47
  continue;
49
- const type = getRelationType({ relation, collection, field });
48
+ const type = getRelationType({ relation, collection, field, useA2O: true });
50
49
  if (type === 'o2m') {
51
50
  deep[key] = filterReplaceM2ADeep(deep[key], relation.collection, schema);
52
51
  }
@@ -1,6 +1,6 @@
1
+ import type { Readable } from 'node:stream';
1
2
  import type { AbstractServiceOptions, Accountability, DirectusError, ExportFormat, File, Query, SchemaOverview } from '@directus/types';
2
3
  import type { Knex } from 'knex';
3
- import type { Readable } from 'node:stream';
4
4
  import type { FieldNode, FunctionFieldNode, NestedCollectionNode } from '../types/index.js';
5
5
  export declare function createErrorTracker(): {
6
6
  addCapturedError: (err: any, rowNumber: number) => void;
@@ -1,3 +1,5 @@
1
+ import { createReadStream, createWriteStream } from 'node:fs';
2
+ import { appendFile } from 'node:fs/promises';
1
3
  import { useEnv } from '@directus/env';
2
4
  import { createError, ErrorCode, ForbiddenError, InvalidPayloadError, ServiceUnavailableError, UnsupportedMediaTypeError, } from '@directus/errors';
3
5
  import { isSystemCollection } from '@directus/system-data';
@@ -9,8 +11,6 @@ import { dump as toYAML } from 'js-yaml';
9
11
  import { parse as toXML } from 'js2xmlparser';
10
12
  import { Parser as CSVParser, transforms as CSVTransforms } from 'json2csv';
11
13
  import { set } from 'lodash-es';
12
- import { createReadStream, createWriteStream } from 'node:fs';
13
- import { appendFile } from 'node:fs/promises';
14
14
  import Papa from 'papaparse';
15
15
  import StreamArray from 'stream-json/streamers/StreamArray.js';
16
16
  import { parseFields } from '../database/get-ast-from-query/lib/parse-fields.js';
@@ -10,6 +10,9 @@ export * from './fields.js';
10
10
  export * from './files.js';
11
11
  export * from './flows.js';
12
12
  export * from './folders.js';
13
+ export * from './deployment.js';
14
+ export * from './deployment-projects.js';
15
+ export * from './deployment-runs.js';
13
16
  export * from './graphql/index.js';
14
17
  export * from './import-export.js';
15
18
  export * from './items.js';
@@ -35,5 +38,4 @@ export * from './translations.js';
35
38
  export * from './users.js';
36
39
  export * from './utils.js';
37
40
  export * from './versions.js';
38
- export * from './webhooks.js';
39
41
  export * from './websocket.js';
@@ -10,6 +10,9 @@ export * from './fields.js';
10
10
  export * from './files.js';
11
11
  export * from './flows.js';
12
12
  export * from './folders.js';
13
+ export * from './deployment.js';
14
+ export * from './deployment-projects.js';
15
+ export * from './deployment-runs.js';
13
16
  export * from './graphql/index.js';
14
17
  export * from './import-export.js';
15
18
  export * from './items.js';
@@ -35,5 +38,4 @@ export * from './translations.js';
35
38
  export * from './users.js';
36
39
  export * from './utils.js';
37
40
  export * from './versions.js';
38
- export * from './webhooks.js';
39
41
  export * from './websocket.js';
@@ -1,10 +1,10 @@
1
+ import path from 'path';
2
+ import { fileURLToPath } from 'url';
1
3
  import { useEnv } from '@directus/env';
2
4
  import { InvalidPayloadError } from '@directus/errors';
3
5
  import { isObject } from '@directus/utils';
4
6
  import fse from 'fs-extra';
5
7
  import { Liquid } from 'liquidjs';
6
- import path from 'path';
7
- import { fileURLToPath } from 'url';
8
8
  import getDatabase from '../../database/index.js';
9
9
  import emitter from '../../emitter.js';
10
10
  import { useLogger } from '../../logger/index.js';
@@ -1,8 +1,8 @@
1
1
  import { useEnv } from '@directus/env';
2
+ import { EmailLimitExceededError } from '@directus/errors';
3
+ import { toBoolean } from '@directus/utils';
2
4
  import { RateLimiterQueue } from 'rate-limiter-flexible';
3
5
  import { createRateLimiter } from '../../rate-limiter.js';
4
- import { toBoolean } from '@directus/utils';
5
- import { EmailLimitExceededError } from '@directus/errors';
6
6
  let emailRateLimiterQueue;
7
7
  const env = useEnv();
8
8
  if (toBoolean(env['RATE_LIMITER_EMAIL_ENABLED']) === true) {
@@ -1,3 +1,4 @@
1
+ import { randomUUID } from 'node:crypto';
1
2
  import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
2
3
  import { UserIntegrityCheckFlag } from '@directus/types';
3
4
  import { parseJSON, toArray } from '@directus/utils';
@@ -5,12 +6,11 @@ import { format, isValid, parseISO } from 'date-fns';
5
6
  import { unflatten } from 'flat';
6
7
  import Joi from 'joi';
7
8
  import { clone, cloneDeep, isNil, isObject, isPlainObject, pick } from 'lodash-es';
8
- import { randomUUID } from 'node:crypto';
9
9
  import { parse as wktToGeoJSON } from 'wellknown';
10
10
  import { getHelpers } from '../database/helpers/index.js';
11
11
  import getDatabase from '../database/index.js';
12
- import { generateHash } from '../utils/generate-hash.js';
13
12
  import { decrypt, encrypt } from '../utils/encrypt.js';
13
+ import { generateHash } from '../utils/generate-hash.js';
14
14
  import { getSecret } from '../utils/get-secret.js';
15
15
  /**
16
16
  * Process a given payload for a collection to ensure the special fields (hash, uuid, date etc) are
@@ -1,5 +1,5 @@
1
- import getDatabase from '../database/index.js';
2
1
  import { ForbiddenError } from '@directus/errors';
2
+ import getDatabase from '../database/index.js';
3
3
  import { applyDiff } from '../utils/apply-diff.js';
4
4
  import { getSnapshotDiff } from '../utils/get-snapshot-diff.js';
5
5
  import { getSnapshot } from '../utils/get-snapshot.js';