@develemit/billing 0.2.0 → 0.2.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Emit Dutcher
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -142,6 +142,7 @@ const shadowedCheckSubscribed = createShadowCheck({
142
142
  entitled: (e) =>
143
143
  e.status === 'active' || e.status === 'trialing' || e.status === 'grace',
144
144
  report: (r) => log.info(r), // injectable sink; ShadowReport
145
+ onReportError: (e) => log.warn(e), // optional; see note below
145
146
  sampleRate: 1, // 0..1, default 1
146
147
  timeoutMs: 800, // shadow fetch budget, default 800ms
147
148
  });
@@ -165,13 +166,36 @@ Invariants:
165
166
  4. `sampleRate` short-circuits before any shadow work — no fetch happens on
166
167
  a skipped call.
167
168
 
169
+ Invariant 1 holds even when the consumer-supplied callbacks themselves
170
+ throw. A `report` sink that throws (uninitialized logger, circular value
171
+ hitting `JSON.stringify`) is swallowed; so is a `subjectIdFor` mapper that
172
+ throws, which simply means the call can't be shadowed — the legacy check
173
+ still runs and still returns its answer. Both escalate through the optional
174
+ `onReportError`. The alternative — letting an observability-only code path
175
+ break real billing checks — is exactly what shadow mode exists to avoid.
176
+ Without `onReportError` set, both failures are silent, so pass it.
177
+
168
178
  Reading disagreement reports: a `report.agree === false` is only worth
169
179
  investigating once you've ruled out two expected, transient causes —
170
180
  `shadowFromCache: true` combined with a recent plan change (the cached
171
181
  answer just hasn't hit its TTL yet), or a webhook that hasn't landed yet
172
- after a provider-side change. A shadow_error report (timeout/network/5xx/
173
- parse) is a health signal for emit-billing itself, not a disagreement — it
174
- means no shadow answer was obtainable, not that it differed.
182
+ after a provider-side change.
183
+
184
+ A `shadowError` report is a health signal, not a disagreement — no shadow
185
+ answer was obtainable, so there was nothing to compare. Its `reason`
186
+ separates whose problem it is:
187
+
188
+ | `reason` | Points at |
189
+ | ----------------- | ------------------------------------------------------ |
190
+ | `timeout` | emit-billing slow, or `timeoutMs` set too tight |
191
+ | `network` | transport — DNS, connection refused, TLS |
192
+ | `http_error` | emit-billing returned a non-2xx |
193
+ | `parse_error` | response didn't match the expected schema |
194
+ | `predicate_error` | **your** `entitled()` callback threw, not emit-billing |
195
+
196
+ Latency is measured with `performance.now()`, so `shadowLatencyMs` keeps
197
+ sub-millisecond resolution — a cache-served shadow read reports e.g. `0.1`
198
+ rather than a flat `0` that's indistinguishable from "never measured."
175
199
 
176
200
  ## Behavior
177
201
 
package/dist/index.cjs CHANGED
@@ -450,8 +450,8 @@ var portalReturnUrlSchema = import_zod10.z.url();
450
450
 
451
451
  // src/cache.ts
452
452
  var TtlCache = class {
453
- constructor(now = Date.now) {
454
- this.now = now;
453
+ constructor(now2 = Date.now) {
454
+ this.now = now2;
455
455
  }
456
456
  now;
457
457
  store = /* @__PURE__ */ new Map();
@@ -619,6 +619,12 @@ async function checkEntitlement(input) {
619
619
  }
620
620
 
621
621
  // src/shadow.ts
622
+ function now() {
623
+ return performance.now();
624
+ }
625
+ function elapsedSince(start) {
626
+ return Math.round((now() - start) * 1e3) / 1e3;
627
+ }
622
628
  var ShadowTimeoutError = class extends Error {
623
629
  constructor() {
624
630
  super("Shadow check timed out");
@@ -653,26 +659,36 @@ function errorMessage(error) {
653
659
  return error instanceof Error ? error.message : String(error);
654
660
  }
655
661
  async function runShadow(options, subjectId, timeoutMs) {
656
- const start = Date.now();
662
+ const start = now();
663
+ let entitlements;
657
664
  try {
658
- const entitlements = await withTimeout(
665
+ entitlements = await withTimeout(
659
666
  options.client.getEntitlements(subjectId),
660
667
  timeoutMs
661
668
  );
669
+ } catch (error) {
670
+ return {
671
+ kind: "error",
672
+ reason: classifyShadowError(error),
673
+ message: errorMessage(error),
674
+ latencyMs: elapsedSince(start)
675
+ };
676
+ }
677
+ try {
662
678
  return {
663
679
  kind: "ok",
664
680
  answer: options.entitled(entitlements),
665
681
  status: entitlements.status,
666
682
  source: entitlements.source,
667
683
  fromCache: entitlements.fromCache ?? false,
668
- latencyMs: Date.now() - start
684
+ latencyMs: elapsedSince(start)
669
685
  };
670
686
  } catch (error) {
671
687
  return {
672
688
  kind: "error",
673
- reason: classifyShadowError(error),
689
+ reason: "predicate_error",
674
690
  message: errorMessage(error),
675
- latencyMs: Date.now() - start
691
+ latencyMs: elapsedSince(start)
676
692
  };
677
693
  }
678
694
  }
@@ -702,25 +718,45 @@ function createShadowCheck(options) {
702
718
  legacy,
703
719
  subjectIdFor,
704
720
  report,
721
+ onReportError,
705
722
  sampleRate = 1,
706
723
  timeoutMs = 800
707
724
  } = options;
725
+ function reportErrorSafely(error) {
726
+ try {
727
+ onReportError?.(error);
728
+ } catch {
729
+ }
730
+ }
731
+ function reportSafely(built) {
732
+ try {
733
+ report(built);
734
+ } catch (error) {
735
+ reportErrorSafely(error);
736
+ }
737
+ }
708
738
  return async function shadowedCheck(id) {
709
- const subjectId = subjectIdFor(id);
710
- const legacyStart = Date.now();
739
+ let mapped = null;
740
+ try {
741
+ mapped = subjectIdFor(id);
742
+ } catch (error) {
743
+ reportErrorSafely(error);
744
+ }
745
+ const subjectId = mapped;
746
+ const legacyStart = now();
711
747
  const legacyPromise = legacy(id).then(
712
- (value) => ({ ok: true, value, latencyMs: Date.now() - legacyStart }),
748
+ (value) => ({ ok: true, value, latencyMs: elapsedSince(legacyStart) }),
713
749
  (error) => ({
714
750
  ok: false,
715
751
  error,
716
- latencyMs: Date.now() - legacyStart
752
+ latencyMs: elapsedSince(legacyStart)
717
753
  })
718
754
  );
719
- const shadowPromise = shouldSample(sampleRate) ? runShadow(options, subjectId, timeoutMs) : null;
755
+ const shadowPromise = subjectId !== null && shouldSample(sampleRate) ? runShadow(options, subjectId, timeoutMs) : null;
720
756
  const legacyOutcome = await legacyPromise;
721
- if (shadowPromise) {
757
+ if (shadowPromise !== null && subjectId !== null) {
722
758
  const shadowOutcome = await shadowPromise;
723
- report(buildReport(subjectId, legacyOutcome, shadowOutcome));
759
+ reportSafely(buildReport(subjectId, legacyOutcome, shadowOutcome));
724
760
  }
725
761
  if (!legacyOutcome.ok) throw legacyOutcome.error;
726
762
  return legacyOutcome.value;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../../shared-types/src/admin.ts","../../shared-types/src/event-log.ts","../../shared-types/src/entitlements.ts","../../shared-types/src/subscriptions.ts","../../shared-types/src/overrides.ts","../../shared-types/src/checkout.ts","../../shared-types/src/projects.ts","../../shared-types/src/errors.ts","../../shared-types/src/webhook-secret.ts","../../shared-types/src/portal-return-url.ts","../src/cache.ts","../src/errors.ts","../src/client.ts","../src/guard.ts","../src/shadow.ts"],"sourcesContent":["export { createClient } from './client.js';\nexport { BillingApiError } from './errors.js';\nexport { checkEntitlement } from './guard.js';\nexport { createShadowCheck } from './shadow.js';\nexport type {\n ShadowCheckOptions,\n ShadowErrorReason,\n ShadowReport,\n} from './shadow.js';\nexport type {\n BillingClient,\n CheckEntitlementInput,\n CheckoutRequest,\n CheckoutResponse,\n ClientOptions,\n EntitlementsResponse,\n EntitlementsResult,\n GuardDecision,\n LimitCheck,\n OnUnavailablePolicy,\n PortalRequest,\n PortalResponse,\n SubscriptionView,\n} from './types.js';\n","import { z } from 'zod';\nimport {\n adminEventDetailSchema,\n adminEventSummarySchema,\n signatureCountSchema,\n wouldApplySchema,\n} from './event-log.js';\nimport { entitlementsResponseSchema } from './entitlements.js';\nimport { overrideRecordSchema } from './overrides.js';\nimport { subscriptionStatusSchema } from './subscriptions.js';\n\n// Per-project and ecosystem-wide subscription state counts. `grace` is\n// computed (never stored) — see @org/entitlements' resolveAccessStatus.\nexport const stateCountsSchema = z.object({\n trialing: z.number(),\n active: z.number(),\n past_due: z.number(),\n grace: z.number(),\n canceled: z.number(),\n unpaid: z.number(),\n});\nexport type StateCounts = z.infer<typeof stateCountsSchema>;\n\nconst dashboardMetricsSchema = z.object({\n mrrCents: z.number(),\n mrrPrevCents: z.number(),\n subjectsCount: z.number(),\n states: stateCountsSchema,\n deadLetterDepth: z.number(),\n oldestUnprocessedSec: z.number().nullable(),\n driftCount: z.number(),\n});\n\nexport const projectMetricsSchema = dashboardMetricsSchema.extend({\n projectId: z.string(),\n projectKey: z.string(),\n projectName: z.string(),\n subjectType: z.string(),\n environment: z.enum(['test', 'live']),\n envLocked: z.boolean(),\n mrrSeries: z.array(z.number()),\n});\nexport type ProjectMetrics = z.infer<typeof projectMetricsSchema>;\n\nexport const adminMetricsResponseSchema = z.object({\n ecosystem: dashboardMetricsSchema,\n // Shared x-axis labels for every project's mrrSeries (same calendar\n // months across the ecosystem) — empty when no project has snapshots yet.\n monthLabels: z.array(z.string()),\n projects: z.array(projectMetricsSchema),\n});\nexport type AdminMetricsResponse = z.infer<typeof adminMetricsResponseSchema>;\n\n// Cursor-based (data-display.md's \"Newer / Older\" contract): the page query\n// and the approximate total are the only two things a caller needs — no\n// offset, no page number.\nexport const adminEventsListResponseSchema = z.object({\n events: z.array(adminEventSummarySchema),\n nextCursor: z.string().nullable(),\n approxTotal: z.number(),\n signatureCounts: z.array(signatureCountSchema).optional(),\n});\nexport type AdminEventsListResponse = z.infer<\n typeof adminEventsListResponseSchema\n>;\n\nexport const adminEventDetailResponseSchema = z.object({\n event: adminEventDetailSchema,\n transition: z.object({\n current: z\n .object({\n state: subscriptionStatusSchema.exclude(['grace', 'none']),\n lastEventAt: z.iso.datetime().nullable(),\n })\n .nullable(),\n wouldApply: wouldApplySchema,\n }),\n});\nexport type AdminEventDetailResponse = z.infer<\n typeof adminEventDetailResponseSchema\n>;\n\nexport const adminSubjectSchema = z.object({\n id: z.string(),\n subjectType: z.string(),\n externalId: z.string(),\n providerCustomerId: z.string().nullable(),\n});\nexport type AdminSubject = z.infer<typeof adminSubjectSchema>;\n\n// Grants are the raw material entitlements are computed from (sprints\n// 12-14): a boolean flag or a numeric limit, keyed by feature name.\nexport const featureGrantsSchema = z.record(\n z.string(),\n z.union([z.boolean(), z.number()]),\n);\nexport type AdminFeatureGrants = z.infer<typeof featureGrantsSchema>;\n\n// The subject's most recent subscription's plan, independent of whether\n// that subscription currently grants access (unlike entitlements.plan,\n// which is null exactly when it doesn't) — sprint 40's plan-aware comp\n// reads this to restore the plan's real grants rather than a synthetic\n// flag. priceInterval is null when the plan has no price to derive one\n// from; comp falls back to a fixed cycle length in that case.\nexport const adminSubjectSubscriptionPlanSchema = z.object({\n key: z.string(),\n featureGrants: featureGrantsSchema,\n priceInterval: z.enum(['month', 'year']).nullable(),\n});\nexport type AdminSubjectSubscriptionPlan = z.infer<\n typeof adminSubjectSubscriptionPlanSchema\n>;\n\nexport const adminSubjectLookupResponseSchema = z.object({\n subject: adminSubjectSchema,\n entitlements: entitlementsResponseSchema,\n recentEvents: z.array(adminEventSummarySchema),\n activeOverrides: z.array(overrideRecordSchema),\n subscriptionPlan: adminSubjectSubscriptionPlanSchema.nullable(),\n});\nexport type AdminSubjectLookupResponse = z.infer<\n typeof adminSubjectLookupResponseSchema\n>;\n\nexport const adminPriceSchema = z.object({\n id: z.string(),\n interval: z.enum(['month', 'year']),\n currency: z.string(),\n unitAmountCents: z.number(),\n syncStatus: z.enum(['synced', 'pending', 'drifted']),\n syncedUnitAmountCents: z.number().nullable(),\n});\nexport type AdminPrice = z.infer<typeof adminPriceSchema>;\n\nexport const adminPlanSchema = z.object({\n id: z.string(),\n key: z.string(),\n name: z.string(),\n featureGrants: featureGrantsSchema,\n activeSubscriptions: z.number(),\n prices: z.array(adminPriceSchema),\n});\nexport type AdminPlan = z.infer<typeof adminPlanSchema>;\n\n// Statement descriptors are project-level, not per-plan (packages/db's\n// projects table), but the plan editor screen renders and edits the field\n// alongside the plans it applies to — so the response carries the project's\n// resolved descriptor config as a sibling of the plan list, not duplicated\n// per plan.\nexport const adminPlansResponseSchema = z.object({\n project: z.object({\n statementDescriptor: z.string().nullable(),\n statementDescriptorPrefix: z.string().nullable(),\n }),\n plans: z.array(adminPlanSchema),\n});\nexport type AdminPlansResponse = z.infer<typeof adminPlansResponseSchema>;\n\n// Never the raw secret — masked to last-4 (or null if never configured).\nexport const adminProviderResponseSchema = z.object({\n projectId: z.string(),\n providerKind: z.string(),\n environment: z.enum(['test', 'live']),\n envLocked: z.boolean(),\n secretKeyMasked: z.string().nullable(),\n publishableKeyMasked: z.string().nullable(),\n webhookEndpoint: z.string(),\n webhookSigningConfigured: z.boolean(),\n defaultPortalReturnUrl: z.string().nullable(),\n});\nexport type AdminProviderResponse = z.infer<typeof adminProviderResponseSchema>;\n\n// The overrides domain has no project_id column of its own (project scope\n// comes from a join through subjects), so the admin service tags each entry\n// with its project as it assembles the cross-project audit response.\nexport const adminAuditEntrySchema = overrideRecordSchema.extend({\n projectId: z.string(),\n});\nexport type AdminAuditEntry = z.infer<typeof adminAuditEntrySchema>;\n\nexport const adminAuditResponseSchema = z.object({\n entries: z.array(adminAuditEntrySchema),\n nextCursor: z.string().nullable(),\n approxTotal: z.number(),\n});\nexport type AdminAuditResponse = z.infer<typeof adminAuditResponseSchema>;\n\n// Parallel arrays, not one row per day, to match EventThroughput's own prop\n// shape (data-display.md chart 3) — the web layer passes these straight\n// through without reshaping.\nexport const adminThroughputResponseSchema = z.object({\n days: z.array(z.string()),\n processed: z.array(z.number()),\n failed: z.array(z.number()),\n});\nexport type AdminThroughputResponse = z.infer<\n typeof adminThroughputResponseSchema\n>;\n\n// No historical snapshot table exists for plan mix (unlike MRR's\n// mrr_snapshots — see packages/db/src/schema/mrr-snapshots.ts's own note\n// that nothing populates it beyond the dev seed). Sprint 38 scopes this to\n// the current month only rather than inventing a snapshot table: `months`\n// and `stacks` always carry exactly one entry.\nexport const adminPlanMixResponseSchema = z.object({\n months: z.array(z.string()),\n plans: z.array(z.object({ key: z.string(), name: z.string() })),\n stacks: z.array(z.array(z.number())),\n});\nexport type AdminPlanMixResponse = z.infer<typeof adminPlanMixResponseSchema>;\n\n// (project_id, error_signature) GROUP BY over the full dead-letter queue —\n// distinct from signatureCountSchema (event-log.ts), which groups by\n// signature alone within one already-project-scoped page and can't tell two\n// projects' identical error text apart.\nexport const adminDeadLetterGroupSchema = z.object({\n projectId: z.string(),\n signature: z.string(),\n count: z.number(),\n sampleEventId: z.string(),\n firstReceivedAt: z.iso.datetime(),\n lastReceivedAt: z.iso.datetime(),\n});\nexport type AdminDeadLetterGroup = z.infer<typeof adminDeadLetterGroupSchema>;\n\nexport const adminDeadLetterGroupsResponseSchema = z.object({\n groups: z.array(adminDeadLetterGroupSchema),\n});\nexport type AdminDeadLetterGroupsResponse = z.infer<\n typeof adminDeadLetterGroupsResponseSchema\n>;\n\n// One project's full overview-card payload in a single round trip — the\n// scoped counterpart to adminMetricsResponseSchema's all-projects bundle,\n// for screens that only ever render one project (project-overview.tsx).\nexport const adminProjectDashboardResponseSchema = z.object({\n metrics: projectMetricsSchema,\n monthLabels: z.array(z.string()),\n providerKind: z.string(),\n plans: z.array(adminPlanSchema),\n});\nexport type AdminProjectDashboardResponse = z.infer<\n typeof adminProjectDashboardResponseSchema\n>;\n","import { z } from 'zod';\n\nexport const eventStatusSchema = z.enum([\n 'received',\n 'processed',\n 'failed',\n 'dead_letter',\n 'discarded',\n]);\nexport type EventStatus = z.infer<typeof eventStatusSchema>;\n\n// Matches @org/db's subscription_state enum — the five states the state\n// machine actually stores, not the computed 'grace'/'none' views.\nexport const subscriptionStateSchema = z.enum([\n 'trialing',\n 'active',\n 'past_due',\n 'canceled',\n 'unpaid',\n]);\nexport type SubscriptionStateContract = z.infer<typeof subscriptionStateSchema>;\n\nexport const adminEventSummarySchema = z.object({\n id: z.string(),\n projectId: z.string(),\n providerEventId: z.string(),\n type: z.string(),\n status: eventStatusSchema,\n attempts: z.number(),\n lastError: z.string().nullable(),\n errorSignature: z.string().nullable(),\n receivedAt: z.iso.datetime(),\n processedAt: z.iso.datetime().nullable(),\n // event_log has no subject/customer reference column (sprint 24's\n // repo-drizzle-subjects.ts documents the same gap the other direction) —\n // derived from the raw Stripe payload's well-known `data.object.customer`\n // path, so this is null for any non-Stripe-shaped payload.\n subjectRef: z.string().nullable(),\n});\nexport type AdminEventSummary = z.infer<typeof adminEventSummarySchema>;\n\nexport const adminEventDetailSchema = adminEventSummarySchema.extend({\n payload: z.record(z.string(), z.unknown()),\n discardReason: z.string().nullable(),\n discardedBy: z.string().nullable(),\n discardedAt: z.iso.datetime().nullable(),\n});\nexport type AdminEventDetail = z.infer<typeof adminEventDetailSchema>;\n\nexport const signatureCountSchema = z.object({\n signature: z.string(),\n count: z.number(),\n});\nexport type SignatureCount = z.infer<typeof signatureCountSchema>;\n\nexport const listEventsResponseSchema = z.object({\n events: z.array(adminEventSummarySchema),\n total: z.number(),\n limit: z.number(),\n offset: z.number(),\n signatureCounts: z.array(signatureCountSchema),\n});\nexport type ListEventsResponse = z.infer<typeof listEventsResponseSchema>;\n\n// Mirrors ApplyResult from apps/api's pure state machine, plus 'error' for\n// when the stored payload itself can't be replayed (e.g. missing provider\n// credentials) — dry-run degrades to this rather than throwing across the\n// HTTP boundary.\nexport const wouldApplySchema = z.discriminatedUnion('kind', [\n z.object({ kind: z.literal('create'), to: subscriptionStateSchema }),\n z.object({\n kind: z.literal('transition'),\n from: subscriptionStateSchema,\n to: subscriptionStateSchema,\n }),\n z.object({ kind: z.literal('stale') }),\n z.object({\n kind: z.literal('invalid'),\n from: subscriptionStateSchema,\n to: subscriptionStateSchema,\n }),\n z.object({ kind: z.literal('unknown_subscription') }),\n z.object({ kind: z.literal('noop'), reason: z.string() }),\n z.object({ kind: z.literal('error'), reason: z.string() }),\n]);\nexport type WouldApply = z.infer<typeof wouldApplySchema>;\n\nexport const dryRunReplayResponseSchema = z.object({\n event: adminEventDetailSchema,\n current: z\n .object({\n state: subscriptionStateSchema,\n lastEventAt: z.iso.datetime().nullable(),\n })\n .nullable(),\n wouldApply: wouldApplySchema,\n});\nexport type DryRunReplayResponse = z.infer<typeof dryRunReplayResponseSchema>;\n\nexport const replayOutcomeSchema = z.enum([\n 'created',\n 'transitioned',\n 'stale',\n 'noop',\n 'invalid',\n 'unknown_subscription',\n 'already_processed',\n 'error',\n]);\n\nexport const replayResponseSchema = z.object({\n outcome: replayOutcomeSchema,\n});\nexport type ReplayResponse = z.infer<typeof replayResponseSchema>;\n\nexport const discardEventRequestSchema = z.object({\n actor: z.string().min(1),\n reason: z.string(),\n});\nexport type DiscardEventRequest = z.infer<typeof discardEventRequestSchema>;\n\n// Operator-triggered, small-N batches (sprint 39): a hard ceiling so a bulk\n// replay can never process an unbounded number of events, whether in one\n// request or paged across several (sprint 48's progress UI). Shared so the\n// api's per-request clamp and the web client's paging loop can't drift.\nexport const BULK_REPLAY_CAP = 200;\n\n// Opaque-enough keyset cursor over (receivedAt, id), mirroring\n// processing-repo.ts's EventCursor — event_log's id is a random uuid, so\n// receivedAt provides the real chronological order and id only breaks ties.\nexport const replayGroupCursorSchema = z.object({\n receivedAt: z.iso.datetime(),\n id: z.string(),\n});\nexport type ReplayGroupCursor = z.infer<typeof replayGroupCursorSchema>;\n\n// A group is targeted either by its (project, signature) pair or by \"every\n// dead-lettered event in this project\" — never both, and never neither.\n// cursor/limit are optional so a bare {projectId, errorSignature|all} still\n// replays a single up-to-BULK_REPLAY_CAP batch exactly as before; the web\n// client's progress UI (sprint 48) pages through with them instead.\nexport const replayGroupRequestSchema = z\n .object({\n projectId: z.string().min(1),\n cursor: replayGroupCursorSchema.optional(),\n limit: z.number().int().positive().optional(),\n })\n .and(\n z.union([\n z.object({ errorSignature: z.string().min(1) }),\n z.object({ all: z.literal(true) }),\n ]),\n );\nexport type ReplayGroupRequest = z.infer<typeof replayGroupRequestSchema>;\n\nexport const replayGroupOutcomeEntrySchema = z.object({\n eventId: z.string(),\n outcome: replayOutcomeSchema,\n});\n\nexport const replayGroupResponseSchema = z.object({\n outcomes: z.array(replayGroupOutcomeEntrySchema),\n // Keyed by ProcessOutcome, but zod v4's record needs .partial() semantics\n // for an exhaustive enum key type, so this stays a plain string map — the\n // set of possible keys is still exactly replayOutcomeSchema's members.\n counts: z.record(z.string(), z.number()),\n // This call's batch (its own limit, or BULK_REPLAY_CAP by default) hit its\n // ceiling: more matching events remain beyond what was just replayed.\n // Paging with nextCursor continues; a non-paging caller reads this the\n // same way it always has, as \"replay again to finish draining it.\"\n truncated: z.boolean(),\n nextCursor: replayGroupCursorSchema.nullable(),\n});\nexport type ReplayGroupResponse = z.infer<typeof replayGroupResponseSchema>;\n","import { z } from 'zod';\n\nimport { subscriptionStatusSchema } from './subscriptions.js';\n\n// Additive alongside the aggregate `source` field below (kept for SDK\n// back-compat): every key in `features`/`limits` gets its own entry here,\n// so a caller no longer has to infer per-key provenance by cross-referencing\n// overrides itself.\nexport const entitlementSourcesSchema = z.object({\n features: z.record(z.string(), z.enum(['subscription', 'override'])),\n limits: z.record(z.string(), z.enum(['subscription', 'override'])),\n});\nexport type EntitlementSources = z.infer<typeof entitlementSourcesSchema>;\n\nexport const entitlementsResponseSchema = z.object({\n plan: z.string().nullable(),\n features: z.record(z.string(), z.boolean()),\n limits: z.record(z.string(), z.number()),\n status: subscriptionStatusSchema,\n source: z.enum(['subscription', 'override', 'none']),\n sources: entitlementSourcesSchema,\n});\nexport type EntitlementsResponse = z.infer<typeof entitlementsResponseSchema>;\n","import { z } from 'zod';\n\n// `grace` and `none` are computed from @org/db's stored enum + graceDays, never stored themselves.\nexport const subscriptionStatusSchema = z.enum([\n 'trialing',\n 'active',\n 'past_due',\n 'grace',\n 'canceled',\n 'unpaid',\n 'none',\n]);\nexport type SubscriptionStatus = z.infer<typeof subscriptionStatusSchema>;\n\nconst activeSubscriptionViewSchema = z.object({\n state: subscriptionStatusSchema.exclude(['none']),\n planKey: z.string(),\n currentPeriodEnd: z.iso.datetime(),\n cancelAtPeriodEnd: z.boolean(),\n});\n\nconst noSubscriptionViewSchema = z.object({\n state: z.literal('none'),\n});\n\nexport const subscriptionViewSchema = z.discriminatedUnion('state', [\n activeSubscriptionViewSchema,\n noSubscriptionViewSchema,\n]);\nexport type SubscriptionView = z.infer<typeof subscriptionViewSchema>;\n","import { z } from 'zod';\n\n// Operator vocabulary (matches the dashboard's comp/extend/revoke buttons),\n// distinct from @org/entitlements' internal grant|revoke computation\n// vocabulary — apps/api's overrides domain translates between the two.\nexport const overrideKindSchema = z.enum(['comp', 'extend', 'revoke']);\nexport type OverrideKind = z.infer<typeof overrideKindSchema>;\n\nexport const overrideTargetSchema = z.discriminatedUnion('type', [\n z.object({ type: z.literal('feature'), feature: z.string().min(1) }),\n z.object({\n type: z.literal('limit'),\n limit: z.string().min(1),\n value: z.number(),\n }),\n]);\nexport type OverrideTarget = z.infer<typeof overrideTargetSchema>;\n\nexport const createOverrideRequestSchema = z.object({\n subjectId: z.string().min(1),\n kind: overrideKindSchema,\n target: overrideTargetSchema,\n actor: z.string().min(1),\n reason: z.string().min(4),\n expiresAt: z.iso.datetime().optional(),\n});\nexport type CreateOverrideRequest = z.infer<typeof createOverrideRequestSchema>;\n\n// Multi-target variant: one subject, one kind/actor/reason/expiresAt, several\n// targets applied atomically (e.g. comping every grant key a plan defines) —\n// see apps/api's overrides service for the all-or-nothing guarantee.\nexport const createBulkOverrideRequestSchema = z.object({\n subjectId: z.string().min(1),\n kind: overrideKindSchema,\n targets: z.array(overrideTargetSchema).min(1),\n actor: z.string().min(1),\n reason: z.string().min(4),\n expiresAt: z.iso.datetime().optional(),\n});\nexport type CreateBulkOverrideRequest = z.infer<\n typeof createBulkOverrideRequestSchema\n>;\n\nexport const overrideRecordSchema = z.object({\n id: z.string(),\n subjectId: z.string(),\n kind: overrideKindSchema,\n target: overrideTargetSchema,\n actor: z.string(),\n reason: z.string(),\n expiresAt: z.iso.datetime().nullable(),\n createdAt: z.iso.datetime(),\n});\nexport type OverrideRecord = z.infer<typeof overrideRecordSchema>;\n\nexport const bulkOverrideResponseSchema = z.object({\n overrides: z.array(overrideRecordSchema),\n});\nexport type BulkOverrideResponse = z.infer<typeof bulkOverrideResponseSchema>;\n\nexport const listOverridesResponseSchema = z.object({\n overrides: z.array(overrideRecordSchema),\n total: z.number(),\n limit: z.number(),\n offset: z.number(),\n});\nexport type ListOverridesResponse = z.infer<typeof listOverridesResponseSchema>;\n","import { z } from 'zod';\n\nexport const checkoutRequestSchema = z.object({\n planKey: z.string(),\n subjectId: z.string(),\n successUrl: z.url(),\n cancelUrl: z.url(),\n email: z.email().optional(),\n});\nexport type CheckoutRequest = z.infer<typeof checkoutRequestSchema>;\n\nexport const checkoutResponseSchema = z.object({\n url: z.url(),\n});\nexport type CheckoutResponse = z.infer<typeof checkoutResponseSchema>;\n\nexport const portalRequestSchema = z.object({\n subjectId: z.string(),\n returnUrl: z.url(),\n});\nexport type PortalRequest = z.infer<typeof portalRequestSchema>;\n\nexport const portalResponseSchema = z.object({\n url: z.url(),\n});\nexport type PortalResponse = z.infer<typeof portalResponseSchema>;\n","import { z } from 'zod';\n\n// The source of truth for a registration payload's shape — apps/api's\n// projects service validates against this directly (no parallel\n// hand-written type), and the admin UI can reuse it for form validation.\nexport const registerProjectRequestSchema = z\n .object({\n key: z.string().trim().min(1, 'is required'),\n name: z.string().trim().min(1, 'is required'),\n subjectType: z.string().trim().min(1, 'is required'),\n environment: z.enum(['test', 'live'], 'must be \"test\" or \"live\"'),\n envLocked: z.boolean().optional(),\n providerKind: z.string().trim().min(1, 'is required'),\n providerSecretKeyRef: z.string().optional(),\n providerPublishableKeyRef: z.string().optional(),\n providerPortalConfigRef: z.string().optional(),\n statementDescriptor: z.string().optional(),\n statementDescriptorPrefix: z.string().optional(),\n graceDays: z.number().min(0, 'must be >= 0').optional(),\n defaultPortalReturnUrl: z.url().optional(),\n })\n .superRefine((input, ctx) => {\n if (!input.statementDescriptor && !input.statementDescriptorPrefix) {\n ctx.addIssue({\n code: 'custom',\n path: ['statementDescriptor'],\n message: 'statementDescriptor or statementDescriptorPrefix is required',\n });\n }\n });\nexport type RegisterProjectRequest = z.infer<\n typeof registerProjectRequestSchema\n>;\n","import { z } from 'zod';\n\nexport const errorEnvelopeSchema = z.object({\n error: z.object({\n code: z.string(),\n message: z.string(),\n }),\n});\nexport type ErrorEnvelope = z.infer<typeof errorEnvelopeSchema>;\n","import { z } from 'zod';\n\nexport class InvalidWebhookSecretError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidWebhookSecretError';\n }\n}\n\n// Stripe generates its own signing secret when a webhook endpoint is\n// created through its dashboard/API — this only validates the shape of a\n// value an operator copies in from there, it never generates one itself.\nconst webhookSigningSecretSchema = z\n .string()\n .regex(/^whsec_.+/, 'must be a non-empty whsec_-prefixed string');\n\nexport function resolveWebhookSigningSecret(value: string): string {\n const result = webhookSigningSecretSchema.safeParse(value);\n if (!result.success) {\n throw new InvalidWebhookSecretError(result.error.issues[0].message);\n }\n return result.data;\n}\n","import { z } from 'zod';\n\nexport class InvalidPortalReturnUrlError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidPortalReturnUrlError';\n }\n}\n\nconst portalReturnUrlSchema = z.url();\n\nexport function resolveDefaultPortalReturnUrl(value: string): string {\n const result = portalReturnUrlSchema.safeParse(value);\n if (!result.success) {\n throw new InvalidPortalReturnUrlError(result.error.issues[0].message);\n }\n return result.data;\n}\n","export interface CacheEntry<T> {\n value: T;\n fetchedAt: number;\n}\n\n// No timers or cleanup — server processes are short-lived at MVP scale, and\n// entries are only ever compared against `now` on read.\nexport class TtlCache<T> {\n private readonly store = new Map<string, CacheEntry<T>>();\n\n constructor(private readonly now: () => number = Date.now) {}\n\n get(key: string): CacheEntry<T> | undefined {\n return this.store.get(key);\n }\n\n set(key: string, value: T): void {\n this.store.set(key, { value, fetchedAt: this.now() });\n }\n\n getFresh(key: string, ttlMs: number): T | undefined {\n const entry = this.store.get(key);\n if (!entry) return undefined;\n return this.now() - entry.fetchedAt < ttlMs ? entry.value : undefined;\n }\n\n getStale(key: string): T | undefined {\n return this.store.get(key)?.value;\n }\n}\n","import { errorEnvelopeSchema } from '@org/shared-types';\n\n// Thrown for every failure path — API denials, transport failures, and\n// malformed responses alike — so consumers can branch on `status`/`code`\n// instead of catching different error shapes.\nexport class BillingApiError extends Error {\n readonly status: number;\n readonly code: string;\n\n constructor(status: number, code: string, message: string) {\n super(message);\n this.name = 'BillingApiError';\n this.status = status;\n this.code = code;\n }\n}\n\nexport async function errorFromResponse(\n response: Response,\n): Promise<BillingApiError> {\n let code = 'unknown_error';\n let message = `Request failed with status ${response.status}`;\n\n try {\n const body: unknown = await response.json();\n const parsed = errorEnvelopeSchema.safeParse(body);\n if (parsed.success) {\n code = parsed.data.error.code;\n message = parsed.data.error.message;\n }\n } catch {\n // Body wasn't JSON or didn't match the envelope — keep the defaults.\n }\n\n return new BillingApiError(response.status, code, message);\n}\n\n// Status 0 distinguishes \"couldn't reach the API\" from a real HTTP response,\n// so middleware (sprint 16) can fail open on outages without also failing\n// open on 4xx denials.\nexport function networkError(cause: unknown): BillingApiError {\n const message =\n cause instanceof Error ? cause.message : 'Network request failed';\n return new BillingApiError(0, 'network_error', message);\n}\n\nexport function invalidResponseError(message: string): BillingApiError {\n return new BillingApiError(502, 'invalid_response', message);\n}\n","import {\n checkoutResponseSchema,\n entitlementsResponseSchema,\n portalResponseSchema,\n subscriptionViewSchema,\n} from '@org/shared-types';\n\nimport { TtlCache } from './cache.js';\nimport {\n BillingApiError,\n errorFromResponse,\n invalidResponseError,\n networkError,\n} from './errors.js';\nimport type {\n BillingClient,\n CheckoutRequest,\n CheckoutResponse,\n ClientOptions,\n EntitlementsResult,\n PortalRequest,\n PortalResponse,\n SubscriptionView,\n} from './types.js';\n\nconst DEFAULT_ENTITLEMENTS_TTL_MS = 5000;\n\nasync function requestJson(\n fetchImpl: typeof fetch,\n url: string,\n apiKey: string,\n init?: RequestInit,\n): Promise<unknown> {\n let response: Response;\n try {\n response = await fetchImpl(url, {\n ...init,\n headers: {\n ...init?.headers,\n Authorization: `Bearer ${apiKey}`,\n ...(init?.body ? { 'Content-Type': 'application/json' } : {}),\n },\n });\n } catch (cause) {\n throw networkError(cause);\n }\n\n if (!response.ok) {\n throw await errorFromResponse(response);\n }\n\n return response.json();\n}\n\n// Structural rather than importing zod's ZodType directly — the package's\n// only runtime (and type) dependency stays @org/shared-types.\ninterface ParseableSchema<T> {\n safeParse(\n data: unknown,\n ):\n { success: true; data: T } | { success: false; error: { message: string } };\n}\n\nfunction parseOrThrow<T>(schema: ParseableSchema<T>, data: unknown): T {\n const parsed = schema.safeParse(data);\n if (!parsed.success) {\n throw invalidResponseError(\n `Response failed validation: ${parsed.error.message}`,\n );\n }\n return parsed.data;\n}\n\nexport function createClient(options: ClientOptions): BillingClient {\n const { apiKey, baseUrl } = options;\n const fetchImpl = options.fetch ?? fetch;\n const entitlementsTtlMs =\n options.entitlementsTtlMs ?? DEFAULT_ENTITLEMENTS_TTL_MS;\n const entitlementsCache = new TtlCache<EntitlementsResult>();\n\n async function getEntitlements(\n subjectId: string,\n ): Promise<EntitlementsResult> {\n const fresh = entitlementsCache.getFresh(subjectId, entitlementsTtlMs);\n if (fresh) return { ...fresh, fromCache: true };\n\n try {\n const data = await requestJson(\n fetchImpl,\n `${baseUrl}/v1/entitlements/${encodeURIComponent(subjectId)}`,\n apiKey,\n );\n const parsed = parseOrThrow(entitlementsResponseSchema, data);\n entitlementsCache.set(subjectId, parsed);\n return { ...parsed, fromCache: false };\n } catch (err) {\n const isClientError =\n err instanceof BillingApiError && err.status >= 400 && err.status < 500;\n if (isClientError) throw err;\n\n const stale = entitlementsCache.getStale(subjectId);\n if (stale) return { ...stale, stale: true, fromCache: true };\n throw err;\n }\n }\n\n async function createCheckout(\n input: CheckoutRequest,\n ): Promise<CheckoutResponse> {\n const data = await requestJson(\n fetchImpl,\n `${baseUrl}/v1/checkout`,\n apiKey,\n {\n method: 'POST',\n body: JSON.stringify(input),\n },\n );\n return parseOrThrow(checkoutResponseSchema, data);\n }\n\n async function portalUrl(input: PortalRequest): Promise<PortalResponse> {\n const data = await requestJson(fetchImpl, `${baseUrl}/v1/portal`, apiKey, {\n method: 'POST',\n body: JSON.stringify(input),\n });\n return parseOrThrow(portalResponseSchema, data);\n }\n\n async function getSubscription(subjectId: string): Promise<SubscriptionView> {\n const data = await requestJson(\n fetchImpl,\n `${baseUrl}/v1/subscriptions/${encodeURIComponent(subjectId)}`,\n apiKey,\n );\n return parseOrThrow(subscriptionViewSchema, data);\n }\n\n return { getEntitlements, createCheckout, portalUrl, getSubscription };\n}\n","import { BillingApiError } from './errors.js';\nimport type {\n CheckEntitlementInput,\n EntitlementsResult,\n GuardDecision,\n LimitCheck,\n} from './types.js';\n\nfunction isMisconfiguration(err: unknown): boolean {\n return (\n err instanceof BillingApiError && err.status >= 400 && err.status < 500\n );\n}\n\nfunction decideOnContent(\n entitlements: EntitlementsResult,\n feature: string | undefined,\n limit: LimitCheck | undefined,\n): GuardDecision {\n if (feature !== undefined) {\n return entitlements.features[feature]\n ? { decision: 'allow', entitlements }\n : { decision: 'deny', reason: `Feature \"${feature}\" is not entitled` };\n }\n\n if (limit !== undefined) {\n const max = entitlements.limits[limit.key];\n return max !== undefined && limit.usage < max\n ? { decision: 'allow', entitlements }\n : { decision: 'deny', reason: `Limit \"${limit.key}\" has been reached` };\n }\n\n return { decision: 'allow', entitlements };\n}\n\n// Framework-agnostic orchestration over BillingClient — Fastify (or any\n// other framework) middleware only needs to map GuardDecision to a\n// response. Keep this file free of any framework import.\nexport async function checkEntitlement(\n input: CheckEntitlementInput,\n): Promise<GuardDecision> {\n const { client, subjectId, feature, limit, onUnavailable } = input;\n\n let entitlements: EntitlementsResult;\n try {\n entitlements = await client.getEntitlements(subjectId);\n } catch (err) {\n if (isMisconfiguration(err)) {\n const message =\n err instanceof Error ? err.message : 'Billing request was rejected';\n return { decision: 'deny', reason: message, misconfigured: true };\n }\n return onUnavailable === 'open'\n ? { decision: 'unavailable-allow' }\n : { decision: 'unavailable-deny', reason: 'Billing is unavailable' };\n }\n\n // Fresh or stale, this is content-based — a stale entitlements payload is\n // the graceful-degradation path working, not an unavailability case.\n return decideOnContent(entitlements, feature, limit);\n}\n","import { BillingApiError } from './errors.js';\nimport type { BillingClient, EntitlementsResult } from './types.js';\n\nexport type ShadowErrorReason =\n 'timeout' | 'network' | 'parse_error' | 'http_error';\n\nexport interface ShadowReport {\n subjectId: string;\n legacyAnswer: boolean | null;\n legacyError: string | null;\n legacyLatencyMs: number;\n shadowAnswer: boolean | null;\n shadowError: { reason: ShadowErrorReason; message: string } | null;\n shadowLatencyMs: number;\n shadowStatus: EntitlementsResult['status'] | null;\n shadowSource: EntitlementsResult['source'] | null;\n shadowFromCache: boolean | null;\n // False whenever legacy or shadow errored — an incomparable pair is\n // never reported as agreement.\n agree: boolean;\n}\n\nexport interface ShadowCheckOptions<TId> {\n legacy: (id: TId) => Promise<boolean>;\n subjectIdFor: (id: TId) => string;\n client: BillingClient;\n entitled: (entitlements: EntitlementsResult) => boolean;\n report: (report: ShadowReport) => void;\n // 0..1, default 1. Short-circuits before any shadow work — no fetch on\n // skipped calls.\n sampleRate?: number;\n // Shadow fetch budget in ms, default 800. The shadow path runs\n // concurrently with legacy, so it adds at most this much latency beyond\n // whatever legacy already takes.\n timeoutMs?: number;\n}\n\nclass ShadowTimeoutError extends Error {\n constructor() {\n super('Shadow check timed out');\n this.name = 'ShadowTimeoutError';\n }\n}\n\nfunction withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => reject(new ShadowTimeoutError()), timeoutMs);\n promise.then(\n (value) => {\n clearTimeout(timer);\n resolve(value);\n },\n (error: unknown) => {\n clearTimeout(timer);\n reject(error);\n },\n );\n });\n}\n\nfunction classifyShadowError(error: unknown): ShadowErrorReason {\n if (error instanceof ShadowTimeoutError) return 'timeout';\n if (error instanceof BillingApiError) {\n if (error.code === 'network_error') return 'network';\n if (error.code === 'invalid_response') return 'parse_error';\n return 'http_error';\n }\n return 'http_error';\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\ntype LegacyOutcome =\n | { ok: true; value: boolean; latencyMs: number }\n | { ok: false; error: unknown; latencyMs: number };\n\ntype ShadowOutcome =\n | {\n kind: 'ok';\n answer: boolean;\n status: EntitlementsResult['status'];\n source: EntitlementsResult['source'];\n fromCache: boolean;\n latencyMs: number;\n }\n | {\n kind: 'error';\n reason: ShadowErrorReason;\n message: string;\n latencyMs: number;\n };\n\nasync function runShadow<TId>(\n options: ShadowCheckOptions<TId>,\n subjectId: string,\n timeoutMs: number,\n): Promise<ShadowOutcome> {\n const start = Date.now();\n try {\n const entitlements = await withTimeout(\n options.client.getEntitlements(subjectId),\n timeoutMs,\n );\n return {\n kind: 'ok',\n answer: options.entitled(entitlements),\n status: entitlements.status,\n source: entitlements.source,\n fromCache: entitlements.fromCache ?? false,\n latencyMs: Date.now() - start,\n };\n } catch (error) {\n return {\n kind: 'error',\n reason: classifyShadowError(error),\n message: errorMessage(error),\n latencyMs: Date.now() - start,\n };\n }\n}\n\nfunction buildReport(\n subjectId: string,\n legacy: LegacyOutcome,\n shadow: ShadowOutcome,\n): ShadowReport {\n const agree =\n legacy.ok && shadow.kind === 'ok' && legacy.value === shadow.answer;\n\n return {\n subjectId,\n legacyAnswer: legacy.ok ? legacy.value : null,\n legacyError: legacy.ok ? null : errorMessage(legacy.error),\n legacyLatencyMs: legacy.latencyMs,\n shadowAnswer: shadow.kind === 'ok' ? shadow.answer : null,\n shadowError:\n shadow.kind === 'error'\n ? { reason: shadow.reason, message: shadow.message }\n : null,\n shadowLatencyMs: shadow.latencyMs,\n shadowStatus: shadow.kind === 'ok' ? shadow.status : null,\n shadowSource: shadow.kind === 'ok' ? shadow.source : null,\n shadowFromCache: shadow.kind === 'ok' ? shadow.fromCache : null,\n agree,\n };\n}\n\nfunction shouldSample(sampleRate: number): boolean {\n if (sampleRate <= 0) return false;\n if (sampleRate >= 1) return true;\n return Math.random() < sampleRate;\n}\n\n// Wraps a legacy entitlement check with emit-billing's answer computed\n// side by side. Always resolves/rejects exactly as `legacy` would; the\n// shadow path only ever produces a report, never affects the return value.\nexport function createShadowCheck<TId>(\n options: ShadowCheckOptions<TId>,\n): (id: TId) => Promise<boolean> {\n const {\n legacy,\n subjectIdFor,\n report,\n sampleRate = 1,\n timeoutMs = 800,\n } = options;\n\n return async function shadowedCheck(id: TId): Promise<boolean> {\n const subjectId = subjectIdFor(id);\n const legacyStart = Date.now();\n const legacyPromise: Promise<LegacyOutcome> = legacy(id).then(\n (value) => ({ ok: true, value, latencyMs: Date.now() - legacyStart }),\n (error: unknown) => ({\n ok: false,\n error,\n latencyMs: Date.now() - legacyStart,\n }),\n );\n\n const shadowPromise = shouldSample(sampleRate)\n ? runShadow(options, subjectId, timeoutMs)\n : null;\n\n const legacyOutcome = await legacyPromise;\n\n if (shadowPromise) {\n const shadowOutcome = await shadowPromise;\n report(buildReport(subjectId, legacyOutcome, shadowOutcome));\n }\n\n if (!legacyOutcome.ok) throw legacyOutcome.error;\n return legacyOutcome.value;\n };\n}\n"],"mappings":"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","names":["import_zod","import_zod","import_zod","import_zod","import_zod","import_zod","import_zod","import_zod","import_zod"]}
1
+ {"version":3,"sources":["../src/index.ts","../../shared-types/src/admin.ts","../../shared-types/src/event-log.ts","../../shared-types/src/entitlements.ts","../../shared-types/src/subscriptions.ts","../../shared-types/src/overrides.ts","../../shared-types/src/checkout.ts","../../shared-types/src/projects.ts","../../shared-types/src/errors.ts","../../shared-types/src/webhook-secret.ts","../../shared-types/src/portal-return-url.ts","../src/cache.ts","../src/errors.ts","../src/client.ts","../src/guard.ts","../src/shadow.ts"],"sourcesContent":["export { createClient } from './client.js';\nexport { BillingApiError } from './errors.js';\nexport { checkEntitlement } from './guard.js';\nexport { createShadowCheck } from './shadow.js';\nexport type {\n ShadowCheckOptions,\n ShadowErrorReason,\n ShadowReport,\n} from './shadow.js';\nexport type {\n BillingClient,\n CheckEntitlementInput,\n CheckoutRequest,\n CheckoutResponse,\n ClientOptions,\n EntitlementsResponse,\n EntitlementsResult,\n GuardDecision,\n LimitCheck,\n OnUnavailablePolicy,\n PortalRequest,\n PortalResponse,\n SubscriptionView,\n} from './types.js';\n","import { z } from 'zod';\nimport {\n adminEventDetailSchema,\n adminEventSummarySchema,\n signatureCountSchema,\n wouldApplySchema,\n} from './event-log.js';\nimport { entitlementsResponseSchema } from './entitlements.js';\nimport { overrideRecordSchema } from './overrides.js';\nimport { subscriptionStatusSchema } from './subscriptions.js';\n\n// Per-project and ecosystem-wide subscription state counts. `grace` is\n// computed (never stored) — see @org/entitlements' resolveAccessStatus.\nexport const stateCountsSchema = z.object({\n trialing: z.number(),\n active: z.number(),\n past_due: z.number(),\n grace: z.number(),\n canceled: z.number(),\n unpaid: z.number(),\n});\nexport type StateCounts = z.infer<typeof stateCountsSchema>;\n\nconst dashboardMetricsSchema = z.object({\n mrrCents: z.number(),\n mrrPrevCents: z.number(),\n subjectsCount: z.number(),\n states: stateCountsSchema,\n deadLetterDepth: z.number(),\n oldestUnprocessedSec: z.number().nullable(),\n driftCount: z.number(),\n});\n\nexport const projectMetricsSchema = dashboardMetricsSchema.extend({\n projectId: z.string(),\n projectKey: z.string(),\n projectName: z.string(),\n subjectType: z.string(),\n environment: z.enum(['test', 'live']),\n envLocked: z.boolean(),\n mrrSeries: z.array(z.number()),\n});\nexport type ProjectMetrics = z.infer<typeof projectMetricsSchema>;\n\nexport const adminMetricsResponseSchema = z.object({\n ecosystem: dashboardMetricsSchema,\n // Shared x-axis labels for every project's mrrSeries (same calendar\n // months across the ecosystem) — empty when no project has snapshots yet.\n monthLabels: z.array(z.string()),\n projects: z.array(projectMetricsSchema),\n});\nexport type AdminMetricsResponse = z.infer<typeof adminMetricsResponseSchema>;\n\n// Cursor-based (data-display.md's \"Newer / Older\" contract): the page query\n// and the approximate total are the only two things a caller needs — no\n// offset, no page number.\nexport const adminEventsListResponseSchema = z.object({\n events: z.array(adminEventSummarySchema),\n nextCursor: z.string().nullable(),\n approxTotal: z.number(),\n signatureCounts: z.array(signatureCountSchema).optional(),\n});\nexport type AdminEventsListResponse = z.infer<\n typeof adminEventsListResponseSchema\n>;\n\nexport const adminEventDetailResponseSchema = z.object({\n event: adminEventDetailSchema,\n transition: z.object({\n current: z\n .object({\n state: subscriptionStatusSchema.exclude(['grace', 'none']),\n lastEventAt: z.iso.datetime().nullable(),\n })\n .nullable(),\n wouldApply: wouldApplySchema,\n }),\n});\nexport type AdminEventDetailResponse = z.infer<\n typeof adminEventDetailResponseSchema\n>;\n\nexport const adminSubjectSchema = z.object({\n id: z.string(),\n subjectType: z.string(),\n externalId: z.string(),\n providerCustomerId: z.string().nullable(),\n});\nexport type AdminSubject = z.infer<typeof adminSubjectSchema>;\n\n// Grants are the raw material entitlements are computed from (sprints\n// 12-14): a boolean flag or a numeric limit, keyed by feature name.\nexport const featureGrantsSchema = z.record(\n z.string(),\n z.union([z.boolean(), z.number()]),\n);\nexport type AdminFeatureGrants = z.infer<typeof featureGrantsSchema>;\n\n// The subject's most recent subscription's plan, independent of whether\n// that subscription currently grants access (unlike entitlements.plan,\n// which is null exactly when it doesn't) — sprint 40's plan-aware comp\n// reads this to restore the plan's real grants rather than a synthetic\n// flag. priceInterval is null when the plan has no price to derive one\n// from; comp falls back to a fixed cycle length in that case.\nexport const adminSubjectSubscriptionPlanSchema = z.object({\n key: z.string(),\n featureGrants: featureGrantsSchema,\n priceInterval: z.enum(['month', 'year']).nullable(),\n});\nexport type AdminSubjectSubscriptionPlan = z.infer<\n typeof adminSubjectSubscriptionPlanSchema\n>;\n\nexport const adminSubjectLookupResponseSchema = z.object({\n subject: adminSubjectSchema,\n entitlements: entitlementsResponseSchema,\n recentEvents: z.array(adminEventSummarySchema),\n activeOverrides: z.array(overrideRecordSchema),\n subscriptionPlan: adminSubjectSubscriptionPlanSchema.nullable(),\n});\nexport type AdminSubjectLookupResponse = z.infer<\n typeof adminSubjectLookupResponseSchema\n>;\n\nexport const adminPriceSchema = z.object({\n id: z.string(),\n interval: z.enum(['month', 'year']),\n currency: z.string(),\n unitAmountCents: z.number(),\n syncStatus: z.enum(['synced', 'pending', 'drifted']),\n syncedUnitAmountCents: z.number().nullable(),\n});\nexport type AdminPrice = z.infer<typeof adminPriceSchema>;\n\nexport const adminPlanSchema = z.object({\n id: z.string(),\n key: z.string(),\n name: z.string(),\n featureGrants: featureGrantsSchema,\n activeSubscriptions: z.number(),\n prices: z.array(adminPriceSchema),\n});\nexport type AdminPlan = z.infer<typeof adminPlanSchema>;\n\n// Statement descriptors are project-level, not per-plan (packages/db's\n// projects table), but the plan editor screen renders and edits the field\n// alongside the plans it applies to — so the response carries the project's\n// resolved descriptor config as a sibling of the plan list, not duplicated\n// per plan.\nexport const adminPlansResponseSchema = z.object({\n project: z.object({\n statementDescriptor: z.string().nullable(),\n statementDescriptorPrefix: z.string().nullable(),\n }),\n plans: z.array(adminPlanSchema),\n});\nexport type AdminPlansResponse = z.infer<typeof adminPlansResponseSchema>;\n\n// Never the raw secret — masked to last-4 (or null if never configured).\nexport const adminProviderResponseSchema = z.object({\n projectId: z.string(),\n providerKind: z.string(),\n environment: z.enum(['test', 'live']),\n envLocked: z.boolean(),\n secretKeyMasked: z.string().nullable(),\n publishableKeyMasked: z.string().nullable(),\n webhookEndpoint: z.string(),\n webhookSigningConfigured: z.boolean(),\n defaultPortalReturnUrl: z.string().nullable(),\n});\nexport type AdminProviderResponse = z.infer<typeof adminProviderResponseSchema>;\n\n// The overrides domain has no project_id column of its own (project scope\n// comes from a join through subjects), so the admin service tags each entry\n// with its project as it assembles the cross-project audit response.\nexport const adminAuditEntrySchema = overrideRecordSchema.extend({\n projectId: z.string(),\n});\nexport type AdminAuditEntry = z.infer<typeof adminAuditEntrySchema>;\n\nexport const adminAuditResponseSchema = z.object({\n entries: z.array(adminAuditEntrySchema),\n nextCursor: z.string().nullable(),\n approxTotal: z.number(),\n});\nexport type AdminAuditResponse = z.infer<typeof adminAuditResponseSchema>;\n\n// Parallel arrays, not one row per day, to match EventThroughput's own prop\n// shape (data-display.md chart 3) — the web layer passes these straight\n// through without reshaping.\nexport const adminThroughputResponseSchema = z.object({\n days: z.array(z.string()),\n processed: z.array(z.number()),\n failed: z.array(z.number()),\n});\nexport type AdminThroughputResponse = z.infer<\n typeof adminThroughputResponseSchema\n>;\n\n// No historical snapshot table exists for plan mix (unlike MRR's\n// mrr_snapshots — see packages/db/src/schema/mrr-snapshots.ts's own note\n// that nothing populates it beyond the dev seed). Sprint 38 scopes this to\n// the current month only rather than inventing a snapshot table: `months`\n// and `stacks` always carry exactly one entry.\nexport const adminPlanMixResponseSchema = z.object({\n months: z.array(z.string()),\n plans: z.array(z.object({ key: z.string(), name: z.string() })),\n stacks: z.array(z.array(z.number())),\n});\nexport type AdminPlanMixResponse = z.infer<typeof adminPlanMixResponseSchema>;\n\n// (project_id, error_signature) GROUP BY over the full dead-letter queue —\n// distinct from signatureCountSchema (event-log.ts), which groups by\n// signature alone within one already-project-scoped page and can't tell two\n// projects' identical error text apart.\nexport const adminDeadLetterGroupSchema = z.object({\n projectId: z.string(),\n signature: z.string(),\n count: z.number(),\n sampleEventId: z.string(),\n firstReceivedAt: z.iso.datetime(),\n lastReceivedAt: z.iso.datetime(),\n});\nexport type AdminDeadLetterGroup = z.infer<typeof adminDeadLetterGroupSchema>;\n\nexport const adminDeadLetterGroupsResponseSchema = z.object({\n groups: z.array(adminDeadLetterGroupSchema),\n});\nexport type AdminDeadLetterGroupsResponse = z.infer<\n typeof adminDeadLetterGroupsResponseSchema\n>;\n\n// One project's full overview-card payload in a single round trip — the\n// scoped counterpart to adminMetricsResponseSchema's all-projects bundle,\n// for screens that only ever render one project (project-overview.tsx).\nexport const adminProjectDashboardResponseSchema = z.object({\n metrics: projectMetricsSchema,\n monthLabels: z.array(z.string()),\n providerKind: z.string(),\n plans: z.array(adminPlanSchema),\n});\nexport type AdminProjectDashboardResponse = z.infer<\n typeof adminProjectDashboardResponseSchema\n>;\n","import { z } from 'zod';\n\nexport const eventStatusSchema = z.enum([\n 'received',\n 'processed',\n 'failed',\n 'dead_letter',\n 'discarded',\n]);\nexport type EventStatus = z.infer<typeof eventStatusSchema>;\n\n// Matches @org/db's subscription_state enum — the five states the state\n// machine actually stores, not the computed 'grace'/'none' views.\nexport const subscriptionStateSchema = z.enum([\n 'trialing',\n 'active',\n 'past_due',\n 'canceled',\n 'unpaid',\n]);\nexport type SubscriptionStateContract = z.infer<typeof subscriptionStateSchema>;\n\nexport const adminEventSummarySchema = z.object({\n id: z.string(),\n projectId: z.string(),\n providerEventId: z.string(),\n type: z.string(),\n status: eventStatusSchema,\n attempts: z.number(),\n lastError: z.string().nullable(),\n errorSignature: z.string().nullable(),\n receivedAt: z.iso.datetime(),\n processedAt: z.iso.datetime().nullable(),\n // event_log has no subject/customer reference column (sprint 24's\n // repo-drizzle-subjects.ts documents the same gap the other direction) —\n // derived from the raw Stripe payload's well-known `data.object.customer`\n // path, so this is null for any non-Stripe-shaped payload.\n subjectRef: z.string().nullable(),\n});\nexport type AdminEventSummary = z.infer<typeof adminEventSummarySchema>;\n\nexport const adminEventDetailSchema = adminEventSummarySchema.extend({\n payload: z.record(z.string(), z.unknown()),\n discardReason: z.string().nullable(),\n discardedBy: z.string().nullable(),\n discardedAt: z.iso.datetime().nullable(),\n});\nexport type AdminEventDetail = z.infer<typeof adminEventDetailSchema>;\n\nexport const signatureCountSchema = z.object({\n signature: z.string(),\n count: z.number(),\n});\nexport type SignatureCount = z.infer<typeof signatureCountSchema>;\n\nexport const listEventsResponseSchema = z.object({\n events: z.array(adminEventSummarySchema),\n total: z.number(),\n limit: z.number(),\n offset: z.number(),\n signatureCounts: z.array(signatureCountSchema),\n});\nexport type ListEventsResponse = z.infer<typeof listEventsResponseSchema>;\n\n// Mirrors ApplyResult from apps/api's pure state machine, plus 'error' for\n// when the stored payload itself can't be replayed (e.g. missing provider\n// credentials) — dry-run degrades to this rather than throwing across the\n// HTTP boundary.\nexport const wouldApplySchema = z.discriminatedUnion('kind', [\n z.object({ kind: z.literal('create'), to: subscriptionStateSchema }),\n z.object({\n kind: z.literal('transition'),\n from: subscriptionStateSchema,\n to: subscriptionStateSchema,\n }),\n z.object({ kind: z.literal('stale') }),\n z.object({\n kind: z.literal('invalid'),\n from: subscriptionStateSchema,\n to: subscriptionStateSchema,\n }),\n z.object({ kind: z.literal('unknown_subscription') }),\n z.object({ kind: z.literal('noop'), reason: z.string() }),\n z.object({ kind: z.literal('error'), reason: z.string() }),\n]);\nexport type WouldApply = z.infer<typeof wouldApplySchema>;\n\nexport const dryRunReplayResponseSchema = z.object({\n event: adminEventDetailSchema,\n current: z\n .object({\n state: subscriptionStateSchema,\n lastEventAt: z.iso.datetime().nullable(),\n })\n .nullable(),\n wouldApply: wouldApplySchema,\n});\nexport type DryRunReplayResponse = z.infer<typeof dryRunReplayResponseSchema>;\n\nexport const replayOutcomeSchema = z.enum([\n 'created',\n 'transitioned',\n 'stale',\n 'noop',\n 'invalid',\n 'unknown_subscription',\n 'already_processed',\n 'error',\n]);\n\nexport const replayResponseSchema = z.object({\n outcome: replayOutcomeSchema,\n});\nexport type ReplayResponse = z.infer<typeof replayResponseSchema>;\n\nexport const discardEventRequestSchema = z.object({\n actor: z.string().min(1),\n reason: z.string(),\n});\nexport type DiscardEventRequest = z.infer<typeof discardEventRequestSchema>;\n\n// Operator-triggered, small-N batches (sprint 39): a hard ceiling so a bulk\n// replay can never process an unbounded number of events, whether in one\n// request or paged across several (sprint 48's progress UI). Shared so the\n// api's per-request clamp and the web client's paging loop can't drift.\nexport const BULK_REPLAY_CAP = 200;\n\n// Opaque-enough keyset cursor over (receivedAt, id), mirroring\n// processing-repo.ts's EventCursor — event_log's id is a random uuid, so\n// receivedAt provides the real chronological order and id only breaks ties.\nexport const replayGroupCursorSchema = z.object({\n receivedAt: z.iso.datetime(),\n id: z.string(),\n});\nexport type ReplayGroupCursor = z.infer<typeof replayGroupCursorSchema>;\n\n// A group is targeted either by its (project, signature) pair or by \"every\n// dead-lettered event in this project\" — never both, and never neither.\n// cursor/limit are optional so a bare {projectId, errorSignature|all} still\n// replays a single up-to-BULK_REPLAY_CAP batch exactly as before; the web\n// client's progress UI (sprint 48) pages through with them instead.\nexport const replayGroupRequestSchema = z\n .object({\n projectId: z.string().min(1),\n cursor: replayGroupCursorSchema.optional(),\n limit: z.number().int().positive().optional(),\n })\n .and(\n z.union([\n z.object({ errorSignature: z.string().min(1) }),\n z.object({ all: z.literal(true) }),\n ]),\n );\nexport type ReplayGroupRequest = z.infer<typeof replayGroupRequestSchema>;\n\nexport const replayGroupOutcomeEntrySchema = z.object({\n eventId: z.string(),\n outcome: replayOutcomeSchema,\n});\n\nexport const replayGroupResponseSchema = z.object({\n outcomes: z.array(replayGroupOutcomeEntrySchema),\n // Keyed by ProcessOutcome, but zod v4's record needs .partial() semantics\n // for an exhaustive enum key type, so this stays a plain string map — the\n // set of possible keys is still exactly replayOutcomeSchema's members.\n counts: z.record(z.string(), z.number()),\n // This call's batch (its own limit, or BULK_REPLAY_CAP by default) hit its\n // ceiling: more matching events remain beyond what was just replayed.\n // Paging with nextCursor continues; a non-paging caller reads this the\n // same way it always has, as \"replay again to finish draining it.\"\n truncated: z.boolean(),\n nextCursor: replayGroupCursorSchema.nullable(),\n});\nexport type ReplayGroupResponse = z.infer<typeof replayGroupResponseSchema>;\n","import { z } from 'zod';\n\nimport { subscriptionStatusSchema } from './subscriptions.js';\n\n// Additive alongside the aggregate `source` field below (kept for SDK\n// back-compat): every key in `features`/`limits` gets its own entry here,\n// so a caller no longer has to infer per-key provenance by cross-referencing\n// overrides itself.\nexport const entitlementSourcesSchema = z.object({\n features: z.record(z.string(), z.enum(['subscription', 'override'])),\n limits: z.record(z.string(), z.enum(['subscription', 'override'])),\n});\nexport type EntitlementSources = z.infer<typeof entitlementSourcesSchema>;\n\nexport const entitlementsResponseSchema = z.object({\n plan: z.string().nullable(),\n features: z.record(z.string(), z.boolean()),\n limits: z.record(z.string(), z.number()),\n status: subscriptionStatusSchema,\n source: z.enum(['subscription', 'override', 'none']),\n sources: entitlementSourcesSchema,\n});\nexport type EntitlementsResponse = z.infer<typeof entitlementsResponseSchema>;\n","import { z } from 'zod';\n\n// `grace` and `none` are computed from @org/db's stored enum + graceDays, never stored themselves.\nexport const subscriptionStatusSchema = z.enum([\n 'trialing',\n 'active',\n 'past_due',\n 'grace',\n 'canceled',\n 'unpaid',\n 'none',\n]);\nexport type SubscriptionStatus = z.infer<typeof subscriptionStatusSchema>;\n\nconst activeSubscriptionViewSchema = z.object({\n state: subscriptionStatusSchema.exclude(['none']),\n planKey: z.string(),\n currentPeriodEnd: z.iso.datetime(),\n cancelAtPeriodEnd: z.boolean(),\n});\n\nconst noSubscriptionViewSchema = z.object({\n state: z.literal('none'),\n});\n\nexport const subscriptionViewSchema = z.discriminatedUnion('state', [\n activeSubscriptionViewSchema,\n noSubscriptionViewSchema,\n]);\nexport type SubscriptionView = z.infer<typeof subscriptionViewSchema>;\n","import { z } from 'zod';\n\n// Operator vocabulary (matches the dashboard's comp/extend/revoke buttons),\n// distinct from @org/entitlements' internal grant|revoke computation\n// vocabulary — apps/api's overrides domain translates between the two.\nexport const overrideKindSchema = z.enum(['comp', 'extend', 'revoke']);\nexport type OverrideKind = z.infer<typeof overrideKindSchema>;\n\nexport const overrideTargetSchema = z.discriminatedUnion('type', [\n z.object({ type: z.literal('feature'), feature: z.string().min(1) }),\n z.object({\n type: z.literal('limit'),\n limit: z.string().min(1),\n value: z.number(),\n }),\n]);\nexport type OverrideTarget = z.infer<typeof overrideTargetSchema>;\n\nexport const createOverrideRequestSchema = z.object({\n subjectId: z.string().min(1),\n kind: overrideKindSchema,\n target: overrideTargetSchema,\n actor: z.string().min(1),\n reason: z.string().min(4),\n expiresAt: z.iso.datetime().optional(),\n});\nexport type CreateOverrideRequest = z.infer<typeof createOverrideRequestSchema>;\n\n// Multi-target variant: one subject, one kind/actor/reason/expiresAt, several\n// targets applied atomically (e.g. comping every grant key a plan defines) —\n// see apps/api's overrides service for the all-or-nothing guarantee.\nexport const createBulkOverrideRequestSchema = z.object({\n subjectId: z.string().min(1),\n kind: overrideKindSchema,\n targets: z.array(overrideTargetSchema).min(1),\n actor: z.string().min(1),\n reason: z.string().min(4),\n expiresAt: z.iso.datetime().optional(),\n});\nexport type CreateBulkOverrideRequest = z.infer<\n typeof createBulkOverrideRequestSchema\n>;\n\nexport const overrideRecordSchema = z.object({\n id: z.string(),\n subjectId: z.string(),\n kind: overrideKindSchema,\n target: overrideTargetSchema,\n actor: z.string(),\n reason: z.string(),\n expiresAt: z.iso.datetime().nullable(),\n createdAt: z.iso.datetime(),\n});\nexport type OverrideRecord = z.infer<typeof overrideRecordSchema>;\n\nexport const bulkOverrideResponseSchema = z.object({\n overrides: z.array(overrideRecordSchema),\n});\nexport type BulkOverrideResponse = z.infer<typeof bulkOverrideResponseSchema>;\n\nexport const listOverridesResponseSchema = z.object({\n overrides: z.array(overrideRecordSchema),\n total: z.number(),\n limit: z.number(),\n offset: z.number(),\n});\nexport type ListOverridesResponse = z.infer<typeof listOverridesResponseSchema>;\n","import { z } from 'zod';\n\nexport const checkoutRequestSchema = z.object({\n planKey: z.string(),\n subjectId: z.string(),\n successUrl: z.url(),\n cancelUrl: z.url(),\n email: z.email().optional(),\n});\nexport type CheckoutRequest = z.infer<typeof checkoutRequestSchema>;\n\nexport const checkoutResponseSchema = z.object({\n url: z.url(),\n});\nexport type CheckoutResponse = z.infer<typeof checkoutResponseSchema>;\n\nexport const portalRequestSchema = z.object({\n subjectId: z.string(),\n returnUrl: z.url(),\n});\nexport type PortalRequest = z.infer<typeof portalRequestSchema>;\n\nexport const portalResponseSchema = z.object({\n url: z.url(),\n});\nexport type PortalResponse = z.infer<typeof portalResponseSchema>;\n","import { z } from 'zod';\n\n// The source of truth for a registration payload's shape — apps/api's\n// projects service validates against this directly (no parallel\n// hand-written type), and the admin UI can reuse it for form validation.\nexport const registerProjectRequestSchema = z\n .object({\n key: z.string().trim().min(1, 'is required'),\n name: z.string().trim().min(1, 'is required'),\n subjectType: z.string().trim().min(1, 'is required'),\n environment: z.enum(['test', 'live'], 'must be \"test\" or \"live\"'),\n envLocked: z.boolean().optional(),\n providerKind: z.string().trim().min(1, 'is required'),\n providerSecretKeyRef: z.string().optional(),\n providerPublishableKeyRef: z.string().optional(),\n providerPortalConfigRef: z.string().optional(),\n statementDescriptor: z.string().optional(),\n statementDescriptorPrefix: z.string().optional(),\n graceDays: z.number().min(0, 'must be >= 0').optional(),\n defaultPortalReturnUrl: z.url().optional(),\n })\n .superRefine((input, ctx) => {\n if (!input.statementDescriptor && !input.statementDescriptorPrefix) {\n ctx.addIssue({\n code: 'custom',\n path: ['statementDescriptor'],\n message: 'statementDescriptor or statementDescriptorPrefix is required',\n });\n }\n });\nexport type RegisterProjectRequest = z.infer<\n typeof registerProjectRequestSchema\n>;\n","import { z } from 'zod';\n\nexport const errorEnvelopeSchema = z.object({\n error: z.object({\n code: z.string(),\n message: z.string(),\n }),\n});\nexport type ErrorEnvelope = z.infer<typeof errorEnvelopeSchema>;\n","import { z } from 'zod';\n\nexport class InvalidWebhookSecretError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidWebhookSecretError';\n }\n}\n\n// Stripe generates its own signing secret when a webhook endpoint is\n// created through its dashboard/API — this only validates the shape of a\n// value an operator copies in from there, it never generates one itself.\nconst webhookSigningSecretSchema = z\n .string()\n .regex(/^whsec_.+/, 'must be a non-empty whsec_-prefixed string');\n\nexport function resolveWebhookSigningSecret(value: string): string {\n const result = webhookSigningSecretSchema.safeParse(value);\n if (!result.success) {\n throw new InvalidWebhookSecretError(result.error.issues[0].message);\n }\n return result.data;\n}\n","import { z } from 'zod';\n\nexport class InvalidPortalReturnUrlError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidPortalReturnUrlError';\n }\n}\n\nconst portalReturnUrlSchema = z.url();\n\nexport function resolveDefaultPortalReturnUrl(value: string): string {\n const result = portalReturnUrlSchema.safeParse(value);\n if (!result.success) {\n throw new InvalidPortalReturnUrlError(result.error.issues[0].message);\n }\n return result.data;\n}\n","export interface CacheEntry<T> {\n value: T;\n fetchedAt: number;\n}\n\n// No timers or cleanup — server processes are short-lived at MVP scale, and\n// entries are only ever compared against `now` on read.\nexport class TtlCache<T> {\n private readonly store = new Map<string, CacheEntry<T>>();\n\n constructor(private readonly now: () => number = Date.now) {}\n\n get(key: string): CacheEntry<T> | undefined {\n return this.store.get(key);\n }\n\n set(key: string, value: T): void {\n this.store.set(key, { value, fetchedAt: this.now() });\n }\n\n getFresh(key: string, ttlMs: number): T | undefined {\n const entry = this.store.get(key);\n if (!entry) return undefined;\n return this.now() - entry.fetchedAt < ttlMs ? entry.value : undefined;\n }\n\n getStale(key: string): T | undefined {\n return this.store.get(key)?.value;\n }\n}\n","import { errorEnvelopeSchema } from '@org/shared-types';\n\n// Thrown for every failure path — API denials, transport failures, and\n// malformed responses alike — so consumers can branch on `status`/`code`\n// instead of catching different error shapes.\nexport class BillingApiError extends Error {\n readonly status: number;\n readonly code: string;\n\n constructor(status: number, code: string, message: string) {\n super(message);\n this.name = 'BillingApiError';\n this.status = status;\n this.code = code;\n }\n}\n\nexport async function errorFromResponse(\n response: Response,\n): Promise<BillingApiError> {\n let code = 'unknown_error';\n let message = `Request failed with status ${response.status}`;\n\n try {\n const body: unknown = await response.json();\n const parsed = errorEnvelopeSchema.safeParse(body);\n if (parsed.success) {\n code = parsed.data.error.code;\n message = parsed.data.error.message;\n }\n } catch {\n // Body wasn't JSON or didn't match the envelope — keep the defaults.\n }\n\n return new BillingApiError(response.status, code, message);\n}\n\n// Status 0 distinguishes \"couldn't reach the API\" from a real HTTP response,\n// so middleware (sprint 16) can fail open on outages without also failing\n// open on 4xx denials.\nexport function networkError(cause: unknown): BillingApiError {\n const message =\n cause instanceof Error ? cause.message : 'Network request failed';\n return new BillingApiError(0, 'network_error', message);\n}\n\nexport function invalidResponseError(message: string): BillingApiError {\n return new BillingApiError(502, 'invalid_response', message);\n}\n","import {\n checkoutResponseSchema,\n entitlementsResponseSchema,\n portalResponseSchema,\n subscriptionViewSchema,\n} from '@org/shared-types';\n\nimport { TtlCache } from './cache.js';\nimport {\n BillingApiError,\n errorFromResponse,\n invalidResponseError,\n networkError,\n} from './errors.js';\nimport type {\n BillingClient,\n CheckoutRequest,\n CheckoutResponse,\n ClientOptions,\n EntitlementsResult,\n PortalRequest,\n PortalResponse,\n SubscriptionView,\n} from './types.js';\n\nconst DEFAULT_ENTITLEMENTS_TTL_MS = 5000;\n\nasync function requestJson(\n fetchImpl: typeof fetch,\n url: string,\n apiKey: string,\n init?: RequestInit,\n): Promise<unknown> {\n let response: Response;\n try {\n response = await fetchImpl(url, {\n ...init,\n headers: {\n ...init?.headers,\n Authorization: `Bearer ${apiKey}`,\n ...(init?.body ? { 'Content-Type': 'application/json' } : {}),\n },\n });\n } catch (cause) {\n throw networkError(cause);\n }\n\n if (!response.ok) {\n throw await errorFromResponse(response);\n }\n\n return response.json();\n}\n\n// Structural rather than importing zod's ZodType directly — the package's\n// only runtime (and type) dependency stays @org/shared-types.\ninterface ParseableSchema<T> {\n safeParse(\n data: unknown,\n ):\n { success: true; data: T } | { success: false; error: { message: string } };\n}\n\nfunction parseOrThrow<T>(schema: ParseableSchema<T>, data: unknown): T {\n const parsed = schema.safeParse(data);\n if (!parsed.success) {\n throw invalidResponseError(\n `Response failed validation: ${parsed.error.message}`,\n );\n }\n return parsed.data;\n}\n\nexport function createClient(options: ClientOptions): BillingClient {\n const { apiKey, baseUrl } = options;\n const fetchImpl = options.fetch ?? fetch;\n const entitlementsTtlMs =\n options.entitlementsTtlMs ?? DEFAULT_ENTITLEMENTS_TTL_MS;\n const entitlementsCache = new TtlCache<EntitlementsResult>();\n\n async function getEntitlements(\n subjectId: string,\n ): Promise<EntitlementsResult> {\n const fresh = entitlementsCache.getFresh(subjectId, entitlementsTtlMs);\n if (fresh) return { ...fresh, fromCache: true };\n\n try {\n const data = await requestJson(\n fetchImpl,\n `${baseUrl}/v1/entitlements/${encodeURIComponent(subjectId)}`,\n apiKey,\n );\n const parsed = parseOrThrow(entitlementsResponseSchema, data);\n entitlementsCache.set(subjectId, parsed);\n return { ...parsed, fromCache: false };\n } catch (err) {\n const isClientError =\n err instanceof BillingApiError && err.status >= 400 && err.status < 500;\n if (isClientError) throw err;\n\n const stale = entitlementsCache.getStale(subjectId);\n if (stale) return { ...stale, stale: true, fromCache: true };\n throw err;\n }\n }\n\n async function createCheckout(\n input: CheckoutRequest,\n ): Promise<CheckoutResponse> {\n const data = await requestJson(\n fetchImpl,\n `${baseUrl}/v1/checkout`,\n apiKey,\n {\n method: 'POST',\n body: JSON.stringify(input),\n },\n );\n return parseOrThrow(checkoutResponseSchema, data);\n }\n\n async function portalUrl(input: PortalRequest): Promise<PortalResponse> {\n const data = await requestJson(fetchImpl, `${baseUrl}/v1/portal`, apiKey, {\n method: 'POST',\n body: JSON.stringify(input),\n });\n return parseOrThrow(portalResponseSchema, data);\n }\n\n async function getSubscription(subjectId: string): Promise<SubscriptionView> {\n const data = await requestJson(\n fetchImpl,\n `${baseUrl}/v1/subscriptions/${encodeURIComponent(subjectId)}`,\n apiKey,\n );\n return parseOrThrow(subscriptionViewSchema, data);\n }\n\n return { getEntitlements, createCheckout, portalUrl, getSubscription };\n}\n","import { BillingApiError } from './errors.js';\nimport type {\n CheckEntitlementInput,\n EntitlementsResult,\n GuardDecision,\n LimitCheck,\n} from './types.js';\n\nfunction isMisconfiguration(err: unknown): boolean {\n return (\n err instanceof BillingApiError && err.status >= 400 && err.status < 500\n );\n}\n\nfunction decideOnContent(\n entitlements: EntitlementsResult,\n feature: string | undefined,\n limit: LimitCheck | undefined,\n): GuardDecision {\n if (feature !== undefined) {\n return entitlements.features[feature]\n ? { decision: 'allow', entitlements }\n : { decision: 'deny', reason: `Feature \"${feature}\" is not entitled` };\n }\n\n if (limit !== undefined) {\n const max = entitlements.limits[limit.key];\n return max !== undefined && limit.usage < max\n ? { decision: 'allow', entitlements }\n : { decision: 'deny', reason: `Limit \"${limit.key}\" has been reached` };\n }\n\n return { decision: 'allow', entitlements };\n}\n\n// Framework-agnostic orchestration over BillingClient — Fastify (or any\n// other framework) middleware only needs to map GuardDecision to a\n// response. Keep this file free of any framework import.\nexport async function checkEntitlement(\n input: CheckEntitlementInput,\n): Promise<GuardDecision> {\n const { client, subjectId, feature, limit, onUnavailable } = input;\n\n let entitlements: EntitlementsResult;\n try {\n entitlements = await client.getEntitlements(subjectId);\n } catch (err) {\n if (isMisconfiguration(err)) {\n const message =\n err instanceof Error ? err.message : 'Billing request was rejected';\n return { decision: 'deny', reason: message, misconfigured: true };\n }\n return onUnavailable === 'open'\n ? { decision: 'unavailable-allow' }\n : { decision: 'unavailable-deny', reason: 'Billing is unavailable' };\n }\n\n // Fresh or stale, this is content-based — a stale entitlements payload is\n // the graceful-degradation path working, not an unavailability case.\n return decideOnContent(entitlements, feature, limit);\n}\n","import { BillingApiError } from './errors.js';\nimport type { BillingClient, EntitlementsResult } from './types.js';\n\nexport type ShadowErrorReason =\n | 'timeout'\n | 'network'\n | 'parse_error'\n | 'http_error'\n // The shadow fetch succeeded but the consumer's own `entitled` predicate\n // threw while interpreting it. Distinguished from the transport failures\n // above because it points at consumer code, not at emit-billing's health.\n | 'predicate_error';\n\nexport interface ShadowReport {\n subjectId: string;\n legacyAnswer: boolean | null;\n legacyError: string | null;\n legacyLatencyMs: number;\n shadowAnswer: boolean | null;\n shadowError: { reason: ShadowErrorReason; message: string } | null;\n shadowLatencyMs: number;\n shadowStatus: EntitlementsResult['status'] | null;\n shadowSource: EntitlementsResult['source'] | null;\n shadowFromCache: boolean | null;\n // False whenever legacy or shadow errored — an incomparable pair is\n // never reported as agreement.\n agree: boolean;\n}\n\nexport interface ShadowCheckOptions<TId> {\n legacy: (id: TId) => Promise<boolean>;\n subjectIdFor: (id: TId) => string;\n client: BillingClient;\n entitled: (entitlements: EntitlementsResult) => boolean;\n report: (report: ShadowReport) => void;\n // Called when `report` itself throws. Shadow mode must never change the\n // caller's outcome, so a broken sink is swallowed rather than propagated;\n // this is the only way to notice it. Defaults to a no-op — and if it\n // throws too, that's swallowed as well.\n onReportError?: (error: unknown) => void;\n // 0..1, default 1. Short-circuits before any shadow work — no fetch on\n // skipped calls.\n sampleRate?: number;\n // Shadow fetch budget in ms, default 800. The shadow path runs\n // concurrently with legacy, so it adds at most this much latency beyond\n // whatever legacy already takes.\n timeoutMs?: number;\n}\n\n// performance.now() rather than Date.now(): a cache-served shadow read\n// finishes well inside a millisecond, and Date.now()'s integer resolution\n// reports that as a flat 0 — indistinguishable from \"never measured.\"\nfunction now(): number {\n return performance.now();\n}\n\nfunction elapsedSince(start: number): number {\n return Math.round((now() - start) * 1000) / 1000;\n}\n\nclass ShadowTimeoutError extends Error {\n constructor() {\n super('Shadow check timed out');\n this.name = 'ShadowTimeoutError';\n }\n}\n\nfunction withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => reject(new ShadowTimeoutError()), timeoutMs);\n promise.then(\n (value) => {\n clearTimeout(timer);\n resolve(value);\n },\n (error: unknown) => {\n clearTimeout(timer);\n reject(error);\n },\n );\n });\n}\n\nfunction classifyShadowError(error: unknown): ShadowErrorReason {\n if (error instanceof ShadowTimeoutError) return 'timeout';\n if (error instanceof BillingApiError) {\n if (error.code === 'network_error') return 'network';\n if (error.code === 'invalid_response') return 'parse_error';\n return 'http_error';\n }\n return 'http_error';\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\ntype LegacyOutcome =\n | { ok: true; value: boolean; latencyMs: number }\n | { ok: false; error: unknown; latencyMs: number };\n\ntype ShadowOutcome =\n | {\n kind: 'ok';\n answer: boolean;\n status: EntitlementsResult['status'];\n source: EntitlementsResult['source'];\n fromCache: boolean;\n latencyMs: number;\n }\n | {\n kind: 'error';\n reason: ShadowErrorReason;\n message: string;\n latencyMs: number;\n };\n\nasync function runShadow<TId>(\n options: ShadowCheckOptions<TId>,\n subjectId: string,\n timeoutMs: number,\n): Promise<ShadowOutcome> {\n const start = now();\n\n let entitlements: EntitlementsResult;\n try {\n entitlements = await withTimeout(\n options.client.getEntitlements(subjectId),\n timeoutMs,\n );\n } catch (error) {\n return {\n kind: 'error',\n reason: classifyShadowError(error),\n message: errorMessage(error),\n latencyMs: elapsedSince(start),\n };\n }\n\n // The predicate is consumer code and gets its own frame, so a throw here\n // is reported as `predicate_error` rather than being misattributed to\n // emit-billing's transport.\n try {\n return {\n kind: 'ok',\n answer: options.entitled(entitlements),\n status: entitlements.status,\n source: entitlements.source,\n fromCache: entitlements.fromCache ?? false,\n latencyMs: elapsedSince(start),\n };\n } catch (error) {\n return {\n kind: 'error',\n reason: 'predicate_error',\n message: errorMessage(error),\n latencyMs: elapsedSince(start),\n };\n }\n}\n\nfunction buildReport(\n subjectId: string,\n legacy: LegacyOutcome,\n shadow: ShadowOutcome,\n): ShadowReport {\n const agree =\n legacy.ok && shadow.kind === 'ok' && legacy.value === shadow.answer;\n\n return {\n subjectId,\n legacyAnswer: legacy.ok ? legacy.value : null,\n legacyError: legacy.ok ? null : errorMessage(legacy.error),\n legacyLatencyMs: legacy.latencyMs,\n shadowAnswer: shadow.kind === 'ok' ? shadow.answer : null,\n shadowError:\n shadow.kind === 'error'\n ? { reason: shadow.reason, message: shadow.message }\n : null,\n shadowLatencyMs: shadow.latencyMs,\n shadowStatus: shadow.kind === 'ok' ? shadow.status : null,\n shadowSource: shadow.kind === 'ok' ? shadow.source : null,\n shadowFromCache: shadow.kind === 'ok' ? shadow.fromCache : null,\n agree,\n };\n}\n\nfunction shouldSample(sampleRate: number): boolean {\n if (sampleRate <= 0) return false;\n if (sampleRate >= 1) return true;\n return Math.random() < sampleRate;\n}\n\n// Wraps a legacy entitlement check with emit-billing's answer computed\n// side by side. Always resolves/rejects exactly as `legacy` would; the\n// shadow path only ever produces a report, never affects the return value.\nexport function createShadowCheck<TId>(\n options: ShadowCheckOptions<TId>,\n): (id: TId) => Promise<boolean> {\n const {\n legacy,\n subjectIdFor,\n report,\n onReportError,\n sampleRate = 1,\n timeoutMs = 800,\n } = options;\n\n function reportErrorSafely(error: unknown): void {\n try {\n onReportError?.(error);\n } catch {\n // Nothing left to escalate to.\n }\n }\n\n // A throwing sink must not become the caller's problem — that would break\n // the \"always returns the legacy answer\" invariant through the one code\n // path that exists purely for observability.\n function reportSafely(built: ShadowReport): void {\n try {\n report(built);\n } catch (error) {\n reportErrorSafely(error);\n }\n }\n\n return async function shadowedCheck(id: TId): Promise<boolean> {\n // Mapping the consumer's id is shadow-only work, so a throwing mapper\n // must not reach the caller either — it just means this call can't be\n // shadowed. Without a subjectId there is no meaningful report to emit,\n // so it escalates through onReportError instead.\n let mapped: string | null = null;\n try {\n mapped = subjectIdFor(id);\n } catch (error) {\n reportErrorSafely(error);\n }\n const subjectId = mapped;\n\n const legacyStart = now();\n const legacyPromise: Promise<LegacyOutcome> = legacy(id).then(\n (value) => ({ ok: true, value, latencyMs: elapsedSince(legacyStart) }),\n (error: unknown) => ({\n ok: false,\n error,\n latencyMs: elapsedSince(legacyStart),\n }),\n );\n\n const shadowPromise =\n subjectId !== null && shouldSample(sampleRate)\n ? runShadow(options, subjectId, timeoutMs)\n : null;\n\n const legacyOutcome = await legacyPromise;\n\n if (shadowPromise !== null && subjectId !== null) {\n const shadowOutcome = await shadowPromise;\n reportSafely(buildReport(subjectId, legacyOutcome, shadowOutcome));\n }\n\n if (!legacyOutcome.ok) throw legacyOutcome.error;\n return legacyOutcome.value;\n };\n}\n"],"mappings":"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","names":["import_zod","import_zod","import_zod","import_zod","import_zod","import_zod","import_zod","import_zod","import_zod","now"]}
package/dist/index.d.cts CHANGED
@@ -12,7 +12,7 @@ declare class BillingApiError extends Error {
12
12
 
13
13
  declare function checkEntitlement(input: CheckEntitlementInput): Promise<GuardDecision>;
14
14
 
15
- type ShadowErrorReason = 'timeout' | 'network' | 'parse_error' | 'http_error';
15
+ type ShadowErrorReason = 'timeout' | 'network' | 'parse_error' | 'http_error' | 'predicate_error';
16
16
  interface ShadowReport {
17
17
  subjectId: string;
18
18
  legacyAnswer: boolean | null;
@@ -35,6 +35,7 @@ interface ShadowCheckOptions<TId> {
35
35
  client: BillingClient;
36
36
  entitled: (entitlements: EntitlementsResult) => boolean;
37
37
  report: (report: ShadowReport) => void;
38
+ onReportError?: (error: unknown) => void;
38
39
  sampleRate?: number;
39
40
  timeoutMs?: number;
40
41
  }
package/dist/index.d.ts CHANGED
@@ -12,7 +12,7 @@ declare class BillingApiError extends Error {
12
12
 
13
13
  declare function checkEntitlement(input: CheckEntitlementInput): Promise<GuardDecision>;
14
14
 
15
- type ShadowErrorReason = 'timeout' | 'network' | 'parse_error' | 'http_error';
15
+ type ShadowErrorReason = 'timeout' | 'network' | 'parse_error' | 'http_error' | 'predicate_error';
16
16
  interface ShadowReport {
17
17
  subjectId: string;
18
18
  legacyAnswer: boolean | null;
@@ -35,6 +35,7 @@ interface ShadowCheckOptions<TId> {
35
35
  client: BillingClient;
36
36
  entitled: (entitlements: EntitlementsResult) => boolean;
37
37
  report: (report: ShadowReport) => void;
38
+ onReportError?: (error: unknown) => void;
38
39
  sampleRate?: number;
39
40
  timeoutMs?: number;
40
41
  }
package/dist/index.js CHANGED
@@ -12,8 +12,8 @@ import {
12
12
 
13
13
  // src/cache.ts
14
14
  var TtlCache = class {
15
- constructor(now = Date.now) {
16
- this.now = now;
15
+ constructor(now2 = Date.now) {
16
+ this.now = now2;
17
17
  }
18
18
  now;
19
19
  store = /* @__PURE__ */ new Map();
@@ -119,6 +119,12 @@ function createClient(options) {
119
119
  }
120
120
 
121
121
  // src/shadow.ts
122
+ function now() {
123
+ return performance.now();
124
+ }
125
+ function elapsedSince(start) {
126
+ return Math.round((now() - start) * 1e3) / 1e3;
127
+ }
122
128
  var ShadowTimeoutError = class extends Error {
123
129
  constructor() {
124
130
  super("Shadow check timed out");
@@ -153,26 +159,36 @@ function errorMessage(error) {
153
159
  return error instanceof Error ? error.message : String(error);
154
160
  }
155
161
  async function runShadow(options, subjectId, timeoutMs) {
156
- const start = Date.now();
162
+ const start = now();
163
+ let entitlements;
157
164
  try {
158
- const entitlements = await withTimeout(
165
+ entitlements = await withTimeout(
159
166
  options.client.getEntitlements(subjectId),
160
167
  timeoutMs
161
168
  );
169
+ } catch (error) {
170
+ return {
171
+ kind: "error",
172
+ reason: classifyShadowError(error),
173
+ message: errorMessage(error),
174
+ latencyMs: elapsedSince(start)
175
+ };
176
+ }
177
+ try {
162
178
  return {
163
179
  kind: "ok",
164
180
  answer: options.entitled(entitlements),
165
181
  status: entitlements.status,
166
182
  source: entitlements.source,
167
183
  fromCache: entitlements.fromCache ?? false,
168
- latencyMs: Date.now() - start
184
+ latencyMs: elapsedSince(start)
169
185
  };
170
186
  } catch (error) {
171
187
  return {
172
188
  kind: "error",
173
- reason: classifyShadowError(error),
189
+ reason: "predicate_error",
174
190
  message: errorMessage(error),
175
- latencyMs: Date.now() - start
191
+ latencyMs: elapsedSince(start)
176
192
  };
177
193
  }
178
194
  }
@@ -202,25 +218,45 @@ function createShadowCheck(options) {
202
218
  legacy,
203
219
  subjectIdFor,
204
220
  report,
221
+ onReportError,
205
222
  sampleRate = 1,
206
223
  timeoutMs = 800
207
224
  } = options;
225
+ function reportErrorSafely(error) {
226
+ try {
227
+ onReportError?.(error);
228
+ } catch {
229
+ }
230
+ }
231
+ function reportSafely(built) {
232
+ try {
233
+ report(built);
234
+ } catch (error) {
235
+ reportErrorSafely(error);
236
+ }
237
+ }
208
238
  return async function shadowedCheck(id) {
209
- const subjectId = subjectIdFor(id);
210
- const legacyStart = Date.now();
239
+ let mapped = null;
240
+ try {
241
+ mapped = subjectIdFor(id);
242
+ } catch (error) {
243
+ reportErrorSafely(error);
244
+ }
245
+ const subjectId = mapped;
246
+ const legacyStart = now();
211
247
  const legacyPromise = legacy(id).then(
212
- (value) => ({ ok: true, value, latencyMs: Date.now() - legacyStart }),
248
+ (value) => ({ ok: true, value, latencyMs: elapsedSince(legacyStart) }),
213
249
  (error) => ({
214
250
  ok: false,
215
251
  error,
216
- latencyMs: Date.now() - legacyStart
252
+ latencyMs: elapsedSince(legacyStart)
217
253
  })
218
254
  );
219
- const shadowPromise = shouldSample(sampleRate) ? runShadow(options, subjectId, timeoutMs) : null;
255
+ const shadowPromise = subjectId !== null && shouldSample(sampleRate) ? runShadow(options, subjectId, timeoutMs) : null;
220
256
  const legacyOutcome = await legacyPromise;
221
- if (shadowPromise) {
257
+ if (shadowPromise !== null && subjectId !== null) {
222
258
  const shadowOutcome = await shadowPromise;
223
- report(buildReport(subjectId, legacyOutcome, shadowOutcome));
259
+ reportSafely(buildReport(subjectId, legacyOutcome, shadowOutcome));
224
260
  }
225
261
  if (!legacyOutcome.ok) throw legacyOutcome.error;
226
262
  return legacyOutcome.value;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cache.ts","../src/client.ts","../src/shadow.ts"],"sourcesContent":["export interface CacheEntry<T> {\n value: T;\n fetchedAt: number;\n}\n\n// No timers or cleanup — server processes are short-lived at MVP scale, and\n// entries are only ever compared against `now` on read.\nexport class TtlCache<T> {\n private readonly store = new Map<string, CacheEntry<T>>();\n\n constructor(private readonly now: () => number = Date.now) {}\n\n get(key: string): CacheEntry<T> | undefined {\n return this.store.get(key);\n }\n\n set(key: string, value: T): void {\n this.store.set(key, { value, fetchedAt: this.now() });\n }\n\n getFresh(key: string, ttlMs: number): T | undefined {\n const entry = this.store.get(key);\n if (!entry) return undefined;\n return this.now() - entry.fetchedAt < ttlMs ? entry.value : undefined;\n }\n\n getStale(key: string): T | undefined {\n return this.store.get(key)?.value;\n }\n}\n","import {\n checkoutResponseSchema,\n entitlementsResponseSchema,\n portalResponseSchema,\n subscriptionViewSchema,\n} from '@org/shared-types';\n\nimport { TtlCache } from './cache.js';\nimport {\n BillingApiError,\n errorFromResponse,\n invalidResponseError,\n networkError,\n} from './errors.js';\nimport type {\n BillingClient,\n CheckoutRequest,\n CheckoutResponse,\n ClientOptions,\n EntitlementsResult,\n PortalRequest,\n PortalResponse,\n SubscriptionView,\n} from './types.js';\n\nconst DEFAULT_ENTITLEMENTS_TTL_MS = 5000;\n\nasync function requestJson(\n fetchImpl: typeof fetch,\n url: string,\n apiKey: string,\n init?: RequestInit,\n): Promise<unknown> {\n let response: Response;\n try {\n response = await fetchImpl(url, {\n ...init,\n headers: {\n ...init?.headers,\n Authorization: `Bearer ${apiKey}`,\n ...(init?.body ? { 'Content-Type': 'application/json' } : {}),\n },\n });\n } catch (cause) {\n throw networkError(cause);\n }\n\n if (!response.ok) {\n throw await errorFromResponse(response);\n }\n\n return response.json();\n}\n\n// Structural rather than importing zod's ZodType directly — the package's\n// only runtime (and type) dependency stays @org/shared-types.\ninterface ParseableSchema<T> {\n safeParse(\n data: unknown,\n ):\n { success: true; data: T } | { success: false; error: { message: string } };\n}\n\nfunction parseOrThrow<T>(schema: ParseableSchema<T>, data: unknown): T {\n const parsed = schema.safeParse(data);\n if (!parsed.success) {\n throw invalidResponseError(\n `Response failed validation: ${parsed.error.message}`,\n );\n }\n return parsed.data;\n}\n\nexport function createClient(options: ClientOptions): BillingClient {\n const { apiKey, baseUrl } = options;\n const fetchImpl = options.fetch ?? fetch;\n const entitlementsTtlMs =\n options.entitlementsTtlMs ?? DEFAULT_ENTITLEMENTS_TTL_MS;\n const entitlementsCache = new TtlCache<EntitlementsResult>();\n\n async function getEntitlements(\n subjectId: string,\n ): Promise<EntitlementsResult> {\n const fresh = entitlementsCache.getFresh(subjectId, entitlementsTtlMs);\n if (fresh) return { ...fresh, fromCache: true };\n\n try {\n const data = await requestJson(\n fetchImpl,\n `${baseUrl}/v1/entitlements/${encodeURIComponent(subjectId)}`,\n apiKey,\n );\n const parsed = parseOrThrow(entitlementsResponseSchema, data);\n entitlementsCache.set(subjectId, parsed);\n return { ...parsed, fromCache: false };\n } catch (err) {\n const isClientError =\n err instanceof BillingApiError && err.status >= 400 && err.status < 500;\n if (isClientError) throw err;\n\n const stale = entitlementsCache.getStale(subjectId);\n if (stale) return { ...stale, stale: true, fromCache: true };\n throw err;\n }\n }\n\n async function createCheckout(\n input: CheckoutRequest,\n ): Promise<CheckoutResponse> {\n const data = await requestJson(\n fetchImpl,\n `${baseUrl}/v1/checkout`,\n apiKey,\n {\n method: 'POST',\n body: JSON.stringify(input),\n },\n );\n return parseOrThrow(checkoutResponseSchema, data);\n }\n\n async function portalUrl(input: PortalRequest): Promise<PortalResponse> {\n const data = await requestJson(fetchImpl, `${baseUrl}/v1/portal`, apiKey, {\n method: 'POST',\n body: JSON.stringify(input),\n });\n return parseOrThrow(portalResponseSchema, data);\n }\n\n async function getSubscription(subjectId: string): Promise<SubscriptionView> {\n const data = await requestJson(\n fetchImpl,\n `${baseUrl}/v1/subscriptions/${encodeURIComponent(subjectId)}`,\n apiKey,\n );\n return parseOrThrow(subscriptionViewSchema, data);\n }\n\n return { getEntitlements, createCheckout, portalUrl, getSubscription };\n}\n","import { BillingApiError } from './errors.js';\nimport type { BillingClient, EntitlementsResult } from './types.js';\n\nexport type ShadowErrorReason =\n 'timeout' | 'network' | 'parse_error' | 'http_error';\n\nexport interface ShadowReport {\n subjectId: string;\n legacyAnswer: boolean | null;\n legacyError: string | null;\n legacyLatencyMs: number;\n shadowAnswer: boolean | null;\n shadowError: { reason: ShadowErrorReason; message: string } | null;\n shadowLatencyMs: number;\n shadowStatus: EntitlementsResult['status'] | null;\n shadowSource: EntitlementsResult['source'] | null;\n shadowFromCache: boolean | null;\n // False whenever legacy or shadow errored — an incomparable pair is\n // never reported as agreement.\n agree: boolean;\n}\n\nexport interface ShadowCheckOptions<TId> {\n legacy: (id: TId) => Promise<boolean>;\n subjectIdFor: (id: TId) => string;\n client: BillingClient;\n entitled: (entitlements: EntitlementsResult) => boolean;\n report: (report: ShadowReport) => void;\n // 0..1, default 1. Short-circuits before any shadow work — no fetch on\n // skipped calls.\n sampleRate?: number;\n // Shadow fetch budget in ms, default 800. The shadow path runs\n // concurrently with legacy, so it adds at most this much latency beyond\n // whatever legacy already takes.\n timeoutMs?: number;\n}\n\nclass ShadowTimeoutError extends Error {\n constructor() {\n super('Shadow check timed out');\n this.name = 'ShadowTimeoutError';\n }\n}\n\nfunction withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => reject(new ShadowTimeoutError()), timeoutMs);\n promise.then(\n (value) => {\n clearTimeout(timer);\n resolve(value);\n },\n (error: unknown) => {\n clearTimeout(timer);\n reject(error);\n },\n );\n });\n}\n\nfunction classifyShadowError(error: unknown): ShadowErrorReason {\n if (error instanceof ShadowTimeoutError) return 'timeout';\n if (error instanceof BillingApiError) {\n if (error.code === 'network_error') return 'network';\n if (error.code === 'invalid_response') return 'parse_error';\n return 'http_error';\n }\n return 'http_error';\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\ntype LegacyOutcome =\n | { ok: true; value: boolean; latencyMs: number }\n | { ok: false; error: unknown; latencyMs: number };\n\ntype ShadowOutcome =\n | {\n kind: 'ok';\n answer: boolean;\n status: EntitlementsResult['status'];\n source: EntitlementsResult['source'];\n fromCache: boolean;\n latencyMs: number;\n }\n | {\n kind: 'error';\n reason: ShadowErrorReason;\n message: string;\n latencyMs: number;\n };\n\nasync function runShadow<TId>(\n options: ShadowCheckOptions<TId>,\n subjectId: string,\n timeoutMs: number,\n): Promise<ShadowOutcome> {\n const start = Date.now();\n try {\n const entitlements = await withTimeout(\n options.client.getEntitlements(subjectId),\n timeoutMs,\n );\n return {\n kind: 'ok',\n answer: options.entitled(entitlements),\n status: entitlements.status,\n source: entitlements.source,\n fromCache: entitlements.fromCache ?? false,\n latencyMs: Date.now() - start,\n };\n } catch (error) {\n return {\n kind: 'error',\n reason: classifyShadowError(error),\n message: errorMessage(error),\n latencyMs: Date.now() - start,\n };\n }\n}\n\nfunction buildReport(\n subjectId: string,\n legacy: LegacyOutcome,\n shadow: ShadowOutcome,\n): ShadowReport {\n const agree =\n legacy.ok && shadow.kind === 'ok' && legacy.value === shadow.answer;\n\n return {\n subjectId,\n legacyAnswer: legacy.ok ? legacy.value : null,\n legacyError: legacy.ok ? null : errorMessage(legacy.error),\n legacyLatencyMs: legacy.latencyMs,\n shadowAnswer: shadow.kind === 'ok' ? shadow.answer : null,\n shadowError:\n shadow.kind === 'error'\n ? { reason: shadow.reason, message: shadow.message }\n : null,\n shadowLatencyMs: shadow.latencyMs,\n shadowStatus: shadow.kind === 'ok' ? shadow.status : null,\n shadowSource: shadow.kind === 'ok' ? shadow.source : null,\n shadowFromCache: shadow.kind === 'ok' ? shadow.fromCache : null,\n agree,\n };\n}\n\nfunction shouldSample(sampleRate: number): boolean {\n if (sampleRate <= 0) return false;\n if (sampleRate >= 1) return true;\n return Math.random() < sampleRate;\n}\n\n// Wraps a legacy entitlement check with emit-billing's answer computed\n// side by side. Always resolves/rejects exactly as `legacy` would; the\n// shadow path only ever produces a report, never affects the return value.\nexport function createShadowCheck<TId>(\n options: ShadowCheckOptions<TId>,\n): (id: TId) => Promise<boolean> {\n const {\n legacy,\n subjectIdFor,\n report,\n sampleRate = 1,\n timeoutMs = 800,\n } = options;\n\n return async function shadowedCheck(id: TId): Promise<boolean> {\n const subjectId = subjectIdFor(id);\n const legacyStart = Date.now();\n const legacyPromise: Promise<LegacyOutcome> = legacy(id).then(\n (value) => ({ ok: true, value, latencyMs: Date.now() - legacyStart }),\n (error: unknown) => ({\n ok: false,\n error,\n latencyMs: Date.now() - legacyStart,\n }),\n );\n\n const shadowPromise = shouldSample(sampleRate)\n ? runShadow(options, subjectId, timeoutMs)\n : null;\n\n const legacyOutcome = await legacyPromise;\n\n if (shadowPromise) {\n const shadowOutcome = await shadowPromise;\n report(buildReport(subjectId, legacyOutcome, shadowOutcome));\n }\n\n if (!legacyOutcome.ok) throw legacyOutcome.error;\n return legacyOutcome.value;\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/cache.ts","../src/client.ts","../src/shadow.ts"],"sourcesContent":["export interface CacheEntry<T> {\n value: T;\n fetchedAt: number;\n}\n\n// No timers or cleanup — server processes are short-lived at MVP scale, and\n// entries are only ever compared against `now` on read.\nexport class TtlCache<T> {\n private readonly store = new Map<string, CacheEntry<T>>();\n\n constructor(private readonly now: () => number = Date.now) {}\n\n get(key: string): CacheEntry<T> | undefined {\n return this.store.get(key);\n }\n\n set(key: string, value: T): void {\n this.store.set(key, { value, fetchedAt: this.now() });\n }\n\n getFresh(key: string, ttlMs: number): T | undefined {\n const entry = this.store.get(key);\n if (!entry) return undefined;\n return this.now() - entry.fetchedAt < ttlMs ? entry.value : undefined;\n }\n\n getStale(key: string): T | undefined {\n return this.store.get(key)?.value;\n }\n}\n","import {\n checkoutResponseSchema,\n entitlementsResponseSchema,\n portalResponseSchema,\n subscriptionViewSchema,\n} from '@org/shared-types';\n\nimport { TtlCache } from './cache.js';\nimport {\n BillingApiError,\n errorFromResponse,\n invalidResponseError,\n networkError,\n} from './errors.js';\nimport type {\n BillingClient,\n CheckoutRequest,\n CheckoutResponse,\n ClientOptions,\n EntitlementsResult,\n PortalRequest,\n PortalResponse,\n SubscriptionView,\n} from './types.js';\n\nconst DEFAULT_ENTITLEMENTS_TTL_MS = 5000;\n\nasync function requestJson(\n fetchImpl: typeof fetch,\n url: string,\n apiKey: string,\n init?: RequestInit,\n): Promise<unknown> {\n let response: Response;\n try {\n response = await fetchImpl(url, {\n ...init,\n headers: {\n ...init?.headers,\n Authorization: `Bearer ${apiKey}`,\n ...(init?.body ? { 'Content-Type': 'application/json' } : {}),\n },\n });\n } catch (cause) {\n throw networkError(cause);\n }\n\n if (!response.ok) {\n throw await errorFromResponse(response);\n }\n\n return response.json();\n}\n\n// Structural rather than importing zod's ZodType directly — the package's\n// only runtime (and type) dependency stays @org/shared-types.\ninterface ParseableSchema<T> {\n safeParse(\n data: unknown,\n ):\n { success: true; data: T } | { success: false; error: { message: string } };\n}\n\nfunction parseOrThrow<T>(schema: ParseableSchema<T>, data: unknown): T {\n const parsed = schema.safeParse(data);\n if (!parsed.success) {\n throw invalidResponseError(\n `Response failed validation: ${parsed.error.message}`,\n );\n }\n return parsed.data;\n}\n\nexport function createClient(options: ClientOptions): BillingClient {\n const { apiKey, baseUrl } = options;\n const fetchImpl = options.fetch ?? fetch;\n const entitlementsTtlMs =\n options.entitlementsTtlMs ?? DEFAULT_ENTITLEMENTS_TTL_MS;\n const entitlementsCache = new TtlCache<EntitlementsResult>();\n\n async function getEntitlements(\n subjectId: string,\n ): Promise<EntitlementsResult> {\n const fresh = entitlementsCache.getFresh(subjectId, entitlementsTtlMs);\n if (fresh) return { ...fresh, fromCache: true };\n\n try {\n const data = await requestJson(\n fetchImpl,\n `${baseUrl}/v1/entitlements/${encodeURIComponent(subjectId)}`,\n apiKey,\n );\n const parsed = parseOrThrow(entitlementsResponseSchema, data);\n entitlementsCache.set(subjectId, parsed);\n return { ...parsed, fromCache: false };\n } catch (err) {\n const isClientError =\n err instanceof BillingApiError && err.status >= 400 && err.status < 500;\n if (isClientError) throw err;\n\n const stale = entitlementsCache.getStale(subjectId);\n if (stale) return { ...stale, stale: true, fromCache: true };\n throw err;\n }\n }\n\n async function createCheckout(\n input: CheckoutRequest,\n ): Promise<CheckoutResponse> {\n const data = await requestJson(\n fetchImpl,\n `${baseUrl}/v1/checkout`,\n apiKey,\n {\n method: 'POST',\n body: JSON.stringify(input),\n },\n );\n return parseOrThrow(checkoutResponseSchema, data);\n }\n\n async function portalUrl(input: PortalRequest): Promise<PortalResponse> {\n const data = await requestJson(fetchImpl, `${baseUrl}/v1/portal`, apiKey, {\n method: 'POST',\n body: JSON.stringify(input),\n });\n return parseOrThrow(portalResponseSchema, data);\n }\n\n async function getSubscription(subjectId: string): Promise<SubscriptionView> {\n const data = await requestJson(\n fetchImpl,\n `${baseUrl}/v1/subscriptions/${encodeURIComponent(subjectId)}`,\n apiKey,\n );\n return parseOrThrow(subscriptionViewSchema, data);\n }\n\n return { getEntitlements, createCheckout, portalUrl, getSubscription };\n}\n","import { BillingApiError } from './errors.js';\nimport type { BillingClient, EntitlementsResult } from './types.js';\n\nexport type ShadowErrorReason =\n | 'timeout'\n | 'network'\n | 'parse_error'\n | 'http_error'\n // The shadow fetch succeeded but the consumer's own `entitled` predicate\n // threw while interpreting it. Distinguished from the transport failures\n // above because it points at consumer code, not at emit-billing's health.\n | 'predicate_error';\n\nexport interface ShadowReport {\n subjectId: string;\n legacyAnswer: boolean | null;\n legacyError: string | null;\n legacyLatencyMs: number;\n shadowAnswer: boolean | null;\n shadowError: { reason: ShadowErrorReason; message: string } | null;\n shadowLatencyMs: number;\n shadowStatus: EntitlementsResult['status'] | null;\n shadowSource: EntitlementsResult['source'] | null;\n shadowFromCache: boolean | null;\n // False whenever legacy or shadow errored — an incomparable pair is\n // never reported as agreement.\n agree: boolean;\n}\n\nexport interface ShadowCheckOptions<TId> {\n legacy: (id: TId) => Promise<boolean>;\n subjectIdFor: (id: TId) => string;\n client: BillingClient;\n entitled: (entitlements: EntitlementsResult) => boolean;\n report: (report: ShadowReport) => void;\n // Called when `report` itself throws. Shadow mode must never change the\n // caller's outcome, so a broken sink is swallowed rather than propagated;\n // this is the only way to notice it. Defaults to a no-op — and if it\n // throws too, that's swallowed as well.\n onReportError?: (error: unknown) => void;\n // 0..1, default 1. Short-circuits before any shadow work — no fetch on\n // skipped calls.\n sampleRate?: number;\n // Shadow fetch budget in ms, default 800. The shadow path runs\n // concurrently with legacy, so it adds at most this much latency beyond\n // whatever legacy already takes.\n timeoutMs?: number;\n}\n\n// performance.now() rather than Date.now(): a cache-served shadow read\n// finishes well inside a millisecond, and Date.now()'s integer resolution\n// reports that as a flat 0 — indistinguishable from \"never measured.\"\nfunction now(): number {\n return performance.now();\n}\n\nfunction elapsedSince(start: number): number {\n return Math.round((now() - start) * 1000) / 1000;\n}\n\nclass ShadowTimeoutError extends Error {\n constructor() {\n super('Shadow check timed out');\n this.name = 'ShadowTimeoutError';\n }\n}\n\nfunction withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => reject(new ShadowTimeoutError()), timeoutMs);\n promise.then(\n (value) => {\n clearTimeout(timer);\n resolve(value);\n },\n (error: unknown) => {\n clearTimeout(timer);\n reject(error);\n },\n );\n });\n}\n\nfunction classifyShadowError(error: unknown): ShadowErrorReason {\n if (error instanceof ShadowTimeoutError) return 'timeout';\n if (error instanceof BillingApiError) {\n if (error.code === 'network_error') return 'network';\n if (error.code === 'invalid_response') return 'parse_error';\n return 'http_error';\n }\n return 'http_error';\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\ntype LegacyOutcome =\n | { ok: true; value: boolean; latencyMs: number }\n | { ok: false; error: unknown; latencyMs: number };\n\ntype ShadowOutcome =\n | {\n kind: 'ok';\n answer: boolean;\n status: EntitlementsResult['status'];\n source: EntitlementsResult['source'];\n fromCache: boolean;\n latencyMs: number;\n }\n | {\n kind: 'error';\n reason: ShadowErrorReason;\n message: string;\n latencyMs: number;\n };\n\nasync function runShadow<TId>(\n options: ShadowCheckOptions<TId>,\n subjectId: string,\n timeoutMs: number,\n): Promise<ShadowOutcome> {\n const start = now();\n\n let entitlements: EntitlementsResult;\n try {\n entitlements = await withTimeout(\n options.client.getEntitlements(subjectId),\n timeoutMs,\n );\n } catch (error) {\n return {\n kind: 'error',\n reason: classifyShadowError(error),\n message: errorMessage(error),\n latencyMs: elapsedSince(start),\n };\n }\n\n // The predicate is consumer code and gets its own frame, so a throw here\n // is reported as `predicate_error` rather than being misattributed to\n // emit-billing's transport.\n try {\n return {\n kind: 'ok',\n answer: options.entitled(entitlements),\n status: entitlements.status,\n source: entitlements.source,\n fromCache: entitlements.fromCache ?? false,\n latencyMs: elapsedSince(start),\n };\n } catch (error) {\n return {\n kind: 'error',\n reason: 'predicate_error',\n message: errorMessage(error),\n latencyMs: elapsedSince(start),\n };\n }\n}\n\nfunction buildReport(\n subjectId: string,\n legacy: LegacyOutcome,\n shadow: ShadowOutcome,\n): ShadowReport {\n const agree =\n legacy.ok && shadow.kind === 'ok' && legacy.value === shadow.answer;\n\n return {\n subjectId,\n legacyAnswer: legacy.ok ? legacy.value : null,\n legacyError: legacy.ok ? null : errorMessage(legacy.error),\n legacyLatencyMs: legacy.latencyMs,\n shadowAnswer: shadow.kind === 'ok' ? shadow.answer : null,\n shadowError:\n shadow.kind === 'error'\n ? { reason: shadow.reason, message: shadow.message }\n : null,\n shadowLatencyMs: shadow.latencyMs,\n shadowStatus: shadow.kind === 'ok' ? shadow.status : null,\n shadowSource: shadow.kind === 'ok' ? shadow.source : null,\n shadowFromCache: shadow.kind === 'ok' ? shadow.fromCache : null,\n agree,\n };\n}\n\nfunction shouldSample(sampleRate: number): boolean {\n if (sampleRate <= 0) return false;\n if (sampleRate >= 1) return true;\n return Math.random() < sampleRate;\n}\n\n// Wraps a legacy entitlement check with emit-billing's answer computed\n// side by side. Always resolves/rejects exactly as `legacy` would; the\n// shadow path only ever produces a report, never affects the return value.\nexport function createShadowCheck<TId>(\n options: ShadowCheckOptions<TId>,\n): (id: TId) => Promise<boolean> {\n const {\n legacy,\n subjectIdFor,\n report,\n onReportError,\n sampleRate = 1,\n timeoutMs = 800,\n } = options;\n\n function reportErrorSafely(error: unknown): void {\n try {\n onReportError?.(error);\n } catch {\n // Nothing left to escalate to.\n }\n }\n\n // A throwing sink must not become the caller's problem — that would break\n // the \"always returns the legacy answer\" invariant through the one code\n // path that exists purely for observability.\n function reportSafely(built: ShadowReport): void {\n try {\n report(built);\n } catch (error) {\n reportErrorSafely(error);\n }\n }\n\n return async function shadowedCheck(id: TId): Promise<boolean> {\n // Mapping the consumer's id is shadow-only work, so a throwing mapper\n // must not reach the caller either — it just means this call can't be\n // shadowed. Without a subjectId there is no meaningful report to emit,\n // so it escalates through onReportError instead.\n let mapped: string | null = null;\n try {\n mapped = subjectIdFor(id);\n } catch (error) {\n reportErrorSafely(error);\n }\n const subjectId = mapped;\n\n const legacyStart = now();\n const legacyPromise: Promise<LegacyOutcome> = legacy(id).then(\n (value) => ({ ok: true, value, latencyMs: elapsedSince(legacyStart) }),\n (error: unknown) => ({\n ok: false,\n error,\n latencyMs: elapsedSince(legacyStart),\n }),\n );\n\n const shadowPromise =\n subjectId !== null && shouldSample(sampleRate)\n ? runShadow(options, subjectId, timeoutMs)\n : null;\n\n const legacyOutcome = await legacyPromise;\n\n if (shadowPromise !== null && subjectId !== null) {\n const shadowOutcome = await shadowPromise;\n reportSafely(buildReport(subjectId, legacyOutcome, shadowOutcome));\n }\n\n if (!legacyOutcome.ok) throw legacyOutcome.error;\n return legacyOutcome.value;\n };\n}\n"],"mappings":"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","names":["now"]}
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "@develemit/billing",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
+ "license": "MIT",
4
5
  "type": "module",
6
+ "engines": {
7
+ "node": ">=18"
8
+ },
5
9
  "exports": {
6
10
  ".": {
7
11
  "types": "./dist/index.d.ts",
@@ -17,7 +21,8 @@
17
21
  "main": "./dist/index.cjs",
18
22
  "types": "./dist/index.d.ts",
19
23
  "files": [
20
- "dist"
24
+ "dist",
25
+ "LICENSE"
21
26
  ],
22
27
  "publishConfig": {
23
28
  "access": "public"