@beignet/core 0.0.13 → 0.0.14

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 (52) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +156 -2
  3. package/dist/entitlements/index.d.ts +234 -0
  4. package/dist/entitlements/index.d.ts.map +1 -0
  5. package/dist/entitlements/index.js +172 -0
  6. package/dist/entitlements/index.js.map +1 -0
  7. package/dist/error-reporting/index.d.ts +131 -0
  8. package/dist/error-reporting/index.d.ts.map +1 -0
  9. package/dist/error-reporting/index.js +112 -0
  10. package/dist/error-reporting/index.js.map +1 -0
  11. package/dist/flags/index.d.ts +293 -0
  12. package/dist/flags/index.d.ts.map +1 -0
  13. package/dist/flags/index.js +204 -0
  14. package/dist/flags/index.js.map +1 -0
  15. package/dist/locks/index.d.ts +155 -0
  16. package/dist/locks/index.d.ts.map +1 -0
  17. package/dist/locks/index.js +248 -0
  18. package/dist/locks/index.js.map +1 -0
  19. package/dist/payments/index.d.ts +430 -0
  20. package/dist/payments/index.d.ts.map +1 -0
  21. package/dist/payments/index.js +230 -0
  22. package/dist/payments/index.js.map +1 -0
  23. package/dist/ports/index.d.ts +61 -1
  24. package/dist/ports/index.d.ts.map +1 -1
  25. package/dist/ports/index.js +33 -0
  26. package/dist/ports/index.js.map +1 -1
  27. package/dist/ports/policy.d.ts +104 -0
  28. package/dist/ports/policy.d.ts.map +1 -1
  29. package/dist/ports/policy.js +102 -7
  30. package/dist/ports/policy.js.map +1 -1
  31. package/dist/search/index.d.ts +175 -0
  32. package/dist/search/index.d.ts.map +1 -0
  33. package/dist/search/index.js +344 -0
  34. package/dist/search/index.js.map +1 -0
  35. package/dist/server/server.d.ts.map +1 -1
  36. package/dist/server/server.js +9 -1
  37. package/dist/server/server.js.map +1 -1
  38. package/dist/testing/index.d.ts +46 -1
  39. package/dist/testing/index.d.ts.map +1 -1
  40. package/dist/testing/index.js +33 -1
  41. package/dist/testing/index.js.map +1 -1
  42. package/package.json +25 -1
  43. package/src/entitlements/index.ts +479 -0
  44. package/src/error-reporting/index.ts +273 -0
  45. package/src/flags/index.ts +608 -0
  46. package/src/locks/index.ts +440 -0
  47. package/src/payments/index.ts +699 -0
  48. package/src/ports/index.ts +194 -0
  49. package/src/ports/policy.ts +272 -7
  50. package/src/search/index.ts +632 -0
  51. package/src/server/server.ts +15 -0
  52. package/src/testing/index.ts +83 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @beignet/core
2
2
 
3
+ ## 0.0.14
4
+
5
+ ### Patch Changes
6
+
7
+ - 0b106c3: Add first-class error reporting primitives and a Sentry-backed error reporting
8
+ provider.
9
+ - 6828442: Add first-class feature flag primitives, an OpenFeature-backed flags provider,
10
+ and devtools support for flag evaluation, exposure, and tracking events.
11
+ - edf5ddc: Add first-class entitlement primitives for product access checks, devtools-ready entitlement decision observation, and doctor checks for entitlement wiring drift. Generated billing slices expose a billing-backed entitlements port.
12
+ - bcd3864: Add lease-backed lock primitives and a Redis-backed locks provider.
13
+ - d51a35f: Add the provider-neutral payments port, memory payments adapter, Stripe payments provider, Next.js payment webhook route helper, `beignet make payments`, and first-class payments devtools view.
14
+ - 655c101: Add keyed authorization batch checks and best-effort policy decision observation, plus a first-class devtools watcher/view for policy decision events.
15
+ - bcd3864: Add a provider-neutral search port, memory adapter, and Meilisearch provider.
16
+
3
17
  ## 0.0.13
4
18
 
5
19
  ### Patch Changes
package/README.md CHANGED
@@ -8,8 +8,9 @@
8
8
 
9
9
  This package provides Beignet's framework primitives: contracts, server runtime,
10
10
  typed client, use cases, ports, domain helpers, app errors, config, events,
11
- idempotency, outbox, mail, notifications, schedules, uploads, pagination helpers,
12
- testing helpers, and OpenAPI generation.
11
+ idempotency, locks, outbox, mail, notifications, payments, search, feature
12
+ flags, error reporting, schedules, uploads, entitlements, pagination helpers, testing
13
+ helpers, and OpenAPI generation.
13
14
 
14
15
  ## Installation
15
16
 
@@ -43,18 +44,24 @@ name the framework area they depend on.
43
44
  | `@beignet/core/config` | Environment config validation |
44
45
  | `@beignet/core/contracts` | HTTP contract builders, types, path helpers, and contract metadata |
45
46
  | `@beignet/core/domain` | Entities, value objects, and domain events |
47
+ | `@beignet/core/entitlements` | Product access decision types, helpers, and static entitlement adapter |
48
+ | `@beignet/core/error-reporting` | Error reporting port, memory adapter, no-op adapter, and helpers |
46
49
  | `@beignet/core/errors` | Error catalogs and response helpers |
47
50
  | `@beignet/core/events` | Events and listeners |
51
+ | `@beignet/core/flags` | Feature flag definitions, FlagsPort, memory/static adapters, and helpers |
48
52
  | `@beignet/core/idempotency` | Retry-safe command, webhook, and job primitives |
49
53
  | `@beignet/core/jobs` | Job definitions, retry policies, and inline job dispatch |
54
+ | `@beignet/core/locks` | Lease-backed LocksPort, memory adapter, memory provider, and helpers |
50
55
  | `@beignet/core/mail` | Mail port, memory mailer, and memory mailer provider |
51
56
  | `@beignet/core/notifications` | Notification definitions, dispatchers, inline notifications provider, mail channels, and test adapters |
52
57
  | `@beignet/core/openapi` | OpenAPI generation |
53
58
  | `@beignet/core/outbox` | Durable event and job outbox |
59
+ | `@beignet/core/payments` | Payments port, memory payments adapter, and memory payments provider |
54
60
  | `@beignet/core/pagination` | Offset/cursor page types, normalizers, and result helpers |
55
61
  | `@beignet/core/ports` | App-facing ports, auth, audit, policies, cache, storage, logging, and redaction |
56
62
  | `@beignet/core/ports/testing` | Port and policy test helpers |
57
63
  | `@beignet/core/providers` | Provider lifecycle and instrumentation primitives |
64
+ | `@beignet/core/search` | Search index definitions, SearchPort, memory adapter, memory provider, and helpers |
58
65
  | `@beignet/core/schedules` | Schedule primitives |
59
66
  | `@beignet/core/server` | Framework-agnostic server runtime and hook helpers |
60
67
  | `@beignet/core/server-only` | Static lint marker for modules that must stay out of client bundles |
@@ -177,6 +184,148 @@ Replace `createMemoryMailerProvider(...)` with a real mail provider such as
177
184
  keep the inline notifications provider and run it from jobs or listeners, or
178
185
  replace it with an app-owned dispatcher when delivery needs custom semantics.
179
186
 
187
+ ## Entitlements
188
+
189
+ Use `@beignet/core/entitlements` for product access decisions derived from
190
+ app-owned billing or plan state. The resolver maps durable app state to
191
+ allow/deny decisions; `requireEntitlement(...)` enforces the decision from a
192
+ use case and throws a framework-owned 403 by default.
193
+
194
+ ```typescript
195
+ import {
196
+ createEntitlements,
197
+ type EntitlementDecisionObserver,
198
+ requireEntitlement,
199
+ } from "@beignet/core/entitlements";
200
+
201
+ function createBillingEntitlements(
202
+ billing: BillingRepository,
203
+ recordDecision?: EntitlementDecisionObserver,
204
+ ) {
205
+ return createEntitlements({
206
+ async inspect(input) {
207
+ const account = await billing.findByTenantId(input.subject.id);
208
+ return account?.status === "active";
209
+ },
210
+ onDecision: recordDecision,
211
+ });
212
+ }
213
+
214
+ await requireEntitlement(ctx, {
215
+ entitlement: "todos.create",
216
+ subject: { type: "tenant", id: tenantId },
217
+ });
218
+ ```
219
+
220
+ `onDecision` is diagnostic only. Observer errors are ignored and cannot change
221
+ the entitlement result.
222
+
223
+ ## Feature flags
224
+
225
+ Use `@beignet/core/flags` for typed feature flag definitions and
226
+ provider-neutral evaluation. Flags always carry a default value, and provider
227
+ failures return that default instead of throwing into product workflows.
228
+
229
+ ```typescript
230
+ import { defineFlag, defineFlags } from "@beignet/core/flags";
231
+
232
+ export const billingFlags = defineFlags({
233
+ newCheckout: defineFlag.boolean("billing.new-checkout", {
234
+ default: false,
235
+ }),
236
+ });
237
+
238
+ const enabled = await ctx.ports.flags.evaluate(billingFlags.newCheckout, {
239
+ context: {
240
+ targetingKey: ctx.actor.id,
241
+ tenant: ctx.tenant,
242
+ requestId: ctx.requestId,
243
+ },
244
+ });
245
+ ```
246
+
247
+ Plain evaluation does not record exposure. Call `recordExposure(...)`
248
+ explicitly when a user actually sees or can be affected by the flagged
249
+ behavior. Use `createMemoryFlags(...)` or `createStaticFlags(...)` in tests, or
250
+ install `@beignet/provider-flags-openfeature` for production providers.
251
+ String and number flags widen to `string` and `number` by default; pass a
252
+ generic when an app wants a closed variant union.
253
+
254
+ ## Error reporting
255
+
256
+ Use `@beignet/core/error-reporting` for provider-neutral exception and message
257
+ capture. The port accepts severity, tags, user, contexts, extra metadata, and
258
+ request/trace correlation IDs.
259
+
260
+ ```typescript
261
+ import { createMemoryErrorReporter } from "@beignet/core/error-reporting";
262
+
263
+ await ctx.ports.errorReporter.captureException(error, {
264
+ level: "error",
265
+ requestId: ctx.requestId,
266
+ traceId: ctx.traceId,
267
+ tags: { feature: "billing" },
268
+ });
269
+
270
+ const errorReporter = createMemoryErrorReporter();
271
+ ```
272
+
273
+ Use `createMemoryErrorReporter(...)` in tests, `createNoopErrorReporter()` when
274
+ an app needs a bound port without capture, or install
275
+ `@beignet/provider-error-reporting-sentry` for production providers.
276
+
277
+ ## Locks and leases
278
+
279
+ Use `@beignet/core/locks` for provider-neutral lease-backed lock coordination.
280
+ Locks prevent overlapping schedules, singleton jobs, cache stampedes, and short
281
+ critical sections across multiple workers or servers.
282
+
283
+ ```typescript
284
+ import { createMemoryLocks } from "@beignet/core/locks";
285
+
286
+ await ctx.ports.locks.withLease(
287
+ "schedule:daily-report",
288
+ { ttlMs: 60_000, waitMs: 0 },
289
+ async ({ lease }) => {
290
+ await runDailyReport(ctx, { fencingToken: lease.fencingToken });
291
+ },
292
+ );
293
+
294
+ const locks = createMemoryLocks();
295
+ ```
296
+
297
+ Use `createMemoryLocks(...)` in tests, `createMemoryLocksProvider()` for local
298
+ provider wiring, or install `@beignet/provider-locks-redis` for production
299
+ leases.
300
+
301
+ ## Search
302
+
303
+ Use `@beignet/core/search` for provider-neutral search index definitions,
304
+ document indexing, and querying searchable read models.
305
+
306
+ ```typescript
307
+ import { defineSearchIndex } from "@beignet/core/search";
308
+
309
+ const issueSearchIndex = defineSearchIndex("issues", {
310
+ searchableAttributes: ["key", "title", "description"],
311
+ filterableAttributes: ["tenantId", "status"],
312
+ sortableAttributes: ["createdAt"],
313
+ });
314
+
315
+ await ctx.ports.search.indexDocuments(issueSearchIndex, issueDocument);
316
+
317
+ const results = await ctx.ports.search.search(issueSearchIndex, {
318
+ query: "billing",
319
+ filters: { tenantId },
320
+ sort: ["createdAt:desc"],
321
+ limit: 20,
322
+ });
323
+ ```
324
+
325
+ Use `createMemorySearch(...)` in tests, `createMemorySearchProvider()` for local
326
+ provider wiring, or install `@beignet/provider-search-meilisearch` for
327
+ production search.
328
+
180
329
  ## Provider metadata
181
330
 
182
331
  Reusable provider packages should declare static metadata in `package.json`
@@ -665,6 +814,11 @@ await tester.assertMatrix([
665
814
  },
666
815
  ]);
667
816
 
817
+ const permissions = await tester.gate.canMany(activity, {
818
+ publish: ["posts.publish", post],
819
+ });
820
+ expect(permissions.publish).toBe(true);
821
+
668
822
  assertAuditEntry(audit.entries, {
669
823
  action: "posts.publish",
670
824
  actorId: "user_1",
@@ -0,0 +1,234 @@
1
+ /**
2
+ * @beignet/core/entitlements
3
+ *
4
+ * Provider-neutral product access primitives for Beignet applications.
5
+ */
6
+ type MaybePromise<T> = T | Promise<T>;
7
+ /**
8
+ * Stable app-defined capability key, such as "issues.create".
9
+ */
10
+ export type EntitlementKey = string;
11
+ /**
12
+ * Principal whose product access is being checked.
13
+ */
14
+ export type EntitlementSubject = {
15
+ /**
16
+ * App-defined subject type, commonly "tenant" or "account".
17
+ */
18
+ type: string;
19
+ /**
20
+ * Stable subject identifier.
21
+ */
22
+ id: string;
23
+ };
24
+ /**
25
+ * Input for checking whether a subject has access to a capability.
26
+ */
27
+ export type EntitlementCheckInput<TKey extends EntitlementKey = EntitlementKey> = {
28
+ /**
29
+ * Capability being checked.
30
+ */
31
+ entitlement: TKey;
32
+ /**
33
+ * Tenant, account, or other app-owned subject whose product access is checked.
34
+ */
35
+ subject: EntitlementSubject;
36
+ };
37
+ /**
38
+ * An entitlement decision that allows the capability.
39
+ */
40
+ export type EntitlementAllowedDecision = {
41
+ allowed: true;
42
+ };
43
+ /**
44
+ * An entitlement decision that denies the capability.
45
+ */
46
+ export type EntitlementDeniedDecision = {
47
+ allowed: false;
48
+ reason?: string;
49
+ code?: string;
50
+ details?: unknown;
51
+ };
52
+ /**
53
+ * Normalized entitlement decision.
54
+ */
55
+ export type EntitlementDecision = EntitlementAllowedDecision | EntitlementDeniedDecision;
56
+ /**
57
+ * Value an entitlement resolver may return.
58
+ */
59
+ export type EntitlementResolverResult = boolean | EntitlementDecision;
60
+ /**
61
+ * Entitlement port method that produced a decision.
62
+ */
63
+ export type EntitlementDecisionSource = "can" | "inspect" | "require";
64
+ /**
65
+ * Optional context for entitlement checks.
66
+ */
67
+ export type EntitlementCheckOptions<TContext = unknown> = {
68
+ /**
69
+ * Application context used for the check. Observers can use this to copy
70
+ * request correlation fields without coupling the resolver to app context.
71
+ */
72
+ ctx?: TContext;
73
+ /**
74
+ * Source to report to observers. Port methods set this automatically.
75
+ */
76
+ source?: EntitlementDecisionSource;
77
+ };
78
+ /**
79
+ * App-facing product access port.
80
+ */
81
+ export type EntitlementsPort<TKey extends EntitlementKey = EntitlementKey, TContext = unknown> = {
82
+ /**
83
+ * Return only whether the capability is allowed.
84
+ */
85
+ can(input: EntitlementCheckInput<TKey>, options?: EntitlementCheckOptions<TContext>): Promise<boolean>;
86
+ /**
87
+ * Return the full allow/deny decision without throwing.
88
+ */
89
+ inspect(input: EntitlementCheckInput<TKey>, options?: EntitlementCheckOptions<TContext>): Promise<EntitlementDecision>;
90
+ };
91
+ /**
92
+ * Function that decides whether an entitlement is granted.
93
+ */
94
+ export type EntitlementResolver<TKey extends EntitlementKey = EntitlementKey> = (input: EntitlementCheckInput<TKey>) => MaybePromise<EntitlementResolverResult>;
95
+ /**
96
+ * Best-effort entitlement decision observation emitted by an entitlements port.
97
+ *
98
+ * Observers are diagnostic only. They do not participate in entitlement
99
+ * control flow and thrown/rejected observer errors are ignored.
100
+ */
101
+ export type EntitlementDecisionObservation<TContext = unknown, TKey extends EntitlementKey = EntitlementKey> = {
102
+ /**
103
+ * Port helper that produced the decision.
104
+ */
105
+ source: EntitlementDecisionSource;
106
+ /**
107
+ * Application context passed by the caller, when available.
108
+ */
109
+ ctx?: TContext;
110
+ /**
111
+ * Capability being evaluated.
112
+ */
113
+ entitlement: TKey;
114
+ /**
115
+ * Subject whose product access is checked.
116
+ */
117
+ subject: EntitlementSubject;
118
+ /**
119
+ * Normalized decision, when resolver evaluation returned normally.
120
+ */
121
+ decision?: EntitlementDecision;
122
+ /**
123
+ * Error thrown by the resolver, when evaluation failed.
124
+ */
125
+ error?: unknown;
126
+ /**
127
+ * Resolver duration, excluding observer work.
128
+ */
129
+ durationMs: number;
130
+ /**
131
+ * Correlation fields copied from the context when present.
132
+ */
133
+ requestId?: string;
134
+ traceId?: string;
135
+ spanId?: string;
136
+ parentSpanId?: string;
137
+ traceparent?: string;
138
+ };
139
+ /**
140
+ * Best-effort observer called after each entitlement decision or resolver
141
+ * error.
142
+ */
143
+ export type EntitlementDecisionObserver<TContext = unknown, TKey extends EntitlementKey = EntitlementKey> = (observation: EntitlementDecisionObservation<TContext, TKey>) => MaybePromise<void>;
144
+ /**
145
+ * Options for `createEntitlements(...)`.
146
+ */
147
+ export type CreateEntitlementsOptions<TKey extends EntitlementKey = EntitlementKey, TContext = unknown> = {
148
+ /**
149
+ * Resolver that maps app-owned product state to an entitlement decision.
150
+ */
151
+ inspect: EntitlementResolver<TKey>;
152
+ /**
153
+ * Optional best-effort observer for entitlement decisions.
154
+ *
155
+ * This hook is diagnostic only: it cannot change decisions or thrown errors.
156
+ */
157
+ onDecision?: EntitlementDecisionObserver<TContext, TKey>;
158
+ };
159
+ /**
160
+ * Static grant map keyed by subject key.
161
+ */
162
+ export type StaticEntitlementGrantMap<TKey extends EntitlementKey = EntitlementKey> = Record<string, readonly TKey[]>;
163
+ /**
164
+ * Options for `createStaticEntitlements(...)`.
165
+ */
166
+ export type CreateStaticEntitlementsOptions<TKey extends EntitlementKey = EntitlementKey, TContext = unknown> = {
167
+ /**
168
+ * Granted capabilities keyed by subject. The default key is
169
+ * `${subject.type}:${subject.id}`.
170
+ */
171
+ grants: StaticEntitlementGrantMap<TKey>;
172
+ /**
173
+ * Optional subject key mapper.
174
+ */
175
+ subjectKey?: (subject: EntitlementSubject) => string;
176
+ /**
177
+ * Default denial decision fields.
178
+ */
179
+ denied?: Omit<EntitlementDeniedDecision, "allowed">;
180
+ /**
181
+ * Optional best-effort observer for entitlement decisions.
182
+ */
183
+ onDecision?: EntitlementDecisionObserver<TContext, TKey>;
184
+ };
185
+ /**
186
+ * Options accepted by `requireEntitlement(...)`.
187
+ */
188
+ export type RequireEntitlementOptions<TKey extends EntitlementKey = EntitlementKey> = {
189
+ /**
190
+ * Create the error to throw instead of the framework default.
191
+ */
192
+ error?: (decision: EntitlementDeniedDecision, input: EntitlementCheckInput<TKey>) => unknown;
193
+ };
194
+ /**
195
+ * Context shape consumed by `requireEntitlement(...)`.
196
+ */
197
+ export type EntitlementsContext<TKey extends EntitlementKey = EntitlementKey> = {
198
+ ports: {
199
+ entitlements: EntitlementsPort<TKey>;
200
+ };
201
+ };
202
+ /**
203
+ * Error thrown by `requireEntitlement(...)` when product access is denied.
204
+ */
205
+ export declare class EntitlementRequiredError extends Error {
206
+ readonly code: string;
207
+ readonly status = 403;
208
+ readonly details?: unknown;
209
+ readonly entitlement: string;
210
+ readonly subject: EntitlementSubject;
211
+ constructor(input: EntitlementCheckInput, decision?: EntitlementDeniedDecision);
212
+ }
213
+ /**
214
+ * Create an explicit allow decision.
215
+ */
216
+ export declare function allowEntitlement(): EntitlementAllowedDecision;
217
+ /**
218
+ * Create an explicit deny decision.
219
+ */
220
+ export declare function denyEntitlement(reasonOrDecision?: string | Omit<EntitlementDeniedDecision, "allowed">): EntitlementDeniedDecision;
221
+ /**
222
+ * Create an entitlement port from an app-owned resolver.
223
+ */
224
+ export declare function createEntitlements<TKey extends EntitlementKey = EntitlementKey, TContext = unknown>(options: CreateEntitlementsOptions<TKey, TContext>): EntitlementsPort<TKey, TContext>;
225
+ /**
226
+ * Create a static entitlement port for tests, starters, and simple apps.
227
+ */
228
+ export declare function createStaticEntitlements<TKey extends EntitlementKey = EntitlementKey, TContext = unknown>(options: CreateStaticEntitlementsOptions<TKey, TContext>): EntitlementsPort<TKey, TContext>;
229
+ /**
230
+ * Require an entitlement or throw a framework-owned 403 error.
231
+ */
232
+ export declare function requireEntitlement<TKey extends EntitlementKey = EntitlementKey>(ctx: EntitlementsContext<TKey>, input: EntitlementCheckInput<TKey>, options?: RequireEntitlementOptions<TKey>): Promise<EntitlementAllowedDecision>;
233
+ export {};
234
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/entitlements/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,172 @@
1
+ /**
2
+ * @beignet/core/entitlements
3
+ *
4
+ * Provider-neutral product access primitives for Beignet applications.
5
+ */
6
+ /**
7
+ * Error thrown by `requireEntitlement(...)` when product access is denied.
8
+ */
9
+ export class EntitlementRequiredError extends Error {
10
+ code;
11
+ status = 403;
12
+ details;
13
+ entitlement;
14
+ subject;
15
+ constructor(input, decision = denyEntitlement()) {
16
+ super(decision.reason ??
17
+ `Entitlement "${input.entitlement}" is required for this action.`);
18
+ this.name = "EntitlementRequiredError";
19
+ this.code = decision.code ?? "ENTITLEMENT_REQUIRED";
20
+ this.details = decision.details;
21
+ this.entitlement = input.entitlement;
22
+ this.subject = input.subject;
23
+ }
24
+ }
25
+ /**
26
+ * Create an explicit allow decision.
27
+ */
28
+ export function allowEntitlement() {
29
+ return { allowed: true };
30
+ }
31
+ /**
32
+ * Create an explicit deny decision.
33
+ */
34
+ export function denyEntitlement(reasonOrDecision) {
35
+ if (typeof reasonOrDecision === "string") {
36
+ return { allowed: false, reason: reasonOrDecision };
37
+ }
38
+ return {
39
+ allowed: false,
40
+ ...reasonOrDecision,
41
+ };
42
+ }
43
+ /**
44
+ * Create an entitlement port from an app-owned resolver.
45
+ */
46
+ export function createEntitlements(options) {
47
+ const observeDecision = createDecisionObserver(options.onDecision);
48
+ async function evaluate(input, checkOptions) {
49
+ const startedAt = entitlementNow();
50
+ try {
51
+ const decision = normalizeEntitlementDecision(await options.inspect(input));
52
+ observeDecision({
53
+ source: checkOptions?.source ?? "inspect",
54
+ ctx: checkOptions?.ctx,
55
+ entitlement: input.entitlement,
56
+ subject: input.subject,
57
+ decision,
58
+ durationMs: entitlementNow() - startedAt,
59
+ });
60
+ return decision;
61
+ }
62
+ catch (error) {
63
+ observeDecision({
64
+ source: checkOptions?.source ?? "inspect",
65
+ ctx: checkOptions?.ctx,
66
+ entitlement: input.entitlement,
67
+ subject: input.subject,
68
+ error,
69
+ durationMs: entitlementNow() - startedAt,
70
+ });
71
+ throw error;
72
+ }
73
+ }
74
+ return {
75
+ async can(input, checkOptions) {
76
+ return (await evaluate(input, { ...checkOptions, source: "can" }))
77
+ .allowed;
78
+ },
79
+ inspect(input, checkOptions) {
80
+ return evaluate(input, {
81
+ ...checkOptions,
82
+ source: checkOptions?.source ?? "inspect",
83
+ });
84
+ },
85
+ };
86
+ }
87
+ /**
88
+ * Create a static entitlement port for tests, starters, and simple apps.
89
+ */
90
+ export function createStaticEntitlements(options) {
91
+ const subjectKey = options.subjectKey ?? defaultSubjectKey;
92
+ return createEntitlements({
93
+ inspect(input) {
94
+ const granted = options.grants[subjectKey(input.subject)] ?? [];
95
+ if (granted.includes(input.entitlement))
96
+ return allowEntitlement();
97
+ return denyEntitlement({
98
+ reason: `Entitlement "${input.entitlement}" is not granted.`,
99
+ code: "ENTITLEMENT_NOT_GRANTED",
100
+ details: {
101
+ entitlement: input.entitlement,
102
+ subject: input.subject,
103
+ },
104
+ ...options.denied,
105
+ });
106
+ },
107
+ onDecision: options.onDecision,
108
+ });
109
+ }
110
+ /**
111
+ * Require an entitlement or throw a framework-owned 403 error.
112
+ */
113
+ export async function requireEntitlement(ctx, input, options) {
114
+ const decision = await ctx.ports.entitlements.inspect(input, {
115
+ ctx,
116
+ source: "require",
117
+ });
118
+ if (decision.allowed)
119
+ return decision;
120
+ throw options?.error
121
+ ? options.error(decision, input)
122
+ : new EntitlementRequiredError(input, decision);
123
+ }
124
+ function normalizeEntitlementDecision(result) {
125
+ if (typeof result === "boolean") {
126
+ return result ? allowEntitlement() : denyEntitlement();
127
+ }
128
+ return result;
129
+ }
130
+ function entitlementNow() {
131
+ return typeof performance !== "undefined" ? performance.now() : Date.now();
132
+ }
133
+ function contextStringField(ctx, field) {
134
+ if (!ctx || typeof ctx !== "object")
135
+ return undefined;
136
+ const value = ctx[field];
137
+ return typeof value === "string" ? value : undefined;
138
+ }
139
+ function enrichObservation(observation) {
140
+ return {
141
+ ...observation,
142
+ requestId: contextStringField(observation.ctx, "requestId"),
143
+ traceId: contextStringField(observation.ctx, "traceId"),
144
+ spanId: contextStringField(observation.ctx, "spanId"),
145
+ parentSpanId: contextStringField(observation.ctx, "parentSpanId"),
146
+ traceparent: contextStringField(observation.ctx, "traceparent"),
147
+ };
148
+ }
149
+ function isPromiseLike(value) {
150
+ return (value !== null &&
151
+ (typeof value === "object" || typeof value === "function") &&
152
+ typeof value.then === "function");
153
+ }
154
+ function createDecisionObserver(observer) {
155
+ return (observation) => {
156
+ if (!observer)
157
+ return;
158
+ try {
159
+ const result = observer(enrichObservation(observation));
160
+ if (isPromiseLike(result)) {
161
+ Promise.resolve(result).catch(() => undefined);
162
+ }
163
+ }
164
+ catch {
165
+ // Observers are diagnostic only and must not affect entitlement checks.
166
+ }
167
+ };
168
+ }
169
+ function defaultSubjectKey(subject) {
170
+ return `${subject.type}:${subject.id}`;
171
+ }
172
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/entitlements/index.ts"],"names":[],"mappings":"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"}