@directus/api 36.0.0-rc.1 → 36.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/ai/chat/lib/create-ui-stream.js +8 -17
- package/dist/ai/chat/utils/format-context.js +2 -2
- package/dist/ai/chat/utils/prompt-caching.js +85 -0
- package/dist/database/errors/dialects/postgres.js +2 -1
- package/dist/license/entitlements/lib/custom-permission-rules-enabled.js +2 -7
- package/dist/license/entitlements/lib/seats.js +42 -18
- package/dist/license/entitlements/lib/sso-enabled.js +2 -1
- package/dist/services/permissions.js +2 -2
- package/dist/utils/get-cache-key.js +1 -0
- package/package.json +29 -29
|
@@ -6,6 +6,7 @@ import "../../providers/index.js";
|
|
|
6
6
|
import { getAITelemetryConfig } from "../../telemetry/index.js";
|
|
7
7
|
import { SYSTEM_PROMPT } from "../constants/system-prompt.js";
|
|
8
8
|
import { formatContextForSystemPrompt } from "../utils/format-context.js";
|
|
9
|
+
import { applyAnthropicConversationCaching, buildCacheAwareSystemPrompt, formatUsageWithCacheTokens, sortToolsByName } from "../utils/prompt-caching.js";
|
|
9
10
|
import { transformFilePartsForProvider } from "./transform-file-parts.js";
|
|
10
11
|
import { ServiceUnavailableError } from "@directus/errors";
|
|
11
12
|
import { convertToModelMessages, stepCountIs, streamText, wrapLanguageModel } from "ai";
|
|
@@ -27,35 +28,25 @@ const createUiStream = async (messages, { provider, model, tools, aiSettings, sy
|
|
|
27
28
|
model: languageModel,
|
|
28
29
|
middleware: devToolsMiddleware
|
|
29
30
|
});
|
|
30
|
-
const
|
|
31
|
-
const finalTools = applyAnthropicToolSearch(provider, model, tools);
|
|
31
|
+
const streamSystemPrompt = buildCacheAwareSystemPrompt(provider, provider === "anthropic" || !contextBlock ? baseSystemPrompt : baseSystemPrompt + contextBlock);
|
|
32
|
+
const finalTools = sortToolsByName(applyAnthropicToolSearch(provider, model, tools));
|
|
32
33
|
const telemetryConfig = getAITelemetryConfig({
|
|
33
34
|
provider,
|
|
34
35
|
model,
|
|
35
36
|
userId,
|
|
36
37
|
role
|
|
37
38
|
});
|
|
39
|
+
const streamMessages = applyAnthropicConversationCaching(provider, await convertToModelMessages(transformFilePartsForProvider(messages)), contextBlock);
|
|
38
40
|
return streamText({
|
|
39
|
-
system:
|
|
41
|
+
system: streamSystemPrompt,
|
|
40
42
|
model: languageModel,
|
|
41
|
-
messages:
|
|
43
|
+
messages: streamMessages,
|
|
42
44
|
stopWhen: [stepCountIs(10)],
|
|
43
45
|
providerOptions,
|
|
44
46
|
tools: finalTools,
|
|
45
47
|
...telemetryConfig ? { experimental_telemetry: telemetryConfig } : {},
|
|
46
|
-
|
|
47
|
-
if (
|
|
48
|
-
return {};
|
|
49
|
-
},
|
|
50
|
-
onFinish({ usage }) {
|
|
51
|
-
if (onUsage) {
|
|
52
|
-
const { inputTokens, outputTokens, totalTokens } = usage;
|
|
53
|
-
onUsage({
|
|
54
|
-
inputTokens,
|
|
55
|
-
outputTokens,
|
|
56
|
-
totalTokens
|
|
57
|
-
});
|
|
58
|
-
}
|
|
48
|
+
onFinish(result) {
|
|
49
|
+
if (onUsage) onUsage(formatUsageWithCacheTokens(result));
|
|
59
50
|
}
|
|
60
51
|
});
|
|
61
52
|
};
|
|
@@ -70,8 +70,6 @@ ${promptBlocks}
|
|
|
70
70
|
</custom_instructions>`);
|
|
71
71
|
}
|
|
72
72
|
const sections = [];
|
|
73
|
-
const now = /* @__PURE__ */ new Date();
|
|
74
|
-
sections.push(`## Current Date\n${now.toISOString().split("T")[0]}`);
|
|
75
73
|
if (context.page) {
|
|
76
74
|
const page = context.page;
|
|
77
75
|
const pageLines = [`Path: ${escapeAngleBrackets(String(page.path))}`];
|
|
@@ -89,6 +87,8 @@ Use the items tool to fetch additional fields or update items when asked.
|
|
|
89
87
|
|
|
90
88
|
${itemLines}`);
|
|
91
89
|
}
|
|
90
|
+
const now = /* @__PURE__ */ new Date();
|
|
91
|
+
sections.push(`## Current Date\n${now.toISOString().split("T")[0]}`);
|
|
92
92
|
if (sections.length > 0) parts.push(`<user_context>\n${sections.join("\n\n")}\n</user_context>`);
|
|
93
93
|
if (groups.visualElements.length > 0) {
|
|
94
94
|
const elementLines = groups.visualElements.map(formatVisualElement).join("\n\n");
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
//#region src/ai/chat/utils/prompt-caching.ts
|
|
2
|
+
function buildCacheAwareSystemPrompt(provider, content) {
|
|
3
|
+
if (provider !== "anthropic") return content;
|
|
4
|
+
return {
|
|
5
|
+
role: "system",
|
|
6
|
+
content,
|
|
7
|
+
providerOptions: { anthropic: { cacheControl: { type: "ephemeral" } } }
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* For Anthropic, place a cache breakpoint on the last existing message so the conversation
|
|
12
|
+
* prefix (tools + system + history) caches as it grows, and append the per-request page
|
|
13
|
+
* context as a new user message after the breakpoint so it stays out of the cached prefix.
|
|
14
|
+
*
|
|
15
|
+
* Context is only appended after a real user turn. On multi-step continuations the last
|
|
16
|
+
* message is an assistant or tool result; appending a user message there would make the
|
|
17
|
+
* model respond to the context instead of synthesizing the tool output. The model still
|
|
18
|
+
* sees context from the originating user turn earlier in the conversation.
|
|
19
|
+
*
|
|
20
|
+
* Other providers either auto-cache (Google/OpenAI) or don't support this, so we keep the
|
|
21
|
+
* context inside the system prompt for them (handled upstream).
|
|
22
|
+
*/
|
|
23
|
+
function applyAnthropicConversationCaching(provider, messages, contextBlock) {
|
|
24
|
+
if (provider !== "anthropic" || messages.length === 0) return messages;
|
|
25
|
+
const lastIndex = messages.length - 1;
|
|
26
|
+
const messagesWithCache = messages.map((message, index) => index === lastIndex ? {
|
|
27
|
+
...message,
|
|
28
|
+
providerOptions: {
|
|
29
|
+
...message.providerOptions,
|
|
30
|
+
anthropic: {
|
|
31
|
+
...message.providerOptions?.["anthropic"],
|
|
32
|
+
cacheControl: { type: "ephemeral" }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} : message);
|
|
36
|
+
if (!contextBlock || messages[lastIndex]?.role !== "user") return messagesWithCache;
|
|
37
|
+
return [...messagesWithCache, {
|
|
38
|
+
role: "user",
|
|
39
|
+
content: contextBlock
|
|
40
|
+
}];
|
|
41
|
+
}
|
|
42
|
+
function sortToolsByName(tools) {
|
|
43
|
+
return Object.fromEntries(Object.entries(tools).sort(([a], [b]) => {
|
|
44
|
+
if (a < b) return -1;
|
|
45
|
+
if (a > b) return 1;
|
|
46
|
+
return 0;
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
function formatUsageWithCacheTokens(result) {
|
|
50
|
+
const usage = result.totalUsage ?? result.usage;
|
|
51
|
+
const providerMetadata = result.steps?.map((step) => step.providerMetadata) ?? [result.providerMetadata];
|
|
52
|
+
const { inputTokens, outputTokens, totalTokens } = usage;
|
|
53
|
+
return {
|
|
54
|
+
inputTokens,
|
|
55
|
+
outputTokens,
|
|
56
|
+
totalTokens,
|
|
57
|
+
...getCacheTokenUsage(usage, providerMetadata)
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function getCacheTokenUsage(usage, providerMetadata) {
|
|
61
|
+
const cacheReadTokens = usage.inputTokenDetails?.cacheReadTokens ?? usage.cachedInputTokens ?? sumNumbers([...providerMetadata.map((metadata) => getProviderMetadataNumber(metadata, "anthropic", "cacheReadInputTokens")), ...providerMetadata.map(getGoogleCachedContentTokenCount)]);
|
|
62
|
+
const cacheCreationTokens = usage.inputTokenDetails?.cacheWriteTokens ?? sumNumbers(providerMetadata.map((metadata) => getProviderMetadataNumber(metadata, "anthropic", "cacheCreationInputTokens")));
|
|
63
|
+
return {
|
|
64
|
+
...cacheReadTokens !== void 0 ? { cacheReadTokens } : {},
|
|
65
|
+
...cacheCreationTokens !== void 0 ? { cacheCreationTokens } : {}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function getProviderMetadataNumber(providerMetadata, providerName, fieldName) {
|
|
69
|
+
const value = providerMetadata?.[providerName]?.[fieldName];
|
|
70
|
+
return typeof value === "number" ? value : void 0;
|
|
71
|
+
}
|
|
72
|
+
function getGoogleCachedContentTokenCount(providerMetadata) {
|
|
73
|
+
const usageMetadata = providerMetadata?.["google"]?.["usageMetadata"];
|
|
74
|
+
if (!usageMetadata || typeof usageMetadata !== "object" || Array.isArray(usageMetadata)) return;
|
|
75
|
+
const cachedContentTokenCount = usageMetadata["cachedContentTokenCount"];
|
|
76
|
+
return typeof cachedContentTokenCount === "number" ? cachedContentTokenCount : void 0;
|
|
77
|
+
}
|
|
78
|
+
function sumNumbers(values) {
|
|
79
|
+
const definedValues = values.filter((value) => typeof value === "number");
|
|
80
|
+
if (definedValues.length === 0) return;
|
|
81
|
+
return definedValues.reduce((sum, value) => sum + value, 0);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
//#endregion
|
|
85
|
+
export { applyAnthropicConversationCaching, buildCacheAwareSystemPrompt, formatUsageWithCacheTokens, sortToolsByName };
|
|
@@ -34,7 +34,8 @@ function extractError(error, data) {
|
|
|
34
34
|
const matches = error.message.match(/"(.*?)"/g);
|
|
35
35
|
if (!matches) return error;
|
|
36
36
|
const collection = matches[0].slice(1, -1);
|
|
37
|
-
const
|
|
37
|
+
const fields = Object.keys(data);
|
|
38
|
+
const field = fields.length === 1 ? fields[0] : null;
|
|
38
39
|
return new ValueOutOfRangeError({
|
|
39
40
|
collection,
|
|
40
41
|
field,
|
|
@@ -3,7 +3,7 @@ import { ItemsService } from "../../../services/items.js";
|
|
|
3
3
|
import { getSchema } from "../../../utils/get-schema.js";
|
|
4
4
|
import "../../../services/index.js";
|
|
5
5
|
import { isEqual } from "lodash-es";
|
|
6
|
-
import {
|
|
6
|
+
import { appRecommendedPermissions } from "@directus/system-data";
|
|
7
7
|
|
|
8
8
|
//#region src/license/entitlements/lib/custom-permission-rules-enabled.ts
|
|
9
9
|
function hasCustomRule(permission) {
|
|
@@ -16,11 +16,6 @@ function isRecommendedAppPermission(permission) {
|
|
|
16
16
|
if (!foundPermission) return false;
|
|
17
17
|
return isEqual(foundPermission.fields ?? null, permission.fields ?? null) && isEqual(foundPermission.permissions ?? null, permission.permissions ?? null);
|
|
18
18
|
}
|
|
19
|
-
function isMinimumAppPermission(permission) {
|
|
20
|
-
const foundPermission = appAccessMinimalPermissions.find((p) => p.action === permission.action && p.collection === permission.collection);
|
|
21
|
-
if (!foundPermission) return false;
|
|
22
|
-
return isEqual(foundPermission.fields ?? null, permission.fields ?? null) && isEqual(foundPermission.permissions ?? null, permission.permissions ?? null) && isEqual(foundPermission.validation ?? null, permission.validation ?? null) && isEqual(foundPermission.presets ?? null, permission.presets ?? null);
|
|
23
|
-
}
|
|
24
19
|
async function checkCustomPermissionRules(opts) {
|
|
25
20
|
const knex = opts?.knex ?? database_default();
|
|
26
21
|
return (await new ItemsService("directus_permissions", {
|
|
@@ -38,4 +33,4 @@ async function checkCustomPermissionRules(opts) {
|
|
|
38
33
|
}
|
|
39
34
|
|
|
40
35
|
//#endregion
|
|
41
|
-
export { checkCustomPermissionRules, hasCustomRule,
|
|
36
|
+
export { checkCustomPermissionRules, hasCustomRule, isRecommendedAppPermission };
|
|
@@ -8,10 +8,49 @@ import { toBoolean } from "@directus/utils";
|
|
|
8
8
|
import { USER_INACTIVE_LICENSE_STATUS } from "@directus/constants";
|
|
9
9
|
|
|
10
10
|
//#region src/license/entitlements/lib/seats.ts
|
|
11
|
+
/**
|
|
12
|
+
* Group access rows to the admin/app users and roles that occupy a seat.
|
|
13
|
+
*/
|
|
14
|
+
function getSeatUsersAndRoles(accessRows) {
|
|
15
|
+
const adminRoles = /* @__PURE__ */ new Set();
|
|
16
|
+
const appRoles = /* @__PURE__ */ new Set();
|
|
17
|
+
const adminUsers = /* @__PURE__ */ new Set();
|
|
18
|
+
const appUsers = /* @__PURE__ */ new Set();
|
|
19
|
+
const appUsersByRole = /* @__PURE__ */ new Map();
|
|
20
|
+
for (const accessRow of accessRows) {
|
|
21
|
+
const { admin_access, app_access } = accessRow["policy"] || {};
|
|
22
|
+
const isAdmin = toBoolean(admin_access);
|
|
23
|
+
const isApp = !isAdmin && toBoolean(app_access);
|
|
24
|
+
if (!isAdmin && !isApp) continue;
|
|
25
|
+
if (accessRow["user"] && accessRow["user"].status === "active") {
|
|
26
|
+
const { id, role } = accessRow["user"];
|
|
27
|
+
if (isAdmin) {
|
|
28
|
+
adminUsers.add(id);
|
|
29
|
+
appUsers.delete(id);
|
|
30
|
+
} else if (adminUsers.has(id) === false && (!role || adminRoles.has(role) === false)) {
|
|
31
|
+
appUsers.add(id);
|
|
32
|
+
if (role) {
|
|
33
|
+
const roleUsers = appUsersByRole.get(role) ?? /* @__PURE__ */ new Set();
|
|
34
|
+
appUsersByRole.set(role, roleUsers.add(id));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (accessRow["role"]) if (isAdmin) {
|
|
39
|
+
adminRoles.add(accessRow["role"]);
|
|
40
|
+
for (const id of appUsersByRole.get(accessRow["role"]) ?? []) appUsers.delete(id);
|
|
41
|
+
} else appRoles.add(accessRow["role"]);
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
adminUsers,
|
|
45
|
+
appUsers,
|
|
46
|
+
adminRoles,
|
|
47
|
+
appRoles
|
|
48
|
+
};
|
|
49
|
+
}
|
|
11
50
|
async function getActiveSeats(opts) {
|
|
12
51
|
const knex = opts?.knex ?? database_default();
|
|
13
52
|
const schema = await getSchema({ database: knex });
|
|
14
|
-
const
|
|
53
|
+
const { adminUsers, appUsers, adminRoles, appRoles } = getSeatUsersAndRoles(await new AccessService({
|
|
15
54
|
schema,
|
|
16
55
|
knex
|
|
17
56
|
}).readByQuery({
|
|
@@ -24,22 +63,7 @@ async function getActiveSeats(opts) {
|
|
|
24
63
|
"policy.admin_access"
|
|
25
64
|
],
|
|
26
65
|
limit: -1
|
|
27
|
-
});
|
|
28
|
-
const adminRoles = /* @__PURE__ */ new Set();
|
|
29
|
-
const appRoles = /* @__PURE__ */ new Set();
|
|
30
|
-
const adminUsers = /* @__PURE__ */ new Set();
|
|
31
|
-
const appUsers = /* @__PURE__ */ new Set();
|
|
32
|
-
for (const accessRow of accessRows) {
|
|
33
|
-
const isAdmin = toBoolean(accessRow["policy"]?.["admin_access"]);
|
|
34
|
-
const isApp = !isAdmin && toBoolean(accessRow["policy"]?.["app_access"]);
|
|
35
|
-
if (!isAdmin && !isApp) continue;
|
|
36
|
-
if (accessRow["user"] && accessRow["user"].status === "active") {
|
|
37
|
-
if (isAdmin) adminUsers.add(accessRow["user"].id);
|
|
38
|
-
else if (adminUsers.has(accessRow["user"].id) === false && adminRoles.has(accessRow["user"]?.role) === false) appUsers.add(accessRow["user"].id);
|
|
39
|
-
}
|
|
40
|
-
if (accessRow["role"]) if (isAdmin) adminRoles.add(accessRow["role"]);
|
|
41
|
-
else appRoles.add(accessRow["role"]);
|
|
42
|
-
}
|
|
66
|
+
}));
|
|
43
67
|
const { adminRoles: allAdminRoles, appRoles: allAppRoles } = await fetchAccessRoles({
|
|
44
68
|
adminRoles,
|
|
45
69
|
appRoles
|
|
@@ -100,4 +124,4 @@ async function resolveSeats(seats, ctx) {
|
|
|
100
124
|
}
|
|
101
125
|
|
|
102
126
|
//#endregion
|
|
103
|
-
export { countActiveSeats, getActiveSeats, resolveSeats };
|
|
127
|
+
export { countActiveSeats, getActiveSeats, getSeatUsersAndRoles, resolveSeats };
|
|
@@ -3,6 +3,7 @@ import database_default from "../../../database/index.js";
|
|
|
3
3
|
import { getSchema } from "../../../utils/get-schema.js";
|
|
4
4
|
import { UsersService } from "../../../services/users.js";
|
|
5
5
|
import "../../../services/index.js";
|
|
6
|
+
import { isObject } from "@directus/utils";
|
|
6
7
|
import { USER_INACTIVE_LICENSE_STATUS } from "@directus/constants";
|
|
7
8
|
|
|
8
9
|
//#region src/license/entitlements/lib/sso-enabled.ts
|
|
@@ -33,7 +34,7 @@ async function resolveSSOUsers(resolution, ctx) {
|
|
|
33
34
|
_neq: DEFAULT_AUTH_PROVIDER,
|
|
34
35
|
_nnull: true
|
|
35
36
|
} }, { id: { _neq: adminId } }] } }, { status: USER_INACTIVE_LICENSE_STATUS });
|
|
36
|
-
if (
|
|
37
|
+
if (isObject(resolution) && Object.keys(resolution.admin ?? {}).length) {
|
|
37
38
|
const payload = { provider: DEFAULT_AUTH_PROVIDER };
|
|
38
39
|
if (resolution.admin.email?.length) payload["email"] = resolution.admin.email;
|
|
39
40
|
if (resolution.admin.password?.length) payload["password"] = resolution.admin.password;
|
|
@@ -41,13 +41,13 @@ var PermissionsService = class extends ItemsService {
|
|
|
41
41
|
return withAppMinimalPermissions(this.accountability, mappedPermissions, query.filter);
|
|
42
42
|
}
|
|
43
43
|
async createOne(data, opts) {
|
|
44
|
-
if (hasCustomRule(data) && !isRecommendedAppPermission(data))
|
|
44
|
+
if (!getEntitlementManager().isEntitled("custom_permission_rules_enabled") && hasCustomRule(data) && !isRecommendedAppPermission(data)) throw new ResourceRestrictedError({ category: "custom_permission_rules_enabled" });
|
|
45
45
|
const res = await super.createOne(data, opts);
|
|
46
46
|
await this.clearCaches(opts);
|
|
47
47
|
return res;
|
|
48
48
|
}
|
|
49
49
|
async updateMany(keys, data, opts) {
|
|
50
|
-
if (hasCustomRule(data) && !isRecommendedAppPermission(data))
|
|
50
|
+
if (!getEntitlementManager().isEntitled("custom_permission_rules_enabled") && hasCustomRule(data) && !isRecommendedAppPermission(data)) throw new ResourceRestrictedError({ category: "custom_permission_rules_enabled" });
|
|
51
51
|
const res = await super.updateMany(keys, data, opts);
|
|
52
52
|
await this.clearCaches(opts);
|
|
53
53
|
return res;
|
|
@@ -34,6 +34,7 @@ async function getCacheKey(req) {
|
|
|
34
34
|
path,
|
|
35
35
|
query: isGraphQl ? getGraphqlQueryAndVariables(req) : req.sanitizedQuery,
|
|
36
36
|
...flowTriggerQuery && { rawQuery: flowTriggerQuery },
|
|
37
|
+
...req.accountability?.share && { share: req.accountability.share },
|
|
37
38
|
...includeIp && { ip: req.accountability.ip }
|
|
38
39
|
});
|
|
39
40
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@directus/api",
|
|
3
|
-
"version": "36.0.0
|
|
3
|
+
"version": "36.0.0",
|
|
4
4
|
"description": "Directus is a real-time API and App dashboard for managing SQL database content",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"directus",
|
|
@@ -166,30 +166,30 @@
|
|
|
166
166
|
"ws": "8.18.3",
|
|
167
167
|
"zod": "4.1.12",
|
|
168
168
|
"zod-validation-error": "4.0.2",
|
|
169
|
-
"@directus/app": "16.0.0
|
|
170
|
-
"@directus/
|
|
171
|
-
"@directus/
|
|
172
|
-
"@directus/
|
|
173
|
-
"@directus/
|
|
174
|
-
"@directus/extensions": "4.0.0
|
|
175
|
-
"@directus/
|
|
176
|
-
"@directus/
|
|
177
|
-
"@directus/memory": "4.0.0
|
|
178
|
-
"@directus/
|
|
179
|
-
"@directus/
|
|
180
|
-
"@directus/
|
|
181
|
-
"@directus/storage": "13.0.0
|
|
182
|
-
"@directus/
|
|
183
|
-
"@directus/storage-driver-
|
|
184
|
-
"@directus/storage-driver-
|
|
185
|
-
"@directus/storage-driver-
|
|
186
|
-
"@directus/
|
|
187
|
-
"@directus/storage-driver-s3": "13.0.0
|
|
188
|
-
"@directus/system-data": "4.5.0
|
|
189
|
-
"@directus/
|
|
190
|
-
"@directus/
|
|
191
|
-
"@directus/validation": "3.0.0
|
|
192
|
-
"directus": "12.0.0
|
|
169
|
+
"@directus/app": "16.0.0",
|
|
170
|
+
"@directus/ai": "1.3.2",
|
|
171
|
+
"@directus/env": "6.0.0",
|
|
172
|
+
"@directus/constants": "14.4.0",
|
|
173
|
+
"@directus/errors": "2.4.0",
|
|
174
|
+
"@directus/extensions-registry": "4.0.0",
|
|
175
|
+
"@directus/extensions": "4.0.0",
|
|
176
|
+
"@directus/format-title": "13.0.0",
|
|
177
|
+
"@directus/memory": "4.0.0",
|
|
178
|
+
"@directus/extensions-sdk": "18.0.0",
|
|
179
|
+
"@directus/schema": "14.0.0",
|
|
180
|
+
"@directus/pressure": "4.0.0",
|
|
181
|
+
"@directus/storage": "13.0.0",
|
|
182
|
+
"@directus/storage-driver-azure": "13.0.0",
|
|
183
|
+
"@directus/storage-driver-local": "13.0.0",
|
|
184
|
+
"@directus/storage-driver-gcs": "13.0.0",
|
|
185
|
+
"@directus/storage-driver-cloudinary": "13.0.0",
|
|
186
|
+
"@directus/specs": "14.0.0",
|
|
187
|
+
"@directus/storage-driver-s3": "13.0.0",
|
|
188
|
+
"@directus/system-data": "4.5.0",
|
|
189
|
+
"@directus/storage-driver-supabase": "4.0.0",
|
|
190
|
+
"@directus/utils": "13.5.0",
|
|
191
|
+
"@directus/validation": "3.0.0",
|
|
192
|
+
"directus": "12.0.0"
|
|
193
193
|
},
|
|
194
194
|
"devDependencies": {
|
|
195
195
|
"@directus/tsconfig": "4.0.0",
|
|
@@ -224,15 +224,15 @@
|
|
|
224
224
|
"@types/stream-json": "1.7.8",
|
|
225
225
|
"@types/wellknown": "0.5.8",
|
|
226
226
|
"@types/ws": "8.18.1",
|
|
227
|
-
"@vitest/coverage-v8": "3.2.
|
|
227
|
+
"@vitest/coverage-v8": "3.2.6",
|
|
228
228
|
"copyfiles": "2.4.1",
|
|
229
229
|
"form-data": "4.0.4",
|
|
230
230
|
"get-port": "7.1.0",
|
|
231
231
|
"knex-mock-client": "3.0.2",
|
|
232
232
|
"typescript": "5.9.3",
|
|
233
|
-
"vitest": "3.2.
|
|
234
|
-
"@directus/schema-builder": "1.0.0
|
|
235
|
-
"@directus/types": "16.0.0
|
|
233
|
+
"vitest": "3.2.6",
|
|
234
|
+
"@directus/schema-builder": "1.0.0",
|
|
235
|
+
"@directus/types": "16.0.0"
|
|
236
236
|
},
|
|
237
237
|
"optionalDependencies": {
|
|
238
238
|
"@keyv/redis": "3.0.1",
|