@directus/api 35.2.0 → 36.0.0-rc.1

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 (192) hide show
  1. package/dist/ai/chat/models/chat-request.js +48 -48
  2. package/dist/ai/chat/models/object-request.js +6 -6
  3. package/dist/ai/chat/models/providers.js +14 -14
  4. package/dist/ai/chat/utils/parse-json-schema-7.js +22 -22
  5. package/dist/ai/mcp/server.js +44 -6
  6. package/dist/ai/mcp/utils.js +31 -0
  7. package/dist/ai/tools/assets/index.js +3 -3
  8. package/dist/ai/tools/collections/index.js +18 -18
  9. package/dist/ai/tools/fields/index.js +18 -18
  10. package/dist/ai/tools/files/index.js +18 -18
  11. package/dist/ai/tools/flows/index.js +16 -16
  12. package/dist/ai/tools/folders/index.js +18 -18
  13. package/dist/ai/tools/items/index.js +17 -17
  14. package/dist/ai/tools/operations/index.js +16 -16
  15. package/dist/ai/tools/relations/index.js +22 -22
  16. package/dist/ai/tools/schema/index.js +3 -3
  17. package/dist/ai/tools/schema.js +159 -159
  18. package/dist/ai/tools/system/index.js +3 -3
  19. package/dist/ai/tools/trigger-flow/index.js +3 -3
  20. package/dist/app.js +35 -11
  21. package/dist/auth/drivers/ldap.js +3 -1
  22. package/dist/auth/drivers/local.js +2 -0
  23. package/dist/auth/drivers/oauth2.js +3 -1
  24. package/dist/auth/drivers/openid.js +3 -1
  25. package/dist/auth/drivers/saml.js +2 -0
  26. package/dist/auth/utils/check-local-disabled.js +16 -0
  27. package/dist/auth/utils/check-sso-enabled.js +14 -0
  28. package/dist/auth.js +8 -5
  29. package/dist/cli/commands/bootstrap/index.js +3 -0
  30. package/dist/cli/commands/cache/clear.js +6 -1
  31. package/dist/cli/commands/roles/create.js +4 -1
  32. package/dist/cli/commands/users/create.js +3 -0
  33. package/dist/constants.js +8 -1
  34. package/dist/controllers/access.js +1 -1
  35. package/dist/controllers/activity.js +2 -1
  36. package/dist/controllers/assets.js +2 -0
  37. package/dist/controllers/auth.js +13 -5
  38. package/dist/controllers/collections.js +1 -1
  39. package/dist/controllers/comments.js +1 -1
  40. package/dist/controllers/dashboards.js +1 -1
  41. package/dist/controllers/fields.js +1 -1
  42. package/dist/controllers/files.js +3 -1
  43. package/dist/controllers/flows.js +6 -5
  44. package/dist/controllers/folders.js +1 -1
  45. package/dist/controllers/graphql.js +2 -0
  46. package/dist/controllers/items.js +3 -1
  47. package/dist/controllers/license.js +119 -0
  48. package/dist/controllers/mcp/index.js +38 -0
  49. package/dist/controllers/mcp/oauth-clients.js +68 -0
  50. package/dist/controllers/mcp/oauth-consent-page.js +316 -0
  51. package/dist/controllers/mcp/oauth.js +381 -0
  52. package/dist/controllers/mcp/templates/oauth-consent.liquid +62 -0
  53. package/dist/controllers/mcp/templates/oauth-error.liquid +28 -0
  54. package/dist/controllers/notifications.js +1 -1
  55. package/dist/controllers/operations.js +1 -1
  56. package/dist/controllers/panels.js +1 -1
  57. package/dist/controllers/permissions.js +1 -1
  58. package/dist/controllers/policies.js +1 -1
  59. package/dist/controllers/presets.js +1 -1
  60. package/dist/controllers/revisions.js +3 -2
  61. package/dist/controllers/roles.js +1 -1
  62. package/dist/controllers/server.js +38 -10
  63. package/dist/controllers/shares.js +1 -1
  64. package/dist/controllers/translations.js +1 -1
  65. package/dist/controllers/users.js +1 -1
  66. package/dist/controllers/utils.js +2 -2
  67. package/dist/controllers/versions.js +12 -5
  68. package/dist/database/get-ast-from-query/lib/convert-wildcards.js +10 -1
  69. package/dist/database/get-ast-from-query/lib/parse-fields.js +2 -1
  70. package/dist/database/helpers/fn/dialects/mysql.js +7 -12
  71. package/dist/database/helpers/fn/dialects/oracle.js +3 -4
  72. package/dist/database/helpers/fn/dialects/postgres.js +4 -26
  73. package/dist/database/helpers/fn/json/mysql-json-path.js +22 -0
  74. package/dist/database/helpers/fn/json/parse-function.js +14 -6
  75. package/dist/database/helpers/fn/json/postgres-json-path.js +54 -0
  76. package/dist/database/migrations/20260110A-add-ai-provider-settings.js +4 -4
  77. package/dist/database/migrations/20260217A-null-item-versions.js +14 -0
  78. package/dist/database/migrations/20260312A-add-ai-translation-settings.js +18 -0
  79. package/dist/database/migrations/20260507A-add-licensing.js +22 -0
  80. package/dist/database/migrations/20260512A-add-autosave-revision-interval.js +14 -0
  81. package/dist/database/migrations/20260512B-add-mcp-oauth.js +87 -0
  82. package/dist/database/run-ast/lib/apply-query/filter/operator.js +116 -33
  83. package/dist/database/run-ast/lib/apply-query/index.js +4 -1
  84. package/dist/database/run-ast/lib/apply-query/sort.js +17 -7
  85. package/dist/database/run-ast/lib/get-db-query.js +21 -9
  86. package/dist/database/run-ast/lib/parse-current-level.js +2 -1
  87. package/dist/database/run-ast/run-ast.js +2 -1
  88. package/dist/database/run-ast/utils/get-column.js +2 -1
  89. package/dist/database/run-ast/utils/merge-with-parent-items.js +5 -3
  90. package/dist/extensions/lib/installation/manager.js +1 -1
  91. package/dist/extensions/lib/sandbox/register/operation.js +1 -1
  92. package/dist/extensions/lib/sync/sync.js +1 -1
  93. package/dist/extensions/manager.js +3 -3
  94. package/dist/flows.js +5 -5
  95. package/dist/license/entitlements/lib/collections.js +37 -0
  96. package/dist/license/entitlements/lib/custom-llms-enabled.js +18 -0
  97. package/dist/license/entitlements/lib/custom-permission-rules-enabled.js +41 -0
  98. package/dist/license/entitlements/lib/flows.js +29 -0
  99. package/dist/license/entitlements/lib/seats.js +103 -0
  100. package/dist/license/entitlements/lib/sso-enabled.js +45 -0
  101. package/dist/license/entitlements/manager.js +256 -0
  102. package/dist/license/index.js +4 -0
  103. package/dist/license/manager.js +505 -0
  104. package/dist/license/utils/compute-license-status.js +27 -0
  105. package/dist/license/utils/get-core-grace-expires-at.js +38 -0
  106. package/dist/license/utils/get-license-key.js +23 -0
  107. package/dist/license/utils/get-license-token.js +23 -0
  108. package/dist/license/utils/handle-license-error.js +41 -0
  109. package/dist/license/utils/is-in-core-grace-period.js +11 -0
  110. package/dist/license/utils/is-sso-bypass-allowed.js +21 -0
  111. package/dist/license/utils/use-rpc.js +33 -0
  112. package/dist/middleware/cache.js +4 -1
  113. package/dist/middleware/error-handler.js +11 -0
  114. package/dist/middleware/extract-token.js +11 -2
  115. package/dist/middleware/is-admin.js +16 -0
  116. package/dist/middleware/is-locked.js +16 -0
  117. package/dist/middleware/mcp-oauth-guard.js +23 -0
  118. package/dist/middleware/request-counter.js +5 -2
  119. package/dist/packages/types/dist/index.js +117 -122
  120. package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.js +10 -1
  121. package/dist/permissions/utils/get-unaliased-field-key.js +2 -1
  122. package/dist/request/is-denied-ip.js +2 -0
  123. package/dist/schedules/license.js +31 -0
  124. package/dist/schedules/oauth-cleanup.js +26 -0
  125. package/dist/schedules/retention.js +1 -1
  126. package/dist/schedules/telemetry.js +4 -1
  127. package/dist/schedules/tus.js +1 -1
  128. package/dist/schedules/utils/duration-to-cron.js +36 -0
  129. package/dist/services/activity.js +15 -0
  130. package/dist/services/authentication.js +12 -5
  131. package/dist/services/collections.js +40 -10
  132. package/dist/services/fields.js +6 -6
  133. package/dist/services/flows.js +12 -0
  134. package/dist/services/graphql/resolvers/system-admin.js +2 -2
  135. package/dist/services/graphql/resolvers/system-global.js +1 -1
  136. package/dist/services/graphql/resolvers/system.js +43 -27
  137. package/dist/services/graphql/schema/get-types.js +28 -7
  138. package/dist/services/graphql/schema/parse-query.js +8 -0
  139. package/dist/services/graphql/schema/read.js +12 -0
  140. package/dist/services/graphql/types/json-filter.js +30 -0
  141. package/dist/services/index.js +6 -6
  142. package/dist/services/items.js +32 -14
  143. package/dist/services/mcp-oauth/cimd.js +307 -0
  144. package/dist/services/mcp-oauth/index.js +1185 -0
  145. package/dist/services/mcp-oauth/types/error.js +22 -0
  146. package/dist/services/mcp-oauth/utils/cimd-egress.js +182 -0
  147. package/dist/services/mcp-oauth/utils/domain.js +21 -0
  148. package/dist/services/mcp-oauth/utils/loopback.js +11 -0
  149. package/dist/services/mcp-oauth/utils/redirect.js +84 -0
  150. package/dist/services/mcp-oauth/utils/registration-debug.js +131 -0
  151. package/dist/services/payload.js +2 -1
  152. package/dist/services/permissions.js +31 -9
  153. package/dist/services/revisions.js +15 -0
  154. package/dist/services/server.js +66 -68
  155. package/dist/services/settings.js +37 -3
  156. package/dist/services/users.js +23 -6
  157. package/dist/services/utils.js +6 -1
  158. package/dist/services/versions.js +160 -70
  159. package/dist/utils/calculate-field-depth.js +1 -0
  160. package/dist/utils/create-admin.js +3 -3
  161. package/dist/utils/deep-freeze.js +24 -0
  162. package/dist/utils/extract-function-name.js +13 -0
  163. package/dist/utils/generate-translations.js +5 -5
  164. package/dist/utils/get-accountability-for-token.js +13 -1
  165. package/dist/utils/get-cache-key.js +1 -1
  166. package/dist/utils/get-history-filter-query.js +22 -0
  167. package/dist/utils/get-schema.js +2 -2
  168. package/dist/utils/get-service.js +3 -3
  169. package/dist/utils/is-admin.js +9 -0
  170. package/dist/utils/is-unauthenticated.js +15 -0
  171. package/dist/utils/parse-oauth-scope.js +12 -0
  172. package/dist/utils/sanitize-query.js +2 -2
  173. package/dist/utils/split-field-path.js +29 -0
  174. package/dist/utils/store.js +1 -1
  175. package/dist/utils/transaction.js +2 -2
  176. package/dist/utils/translations-validation.js +2 -2
  177. package/dist/utils/validate-query.js +35 -4
  178. package/dist/utils/validate-user-count-integrity.js +28 -5
  179. package/dist/utils/verify-session-jwt.js +5 -2
  180. package/dist/utils/versioning/handle-version.js +131 -48
  181. package/dist/utils/versioning/remove-circular.js +17 -0
  182. package/dist/websocket/authenticate.js +2 -1
  183. package/dist/websocket/collab/collab.js +1 -1
  184. package/dist/websocket/collab/room.js +1 -1
  185. package/dist/websocket/controllers/base.js +12 -0
  186. package/dist/websocket/controllers/graphql.js +1 -1
  187. package/dist/websocket/handlers/subscribe.js +1 -1
  188. package/dist/websocket/messages.js +64 -64
  189. package/dist/websocket/utils/items.js +2 -2
  190. package/license +90 -80
  191. package/package.json +33 -32
  192. package/dist/controllers/mcp.js +0 -31
@@ -1,5 +1,7 @@
1
+ import { getOperation } from "../get-operation.js";
2
+ import { parseJsonPath } from "../../../../helpers/fn/json/parse-function.js";
1
3
  import { getColumn } from "../../../utils/get-column.js";
2
- import { getHelpers } from "../../../../helpers/index.js";
4
+ import { getFunctions, getHelpers } from "../../../../helpers/index.js";
3
5
  import { InvalidQueryError } from "@directus/errors";
4
6
  import { getOutputTypeForFunction } from "@directus/utils";
5
7
 
@@ -14,9 +16,51 @@ function castToNumber(value) {
14
16
  if (Number.isNaN(num)) throw new InvalidQueryError({ reason: `Invalid numeric value` });
15
17
  return num;
16
18
  }
19
+ /**
20
+ * Splits "table.column" into ["table", "column"], respecting dots inside parentheses.
21
+ * Handles keys like "table.json(col,path.with.dots)" correctly.
22
+ */
23
+ function splitTableColumn(key) {
24
+ let depth = 0;
25
+ for (let i = 0; i < key.length; i++) if (key[i] === "(") depth++;
26
+ else if (key[i] === ")") depth--;
27
+ else if (key[i] === "." && depth === 0) return [key.substring(0, i), key.substring(i + 1)];
28
+ return ["", key];
29
+ }
17
30
  function applyOperator(knex, dbQuery, schema, key, operator, compareValue, logical = "and", originalCollectionName) {
18
31
  const helpers = getHelpers(knex);
19
- const [table, column] = key.split(".");
32
+ const [table, column] = splitTableColumn(key);
33
+ if (operator === "_json") {
34
+ if (!Object.entries(compareValue).length) return;
35
+ const applyJsonConditions = (group, filterObj, innerLogical) => {
36
+ for (const [jsonPath, innerFilter] of Object.entries(filterObj)) {
37
+ if (jsonPath === "_or" || jsonPath === "_and") {
38
+ const subLogical = jsonPath === "_or" ? "or" : "and";
39
+ group[innerLogical].where((subGroup) => {
40
+ for (const subFilter of innerFilter) applyJsonConditions(subGroup, subFilter, subLogical);
41
+ });
42
+ continue;
43
+ }
44
+ const normalizedPath = parseJsonPath(jsonPath);
45
+ const innerValue = innerFilter[Object.keys(innerFilter)[0]];
46
+ const castNumeric = typeof innerValue === "number" || Array.isArray(innerValue) && innerValue.length > 0 && typeof innerValue[0] === "number";
47
+ const jsonExtractionRaw = getFunctions(knex, schema).json(table, column, {
48
+ type: "json",
49
+ jsonPath: normalizedPath,
50
+ originalCollectionName,
51
+ relationalCountOptions: void 0,
52
+ jsonReturnType: castNumeric ? "numeric" : "text"
53
+ });
54
+ const innerOp = getOperation(Object.keys(innerFilter)[0], Object.values(innerFilter)[0]);
55
+ if (!innerOp) continue;
56
+ applyOperatorToRaw(group, helpers, jsonExtractionRaw, innerOp.operator, innerOp.value, innerLogical);
57
+ }
58
+ };
59
+ dbQuery[logical].where((group) => {
60
+ applyJsonConditions(group, compareValue, "and");
61
+ });
62
+ return;
63
+ }
20
64
  const selectionRaw = getColumn(knex, table, column, false, schema, { originalCollectionName });
21
65
  if (operator === "_null" && compareValue !== false || operator === "_nnull" && compareValue === false || operator === "_eq" && compareValue === null) {
22
66
  dbQuery[logical].whereNull(selectionRaw);
@@ -26,12 +70,18 @@ function applyOperator(knex, dbQuery, schema, key, operator, compareValue, logic
26
70
  dbQuery[logical].whereNotNull(selectionRaw);
27
71
  return;
28
72
  }
29
- if (operator === "_empty" && compareValue !== false || operator === "_nempty" && compareValue === false) dbQuery[logical].andWhere((query) => {
30
- query.whereNull(key).orWhere(key, "=", "");
31
- });
32
- if (operator === "_nempty" && compareValue !== false || operator === "_empty" && compareValue === false) dbQuery[logical].andWhere((query) => {
33
- query.whereNotNull(key).andWhere(key, "!=", "");
34
- });
73
+ if (operator === "_empty" && compareValue !== false || operator === "_nempty" && compareValue === false) {
74
+ dbQuery[logical].andWhere((query) => {
75
+ query.whereNull(selectionRaw).orWhere(selectionRaw, "=", "");
76
+ });
77
+ return;
78
+ }
79
+ if (operator === "_nempty" && compareValue !== false || operator === "_empty" && compareValue === false) {
80
+ dbQuery[logical].andWhere((query) => {
81
+ query.whereNotNull(selectionRaw).andWhere(selectionRaw, "!=", "");
82
+ });
83
+ return;
84
+ }
35
85
  if (compareValue === void 0) return;
36
86
  if (Array.isArray(compareValue)) compareValue = compareValue.filter((val) => val !== void 0);
37
87
  if (column.includes("(") && column.includes(")")) {
@@ -43,7 +93,7 @@ function applyOperator(knex, dbQuery, schema, key, operator, compareValue, logic
43
93
  "decimal"
44
94
  ].includes(type)) compareValue = castToNumber(compareValue);
45
95
  }
46
- const [collection, field] = key.split(".");
96
+ const [collection, field] = splitTableColumn(key);
47
97
  const mappedCollection = originalCollectionName || collection;
48
98
  if (mappedCollection in schema.collections && field in schema.collections[mappedCollection].fields) {
49
99
  const type = schema.collections[mappedCollection].fields[field].type;
@@ -60,47 +110,80 @@ function applyOperator(knex, dbQuery, schema, key, operator, compareValue, logic
60
110
  "decimal"
61
111
  ].includes(type)) compareValue = castToNumber(compareValue);
62
112
  }
63
- if (operator === "_eq") dbQuery[logical].where(selectionRaw, "=", compareValue);
64
- if (operator === "_neq") dbQuery[logical].whereNot(selectionRaw, compareValue);
65
- if (operator === "_ieq") dbQuery[logical].whereRaw(`LOWER(??) = ?`, [selectionRaw, `${compareValue.toLowerCase()}`]);
66
- if (operator === "_nieq") dbQuery[logical].whereRaw(`LOWER(??) <> ?`, [selectionRaw, `${compareValue.toLowerCase()}`]);
67
- if (operator === "_contains") dbQuery[logical].where(selectionRaw, "like", `%${compareValue}%`);
68
- if (operator === "_ncontains") dbQuery[logical].whereNot(selectionRaw, "like", `%${compareValue}%`);
69
- if (operator === "_icontains") dbQuery[logical].whereRaw(`LOWER(??) LIKE ?`, [selectionRaw, `%${compareValue.toLowerCase()}%`]);
70
- if (operator === "_nicontains") dbQuery[logical].whereRaw(`LOWER(??) NOT LIKE ?`, [selectionRaw, `%${compareValue.toLowerCase()}%`]);
71
- if (operator === "_starts_with") dbQuery[logical].where(key, "like", `${compareValue}%`);
72
- if (operator === "_nstarts_with") dbQuery[logical].whereNot(key, "like", `${compareValue}%`);
73
- if (operator === "_istarts_with") dbQuery[logical].whereRaw(`LOWER(??) LIKE ?`, [selectionRaw, `${compareValue.toLowerCase()}%`]);
74
- if (operator === "_nistarts_with") dbQuery[logical].whereRaw(`LOWER(??) NOT LIKE ?`, [selectionRaw, `${compareValue.toLowerCase()}%`]);
75
- if (operator === "_ends_with") dbQuery[logical].where(key, "like", `%${compareValue}`);
76
- if (operator === "_nends_with") dbQuery[logical].whereNot(key, "like", `%${compareValue}`);
77
- if (operator === "_iends_with") dbQuery[logical].whereRaw(`LOWER(??) LIKE ?`, [selectionRaw, `%${compareValue.toLowerCase()}`]);
78
- if (operator === "_niends_with") dbQuery[logical].whereRaw(`LOWER(??) NOT LIKE ?`, [selectionRaw, `%${compareValue.toLowerCase()}`]);
79
- if (operator === "_gt") dbQuery[logical].where(selectionRaw, ">", compareValue);
80
- if (operator === "_gte") dbQuery[logical].where(selectionRaw, ">=", compareValue);
81
- if (operator === "_lt") dbQuery[logical].where(selectionRaw, "<", compareValue);
82
- if (operator === "_lte") dbQuery[logical].where(selectionRaw, "<=", compareValue);
113
+ applyOperatorToRaw(dbQuery, helpers, selectionRaw, operator, compareValue, logical, key);
114
+ }
115
+ function applyOperatorToRaw(dbQuery, helpers, raw, operator, compareValue, logical, key) {
116
+ if (operator === "_null" && compareValue !== false || operator === "_nnull" && compareValue === false || operator === "_eq" && compareValue === null) {
117
+ dbQuery[logical].whereNull(raw);
118
+ return;
119
+ }
120
+ if (operator === "_nnull" && compareValue !== false || operator === "_null" && compareValue === false || operator === "_neq" && compareValue === null) {
121
+ dbQuery[logical].whereNotNull(raw);
122
+ return;
123
+ }
124
+ if (operator === "_empty" && compareValue !== false || operator === "_nempty" && compareValue === false) {
125
+ dbQuery[logical].andWhere((query) => {
126
+ query.whereNull(raw).orWhere(raw, "=", "");
127
+ });
128
+ return;
129
+ }
130
+ if (operator === "_nempty" && compareValue !== false || operator === "_empty" && compareValue === false) {
131
+ dbQuery[logical].andWhere((query) => {
132
+ query.whereNotNull(raw).andWhere(raw, "!=", "");
133
+ });
134
+ return;
135
+ }
136
+ if (compareValue === void 0) return;
137
+ if (Array.isArray(compareValue)) compareValue = compareValue.filter((val) => val !== void 0);
138
+ if (operator === "_eq") dbQuery[logical].where(raw, "=", compareValue);
139
+ if (operator === "_neq") dbQuery[logical].whereNot(raw, compareValue);
140
+ if (operator === "_ieq") dbQuery[logical].whereRaw(`LOWER(??) = ?`, [raw, `${compareValue.toLowerCase()}`]);
141
+ if (operator === "_nieq") dbQuery[logical].whereRaw(`LOWER(??) <> ?`, [raw, `${compareValue.toLowerCase()}`]);
142
+ if (operator === "_contains") dbQuery[logical].where(raw, "like", `%${compareValue}%`);
143
+ if (operator === "_ncontains") dbQuery[logical].whereNot(raw, "like", `%${compareValue}%`);
144
+ if (operator === "_icontains") dbQuery[logical].whereRaw(`LOWER(??) LIKE ?`, [raw, `%${compareValue.toLowerCase()}%`]);
145
+ if (operator === "_nicontains") dbQuery[logical].whereRaw(`LOWER(??) NOT LIKE ?`, [raw, `%${compareValue.toLowerCase()}%`]);
146
+ if (operator === "_starts_with") dbQuery[logical].where(raw, "like", `${compareValue}%`);
147
+ if (operator === "_nstarts_with") dbQuery[logical].whereNot(raw, "like", `${compareValue}%`);
148
+ if (operator === "_istarts_with") dbQuery[logical].whereRaw(`LOWER(??) LIKE ?`, [raw, `${compareValue.toLowerCase()}%`]);
149
+ if (operator === "_nistarts_with") dbQuery[logical].whereRaw(`LOWER(??) NOT LIKE ?`, [raw, `${compareValue.toLowerCase()}%`]);
150
+ if (operator === "_ends_with") dbQuery[logical].where(raw, "like", `%${compareValue}`);
151
+ if (operator === "_nends_with") dbQuery[logical].whereNot(raw, "like", `%${compareValue}`);
152
+ if (operator === "_iends_with") dbQuery[logical].whereRaw(`LOWER(??) LIKE ?`, [raw, `%${compareValue.toLowerCase()}`]);
153
+ if (operator === "_niends_with") dbQuery[logical].whereRaw(`LOWER(??) NOT LIKE ?`, [raw, `%${compareValue.toLowerCase()}`]);
154
+ if (operator === "_gt") dbQuery[logical].where(raw, ">", compareValue);
155
+ if (operator === "_gte") dbQuery[logical].where(raw, ">=", compareValue);
156
+ if (operator === "_lt") dbQuery[logical].where(raw, "<", compareValue);
157
+ if (operator === "_lte") dbQuery[logical].where(raw, "<=", compareValue);
83
158
  if (operator === "_in") {
84
159
  let value = compareValue;
85
160
  if (typeof value === "string") value = value.split(",");
86
- dbQuery[logical].whereIn(selectionRaw, value);
161
+ if (value.length === 0) dbQuery[logical].whereIn(raw, []);
162
+ else {
163
+ const placeholders = value.map(() => "?").join(", ");
164
+ dbQuery[logical].whereRaw(`?? in (${placeholders})`, [raw, ...value]);
165
+ }
87
166
  }
88
167
  if (operator === "_nin") {
89
168
  let value = compareValue;
90
169
  if (typeof value === "string") value = value.split(",");
91
- dbQuery[logical].whereNotIn(selectionRaw, value);
170
+ if (value.length === 0) dbQuery[logical].whereNotIn(raw, []);
171
+ else {
172
+ const placeholders = value.map(() => "?").join(", ");
173
+ dbQuery[logical].whereRaw(`?? not in (${placeholders})`, [raw, ...value]);
174
+ }
92
175
  }
93
176
  if (operator === "_between") {
94
177
  let value = compareValue;
95
178
  if (typeof value === "string") value = value.split(",");
96
179
  if (value.length !== 2) return;
97
- dbQuery[logical].whereBetween(selectionRaw, value);
180
+ dbQuery[logical].whereBetween(raw, value);
98
181
  }
99
182
  if (operator === "_nbetween") {
100
183
  let value = compareValue;
101
184
  if (typeof value === "string") value = value.split(",");
102
185
  if (value.length !== 2) return;
103
- dbQuery[logical].whereNotBetween(selectionRaw, value);
186
+ dbQuery[logical].whereNotBetween(raw, value);
104
187
  }
105
188
  if (operator == "_intersects") dbQuery[logical].whereRaw(helpers.st.intersects(key, compareValue));
106
189
  if (operator == "_nintersects") dbQuery[logical].whereRaw(helpers.st.nintersects(key, compareValue));
@@ -20,7 +20,10 @@ function applyQuery(knex, collection, dbQuery, query, schema, cases, permissions
20
20
  if (query.offset) applyOffset(knex, dbQuery, query.offset);
21
21
  if (query.page && query.limit && query.limit !== -1) applyOffset(knex, dbQuery, query.limit * (query.page - 1));
22
22
  if (query.sort && !options?.isInnerQuery && !options?.hasMultiRelationalSort) {
23
- const sortResult = applySort(knex, schema, dbQuery, query.sort, query.aggregate, collection, aliasMap);
23
+ const sortResult = applySort(knex, schema, dbQuery, query.sort, collection, aliasMap, {
24
+ aggregate: query.aggregate,
25
+ fieldAliasMap: { ...query.alias ?? {} }
26
+ });
24
27
  if (!hasJoins) hasJoins = sortResult.hasJoins;
25
28
  }
26
29
  const filter = joinFilterWithCases(query.filter, cases);
@@ -1,15 +1,18 @@
1
1
  import { getColumnPath } from "../../../../utils/get-column-path.js";
2
2
  import { addJoin } from "./add-join.js";
3
3
  import { getColumn } from "../../utils/get-column.js";
4
+ import { extractFunctionName } from "../../../../utils/extract-function-name.js";
5
+ import { splitFieldPath } from "../../../../utils/split-field-path.js";
4
6
  import { getRelationInfo } from "@directus/utils";
5
7
 
6
8
  //#region src/database/run-ast/lib/apply-query/sort.ts
7
- function applySort(knex, schema, rootQuery, sort, aggregate, collection, aliasMap, returnRecords = false) {
9
+ function applySort(knex, schema, rootQuery, sort, collection, aliasMap, options) {
10
+ const { aggregate, returnRecords = false, fieldAliasMap } = options ?? {};
8
11
  const relations = schema.relations;
9
12
  let hasJoins = false;
10
13
  let hasMultiRelationalSort = false;
11
14
  const sortRecords = sort.map((sortField) => {
12
- const column = sortField.split(".");
15
+ const column = splitFieldPath(sortField);
13
16
  let order = "asc";
14
17
  if (sortField.startsWith("-")) order = "desc";
15
18
  if (column[0].startsWith("-")) column[0] = column[0].substring(1);
@@ -30,11 +33,17 @@ function applySort(knex, schema, rootQuery, sort, aggregate, collection, aliasMa
30
33
  };
31
34
  }
32
35
  if (column.length === 1) {
33
- const pathRoot = column[0].split(":")[0];
36
+ const rawField = column[0];
37
+ const resolvedField = fieldAliasMap?.[rawField] ?? rawField;
38
+ if (extractFunctionName(resolvedField) === "json") return {
39
+ order,
40
+ column: returnRecords ? resolvedField : getColumn(knex, collection, resolvedField, false, schema, { jsonReturnType: "text" })
41
+ };
42
+ const pathRoot = resolvedField.split(":")[0];
34
43
  const { relation, relationType } = getRelationInfo(relations, collection, pathRoot);
35
44
  if (!relation || ["m2o", "a2o"].includes(relationType ?? "")) return {
36
45
  order,
37
- column: returnRecords ? column[0] : getColumn(knex, collection, column[0], false, schema)
46
+ column: returnRecords ? resolvedField : getColumn(knex, collection, resolvedField, false, schema)
38
47
  };
39
48
  }
40
49
  const { hasMultiRelational, isJoinAdded } = addJoin({
@@ -45,19 +54,20 @@ function applySort(knex, schema, rootQuery, sort, aggregate, collection, aliasMa
45
54
  schema,
46
55
  knex
47
56
  });
48
- const { columnPath } = getColumnPath({
57
+ const { columnPath, targetCollection } = getColumnPath({
49
58
  path: column,
50
59
  collection,
51
60
  aliasMap,
52
61
  relations,
53
62
  schema
54
63
  });
55
- const [alias, field] = columnPath.split(".");
64
+ const [alias, ...rest] = splitFieldPath(columnPath);
65
+ const field = rest.join(".");
56
66
  if (!hasJoins) hasJoins = isJoinAdded;
57
67
  if (!hasMultiRelationalSort) hasMultiRelationalSort = hasMultiRelational;
58
68
  return {
59
69
  order,
60
- column: returnRecords ? columnPath : getColumn(knex, alias, field, false, schema)
70
+ column: returnRecords ? columnPath : getColumn(knex, alias, field, false, schema, { originalCollectionName: targetCollection })
61
71
  };
62
72
  });
63
73
  if (returnRecords) return {
@@ -1,7 +1,10 @@
1
1
  import { generateQueryAlias } from "../utils/generate-alias.js";
2
2
  import { applyCaseWhen } from "../utils/apply-case-when.js";
3
+ import { applyFunctionToColumnName } from "../utils/apply-function-to-column-name.js";
3
4
  import { getColumn } from "../utils/get-column.js";
4
5
  import { applyLimit } from "./apply-query/pagination.js";
6
+ import { extractFunctionName } from "../../../utils/extract-function-name.js";
7
+ import { splitFieldPath } from "../../../utils/split-field-path.js";
5
8
  import { applySort } from "./apply-query/sort.js";
6
9
  import applyQuery from "./apply-query/index.js";
7
10
  import { getHelpers } from "../../helpers/index.js";
@@ -43,7 +46,13 @@ function getDBQuery({ table, fieldNodes, o2mNodes, query, cases, permissions, pe
43
46
  const innerQuerySortRecords = [];
44
47
  let hasMultiRelationalSort;
45
48
  if (queryCopy.sort) {
46
- const sortResult = applySort(knex, schema, dbQuery, queryCopy.sort, queryCopy.aggregate, table, aliasMap, true);
49
+ const fieldAliasMap = { ...queryCopy.alias ?? {} };
50
+ for (const node of fieldNodes) if (node.type === "functionField" && extractFunctionName(node.name) === "json") fieldAliasMap[applyFunctionToColumnName(node.fieldKey)] = node.name;
51
+ const sortResult = applySort(knex, schema, dbQuery, queryCopy.sort, table, aliasMap, {
52
+ aggregate: queryCopy.aggregate,
53
+ returnRecords: true,
54
+ fieldAliasMap
55
+ });
47
56
  if (sortResult) {
48
57
  sortRecords = sortResult.sortRecords;
49
58
  hasMultiRelationalSort = sortResult.hasMultiRelationalSort;
@@ -85,16 +94,18 @@ function getDBQuery({ table, fieldNodes, o2mNodes, query, cases, permissions, pe
85
94
  if (orderByString.length !== 0) orderByString += ", ";
86
95
  const sortAlias = generateQueryAlias(table, queryCopy, `sort_${index}_${sortRecord.column}_${sortRecord.order}`);
87
96
  let orderByColumn;
88
- if (sortRecord.column.includes(".")) {
89
- const [alias, field] = sortRecord.column.split(".");
97
+ const colParts = splitFieldPath(sortRecord.column);
98
+ if (colParts.length > 1) {
99
+ const [alias, ...rest] = colParts;
100
+ const field = rest.join(".");
90
101
  const originalCollectionName = getCollectionFromAlias(alias, aliasMap);
91
102
  dbQuery.select(getColumn(knex, alias, field, sortAlias, schema, { originalCollectionName }));
92
103
  orderByString += `?? ${sortRecord.order}`;
93
104
  orderByColumn = getColumn(knex, alias, field, false, schema, { originalCollectionName });
94
105
  } else {
95
- dbQuery.select(getColumn(knex, table, sortRecord.column, sortAlias, schema));
106
+ dbQuery.select(getColumn(knex, table, sortRecord.column, sortAlias, schema, { jsonReturnType: "text" }));
96
107
  orderByString += `?? ${sortRecord.order}`;
97
- orderByColumn = getColumn(knex, table, sortRecord.column, false, schema);
108
+ orderByColumn = getColumn(knex, table, sortRecord.column, false, schema, { jsonReturnType: "text" });
98
109
  }
99
110
  orderByFields.push(orderByColumn);
100
111
  innerQuerySortRecords.push({
@@ -111,10 +122,11 @@ function getDBQuery({ table, fieldNodes, o2mNodes, query, cases, permissions, pe
111
122
  dbQuery.orderByRaw(orderByString, orderByFields);
112
123
  } else {
113
124
  sortRecords.map((sortRecord) => {
114
- if (sortRecord.column.includes(".")) {
115
- const [alias, field] = sortRecord.column.split(".");
116
- sortRecord.column = getColumn(knex, alias, field, false, schema, { originalCollectionName: getCollectionFromAlias(alias, aliasMap) });
117
- } else sortRecord.column = getColumn(knex, table, sortRecord.column, false, schema);
125
+ const colParts = splitFieldPath(sortRecord.column);
126
+ if (colParts.length > 1) {
127
+ const [alias, ...rest] = colParts;
128
+ sortRecord.column = getColumn(knex, alias, rest.join("."), false, schema, { originalCollectionName: getCollectionFromAlias(alias, aliasMap) });
129
+ } else sortRecord.column = getColumn(knex, table, sortRecord.column, false, schema, { jsonReturnType: "text" });
118
130
  });
119
131
  dbQuery.orderBy(sortRecords);
120
132
  }
@@ -1,4 +1,5 @@
1
1
  import { parseJsonFunction } from "../../helpers/fn/json/parse-function.js";
2
+ import { extractFunctionName } from "../../../utils/extract-function-name.js";
2
3
  import { parseFilterKey } from "../../../utils/parse-filter-key.js";
3
4
 
4
5
  //#region src/database/run-ast/lib/parse-current-level.ts
@@ -10,7 +11,7 @@ async function parseCurrentLevel(schema, collection, children, query) {
10
11
  for (const child of children) {
11
12
  if (child.type === "field" || child.type === "functionField") {
12
13
  let fieldName;
13
- if (child.type == "functionField" && child.name.startsWith("json")) fieldName = parseJsonFunction(child.name).field;
14
+ if (child.type === "functionField" && extractFunctionName(child.name) === "json") fieldName = parseJsonFunction(child.name).field;
14
15
  else fieldName = parseFilterKey(child.name).fieldName;
15
16
  if (columnsInCollection.includes(fieldName)) columnsToSelectInternal.push(child.fieldKey);
16
17
  continue;
@@ -1,4 +1,5 @@
1
1
  import { applyFunctionToColumnName } from "./utils/apply-function-to-column-name.js";
2
+ import { extractFunctionName } from "../../utils/extract-function-name.js";
2
3
  import database_default from "../index.js";
3
4
  import { fetchPolicies } from "../../permissions/lib/fetch-policies.js";
4
5
  import { fetchPermissions } from "../../permissions/lib/fetch-permissions.js";
@@ -57,7 +58,7 @@ async function runAst(originalAST, schema, accountability, options) {
57
58
  schema
58
59
  });
59
60
  const aliasMap = { ...query.alias };
60
- for (const child of children) if (child.type === "functionField" && child.name.startsWith("json(")) {
61
+ for (const child of children) if (child.type === "functionField" && extractFunctionName(child.name) === "json") {
61
62
  const alias = applyFunctionToColumnName(child.fieldKey);
62
63
  aliasMap[alias] = child.name;
63
64
  }
@@ -42,7 +42,8 @@ function getColumn(knex, table, column, alias = applyFunctionToColumnName(column
42
42
  permissions: options.permissions
43
43
  } : void 0,
44
44
  originalCollectionName: options?.originalCollectionName,
45
- jsonPath
45
+ jsonPath,
46
+ ...options?.jsonReturnType && { jsonReturnType: options.jsonReturnType }
46
47
  });
47
48
  if (alias) return knex.raw(result + " AS ??", [alias]);
48
49
  return result;
@@ -9,13 +9,13 @@ function mergeWithParentItems(schema, nestedItem, parentItem, nestedNode, fieldA
9
9
  const parentItems = clone(toArray(parentItem));
10
10
  if (nestedNode.type === "m2o") {
11
11
  const parentsByForeignKey = /* @__PURE__ */ new Map();
12
+ const nestedPrimaryKeyField = schema.collections[nestedNode.relation.related_collection].primary;
12
13
  for (const parentItem$1 of parentItems) {
13
- const relationKey = parentItem$1[nestedNode.relation.field];
14
+ const relationKey = parentItem$1[nestedNode.relation.field]?.[nestedPrimaryKeyField] ?? parentItem$1[nestedNode.relation.field];
14
15
  if (!parentsByForeignKey.has(relationKey)) parentsByForeignKey.set(relationKey, []);
15
16
  parentItem$1[nestedNode.fieldKey] = null;
16
17
  parentsByForeignKey.get(relationKey).push(parentItem$1);
17
18
  }
18
- const nestedPrimaryKeyField = schema.collections[nestedNode.relation.related_collection].primary;
19
19
  for (const nestedItem$1 of nestedItems) {
20
20
  const nestedPK = nestedItem$1[nestedPrimaryKeyField];
21
21
  if (nestedPK === null) continue;
@@ -84,8 +84,10 @@ function mergeWithParentItems(schema, nestedItem, parentItem, nestedNode, fieldA
84
84
  parentItem$1[nestedNode.fieldKey] = null;
85
85
  continue;
86
86
  }
87
+ const relatedPrimaryKeyField = schema.collections[relatedCollection].primary;
88
+ const foreignKey = parentItem$1[nestedNode.relation.field]?.[relatedPrimaryKeyField] ?? parentItem$1[nestedNode.relation.field];
87
89
  const itemChild = nestedItem[relatedCollection].find((nestedItem$1) => {
88
- return nestedItem$1[nestedNode.relatedKey[relatedCollection]] == parentItem$1[nestedNode.fieldKey];
90
+ return nestedItem$1[nestedNode.relatedKey[relatedCollection]] == foreignKey;
89
91
  });
90
92
  parentItem$1[nestedNode.fieldKey] = itemChild || null;
91
93
  }
@@ -7,10 +7,10 @@ import { useEnv } from "@directus/env";
7
7
  import { ErrorCode, ServiceUnavailableError, isDirectusError } from "@directus/errors";
8
8
  import { move, remove } from "fs-extra";
9
9
  import { Readable } from "node:stream";
10
- import PQueue from "p-queue";
11
10
  import { download } from "@directus/extensions-registry";
12
11
  import { EXTENSION_PKG_KEY, ExtensionManifest } from "@directus/extensions";
13
12
  import DriverLocal from "@directus/storage-driver-local";
13
+ import PQueue from "p-queue";
14
14
  import { extract } from "tar";
15
15
 
16
16
  //#region src/extensions/lib/installation/manager.ts
@@ -1,5 +1,5 @@
1
- import { callReference } from "./call-reference.js";
2
1
  import { getFlowManager } from "../../../../flows.js";
2
+ import { callReference } from "./call-reference.js";
3
3
 
4
4
  //#region src/extensions/lib/sandbox/register/operation.ts
5
5
  function registerOperationGenerator() {
@@ -13,8 +13,8 @@ import { useEnv } from "@directus/env";
13
13
  import { normalizePath } from "@directus/utils";
14
14
  import { dirname, join, relative, resolve, sep } from "node:path";
15
15
  import { pipeline } from "node:stream/promises";
16
- import PQueue from "p-queue";
17
16
  import { createWriteStream } from "node:fs";
17
+ import PQueue from "p-queue";
18
18
  import mid from "node-machine-id";
19
19
 
20
20
  //#region src/extensions/lib/sync/sync.ts
@@ -4,11 +4,12 @@ import { useLogger } from "../logger/index.js";
4
4
  import { getExtensionsPath } from "./lib/get-extensions-path.js";
5
5
  import database_default from "../database/index.js";
6
6
  import emitter_default, { Emitter } from "../emitter.js";
7
+ import { scheduleSynchronizedJob, validateCron } from "../utils/schedule.js";
7
8
  import { getSchema } from "../utils/get-schema.js";
9
+ import { getFlowManager } from "../flows.js";
8
10
  import { deleteFromRequireCache } from "../utils/delete-from-require-cache.js";
9
11
  import getModuleDefault from "../utils/get-module-default.js";
10
12
  import { importFileUrl } from "../utils/import-file-url.js";
11
- import { scheduleSynchronizedJob, validateCron } from "../utils/schedule.js";
12
13
  import { getExtensionsSettings } from "./lib/get-extensions-settings.js";
13
14
  import { getExtensions } from "./lib/get-extensions.js";
14
15
  import { getSharedDepsMapping } from "./lib/get-shared-deps-mapping.js";
@@ -18,7 +19,6 @@ import { instantiateSandboxSdk } from "./lib/sandbox/sdk/instantiate.js";
18
19
  import { syncExtensions } from "./lib/sync/sync.js";
19
20
  import { wrapEmbeds } from "./lib/wrap-embeds.js";
20
21
  import { services_exports } from "../services/index.js";
21
- import { getFlowManager } from "../flows.js";
22
22
  import { readFile, readdir } from "node:fs/promises";
23
23
  import path from "path";
24
24
  import { useEnv } from "@directus/env";
@@ -29,7 +29,6 @@ import { pathToRelativeUrl, processId } from "@directus/utils/node";
29
29
  import { fileURLToPath } from "node:url";
30
30
  import { HYBRID_EXTENSION_TYPES } from "@directus/constants";
31
31
  import { dirname, join as join$1, relative, resolve, sep } from "node:path";
32
- import PQueue from "p-queue";
33
32
  import os from "node:os";
34
33
  import { APP_SHARED_DEPS } from "@directus/extensions";
35
34
  import { generateExtensionsEntrypoint } from "@directus/extensions/node";
@@ -39,6 +38,7 @@ import nodeResolveDefault from "@rollup/plugin-node-resolve";
39
38
  import virtualDefault from "@rollup/plugin-virtual";
40
39
  import chokidar from "chokidar";
41
40
  import ivm from "isolated-vm";
41
+ import PQueue from "p-queue";
42
42
  import { rolldown } from "rolldown";
43
43
  import { rollup } from "rollup";
44
44
 
package/dist/flows.js CHANGED
@@ -5,23 +5,23 @@ import database_default from "./database/index.js";
5
5
  import { fetchPolicies } from "./permissions/lib/fetch-policies.js";
6
6
  import { fetchPermissions } from "./permissions/lib/fetch-permissions.js";
7
7
  import emitter_default from "./emitter.js";
8
- import { getSchema } from "./utils/get-schema.js";
8
+ import { scheduleSynchronizedJob, validateCron } from "./utils/schedule.js";
9
9
  import { ActivityService } from "./services/activity.js";
10
10
  import { getService } from "./utils/get-service.js";
11
- import { scheduleSynchronizedJob, validateCron } from "./utils/schedule.js";
11
+ import { getSchema } from "./utils/get-schema.js";
12
+ import { FlowsService } from "./services/flows.js";
12
13
  import { RevisionsService } from "./services/revisions.js";
13
- import { services_exports } from "./services/index.js";
14
14
  import { constructFlowTree } from "./utils/construct-flow-tree.js";
15
15
  import { redactObject } from "./utils/redact-object.js";
16
- import { FlowsService } from "./services/flows.js";
16
+ import { services_exports } from "./services/index.js";
17
17
  import { useEnv } from "@directus/env";
18
18
  import { ForbiddenError } from "@directus/errors";
19
19
  import { applyOptionsData, deepMap, getRedactedString, isValidJSON, parseJSON, toArray } from "@directus/utils";
20
20
  import { pick } from "lodash-es";
21
21
  import { Action } from "@directus/constants";
22
22
  import { isSystemCollection } from "@directus/system-data";
23
- import { get as get$1 } from "micromustache";
24
23
  import PQueue from "p-queue";
24
+ import { get as get$1 } from "micromustache";
25
25
 
26
26
  //#region src/flows.ts
27
27
  let flowManager;
@@ -0,0 +1,37 @@
1
+ import database_default from "../../../database/index.js";
2
+ import { getSchema } from "../../../utils/get-schema.js";
3
+ import "../../../services/index.js";
4
+ import { CollectionsService } from "../../../services/collections.js";
5
+ import { useEnv } from "@directus/env";
6
+ import { isSystemCollection } from "@directus/system-data";
7
+
8
+ //#region src/license/entitlements/lib/collections.ts
9
+ async function getActiveCollections(opts) {
10
+ const env = useEnv();
11
+ const knex = opts?.knex ?? database_default();
12
+ return (await new CollectionsService({
13
+ schema: await getSchema({ database: knex }),
14
+ knex
15
+ }).readByQuery()).filter((collection) => {
16
+ const isFolder = collection.schema === null;
17
+ const isDBOnly = collection.meta === null;
18
+ const isDisabled = collection.meta?.status !== "active";
19
+ const isEnvExcluded = env["DB_EXCLUDE_TABLES"].includes(collection.collection);
20
+ return !isFolder && !isSystemCollection(collection.collection) && !isDBOnly && !isDisabled && !isEnvExcluded;
21
+ }).map((collection) => collection.collection);
22
+ }
23
+ async function countActiveCollections(opts) {
24
+ return (await getActiveCollections(opts)).length;
25
+ }
26
+ async function resolveCollections(collections, ctx) {
27
+ const collectionsService = new CollectionsService({
28
+ schema: await getSchema(),
29
+ accountability: ctx?.accountability
30
+ });
31
+ await Promise.allSettled(collections.map((collection) => {
32
+ return collectionsService.updateOne(collection, { meta: { status: "inactive" } });
33
+ }));
34
+ }
35
+
36
+ //#endregion
37
+ export { countActiveCollections, getActiveCollections, resolveCollections };
@@ -0,0 +1,18 @@
1
+ import { CUSTOM_LLM_FIELDS } from "../../../constants.js";
2
+ import database_default from "../../../database/index.js";
3
+ import { getSchema } from "../../../utils/get-schema.js";
4
+ import { SettingsService } from "../../../services/settings.js";
5
+ import "../../../services/index.js";
6
+
7
+ //#region src/license/entitlements/lib/custom-llms-enabled.ts
8
+ async function checkCustomLLM(opts) {
9
+ const knex = opts?.knex ?? database_default();
10
+ const data = await new SettingsService({
11
+ schema: await getSchema({ database: knex }),
12
+ knex
13
+ }).readSingleton({ fields: [...CUSTOM_LLM_FIELDS] });
14
+ return !CUSTOM_LLM_FIELDS.find((key) => data[key] !== null);
15
+ }
16
+
17
+ //#endregion
18
+ export { checkCustomLLM };
@@ -0,0 +1,41 @@
1
+ import database_default from "../../../database/index.js";
2
+ import { ItemsService } from "../../../services/items.js";
3
+ import { getSchema } from "../../../utils/get-schema.js";
4
+ import "../../../services/index.js";
5
+ import { isEqual } from "lodash-es";
6
+ import { appAccessMinimalPermissions, appRecommendedPermissions } from "@directus/system-data";
7
+
8
+ //#region src/license/entitlements/lib/custom-permission-rules-enabled.ts
9
+ function hasCustomRule(permission) {
10
+ if (permission.system === true) return false;
11
+ return permission.fields?.includes("*") !== true || Object.keys(permission.permissions ?? {}).length > 0 || Object.keys(permission.validation ?? {}).length > 0 || Object.keys(permission.presets ?? {}).length > 0;
12
+ }
13
+ function isRecommendedAppPermission(permission) {
14
+ if (permission.validation || permission.presets) return false;
15
+ const foundPermission = appRecommendedPermissions.find((p) => p.action === permission.action && p.collection === permission.collection);
16
+ if (!foundPermission) return false;
17
+ return isEqual(foundPermission.fields ?? null, permission.fields ?? null) && isEqual(foundPermission.permissions ?? null, permission.permissions ?? null);
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
+ async function checkCustomPermissionRules(opts) {
25
+ const knex = opts?.knex ?? database_default();
26
+ return (await new ItemsService("directus_permissions", {
27
+ schema: await getSchema({ database: knex }),
28
+ knex
29
+ }).readByQuery({
30
+ limit: -1,
31
+ filter: { _or: [
32
+ { permissions: { _nnull: true } },
33
+ { validation: { _nnull: true } },
34
+ { presets: { _nnull: true } },
35
+ { fields: { _nnull: true } }
36
+ ] }
37
+ })).filter((p) => hasCustomRule(p) && !isRecommendedAppPermission(p)).length === 0;
38
+ }
39
+
40
+ //#endregion
41
+ export { checkCustomPermissionRules, hasCustomRule, isMinimumAppPermission, isRecommendedAppPermission };