@farthershore/product 0.5.0 → 0.6.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.
@@ -0,0 +1,41 @@
1
+ import type { BackendDefinitionJson, BackendRunnerJson, BackendTransportModeJson, MeterDefinitionJson, RouteLayerJson } from "./ir-types.js";
2
+ import { type KeyRef } from "./refs.js";
3
+ import type { MeterRef } from "./route-metering.js";
4
+ export type BackendTransportOptions = {
5
+ mode?: BackendTransportModeJson;
6
+ runner?: BackendRunnerJson;
7
+ };
8
+ export type BackendOptions = {
9
+ /** Human-friendly label (defaults to the id). */
10
+ name?: string;
11
+ /** Stable slug (defaults to the id). */
12
+ slug?: string;
13
+ transport?: BackendTransportOptions;
14
+ /** Per-request gateway->backend signature verification. Defaults false until
15
+ * the JWKS + signer keystone is proven (deploy-order hard sequence). */
16
+ verification?: {
17
+ required?: boolean;
18
+ };
19
+ /** Meter allow-list. Omitted = all product meters allowed. */
20
+ meters?: Array<KeyRef | MeterRef>;
21
+ /** Marks this as the default backend when a product declares more than one. */
22
+ default?: boolean;
23
+ originUrl?: string;
24
+ originHostname?: string;
25
+ };
26
+ export type BackendNode = {
27
+ id: string;
28
+ } & BackendDefinitionJson;
29
+ export declare function createBackendNode(id: string, options?: BackendOptions): BackendNode;
30
+ /**
31
+ * Project registered backend nodes into a record keyed by id, sorted by id for
32
+ * stable, reviewable IR. The synthetic `id` field is stripped because the map
33
+ * key carries it.
34
+ */
35
+ export declare function buildBackendBlock(backendNodes: readonly BackendNode[]): Record<string, BackendDefinitionJson>;
36
+ /**
37
+ * BYO-Backend V1 — eager build-time validation of route->backend resolution,
38
+ * mirroring the compiler's `emit-backends` diagnostics so a builder gets a
39
+ * friendly error before the bytes ever reach the platform compiler.
40
+ */
41
+ export declare function assertBackendBindingsValid(files: readonly RouteLayerJson[], backendNodes: readonly BackendNode[], meterDefinitions: readonly MeterDefinitionJson[]): void;
@@ -1,2 +1,10 @@
1
- import type { ManifestIrDocument } from "../ir-types.js";
1
+ import type { ManifestIrDocument, PlanSpecJson } from "../ir-types.js";
2
+ /**
3
+ * Emit a single `product.plan("key", { ... })` statement — the structured plan
4
+ * block for WS4 dashboard authoring. Reuses the same faithful `planOptions` +
5
+ * `lit` printer as full-file generation (price sugar, grants, capability_limits,
6
+ * the `raw` escape hatch), so the snippet a builder applies via a GitHub edit /
7
+ * PR is byte-identical to what full regeneration would emit for that plan.
8
+ */
9
+ export declare function generatePlanStatement(plan: PlanSpecJson): string;
2
10
  export declare function generateManifestSource(ir: ManifestIrDocument): string;
@@ -0,0 +1,123 @@
1
+ import type { ActionSpecJson, CacheProfile, CapabilityLayerJson, CountedResourceJson, CreditPolicyJson, GrantJson, MigrationDeclJson, MigrationPinJson, MutationClass, PlanLimitJson, PlanMeterJson, PlanSpecJson, PolicyLayerJson, ProductEntitlementJson, ProductSurfaceJson, ProductSurfaceTypeJson, ProductWorkflowJson, ProductWorkflowKindJson, ProductWorkflowTriggerJson, RouteDefinitionJson, RouteLayerJson } from "./ir-types.js";
2
+ import type { PriceSpec } from "./price.js";
3
+ import { type KeyRef, type PlanCapabilityGrant } from "./refs.js";
4
+ import type { MeterRef, RouteMeteringOptions } from "./route-metering.js";
5
+ type RouteDeclarationOptions = RouteMeteringOptions & {
6
+ action?: KeyRef;
7
+ backend?: KeyRef;
8
+ };
9
+ export type CapabilityDeclarationOptions = {
10
+ title?: string;
11
+ description?: string;
12
+ mutationClass?: MutationClass;
13
+ includesFeatures?: KeyRef[];
14
+ includesPolicies?: KeyRef[];
15
+ includesCapabilities?: KeyRef[];
16
+ };
17
+ export type FeatureDeclarationOptions = {
18
+ description?: string;
19
+ mutationClass?: MutationClass;
20
+ cacheProfile?: CacheProfile;
21
+ upstreamOrigin?: string | null;
22
+ policies?: KeyRef[];
23
+ plans?: KeyRef[];
24
+ rolloutKey?: string;
25
+ requiredFlags?: string[];
26
+ actions?: Array<{
27
+ id: string;
28
+ } & Omit<ActionSpecJson, "id">>;
29
+ routes?: Array<{
30
+ match: string;
31
+ } & RouteDeclarationOptions>;
32
+ };
33
+ export type PolicyDeclarationOptions = {
34
+ type: string;
35
+ config: Record<string, unknown>;
36
+ description?: string;
37
+ mutationClass?: MutationClass;
38
+ cacheProfile?: CacheProfile;
39
+ compatibleWith?: {
40
+ routeTypes?: string[];
41
+ meters?: Array<KeyRef | MeterRef>;
42
+ authModes?: string[];
43
+ };
44
+ };
45
+ export type SurfaceDeclarationOptions = {
46
+ key?: string;
47
+ display?: string;
48
+ description?: string;
49
+ };
50
+ export type WorkflowDeclarationOptions = {
51
+ title?: string;
52
+ description?: string;
53
+ kind?: ProductWorkflowKindJson;
54
+ trigger?: ProductWorkflowTriggerJson;
55
+ capabilities?: KeyRef[];
56
+ meters?: Array<KeyRef | MeterRef>;
57
+ estimates?: Record<string, number>;
58
+ metadata?: Record<string, unknown>;
59
+ };
60
+ export type EntitlementDeclarationOptions = {
61
+ description?: string;
62
+ capabilities?: KeyRef[];
63
+ featureGates?: Record<string, boolean>;
64
+ limits?: PlanLimitJson[];
65
+ meters?: Array<KeyRef | MeterRef>;
66
+ };
67
+ export type PlanDeclarationOptions = {
68
+ name: string;
69
+ description?: string;
70
+ details?: string[];
71
+ price?: PriceSpec;
72
+ meters?: PlanMeterJson[];
73
+ grants?: Array<GrantJson | PlanCapabilityGrant>;
74
+ /** Non-grant credit policies (rollover + auto-recharge). */
75
+ creditPolicy?: CreditPolicyJson;
76
+ trialDays?: number;
77
+ maxMonthlySpendCents?: number;
78
+ minMonthlySpendCents?: number;
79
+ limits?: PlanLimitJson[];
80
+ featureGates?: Record<string, boolean>;
81
+ capabilityLimits?: Record<string, number | boolean>;
82
+ capabilities?: KeyRef[];
83
+ overageBehavior?: "block" | "allow_and_bill";
84
+ selfServeEnabled?: boolean;
85
+ legacy?: boolean;
86
+ archive?: {
87
+ at?: string;
88
+ transitionTo?: string;
89
+ strategy?: "auto" | "explicit" | "block";
90
+ };
91
+ raw?: Record<string, unknown>;
92
+ };
93
+ export type MigrationPlanRefOptions = {
94
+ plan: KeyRef;
95
+ version?: string;
96
+ };
97
+ export type MigrationTargetRefOptions = {
98
+ plan: KeyRef;
99
+ version?: "head";
100
+ };
101
+ export type MigrationPinOptions = Omit<MigrationPinJson, "pinTo"> & {
102
+ pinTo: {
103
+ plan: KeyRef;
104
+ version: string;
105
+ };
106
+ };
107
+ export type MigrationDeclarationOptions = Omit<MigrationDeclJson, "id" | "from" | "to" | "newCustomers" | "pins"> & {
108
+ from: MigrationPlanRefOptions;
109
+ to: MigrationTargetRefOptions;
110
+ newCustomers?: "immediate";
111
+ pins?: MigrationPinOptions[];
112
+ };
113
+ export type ResourceDeclarationOptions = Omit<CountedResourceJson, "name">;
114
+ export declare function buildCapabilityLayer(key: string, options?: CapabilityDeclarationOptions): CapabilityLayerJson;
115
+ export declare function buildFeatureLayer(key: string, options: FeatureDeclarationOptions, buildRoute: (match: string, options: RouteDeclarationOptions) => RouteDefinitionJson): RouteLayerJson;
116
+ export declare function buildPolicyLayer(name: string, options: PolicyDeclarationOptions): PolicyLayerJson;
117
+ export declare function buildSurfaceSpec(type: ProductSurfaceTypeJson, options?: SurfaceDeclarationOptions): ProductSurfaceJson;
118
+ export declare function buildWorkflowSpec(key: string, options?: WorkflowDeclarationOptions): ProductWorkflowJson;
119
+ export declare function buildEntitlementSpec(key: string, options?: EntitlementDeclarationOptions): ProductEntitlementJson;
120
+ export declare function buildPlanSpec(key: string, options: PlanDeclarationOptions): PlanSpecJson;
121
+ export declare function buildMigrationDecl(id: string, options: MigrationDeclarationOptions): MigrationDeclJson;
122
+ export declare function assertUniqueMigrationIds(migrations: readonly MigrationDeclJson[]): void;
123
+ export {};
@@ -0,0 +1,13 @@
1
+ import type { ActionSpecJson, CapabilityLayerJson, FrontendManifestJson, MigrationDeclJson, PlanSpecJson, PolicyLayerJson, ProductEntitlementJson, ProductWorkflowJson, RouteLayerJson } from "./ir-types.js";
2
+ import { type ManifestResourceKind, type ManifestResourceUrn, type MissingManifestResourceDependency } from "./resource-graph.js";
3
+ export type ResourceExists = (kind: ManifestResourceKind, key: string) => boolean;
4
+ export declare function capabilityDependsOn(file: CapabilityLayerJson): ManifestResourceUrn[];
5
+ export declare function policyDependsOn(file: PolicyLayerJson): ManifestResourceUrn[];
6
+ export declare function entitlementDependsOn(entitlement: ProductEntitlementJson, hasResource: ResourceExists): ManifestResourceUrn[];
7
+ export declare function workflowDependsOn(workflow: ProductWorkflowJson): ManifestResourceUrn[];
8
+ export declare function featureDependsOn(file: RouteLayerJson): ManifestResourceUrn[];
9
+ export declare function actionDependsOn(featureKey: string, action: ActionSpecJson): ManifestResourceUrn[];
10
+ export declare function planDependsOn(plan: PlanSpecJson, hasResource: ResourceExists): ManifestResourceUrn[];
11
+ export declare function migrationDependsOn(migration: MigrationDeclJson): ManifestResourceUrn[];
12
+ export declare function frontendDependsOn(manifest: FrontendManifestJson): ManifestResourceUrn[];
13
+ export declare function assertResourceDependenciesSatisfied(missing: readonly MissingManifestResourceDependency[]): void;
@@ -0,0 +1,23 @@
1
+ import type { FrontendComponentId, FrontendGateMode, FrontendManifestJson } from "./ir-types.js";
2
+ import { type KeyRef } from "./refs.js";
3
+ export type FrontendNavItemInput = {
4
+ label: string;
5
+ path: string;
6
+ capability?: KeyRef;
7
+ };
8
+ export type FrontendComponentInput = {
9
+ component: FrontendComponentId;
10
+ props?: Record<string, unknown>;
11
+ capability?: KeyRef;
12
+ gateMode?: FrontendGateMode;
13
+ };
14
+ export type FrontendPageInput = {
15
+ title: string;
16
+ requiresAuth: boolean;
17
+ capability?: KeyRef;
18
+ components?: FrontendComponentInput[];
19
+ };
20
+ export declare function createFrontendManifest(): FrontendManifestJson;
21
+ export declare function setFrontendNav(manifest: FrontendManifestJson, items: FrontendNavItemInput[]): void;
22
+ export declare function addFrontendPage(manifest: FrontendManifestJson, path: string, options: FrontendPageInput): void;
23
+ export declare function frontendCapabilityKeys(manifest: FrontendManifestJson): string[];
@@ -12,9 +12,9 @@ export { price } from "./price.js";
12
12
  export { validateManifestIr, hashIr, canonicalIrJson } from "./validate.js";
13
13
  export { ManifestValidationError, ManifestBuilderError } from "./errors.js";
14
14
  export { SDK_VERSION } from "./version.js";
15
- export type { ProductOptions, MeterOptions, RequestMeterOptions, MeterCost, ResourceOptions, CapabilityOptions, ActionOptions, FrontendNavItemOptions, FrontendPageOptions, FrontendComponentOptions, MigrationOptions, FeatureOptions, RouteOptions, PolicyOptions, PlanOptions, SurfaceOptions, EntitlementOptions, MeterRef, ResourceRef, ActionRef, PolicyRef, PlanRef, SurfaceRef, EntitlementRef, FeatureRef, CapabilityRef, PlanCapabilityGrant, ProductModule, } from "./product.js";
15
+ export type { ProductOptions, MeterOptions, RequestMeterOptions, MeterCost, ResourceOptions, CapabilityOptions, ActionOptions, FrontendNavItemOptions, FrontendPageOptions, FrontendComponentOptions, MigrationOptions, FeatureOptions, RouteOptions, BackendOptions, BackendTransportOptions, PolicyOptions, PlanOptions, SurfaceOptions, EntitlementOptions, MeterRef, ResourceRef, ActionRef, BackendRef, PolicyRef, PlanRef, SurfaceRef, EntitlementRef, FeatureRef, CapabilityRef, PlanCapabilityGrant, ProductModule, } from "./product.js";
16
16
  export type { ManifestResourceGraphSnapshot, ManifestResourceKind, ManifestResourceUrn, } from "./resource-graph.js";
17
17
  export type { PriceSpec } from "./price.js";
18
18
  export type { ManifestIssue } from "./errors.js";
19
19
  export type { ValidationResult } from "./validate.js";
20
- export type { ManifestIrDocument, ManifestBuildResult, ProductSpecJson, PlanSpecJson, RoutesFileJson, PolicyFileJson, CapabilityFileJson, FrontendManifestJson, FrontendNavItemJson, FrontendPageJson, FrontendComponentJson, FrontendComponentId, FrontendGateMode, MeterDefinitionJson, PlanMeterJson, PlanLimitJson, GrantJson, HttpMethod, MutationClass, CacheProfile, } from "./ir-types.js";
20
+ export type { ManifestIrDocument, ManifestBuildResult, ProductSpecJson, PlanSpecJson, RouteLayerJson, RouteDefinitionJson, BackendDefinitionJson, BackendTransportModeJson, BackendRunnerJson, PolicyLayerJson, CapabilityLayerJson, FrontendManifestJson, FrontendNavItemJson, FrontendPageJson, FrontendComponentJson, FrontendComponentId, FrontendGateMode, MeterDefinitionJson, PlanMeterJson, PlanLimitJson, GrantJson, HttpMethod, MutationClass, CacheProfile, } from "./ir-types.js";
@@ -22,9 +22,20 @@ export type PlanMeterJson = {
22
22
  [key: string]: unknown;
23
23
  };
24
24
  export type GrantJson = {
25
- kind: "recurring" | "one_time" | "promotional" | "trial" | "rollover" | "top_up" | "auto_recharge";
25
+ kind: "credit" | "top_up";
26
26
  [key: string]: unknown;
27
27
  };
28
+ /** Non-grant credit policies (rollover + auto-recharge) — mirrors the
29
+ * platform creditPolicySchema. */
30
+ export type CreditPolicyJson = {
31
+ rollover?: {
32
+ percent: number;
33
+ };
34
+ auto_recharge?: {
35
+ threshold_cents: number;
36
+ refill_cents: number;
37
+ };
38
+ };
28
39
  export type PlanLimitWindowJson = {
29
40
  type: "named";
30
41
  name: "second" | "minute" | "hour" | "day" | "week" | "month";
@@ -57,6 +68,8 @@ export type PlanSpecJson = {
57
68
  max_monthly_spend_cents?: number;
58
69
  min_monthly_spend_cents?: number;
59
70
  grants?: GrantJson[];
71
+ creditPolicy?: CreditPolicyJson;
72
+ capabilities?: string[];
60
73
  free?: boolean;
61
74
  limits?: PlanLimitJson[];
62
75
  featureGates?: Record<string, boolean>;
@@ -82,6 +95,28 @@ export type RouteDefinitionJson = {
82
95
  unmetered?: boolean;
83
96
  inheritDefaultMeters?: boolean;
84
97
  action?: string;
98
+ /** BYO-Backend V1 — route→backend binding id. Omitted = the sole / default
99
+ * backend (single-backend products stay zero-config). */
100
+ backend?: string;
101
+ };
102
+ export type BackendTransportModeJson = "public_origin" | "mtls" | "cloudflare_tunnel";
103
+ export type BackendRunnerJson = "managed_cloudflared" | "sidecar";
104
+ export type BackendDefinitionJson = {
105
+ name?: string;
106
+ slug?: string;
107
+ transport?: {
108
+ mode?: BackendTransportModeJson;
109
+ runner?: BackendRunnerJson;
110
+ };
111
+ verification?: {
112
+ required?: boolean;
113
+ };
114
+ /** Meter allow-list. Omitted = all product meters allowed. */
115
+ meters?: string[];
116
+ /** Marks the default backend when a product declares more than one. */
117
+ default?: boolean;
118
+ originUrl?: string;
119
+ originHostname?: string;
85
120
  };
86
121
  export type ActionSpecJson = {
87
122
  id: string;
@@ -100,7 +135,7 @@ export type ActionSpecJson = {
100
135
  effect: "create" | "delete";
101
136
  };
102
137
  };
103
- export type RoutesFileJson = {
138
+ export type RouteLayerJson = {
104
139
  feature: string;
105
140
  description?: string;
106
141
  mutation_class?: MutationClass;
@@ -114,11 +149,13 @@ export type RoutesFileJson = {
114
149
  rollout_key?: string;
115
150
  required_flags?: string[];
116
151
  };
117
- capabilities?: string[];
118
152
  plans?: string[];
119
153
  actions?: ActionSpecJson[];
154
+ /** BYO-Backend V1 — feature-level default backend binding for all routes in
155
+ * this file (routes may override per-route). */
156
+ backend?: string;
120
157
  };
121
- export type PolicyFileJson = {
158
+ export type PolicyLayerJson = {
122
159
  name: string;
123
160
  description?: string;
124
161
  type: string;
@@ -131,7 +168,7 @@ export type PolicyFileJson = {
131
168
  mutation_class?: MutationClass;
132
169
  cacheProfile?: CacheProfile;
133
170
  };
134
- export type CapabilityFileJson = {
171
+ export type CapabilityLayerJson = {
135
172
  capability: string;
136
173
  description?: string;
137
174
  mutation_class?: MutationClass;
@@ -294,6 +331,10 @@ export type ProductSpecJson = {
294
331
  meters?: MeterDefinitionJson[];
295
332
  billOn4xx?: boolean;
296
333
  };
334
+ /** BYO-Backend V1 — first-class backend declarations keyed by backend id.
335
+ * Emitted only when at least one backend is declared (so single-backend /
336
+ * no-backend products keep their pre-BYOB irHash). */
337
+ backend?: Record<string, BackendDefinitionJson>;
297
338
  frontend?: FrontendManifestJson;
298
339
  migrations?: MigrationDeclJson[];
299
340
  resources?: CountedResourceJson[];
@@ -312,14 +353,9 @@ export type ManifestIrDocument = {
312
353
  irVersion: 1;
313
354
  sdkVersion: string;
314
355
  product: ProductSpecJson;
315
- routes: RoutesFileJson[];
316
- policies: PolicyFileJson[];
317
- capabilities: CapabilityFileJson[];
318
- runtime: {
319
- rollout: null;
320
- flags: null;
321
- migrations: null;
322
- };
356
+ routes: RouteLayerJson[];
357
+ policies: PolicyLayerJson[];
358
+ capabilities: CapabilityLayerJson[];
323
359
  };
324
360
  /** Platform compiler result: the validated envelope plus its canonical hash. */
325
361
  export type ManifestBuildResult = {
@@ -0,0 +1,22 @@
1
+ import { type BackendNode } from "./backend.js";
2
+ import type { CountedResourceJson, FrontendManifestJson, MeterDefinitionJson, MigrationDeclJson, PlanSpecJson, ProductEntitlementJson, ProductSpecJson, ProductSurfaceJson, ProductWorkflowJson, RouteLayerJson } from "./ir-types.js";
3
+ import type { ProductOptions } from "./product.js";
4
+ import type { MeterCost } from "./route-metering.js";
5
+ export type ProductSpecAssemblyInput = {
6
+ name: string;
7
+ options: ProductOptions;
8
+ productPatch: Record<string, unknown>;
9
+ meters: readonly MeterDefinitionJson[];
10
+ defaultMeterCosts: readonly MeterCost[];
11
+ backends: readonly BackendNode[];
12
+ surfaces: readonly ProductSurfaceJson[];
13
+ entitlements: readonly ProductEntitlementJson[];
14
+ workflows: readonly ProductWorkflowJson[];
15
+ frontendManifest?: FrontendManifestJson;
16
+ migrations: readonly MigrationDeclJson[];
17
+ resources: readonly CountedResourceJson[];
18
+ plans: readonly PlanSpecJson[];
19
+ featureLayers: readonly RouteLayerJson[];
20
+ };
21
+ export declare function assembleProductSpec(input: ProductSpecAssemblyInput): ProductSpecJson;
22
+ export declare function mergeProductPatch(base: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown>;
@@ -1,6 +1,12 @@
1
- import type { CacheProfile, ActionSpecJson, CapabilityFileJson, FrontendComponentId, FrontendGateMode, FrontendManifestJson, GrantJson, MeterDefinitionJson, MigrationDeclJson, MigrationPinJson, MutationClass, CountedResourceJson, PlanLimitJson, PlanMeterJson, PlanSpecJson, PolicyFileJson, ProductPoliciesJson, ProductCustomerContextJson, ProductWorkflowKindJson, ProductWorkflowTriggerJson, ProductSurfaceTypeJson, RoutesFileJson } from "./ir-types.js";
1
+ import type { CacheProfile, ActionSpecJson, CapabilityLayerJson, FrontendComponentId, FrontendGateMode, FrontendManifestJson, GrantJson, CreditPolicyJson, MeterDefinitionJson, MigrationDeclJson, MigrationPinJson, MutationClass, CountedResourceJson, PlanLimitJson, PlanMeterJson, PlanSpecJson, PolicyLayerJson, ProductPoliciesJson, ProductCustomerContextJson, ProductWorkflowKindJson, ProductWorkflowTriggerJson, ProductSurfaceTypeJson, RouteLayerJson } from "./ir-types.js";
2
2
  import type { PriceSpec } from "./price.js";
3
3
  import { type ManifestResourceGraphSnapshot } from "./resource-graph.js";
4
+ import { type BackendOptions } from "./backend.js";
5
+ import { type PlanCapabilityGrant } from "./refs.js";
6
+ import { type MeterCost, type MeterRef, type RouteMeteringOptions } from "./route-metering.js";
7
+ export type { MeterCost, MeterEstimate, MeterRef } from "./route-metering.js";
8
+ export type { BackendOptions, BackendTransportOptions } from "./backend.js";
9
+ export type { PlanCapabilityGrant } from "./refs.js";
4
10
  /** Brand symbol so the bin can recognize a Product across SDK copies
5
11
  * (the repo's pinned SDK vs whatever loaded the entry). */
6
12
  export declare const PRODUCT_BRAND: unique symbol;
@@ -30,7 +36,6 @@ export type ProductOptions = {
30
36
  customerAuth?: ProductCustomerContextJson["portal_auth"];
31
37
  };
32
38
  billing?: {
33
- gracePeriodDays?: number;
34
39
  applyLimitUpgradesInstantly?: boolean;
35
40
  };
36
41
  };
@@ -50,26 +55,12 @@ export type CapabilityOptions = {
50
55
  includesPolicies?: Array<string | PolicyRef>;
51
56
  includesCapabilities?: Array<string | CapabilityRef>;
52
57
  };
53
- export type MeterCost = {
54
- readonly kind: "meter_cost";
55
- readonly meter: string;
56
- readonly value: number;
57
- };
58
- export type RouteOptions = {
59
- /**
60
- * Dynamic meter keys the upstream may report with @farthershore/metering.
61
- */
62
- reports?: string | MeterRef | Array<string | MeterRef>;
63
- /** Fixed gateway-known route costs. */
64
- costs?: MeterCost | Array<MeterCost>;
65
- /** Route-specific pre-request estimates for dynamic reports. */
66
- estimates?: Record<string, number>;
67
- /** Override the default successful-response range. */
68
- onStatusCodes?: string | number[];
69
- /** Disable product.defaultMeters()/product.requests() for this route only. */
70
- inheritDefaultMeters?: boolean;
71
- unmetered?: boolean;
58
+ export type RouteOptions = RouteMeteringOptions & {
72
59
  action?: string | ActionRef;
60
+ /** BYO-Backend V1 — bind this route to a declared backend. Omitted = the
61
+ * product's sole / default backend (single-backend products stay
62
+ * zero-config). */
63
+ backend?: string | BackendRef;
73
64
  };
74
65
  export type ActionOptions = Omit<ActionSpecJson, "id">;
75
66
  export type FrontendNavItemOptions = {
@@ -89,9 +80,16 @@ export type FrontendPageOptions = {
89
80
  capability?: string | CapabilityRef;
90
81
  components?: FrontendComponentOptions[];
91
82
  };
83
+ /**
84
+ * A plan-version reference: `"head"` (newest ACTIVE version) or a
85
+ * positive-integer string ("1", "2", ...). The template-literal type rejects
86
+ * obvious mistakes like date strings (`"2026-06-01"`) at compile time; the
87
+ * contracts `versionRefSchema` is the runtime source of truth.
88
+ */
89
+ export type VersionRef = "head" | `${number}`;
92
90
  export type MigrationPlanRefOptions = {
93
91
  plan: string | PlanRef;
94
- version?: string;
92
+ version?: VersionRef;
95
93
  };
96
94
  export type MigrationTargetRefOptions = {
97
95
  plan: string | PlanRef;
@@ -100,7 +98,7 @@ export type MigrationTargetRefOptions = {
100
98
  export type MigrationPinOptions = Omit<MigrationPinJson, "pinTo"> & {
101
99
  pinTo: {
102
100
  plan: string | PlanRef;
103
- version: string;
101
+ version: VersionRef;
104
102
  };
105
103
  };
106
104
  export type MigrationOptions = Omit<MigrationDeclJson, "id" | "from" | "to" | "newCustomers" | "pins"> & {
@@ -117,7 +115,6 @@ export type FeatureOptions = {
117
115
  /** Per-feature upstream origin override (default: product origin). */
118
116
  upstreamOrigin?: string | null;
119
117
  policies?: Array<string | PolicyRef>;
120
- capabilities?: Array<string | CapabilityRef>;
121
118
  /** Plans that grant this feature directly. */
122
119
  plans?: Array<string | PlanRef>;
123
120
  rolloutKey?: string;
@@ -152,6 +149,8 @@ export type PlanOptions = {
152
149
  meters?: PlanMeterJson[];
153
150
  /** Credit grants and/or capability enablements from `cap.enable()`. */
154
151
  grants?: Array<GrantJson | PlanCapabilityGrant>;
152
+ /** Non-grant credit policies (rollover + auto-recharge). */
153
+ creditPolicy?: CreditPolicyJson;
155
154
  trialDays?: number;
156
155
  maxMonthlySpendCents?: number;
157
156
  minMonthlySpendCents?: number;
@@ -193,14 +192,6 @@ export type EntitlementOptions = {
193
192
  limits?: PlanLimitJson[];
194
193
  meters?: Array<string | MeterRef>;
195
194
  };
196
- export type MeterRef = {
197
- readonly kind: "meter";
198
- readonly key: string;
199
- /** Fixed gateway-known usage for route/product defaults. */
200
- fixed(value: number): MeterCost;
201
- /** Convenience value for APIs that want an explicit estimate helper. */
202
- estimate(value: number): MeterCost;
203
- };
204
195
  export type ResourceRef = {
205
196
  readonly kind: "resource";
206
197
  readonly key: string;
@@ -229,6 +220,10 @@ export type ActionRef = {
229
220
  readonly kind: "action";
230
221
  readonly key: string;
231
222
  };
223
+ export type BackendRef = {
224
+ readonly kind: "backend";
225
+ readonly key: string;
226
+ };
232
227
  export type FeatureRef = {
233
228
  readonly kind: "feature";
234
229
  readonly key: string;
@@ -246,11 +241,6 @@ export type CapabilityRef = {
246
241
  limits?: Record<string, number | boolean>;
247
242
  }): PlanCapabilityGrant;
248
243
  };
249
- export type PlanCapabilityGrant = {
250
- readonly kind: "capability_grant";
251
- readonly capability: string;
252
- readonly limits?: Record<string, number | boolean>;
253
- };
254
244
  /**
255
245
  * A synchronous Product SDK module. Use this to split a product across files:
256
246
  * `product.use(configureMeters, configureRoutes, configurePlans)`.
@@ -262,7 +252,6 @@ export declare class Product {
262
252
  private readonly options;
263
253
  private readonly graph;
264
254
  private readonly defaultMeterCosts;
265
- private frontendManifest?;
266
255
  private productPatch;
267
256
  /** Sugar for binding API routes to features. */
268
257
  readonly api: {
@@ -279,22 +268,26 @@ export declare class Product {
279
268
  migration: (id: string, options: MigrationOptions) => Product;
280
269
  migrations: (migrations: MigrationDeclJson[]) => Product;
281
270
  };
282
- readonly offering: {
283
- plan: (key: string, options: PlanOptions) => PlanRef;
284
- };
285
271
  /** Escape hatches — raw platform-schema JSON, validated by the compiler. */
286
272
  readonly raw: {
287
273
  /** Deep-merged onto the emitted product spec (usage, webhooks,
288
274
  * environments, add_ons, lifecycle, billing overrides, …). */
289
275
  productPatch: (patch: Record<string, unknown>) => Product;
290
276
  plan: (spec: PlanSpecJson) => Product;
291
- routesFile: (file: RoutesFileJson) => Product;
292
- policyFile: (file: PolicyFileJson) => Product;
293
- capabilityFile: (file: CapabilityFileJson) => Product;
294
- frontend: (manifest: FrontendManifestJson) => Product;
277
+ routeLayer: (layer: RouteLayerJson) => Product;
278
+ policyLayer: (layer: PolicyLayerJson) => Product;
279
+ capabilityLayer: (layer: CapabilityLayerJson) => Product;
295
280
  };
296
281
  constructor(name: string, options: ProductOptions);
297
282
  meter(key: string, options: MeterOptions): MeterRef;
283
+ /**
284
+ * BYO-Backend V1 — declare a first-class backend (an always-running HTTP
285
+ * container Farther Shore wraps). Products may declare MULTIPLE backends;
286
+ * routes bind to one via `route(..., { backend })`. A product with exactly
287
+ * one backend (or exactly one marked `default: true`) makes it the implicit
288
+ * default, so single-backend products stay zero-config.
289
+ */
290
+ backend(id: string, options?: BackendOptions): BackendRef;
298
291
  requests(options?: RequestMeterOptions): MeterRef;
299
292
  defaultMeters(costs: MeterCost | MeterCost[]): Product;
300
293
  resource(name: string, options?: ResourceOptions): ResourceRef;
@@ -309,40 +302,17 @@ export declare class Product {
309
302
  /** Inspect the SDK-local declaration graph used to emit the Manifest IR. */
310
303
  resourceGraph(): ManifestResourceGraphSnapshot;
311
304
  private buildProductSpec;
312
- private buildMeterDefinitions;
313
- private routeValueMeterKeys;
314
305
  private buildRoute;
315
- private makeMeterRef;
316
- private buildRouteMetering;
317
- private materializeFeatureFiles;
318
- private materializeRoute;
306
+ private materializeFeatureLayers;
319
307
  private normalizeMeterCost;
320
- private normalizeMeterCosts;
321
- private normalizeMeterRefs;
322
308
  private ensureFrontendManifest;
323
- private normalizeMigrationPlanRef;
324
- private normalizeMigrationTargetRef;
325
- private assertUniqueMigrationIds;
326
309
  private assertNewKey;
327
310
  private assertGraphDependenciesSatisfied;
328
- private getFeatureFile;
329
- private registerFeatureFile;
311
+ private getFeatureLayer;
312
+ private registerFeatureLayer;
330
313
  private syncFeatureGraphNode;
331
314
  private registerAction;
332
315
  private syncFrontendGraphNode;
333
- private capabilityDependsOn;
334
- private policyDependsOn;
335
- private entitlementDependsOn;
336
- private workflowDependsOn;
337
- private featureDependsOn;
338
- private routeMeterDependencyKeys;
339
- private assertRouteMeteringValid;
340
- private actionDependsOn;
341
- private planDependsOn;
342
- private migrationDependsOn;
343
- private frontendDependsOn;
344
- private dependenciesFor;
345
- private existingDependenciesFor;
346
316
  }
347
317
  export declare function isProduct(value: unknown): value is Product;
348
318
  export declare function product(name: string, options: ProductOptions, configure?: ProductModule): Product;
@@ -0,0 +1,11 @@
1
+ export type KeyRef = string | {
2
+ key: string;
3
+ };
4
+ export type PlanCapabilityGrant = {
5
+ readonly kind: "capability_grant";
6
+ readonly capability: string;
7
+ readonly limits?: Record<string, number | boolean>;
8
+ };
9
+ export declare function isCapabilityGrant(value: unknown): value is PlanCapabilityGrant;
10
+ export declare function keyOf(ref: KeyRef): string;
11
+ export declare function displayFromKey(key: string): string;
@@ -1,4 +1,4 @@
1
- export type ManifestResourceKind = "product" | "meter" | "counted_resource" | "capability" | "surface" | "entitlement" | "workflow" | "policy" | "feature" | "action" | "plan" | "frontend" | "lifecycle_migration";
1
+ export type ManifestResourceKind = "product" | "meter" | "counted_resource" | "capability" | "surface" | "entitlement" | "workflow" | "policy" | "feature" | "action" | "plan" | "backend" | "frontend" | "lifecycle_migration";
2
2
  export type ManifestResourceUrn = `urn:farthershore:product:${ManifestResourceKind}:${string}`;
3
3
  export type ManifestResourceRef = {
4
4
  readonly kind: ManifestResourceKind;
@@ -0,0 +1,53 @@
1
+ import type { MeterDefinitionJson, RouteDefinitionJson, RouteLayerJson } from "./ir-types.js";
2
+ import { type KeyRef } from "./refs.js";
3
+ export type MeterCost = {
4
+ readonly kind: "meter_cost";
5
+ readonly meter: string;
6
+ readonly value: number;
7
+ };
8
+ export type MeterEstimate = {
9
+ readonly kind: "meter_estimate";
10
+ readonly meter: string;
11
+ readonly value: number;
12
+ };
13
+ export type MeterRef = {
14
+ readonly kind: "meter";
15
+ readonly key: string;
16
+ /** Fixed gateway-known usage for route/product defaults. */
17
+ fixed(value: number): MeterCost;
18
+ /** Convenience value for APIs that want an explicit estimate helper. */
19
+ estimate(value: number): MeterEstimate;
20
+ };
21
+ export type RouteMeteringOptions = {
22
+ /**
23
+ * Dynamic meter keys the upstream may report with @farthershore/backend.
24
+ */
25
+ reports?: string | MeterRef | Array<string | MeterRef>;
26
+ /** Fixed gateway-known route costs. */
27
+ costs?: MeterCost | Array<MeterCost>;
28
+ /** Route-specific pre-request estimates for dynamic reports. */
29
+ estimates?: Record<string, number> | MeterEstimate | MeterEstimate[];
30
+ /** Override the default successful-response range. */
31
+ onStatusCodes?: string | number[];
32
+ /** Disable product.defaultMeters()/product.requests() for this route only. */
33
+ inheritDefaultMeters?: boolean;
34
+ unmetered?: boolean;
35
+ };
36
+ type RouteBindingOptions = RouteMeteringOptions & {
37
+ action?: KeyRef;
38
+ /** BYO-Backend V1 — bind this route to a declared backend. Omitted = the
39
+ * product's sole / default backend (single-backend products stay
40
+ * zero-config). */
41
+ backend?: KeyRef;
42
+ };
43
+ type RouteMeteringDeps = {
44
+ normalizeMeterCost(cost: MeterCost): MeterCost;
45
+ getMeterDefinition(key: string): MeterDefinitionJson | undefined;
46
+ };
47
+ export declare function makeMeterRef(key: string): MeterRef;
48
+ export declare function normalizeMeterCost(cost: MeterCost, hasMeter: (meter: string) => boolean): MeterCost;
49
+ export declare function buildRouteDefinition(match: string, options: RouteBindingOptions, deps: RouteMeteringDeps): RouteDefinitionJson;
50
+ export declare function materializeRoute(route: RouteDefinitionJson, defaultMeterCosts: readonly MeterCost[]): RouteDefinitionJson;
51
+ export declare function routeMeterDependencyKeys(route: RouteDefinitionJson): string[];
52
+ export declare function assertRouteMeteringValid(files: RouteLayerJson[], meterDefinitions: Iterable<MeterDefinitionJson>): void;
53
+ export {};