@directus/api 35.2.0 → 36.0.0-rc.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 (188) 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 +33 -9
  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 -9
  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/extensions/lib/installation/manager.js +1 -1
  90. package/dist/extensions/lib/sandbox/register/operation.js +1 -1
  91. package/dist/extensions/lib/sync/sync.js +1 -1
  92. package/dist/extensions/manager.js +3 -3
  93. package/dist/flows.js +5 -5
  94. package/dist/license/entitlements/lib/collections.js +37 -0
  95. package/dist/license/entitlements/lib/custom-llms-enabled.js +18 -0
  96. package/dist/license/entitlements/lib/custom-permission-rules-enabled.js +41 -0
  97. package/dist/license/entitlements/lib/flows.js +29 -0
  98. package/dist/license/entitlements/lib/seats.js +103 -0
  99. package/dist/license/entitlements/lib/sso-enabled.js +45 -0
  100. package/dist/license/entitlements/manager.js +256 -0
  101. package/dist/license/index.js +4 -0
  102. package/dist/license/manager.js +505 -0
  103. package/dist/license/utils/compute-license-status.js +27 -0
  104. package/dist/license/utils/get-core-grace-expires-at.js +38 -0
  105. package/dist/license/utils/get-license-key.js +23 -0
  106. package/dist/license/utils/get-license-token.js +23 -0
  107. package/dist/license/utils/handle-license-error.js +41 -0
  108. package/dist/license/utils/is-in-core-grace-period.js +11 -0
  109. package/dist/license/utils/is-sso-bypass-allowed.js +21 -0
  110. package/dist/license/utils/use-rpc.js +33 -0
  111. package/dist/middleware/cache.js +4 -1
  112. package/dist/middleware/error-handler.js +11 -0
  113. package/dist/middleware/extract-token.js +11 -2
  114. package/dist/middleware/is-admin.js +16 -0
  115. package/dist/middleware/is-locked.js +16 -0
  116. package/dist/middleware/mcp-oauth-guard.js +23 -0
  117. package/dist/middleware/request-counter.js +5 -2
  118. package/dist/packages/types/dist/index.js +117 -122
  119. package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.js +10 -1
  120. package/dist/permissions/utils/get-unaliased-field-key.js +2 -1
  121. package/dist/request/is-denied-ip.js +2 -0
  122. package/dist/schedules/license.js +31 -0
  123. package/dist/schedules/oauth-cleanup.js +26 -0
  124. package/dist/schedules/retention.js +1 -1
  125. package/dist/schedules/telemetry.js +4 -1
  126. package/dist/schedules/tus.js +1 -1
  127. package/dist/schedules/utils/duration-to-cron.js +36 -0
  128. package/dist/services/activity.js +15 -0
  129. package/dist/services/authentication.js +12 -5
  130. package/dist/services/collections.js +40 -10
  131. package/dist/services/fields.js +6 -6
  132. package/dist/services/flows.js +12 -0
  133. package/dist/services/graphql/resolvers/system-admin.js +2 -2
  134. package/dist/services/graphql/resolvers/system-global.js +1 -1
  135. package/dist/services/graphql/resolvers/system.js +34 -18
  136. package/dist/services/graphql/schema/get-types.js +23 -2
  137. package/dist/services/graphql/schema/parse-query.js +8 -0
  138. package/dist/services/graphql/schema/read.js +12 -0
  139. package/dist/services/graphql/types/json-filter.js +30 -0
  140. package/dist/services/index.js +6 -6
  141. package/dist/services/items.js +32 -14
  142. package/dist/services/mcp-oauth/cimd.js +307 -0
  143. package/dist/services/mcp-oauth/index.js +1185 -0
  144. package/dist/services/mcp-oauth/types/error.js +22 -0
  145. package/dist/services/mcp-oauth/utils/cimd-egress.js +182 -0
  146. package/dist/services/mcp-oauth/utils/domain.js +21 -0
  147. package/dist/services/mcp-oauth/utils/loopback.js +11 -0
  148. package/dist/services/mcp-oauth/utils/redirect.js +84 -0
  149. package/dist/services/mcp-oauth/utils/registration-debug.js +131 -0
  150. package/dist/services/payload.js +2 -1
  151. package/dist/services/permissions.js +31 -9
  152. package/dist/services/revisions.js +15 -0
  153. package/dist/services/server.js +21 -4
  154. package/dist/services/settings.js +37 -3
  155. package/dist/services/users.js +13 -6
  156. package/dist/services/utils.js +6 -1
  157. package/dist/services/versions.js +137 -69
  158. package/dist/utils/calculate-field-depth.js +1 -0
  159. package/dist/utils/deep-freeze.js +24 -0
  160. package/dist/utils/extract-function-name.js +13 -0
  161. package/dist/utils/generate-translations.js +5 -5
  162. package/dist/utils/get-accountability-for-token.js +13 -1
  163. package/dist/utils/get-cache-key.js +1 -1
  164. package/dist/utils/get-history-filter-query.js +22 -0
  165. package/dist/utils/get-schema.js +2 -2
  166. package/dist/utils/get-service.js +3 -3
  167. package/dist/utils/is-admin.js +9 -0
  168. package/dist/utils/parse-oauth-scope.js +12 -0
  169. package/dist/utils/sanitize-query.js +1 -1
  170. package/dist/utils/split-field-path.js +29 -0
  171. package/dist/utils/transaction.js +2 -2
  172. package/dist/utils/translations-validation.js +2 -2
  173. package/dist/utils/validate-query.js +35 -4
  174. package/dist/utils/validate-user-count-integrity.js +28 -5
  175. package/dist/utils/verify-session-jwt.js +5 -2
  176. package/dist/utils/versioning/handle-version.js +130 -48
  177. package/dist/utils/versioning/remove-circular.js +17 -0
  178. package/dist/websocket/authenticate.js +2 -1
  179. package/dist/websocket/collab/collab.js +1 -1
  180. package/dist/websocket/collab/room.js +1 -1
  181. package/dist/websocket/controllers/base.js +12 -0
  182. package/dist/websocket/controllers/graphql.js +1 -1
  183. package/dist/websocket/handlers/subscribe.js +1 -1
  184. package/dist/websocket/messages.js +64 -64
  185. package/dist/websocket/utils/items.js +2 -2
  186. package/license +90 -80
  187. package/package.json +33 -32
  188. package/dist/controllers/mcp.js +0 -31
@@ -0,0 +1,103 @@
1
+ import database_default from "../../../database/index.js";
2
+ import { fetchAccessRoles } from "../../../utils/fetch-user-count/fetch-access-roles.js";
3
+ import { AccessService } from "../../../services/access.js";
4
+ import { getSchema } from "../../../utils/get-schema.js";
5
+ import { UsersService } from "../../../services/users.js";
6
+ import "../../../services/index.js";
7
+ import { toBoolean } from "@directus/utils";
8
+ import { USER_INACTIVE_LICENSE_STATUS } from "@directus/constants";
9
+
10
+ //#region src/license/entitlements/lib/seats.ts
11
+ async function getActiveSeats(opts) {
12
+ const knex = opts?.knex ?? database_default();
13
+ const schema = await getSchema({ database: knex });
14
+ const accessRows = await new AccessService({
15
+ schema,
16
+ knex
17
+ }).readByQuery({
18
+ fields: [
19
+ "role",
20
+ "user.id",
21
+ "user.status",
22
+ "user.role",
23
+ "policy.app_access",
24
+ "policy.admin_access"
25
+ ],
26
+ 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
+ }
43
+ const { adminRoles: allAdminRoles, appRoles: allAppRoles } = await fetchAccessRoles({
44
+ adminRoles,
45
+ appRoles
46
+ }, { knex });
47
+ const usersService = new UsersService({
48
+ schema,
49
+ knex
50
+ });
51
+ const adminFilters = [{ _or: [{ id: { _in: Array.from(adminUsers) } }, { role: { _in: Array.from(allAdminRoles) } }] }, { status: { _eq: "active" } }];
52
+ const appFilters = [{ _or: [{ id: {
53
+ _in: Array.from(appUsers),
54
+ _nin: Array.from(adminUsers)
55
+ } }, { role: {
56
+ _in: Array.from(allAppRoles),
57
+ _nin: Array.from(allAdminRoles)
58
+ } }] }, { status: { _eq: "active" } }];
59
+ if (opts?.adminId) {
60
+ adminFilters.push({ id: { _neq: opts.adminId } });
61
+ appFilters.push({ id: { _neq: opts.adminId } });
62
+ }
63
+ const adminCandidates = await usersService.readByQuery({
64
+ fields: [
65
+ "id",
66
+ "first_name",
67
+ "last_name",
68
+ "avatar",
69
+ "email"
70
+ ],
71
+ filter: { _and: adminFilters },
72
+ limit: -1
73
+ });
74
+ return [...await usersService.readByQuery({
75
+ fields: [
76
+ "id",
77
+ "first_name",
78
+ "last_name",
79
+ "avatar",
80
+ "email"
81
+ ],
82
+ filter: { _and: appFilters },
83
+ limit: -1
84
+ }), ...adminCandidates.map((admin) => ({
85
+ ...admin,
86
+ admin: true
87
+ }))];
88
+ }
89
+ async function countActiveSeats(opts) {
90
+ return (await getActiveSeats(opts)).length;
91
+ }
92
+ async function resolveSeats(seats, ctx) {
93
+ if (!ctx?.accountability?.user) return;
94
+ const usersService = new UsersService({
95
+ schema: await getSchema(),
96
+ accountability: ctx.accountability
97
+ });
98
+ const users = seats.filter((user_id) => user_id !== ctx.accountability.user);
99
+ await Promise.allSettled(users.map((user_id) => usersService.updateOne(user_id, { status: USER_INACTIVE_LICENSE_STATUS })));
100
+ }
101
+
102
+ //#endregion
103
+ export { countActiveSeats, getActiveSeats, resolveSeats };
@@ -0,0 +1,45 @@
1
+ import { DEFAULT_AUTH_PROVIDER } from "../../../constants.js";
2
+ import database_default from "../../../database/index.js";
3
+ import { getSchema } from "../../../utils/get-schema.js";
4
+ import { UsersService } from "../../../services/users.js";
5
+ import "../../../services/index.js";
6
+ import { USER_INACTIVE_LICENSE_STATUS } from "@directus/constants";
7
+
8
+ //#region src/license/entitlements/lib/sso-enabled.ts
9
+ /**
10
+ * Counting the current amount of users with sso enabled
11
+ */
12
+ async function checkUsersSSO(opts) {
13
+ const knex = opts?.knex ?? database_default();
14
+ return (await new UsersService({
15
+ schema: await getSchema({ database: knex }),
16
+ knex
17
+ }).readByQuery({
18
+ fields: ["id"],
19
+ filter: {
20
+ provider: { _neq: DEFAULT_AUTH_PROVIDER },
21
+ status: { _eq: "active" }
22
+ }
23
+ })).length === 0;
24
+ }
25
+ async function resolveSSOUsers(resolution, ctx) {
26
+ if (!ctx?.accountability?.user) return;
27
+ const adminId = ctx.accountability.user;
28
+ const usersService = new UsersService({
29
+ schema: await getSchema(),
30
+ accountability: ctx?.accountability
31
+ });
32
+ await usersService.updateByQuery({ filter: { _and: [{ provider: {
33
+ _neq: DEFAULT_AUTH_PROVIDER,
34
+ _nnull: true
35
+ } }, { id: { _neq: adminId } }] } }, { status: USER_INACTIVE_LICENSE_STATUS });
36
+ if (typeof resolution === "object" && Object.keys(resolution.admin).length) {
37
+ const payload = { provider: DEFAULT_AUTH_PROVIDER };
38
+ if (resolution.admin.email?.length) payload["email"] = resolution.admin.email;
39
+ if (resolution.admin.password?.length) payload["password"] = resolution.admin.password;
40
+ await usersService.updateOne(adminId, payload);
41
+ }
42
+ }
43
+
44
+ //#endregion
45
+ export { checkUsersSSO, resolveSSOUsers };
@@ -0,0 +1,256 @@
1
+ import { useBus } from "../../bus/lib/use-bus.js";
2
+ import "../../bus/index.js";
3
+ import { countActiveCollections, resolveCollections } from "./lib/collections.js";
4
+ import { checkCustomLLM } from "./lib/custom-llms-enabled.js";
5
+ import { checkCustomPermissionRules } from "./lib/custom-permission-rules-enabled.js";
6
+ import { countActiveFlows, resolveFlows } from "./lib/flows.js";
7
+ import { countActiveSeats, resolveSeats } from "./lib/seats.js";
8
+ import { checkUsersSSO, resolveSSOUsers } from "./lib/sso-enabled.js";
9
+ import { LimitExceededError, ResourceRestrictedError } from "@directus/errors";
10
+ import { CORE_LICENSE, COUNTABLE_ENTITLEMENT_KEYS, FEATURE_FLAG_ENTITLEMENT_KEYS } from "@directus/license";
11
+
12
+ //#region src/license/entitlements/manager.ts
13
+ const BUS_CHANNEL = "entitlements.invalidate";
14
+ let entitlementManager;
15
+ function getEntitlementManager() {
16
+ if (!entitlementManager) entitlementManager = new EntitlementManager();
17
+ return entitlementManager;
18
+ }
19
+ var EntitlementManager = class EntitlementManager {
20
+ entitlements = CORE_LICENSE["entitlements"];
21
+ counterSources = /* @__PURE__ */ new Map();
22
+ validatorSources = /* @__PURE__ */ new Map();
23
+ resolverSources = /* @__PURE__ */ new Map();
24
+ cache = /* @__PURE__ */ new Map();
25
+ initialized = false;
26
+ constructor() {
27
+ this.registerHandlers();
28
+ }
29
+ registerHandlers() {
30
+ this.registerCounter("collections", countActiveCollections);
31
+ this.registerCounter("seats", countActiveSeats);
32
+ this.registerCounter("flows", countActiveFlows);
33
+ this.registerValidator("sso_enabled", checkUsersSSO);
34
+ this.registerValidator("custom_llms_enabled", checkCustomLLM);
35
+ this.registerValidator("custom_permission_rules_enabled", checkCustomPermissionRules);
36
+ this.registerResolver("collections", resolveCollections);
37
+ this.registerResolver("seats", resolveSeats);
38
+ this.registerResolver("flows", resolveFlows);
39
+ this.registerResolver("sso_enabled", resolveSSOUsers);
40
+ }
41
+ initialize() {
42
+ if (this.initialized) return;
43
+ this.initialized = true;
44
+ useBus().subscribe(BUS_CHANNEL, async (msg) => {
45
+ this.clearCacheNoPublish(...msg?.keys ?? []);
46
+ });
47
+ }
48
+ /**
49
+ * Replace the active license. Pass `null` to reset to the core license.
50
+ */
51
+ setEntitlements(entitlements) {
52
+ this.entitlements = entitlements ?? CORE_LICENSE["entitlements"];
53
+ this.clearCache();
54
+ }
55
+ /**
56
+ * Create a manager that uses a different entitlement set while sharing
57
+ * this instance's cache and registered sources. Used to preview how a
58
+ * license change would affect the current user. Intended for read-only
59
+ * checks. Mutating methods (`setEntitlements`, `clearCache`) on the
60
+ * fork will affect the shared cache.
61
+ */
62
+ fork(entitlements) {
63
+ const forked = Object.create(EntitlementManager.prototype);
64
+ forked.entitlements = entitlements ?? CORE_LICENSE["entitlements"];
65
+ forked.counterSources = this.counterSources;
66
+ forked.validatorSources = this.validatorSources;
67
+ forked.resolverSources = this.resolverSources;
68
+ forked.cache = this.cache;
69
+ return forked;
70
+ }
71
+ clearCacheNoPublish(...keys) {
72
+ if (keys.length === 0) this.cache.clear();
73
+ else for (const key of keys) this.cache.delete(key);
74
+ }
75
+ /**
76
+ * Drop cached usage/validity locally and notify other nodes. Pass specific
77
+ * keys to clear only those entries; call with no args to clear everything.
78
+ * Used by mutation paths (services) and by the manual cache-clear endpoint
79
+ * and CLI command.
80
+ */
81
+ async clearCache(...keys) {
82
+ this.clearCacheNoPublish(...keys);
83
+ if (this.initialized) await useBus().publish(BUS_CHANNEL, { keys });
84
+ }
85
+ /**
86
+ * Returns a cached value by key
87
+ */
88
+ getCached(key) {
89
+ return this.cache.get(key);
90
+ }
91
+ /**
92
+ * Returns whether a feature flag is enabled, applying `override` when
93
+ * present and falling back to `default` otherwise.
94
+ */
95
+ isEntitled(key) {
96
+ const entitlement = this.entitlements[key];
97
+ return entitlement.override ?? entitlement.default;
98
+ }
99
+ /**
100
+ * Wire up a validator function for a feature flag entitlement.
101
+ */
102
+ registerValidator(key, validator) {
103
+ if (this.validatorSources.has(key)) throw new Error(`Validator was already registered for entitlement "${String(key)}"`);
104
+ this.validatorSources.set(key, validator);
105
+ }
106
+ /**
107
+ * Resolve the validity of a feature flag by invoking its registered
108
+ * validator. Throws if no validator has been registered for `key`.
109
+ */
110
+ async isValid(key, opts) {
111
+ const validator = this.validatorSources.get(key);
112
+ if (!validator) throw new Error(`No validator registered for entitlement "${String(key)}"`);
113
+ if (opts?.knex?.isTransaction) return await validator(opts);
114
+ let cached = this.cache.get(key);
115
+ if (typeof cached !== "boolean") {
116
+ cached = await validator(opts);
117
+ this.cache.set(key, cached);
118
+ }
119
+ return cached;
120
+ }
121
+ /**
122
+ * Returns the resolved values of app-only entitlements as a single bundle
123
+ * for exposure to the client. The package does not enforce these — the app
124
+ * uses them to adapt its UI (production indicator, powered-by branding).
125
+ */
126
+ getAppEntitlements() {
127
+ const { production_enabled, display_powered_by, ai_translations_enabled } = this.entitlements;
128
+ return {
129
+ production_enabled: production_enabled.override ?? production_enabled.default,
130
+ ai_translations_enabled: ai_translations_enabled.override ?? ai_translations_enabled.default,
131
+ display_powered_by
132
+ };
133
+ }
134
+ /**
135
+ * Returns the effective hard limit (`limit + overage + addon`) for a numeric
136
+ * entitlement with `-1` denoting unlimited
137
+ */
138
+ getEntitlementLimit(key) {
139
+ const { limit, overage, addon } = this.entitlements[key];
140
+ if (limit === -1 || overage === -1 || addon === -1) return -1;
141
+ return limit + (overage ?? 0) + (addon ?? 0);
142
+ }
143
+ /**
144
+ * Wire up a usage counter function for a countable entitlement.
145
+ */
146
+ registerCounter(key, source) {
147
+ if (this.counterSources.has(key)) throw new Error(`Counter was already registered for entitlement "${String(key)}"`);
148
+ this.counterSources.set(key, source);
149
+ }
150
+ /**
151
+ * Wire up a resolver function for an entitlement.
152
+ */
153
+ registerResolver(key, source) {
154
+ if (this.resolverSources.has(key)) throw new Error(`Resolver was already registered for entitlement "${String(key)}"`);
155
+ this.resolverSources.set(key, source);
156
+ }
157
+ /**
158
+ * Resolve current usage for a countable entitlement by invoking the
159
+ * registered source. Throws if no source has been registered for `key`.
160
+ */
161
+ async getUsage(key, opts) {
162
+ const source = this.counterSources.get(key);
163
+ if (!source) throw new Error(`No usage source registered for entitlement "${String(key)}"`);
164
+ if (opts?.knex?.isTransaction) return await source(opts);
165
+ let cached = this.cache.get(key);
166
+ if (typeof cached !== "number") {
167
+ cached = await source(opts);
168
+ this.cache.set(key, cached);
169
+ }
170
+ return cached;
171
+ }
172
+ async check(key, opts) {
173
+ if (this.isCountableKey(key)) {
174
+ const hardLimit = this.getEntitlementLimit(key);
175
+ if (hardLimit === -1) return {
176
+ allowed: true,
177
+ hardLimit: -1,
178
+ usage: 0,
179
+ remaining: null
180
+ };
181
+ const usage = await this.getUsage(key, { knex: opts?.knex });
182
+ const adding = opts?.adding ?? 0;
183
+ const removing = opts?.removing ?? 0;
184
+ return {
185
+ allowed: usage + adding - removing <= hardLimit,
186
+ hardLimit,
187
+ usage,
188
+ remaining: hardLimit - usage
189
+ };
190
+ }
191
+ const entitled = this.isEntitled(key);
192
+ if (!entitled) return {
193
+ valid: await this.isValid(key, { knex: opts?.knex }),
194
+ entitled
195
+ };
196
+ else return {
197
+ valid: true,
198
+ entitled
199
+ };
200
+ }
201
+ async assert(key, opts) {
202
+ if (this.isCountableKey(key)) {
203
+ const hardLimit = this.getEntitlementLimit(key);
204
+ if (hardLimit === -1) return;
205
+ const adding = opts?.adding ?? 0;
206
+ const removing = opts?.removing ?? 0;
207
+ if (await this.getUsage(key, { knex: opts?.knex }) + adding - removing > hardLimit) throw new LimitExceededError({ category: key });
208
+ return;
209
+ }
210
+ if (!this.isEntitled(key) && !await this.isValid(key, { knex: opts?.knex })) throw new ResourceRestrictedError({ category: key });
211
+ }
212
+ /**
213
+ * Checks all entitlements and returns true if all are within the limits
214
+ */
215
+ async checkAll(opts) {
216
+ for (const key of COUNTABLE_ENTITLEMENT_KEYS) {
217
+ if (!this.counterSources.has(key)) continue;
218
+ const { allowed } = await this.check(key, opts);
219
+ if (!allowed) return false;
220
+ }
221
+ for (const key of FEATURE_FLAG_ENTITLEMENT_KEYS) {
222
+ if (!this.validatorSources.has(key) || !this.resolverSources.has(key)) continue;
223
+ const { valid } = await this.check(key, opts);
224
+ if (!valid) return false;
225
+ }
226
+ return true;
227
+ }
228
+ /**
229
+ * Asserts all entitlements and throws if a limit is breached
230
+ */
231
+ async assertAll(opts) {
232
+ for (const key of COUNTABLE_ENTITLEMENT_KEYS) {
233
+ if (!this.counterSources.has(key)) continue;
234
+ await this.assert(key, opts);
235
+ }
236
+ for (const key of FEATURE_FLAG_ENTITLEMENT_KEYS) {
237
+ if (!this.validatorSources.has(key)) continue;
238
+ await this.assert(key, opts);
239
+ }
240
+ }
241
+ /**
242
+ * Apply a resolution payload to an entitlement by invoking its registered
243
+ * resolver
244
+ */
245
+ async resolve(key, input, ctx) {
246
+ const source = this.resolverSources.get(key);
247
+ if (!source) throw new Error(`No resolver registered for entitlement "${String(key)}"`);
248
+ await source(input, ctx);
249
+ }
250
+ isCountableKey(key) {
251
+ return COUNTABLE_ENTITLEMENT_KEYS.includes(key);
252
+ }
253
+ };
254
+
255
+ //#endregion
256
+ export { EntitlementManager, getEntitlementManager };
@@ -0,0 +1,4 @@
1
+ import { getEntitlementManager } from "./entitlements/manager.js";
2
+ import { getLicenseManager } from "./manager.js";
3
+
4
+ export { getEntitlementManager, getLicenseManager };