@farthershore/product 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -31
- package/dist/bin.js +241 -30
- package/dist/codegen.js +43 -16
- package/dist/index.js +246 -37
- package/dist/types/errors.d.ts +1 -1
- package/dist/types/index.d.ts +4 -6
- package/dist/types/ir-types.d.ts +45 -0
- package/dist/types/{business.d.ts → product.d.ts} +73 -29
- package/dist/types/resource-graph.d.ts +1 -1
- package/package.json +4 -4
package/dist/codegen.js
CHANGED
|
@@ -28,6 +28,9 @@ var TOP_LEVEL_HANDLED = /* @__PURE__ */ new Set([
|
|
|
28
28
|
"policies",
|
|
29
29
|
"customer_context",
|
|
30
30
|
"resources",
|
|
31
|
+
"surfaces",
|
|
32
|
+
"entitlements",
|
|
33
|
+
"workflows",
|
|
31
34
|
"plans"
|
|
32
35
|
]);
|
|
33
36
|
var PLAN_HANDLED_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -58,8 +61,8 @@ function generateManifestSource(ir) {
|
|
|
58
61
|
lines.push(`import { fs } from "@farthershore/product";`);
|
|
59
62
|
lines.push("");
|
|
60
63
|
lines.push(
|
|
61
|
-
`const
|
|
62
|
-
|
|
64
|
+
`const product = fs.product(${lit(product.product.name)}, ${lit(
|
|
65
|
+
productOptions(product),
|
|
63
66
|
0
|
|
64
67
|
)});`
|
|
65
68
|
);
|
|
@@ -68,7 +71,7 @@ function generateManifestSource(ir) {
|
|
|
68
71
|
lines.push("");
|
|
69
72
|
for (const meter of meters) {
|
|
70
73
|
const { key, ...rest } = meter;
|
|
71
|
-
lines.push(`
|
|
74
|
+
lines.push(`product.meter(${lit(key)}, ${lit(rest, 0)});`);
|
|
72
75
|
}
|
|
73
76
|
}
|
|
74
77
|
const resources = product.resources ?? [];
|
|
@@ -76,7 +79,15 @@ function generateManifestSource(ir) {
|
|
|
76
79
|
lines.push("");
|
|
77
80
|
for (const resource of resources) {
|
|
78
81
|
const { name, ...rest } = resource;
|
|
79
|
-
lines.push(`
|
|
82
|
+
lines.push(`product.resource(${lit(name)}, ${lit(rest, 0)});`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const surfaces = product.surfaces ?? [];
|
|
86
|
+
if (surfaces.length) {
|
|
87
|
+
lines.push("");
|
|
88
|
+
for (const surface of surfaces) {
|
|
89
|
+
const { type, key, ...rest } = surface;
|
|
90
|
+
lines.push(`product.surface(${lit(type)}, ${lit({ key, ...rest }, 0)});`);
|
|
80
91
|
}
|
|
81
92
|
}
|
|
82
93
|
if (ir.capabilities.length) {
|
|
@@ -93,7 +104,7 @@ function generateManifestSource(ir) {
|
|
|
93
104
|
if (cap.includes_capabilities?.length)
|
|
94
105
|
options.includesCapabilities = cap.includes_capabilities;
|
|
95
106
|
lines.push(
|
|
96
|
-
`
|
|
107
|
+
`product.capability(${lit(cap.capability)}, ${lit(options, 0)});`
|
|
97
108
|
);
|
|
98
109
|
}
|
|
99
110
|
}
|
|
@@ -118,7 +129,7 @@ function generateManifestSource(ir) {
|
|
|
118
129
|
...cw.auth_modes ? { authModes: cw.auth_modes } : {}
|
|
119
130
|
};
|
|
120
131
|
}
|
|
121
|
-
lines.push(`
|
|
132
|
+
lines.push(`product.policy(${lit(policy.name)}, ${lit(options, 0)});`);
|
|
122
133
|
}
|
|
123
134
|
}
|
|
124
135
|
if (ir.routes.length) {
|
|
@@ -155,43 +166,59 @@ function generateManifestSource(ir) {
|
|
|
155
166
|
...route.inheritDefaultMeters !== void 0 ? { inheritDefaultMeters: route.inheritDefaultMeters } : {},
|
|
156
167
|
...route.action !== void 0 ? { action: route.action } : {}
|
|
157
168
|
}));
|
|
158
|
-
lines.push(`
|
|
169
|
+
lines.push(`product.feature(${lit(file.feature)}, ${lit(options, 0)});`);
|
|
159
170
|
}
|
|
160
171
|
}
|
|
161
172
|
if (product.frontend !== void 0) {
|
|
162
173
|
lines.push("");
|
|
163
|
-
lines.push(`
|
|
174
|
+
lines.push(`product.frontend.manifest(${lit(product.frontend, 0)});`);
|
|
175
|
+
}
|
|
176
|
+
const entitlements = product.entitlements ?? [];
|
|
177
|
+
if (entitlements.length) {
|
|
178
|
+
lines.push("");
|
|
179
|
+
for (const entitlement of entitlements) {
|
|
180
|
+
const { key, ...rest } = entitlement;
|
|
181
|
+
lines.push(`product.entitlement(${lit(key)}, ${lit(rest, 0)});`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const workflows = product.workflows ?? [];
|
|
185
|
+
if (workflows.length) {
|
|
186
|
+
lines.push("");
|
|
187
|
+
for (const workflow of workflows) {
|
|
188
|
+
const { key, ...rest } = workflow;
|
|
189
|
+
lines.push(`product.workflow(${lit(key)}, ${lit(rest, 0)});`);
|
|
190
|
+
}
|
|
164
191
|
}
|
|
165
192
|
if (product.migrations?.length) {
|
|
166
193
|
lines.push("");
|
|
167
|
-
lines.push(`
|
|
194
|
+
lines.push(`product.lifecycle.migrations(${lit(product.migrations, 0)});`);
|
|
168
195
|
}
|
|
169
196
|
const plans = product.plans ?? [];
|
|
170
197
|
if (plans.length) {
|
|
171
198
|
lines.push("");
|
|
172
199
|
for (const plan of plans) {
|
|
173
200
|
lines.push(
|
|
174
|
-
`
|
|
201
|
+
`product.plan(${lit(plan.key)}, ${lit(planOptions(plan), 0)});`
|
|
175
202
|
);
|
|
176
203
|
}
|
|
177
204
|
}
|
|
178
205
|
const patch = productPatch(product);
|
|
179
206
|
if (Object.keys(patch).length) {
|
|
180
207
|
lines.push("");
|
|
181
|
-
lines.push(`
|
|
208
|
+
lines.push(`product.raw.productPatch(${lit(patch, 0)});`);
|
|
182
209
|
}
|
|
183
210
|
lines.push("");
|
|
184
|
-
lines.push("export default
|
|
211
|
+
lines.push("export default product;");
|
|
185
212
|
lines.push("");
|
|
186
213
|
return lines.join("\n");
|
|
187
214
|
}
|
|
188
|
-
function
|
|
215
|
+
function productOptions(product) {
|
|
189
216
|
const block = product.product;
|
|
190
|
-
const options = {
|
|
217
|
+
const options = { origin: block.baseUrl };
|
|
191
218
|
if (block.displayName !== void 0) options.displayName = block.displayName;
|
|
192
219
|
if (block.description !== void 0) options.description = block.description;
|
|
193
220
|
if (block.sandboxBaseUrl !== void 0)
|
|
194
|
-
options.
|
|
221
|
+
options.sandboxOrigin = block.sandboxBaseUrl;
|
|
195
222
|
if (block.visibility !== void 0) options.visibility = block.visibility;
|
|
196
223
|
if (block.logoUrl !== void 0) options.logoUrl = block.logoUrl;
|
|
197
224
|
if (block.primaryColor !== void 0)
|
|
@@ -224,7 +251,7 @@ function customerContextOptions(context) {
|
|
|
224
251
|
return {
|
|
225
252
|
...context.identity_requirement !== void 0 ? { identityRequirement: context.identity_requirement } : {},
|
|
226
253
|
...context.context_tokens !== void 0 ? { contextTokens: context.context_tokens } : {},
|
|
227
|
-
...context.portal_auth !== void 0 ? {
|
|
254
|
+
...context.portal_auth !== void 0 ? { customerAuth: context.portal_auth } : {}
|
|
228
255
|
};
|
|
229
256
|
}
|
|
230
257
|
function planOptions(plan) {
|
package/dist/index.js
CHANGED
|
@@ -1385,7 +1385,7 @@ var customerPortalAuthStrategySchema = z13.enum([
|
|
|
1385
1385
|
var productCustomerContextSchema = z13.object({
|
|
1386
1386
|
/**
|
|
1387
1387
|
* Edge credential identity policy. This is intentionally Product-scoped:
|
|
1388
|
-
* B7 keeps Product as the
|
|
1388
|
+
* B7 keeps Product as the product boundary and avoids customer-side
|
|
1389
1389
|
* Workspace/subject models until per-subject entitlements need them.
|
|
1390
1390
|
*/
|
|
1391
1391
|
identity_requirement: customerIdentityRequirementSchema.optional(),
|
|
@@ -1406,6 +1406,70 @@ var productCustomerContextSchema = z13.object({
|
|
|
1406
1406
|
strategy: customerPortalAuthStrategySchema
|
|
1407
1407
|
}).optional()
|
|
1408
1408
|
});
|
|
1409
|
+
var productSurfaceTypeSchema = z13.enum([
|
|
1410
|
+
"frontend",
|
|
1411
|
+
"api",
|
|
1412
|
+
"docs",
|
|
1413
|
+
"widget",
|
|
1414
|
+
"dashboard",
|
|
1415
|
+
"webhook",
|
|
1416
|
+
"worker",
|
|
1417
|
+
"agent"
|
|
1418
|
+
]);
|
|
1419
|
+
var productSurfaceSchema = z13.object({
|
|
1420
|
+
key: z13.string().min(1).max(64).regex(/^[a-z0-9_-]+$/, "Surface key must be lowercase alphanumeric with hyphens/underscores"),
|
|
1421
|
+
type: productSurfaceTypeSchema,
|
|
1422
|
+
display: z13.string().min(1).max(100).optional(),
|
|
1423
|
+
description: z13.string().max(500).optional()
|
|
1424
|
+
});
|
|
1425
|
+
var productEntitlementSchema = z13.object({
|
|
1426
|
+
key: z13.string().min(1).max(64).regex(/^[a-z0-9_-]+$/, "Entitlement key must be lowercase alphanumeric with hyphens/underscores"),
|
|
1427
|
+
description: z13.string().max(500).optional(),
|
|
1428
|
+
capabilities: z13.array(z13.string().min(1).max(100)).max(100).optional(),
|
|
1429
|
+
featureGates: z13.record(z13.string().min(1), z13.boolean()).optional(),
|
|
1430
|
+
limits: z13.array(planLimitRuleSchema).max(100).optional(),
|
|
1431
|
+
meters: z13.array(z13.string().min(1).max(64)).max(100).optional()
|
|
1432
|
+
});
|
|
1433
|
+
var productSurfacesSchema = z13.array(productSurfaceSchema).max(20).default([]);
|
|
1434
|
+
var productEntitlementsSchema = z13.array(productEntitlementSchema).max(100).default([]);
|
|
1435
|
+
var productWorkflowKindSchema = z13.enum([
|
|
1436
|
+
"async_job",
|
|
1437
|
+
"agent_task",
|
|
1438
|
+
"scheduled",
|
|
1439
|
+
"lifecycle",
|
|
1440
|
+
"background"
|
|
1441
|
+
]);
|
|
1442
|
+
var productWorkflowTriggerSchema = z13.discriminatedUnion("type", [
|
|
1443
|
+
z13.object({ type: z13.literal("manual") }),
|
|
1444
|
+
z13.object({
|
|
1445
|
+
type: z13.literal("schedule"),
|
|
1446
|
+
cron: z13.string().min(1).max(120)
|
|
1447
|
+
}),
|
|
1448
|
+
z13.object({
|
|
1449
|
+
type: z13.literal("event"),
|
|
1450
|
+
event: z13.string().min(1).max(120)
|
|
1451
|
+
}),
|
|
1452
|
+
z13.object({
|
|
1453
|
+
type: z13.literal("api"),
|
|
1454
|
+
path: z13.string().min(1).max(240).regex(/^\//, "path must start with /")
|
|
1455
|
+
}),
|
|
1456
|
+
z13.object({
|
|
1457
|
+
type: z13.literal("lifecycle"),
|
|
1458
|
+
event: z13.string().min(1).max(120)
|
|
1459
|
+
})
|
|
1460
|
+
]);
|
|
1461
|
+
var productWorkflowSchema = z13.object({
|
|
1462
|
+
key: z13.string().min(1).max(64).regex(/^[a-z0-9_-]+$/, "Workflow key must be lowercase alphanumeric with hyphens/underscores"),
|
|
1463
|
+
title: z13.string().min(1).max(120).optional(),
|
|
1464
|
+
description: z13.string().max(1e3).optional(),
|
|
1465
|
+
kind: productWorkflowKindSchema,
|
|
1466
|
+
trigger: productWorkflowTriggerSchema,
|
|
1467
|
+
capabilities: z13.array(z13.string().min(1).max(100)).max(100).optional(),
|
|
1468
|
+
meters: z13.array(z13.string().min(1).max(64)).max(100).optional(),
|
|
1469
|
+
estimates: z13.record(z13.string().min(1).max(64), z13.number().finite()).optional(),
|
|
1470
|
+
metadata: z13.record(z13.string().min(1), z13.unknown()).optional()
|
|
1471
|
+
});
|
|
1472
|
+
var productWorkflowsSchema = z13.array(productWorkflowSchema).max(100).default([]);
|
|
1409
1473
|
var productSpecSchema = z13.object({
|
|
1410
1474
|
product: z13.object({
|
|
1411
1475
|
name: z13.string().min(1).max(100),
|
|
@@ -1439,6 +1503,9 @@ var productSpecSchema = z13.object({
|
|
|
1439
1503
|
resources: countedResourcesSchema,
|
|
1440
1504
|
policies: productOperatorPoliciesSchema,
|
|
1441
1505
|
customer_context: productCustomerContextSchema.optional(),
|
|
1506
|
+
surfaces: productSurfacesSchema,
|
|
1507
|
+
entitlements: productEntitlementsSchema,
|
|
1508
|
+
workflows: productWorkflowsSchema,
|
|
1442
1509
|
/**
|
|
1443
1510
|
* Legacy/internal declarative frontend surface. New product repos customize
|
|
1444
1511
|
* the generated `frontend/` project directly; this remains for existing IR
|
|
@@ -2148,6 +2215,9 @@ var productSpecV2Schema = z20.object({
|
|
|
2148
2215
|
resources: countedResourcesSchema,
|
|
2149
2216
|
policies: productOperatorPoliciesSchema,
|
|
2150
2217
|
customer_context: productCustomerContextSchema.optional(),
|
|
2218
|
+
surfaces: productSurfacesSchema,
|
|
2219
|
+
entitlements: productEntitlementsSchema,
|
|
2220
|
+
workflows: productWorkflowsSchema,
|
|
2151
2221
|
billing: z20.object({
|
|
2152
2222
|
gracePeriodDays: z20.number().int().nonnegative().default(3),
|
|
2153
2223
|
// When true (default), a plan limit INCREASE re-projects onto active
|
|
@@ -2348,10 +2418,10 @@ function canonicalIrJson(ir) {
|
|
|
2348
2418
|
}
|
|
2349
2419
|
|
|
2350
2420
|
// src/version.ts
|
|
2351
|
-
var SDK_VERSION = true ? "0.
|
|
2421
|
+
var SDK_VERSION = true ? "0.4.0" : "0.0.0-dev";
|
|
2352
2422
|
|
|
2353
|
-
// src/
|
|
2354
|
-
var
|
|
2423
|
+
// src/product.ts
|
|
2424
|
+
var PRODUCT_BRAND = Symbol.for("farthershore.product.product");
|
|
2355
2425
|
function isCapabilityGrant(value) {
|
|
2356
2426
|
return typeof value === "object" && value !== null && value.kind === "capability_grant";
|
|
2357
2427
|
}
|
|
@@ -2396,8 +2466,8 @@ function parseRouteMatch(match) {
|
|
|
2396
2466
|
}
|
|
2397
2467
|
return { method, path };
|
|
2398
2468
|
}
|
|
2399
|
-
var
|
|
2400
|
-
[
|
|
2469
|
+
var Product = class {
|
|
2470
|
+
[PRODUCT_BRAND] = true;
|
|
2401
2471
|
name;
|
|
2402
2472
|
options;
|
|
2403
2473
|
graph = new ManifestResourceGraph();
|
|
@@ -2408,15 +2478,16 @@ var Business = class {
|
|
|
2408
2478
|
api;
|
|
2409
2479
|
frontend;
|
|
2410
2480
|
lifecycle;
|
|
2481
|
+
offering;
|
|
2411
2482
|
/** Escape hatches — raw platform-schema JSON, validated at toIR(). */
|
|
2412
2483
|
raw;
|
|
2413
2484
|
constructor(name, options) {
|
|
2414
2485
|
if (!name || typeof name !== "string") {
|
|
2415
|
-
throw new ManifestBuilderError("fs.
|
|
2486
|
+
throw new ManifestBuilderError("fs.product(name, \u2026): name is required");
|
|
2416
2487
|
}
|
|
2417
|
-
if (!options?.
|
|
2488
|
+
if (!options?.origin) {
|
|
2418
2489
|
throw new ManifestBuilderError(
|
|
2419
|
-
`fs.
|
|
2490
|
+
`fs.product("${name}", \u2026): options.origin is required (the business logic origin Farther Shore calls for customer-facing actions)`
|
|
2420
2491
|
);
|
|
2421
2492
|
}
|
|
2422
2493
|
this.name = name;
|
|
@@ -2431,7 +2502,7 @@ var Business = class {
|
|
|
2431
2502
|
const file = this.getFeatureFile(featureKey);
|
|
2432
2503
|
if (!file) {
|
|
2433
2504
|
throw new ManifestBuilderError(
|
|
2434
|
-
`api.route("${match}"): feature "${featureKey}" is not declared \u2014 call
|
|
2505
|
+
`api.route("${match}"): feature "${featureKey}" is not declared \u2014 call product.feature("${featureKey}", \u2026) first`
|
|
2435
2506
|
);
|
|
2436
2507
|
}
|
|
2437
2508
|
file.routes.push(this.buildRoute(match, options2));
|
|
@@ -2524,6 +2595,9 @@ var Business = class {
|
|
|
2524
2595
|
return this;
|
|
2525
2596
|
}
|
|
2526
2597
|
};
|
|
2598
|
+
this.offering = {
|
|
2599
|
+
plan: (key, options2) => this.plan(key, options2)
|
|
2600
|
+
};
|
|
2527
2601
|
this.raw = {
|
|
2528
2602
|
productPatch: (patch) => {
|
|
2529
2603
|
this.productPatch = deepMerge(this.productPatch, patch);
|
|
@@ -2694,6 +2768,57 @@ var Business = class {
|
|
|
2694
2768
|
this.graph.register("policy", name, file, this.policyDependsOn(file));
|
|
2695
2769
|
return { kind: "policy", key: name };
|
|
2696
2770
|
}
|
|
2771
|
+
surface(type, options = {}) {
|
|
2772
|
+
const key = options.key ?? type;
|
|
2773
|
+
this.assertNewKey("surface", key, "surface");
|
|
2774
|
+
const surface = {
|
|
2775
|
+
key,
|
|
2776
|
+
type,
|
|
2777
|
+
...options.display !== void 0 ? { display: options.display } : {},
|
|
2778
|
+
...options.description !== void 0 ? { description: options.description } : {}
|
|
2779
|
+
};
|
|
2780
|
+
this.graph.register("surface", key, surface);
|
|
2781
|
+
return { kind: "surface", key };
|
|
2782
|
+
}
|
|
2783
|
+
workflow(key, options = {}) {
|
|
2784
|
+
this.assertNewKey("workflow", key, "workflow");
|
|
2785
|
+
const workflow = {
|
|
2786
|
+
key,
|
|
2787
|
+
kind: options.kind ?? "async_job",
|
|
2788
|
+
trigger: options.trigger ?? { type: "manual" },
|
|
2789
|
+
...options.title !== void 0 ? { title: options.title } : {},
|
|
2790
|
+
...options.description !== void 0 ? { description: options.description } : {},
|
|
2791
|
+
...options.capabilities?.length ? { capabilities: options.capabilities.map(keyOf) } : {},
|
|
2792
|
+
...options.meters?.length ? { meters: options.meters.map(keyOf) } : {},
|
|
2793
|
+
...options.estimates !== void 0 ? { estimates: options.estimates } : {},
|
|
2794
|
+
...options.metadata !== void 0 ? { metadata: options.metadata } : {}
|
|
2795
|
+
};
|
|
2796
|
+
this.graph.register(
|
|
2797
|
+
"workflow",
|
|
2798
|
+
key,
|
|
2799
|
+
workflow,
|
|
2800
|
+
this.workflowDependsOn(workflow)
|
|
2801
|
+
);
|
|
2802
|
+
return { kind: "workflow", key };
|
|
2803
|
+
}
|
|
2804
|
+
entitlement(key, options = {}) {
|
|
2805
|
+
this.assertNewKey("entitlement", key, "entitlement");
|
|
2806
|
+
const entitlement = {
|
|
2807
|
+
key,
|
|
2808
|
+
...options.description !== void 0 ? { description: options.description } : {},
|
|
2809
|
+
...options.capabilities?.length ? { capabilities: options.capabilities.map(keyOf) } : {},
|
|
2810
|
+
...options.featureGates !== void 0 ? { featureGates: options.featureGates } : {},
|
|
2811
|
+
...options.limits?.length ? { limits: options.limits } : {},
|
|
2812
|
+
...options.meters?.length ? { meters: options.meters.map(keyOf) } : {}
|
|
2813
|
+
};
|
|
2814
|
+
this.graph.register(
|
|
2815
|
+
"entitlement",
|
|
2816
|
+
key,
|
|
2817
|
+
entitlement,
|
|
2818
|
+
this.entitlementDependsOn(entitlement)
|
|
2819
|
+
);
|
|
2820
|
+
return { kind: "entitlement", key };
|
|
2821
|
+
}
|
|
2697
2822
|
plan(key, options) {
|
|
2698
2823
|
this.assertNewKey("plan", key, "plan");
|
|
2699
2824
|
const capabilityKeys = (options.capabilities ?? []).map(keyOf);
|
|
@@ -2760,16 +2885,14 @@ var Business = class {
|
|
|
2760
2885
|
/** Assemble + validate the Manifest IR. Throws ManifestValidationError
|
|
2761
2886
|
* with structured issues when the declared state is invalid. */
|
|
2762
2887
|
toIR() {
|
|
2888
|
+
const routes = this.materializeFeatureFiles();
|
|
2889
|
+
this.assertRouteMeteringValid(routes);
|
|
2763
2890
|
this.assertGraphDependenciesSatisfied();
|
|
2764
|
-
this.assertRouteMeteringValid();
|
|
2765
2891
|
const candidate = {
|
|
2766
2892
|
irVersion: 1,
|
|
2767
2893
|
sdkVersion: SDK_VERSION,
|
|
2768
2894
|
product: this.buildProductSpec(),
|
|
2769
|
-
routes
|
|
2770
|
-
"feature",
|
|
2771
|
-
(file) => file.feature
|
|
2772
|
-
),
|
|
2895
|
+
routes,
|
|
2773
2896
|
policies: this.graph.sortedValues(
|
|
2774
2897
|
"policy",
|
|
2775
2898
|
(file) => file.name
|
|
@@ -2789,10 +2912,10 @@ var Business = class {
|
|
|
2789
2912
|
const base = {
|
|
2790
2913
|
product: {
|
|
2791
2914
|
name: this.name,
|
|
2792
|
-
baseUrl: options.
|
|
2915
|
+
baseUrl: options.origin,
|
|
2793
2916
|
...options.displayName !== void 0 ? { displayName: options.displayName } : {},
|
|
2794
2917
|
...options.description !== void 0 ? { description: options.description } : {},
|
|
2795
|
-
...options.
|
|
2918
|
+
...options.sandboxOrigin !== void 0 ? { sandboxBaseUrl: options.sandboxOrigin } : {},
|
|
2796
2919
|
...options.visibility !== void 0 ? { visibility: options.visibility } : {},
|
|
2797
2920
|
...options.logoUrl !== void 0 ? { logoUrl: options.logoUrl } : {},
|
|
2798
2921
|
...options.primaryColor !== void 0 ? { primaryColor: options.primaryColor } : {},
|
|
@@ -2809,6 +2932,24 @@ var Business = class {
|
|
|
2809
2932
|
...options.billing !== void 0 ? { billing: options.billing } : {},
|
|
2810
2933
|
...options.operatorPolicies !== void 0 ? { policies: options.operatorPolicies } : {},
|
|
2811
2934
|
...options.customerContext !== void 0 ? { customer_context: buildCustomerContext(options.customerContext) } : {},
|
|
2935
|
+
...this.graph.values("surface").length ? {
|
|
2936
|
+
surfaces: this.graph.sortedValues(
|
|
2937
|
+
"surface",
|
|
2938
|
+
(surface) => surface.key
|
|
2939
|
+
)
|
|
2940
|
+
} : {},
|
|
2941
|
+
...this.graph.values("entitlement").length ? {
|
|
2942
|
+
entitlements: this.graph.sortedValues(
|
|
2943
|
+
"entitlement",
|
|
2944
|
+
(entitlement) => entitlement.key
|
|
2945
|
+
)
|
|
2946
|
+
} : {},
|
|
2947
|
+
...this.graph.values("workflow").length ? {
|
|
2948
|
+
workflows: this.graph.sortedValues(
|
|
2949
|
+
"workflow",
|
|
2950
|
+
(workflow) => workflow.key
|
|
2951
|
+
)
|
|
2952
|
+
} : {},
|
|
2812
2953
|
...this.graph.has("frontend", "manifest") ? {
|
|
2813
2954
|
frontend: this.graph.get(
|
|
2814
2955
|
"frontend",
|
|
@@ -2844,6 +2985,9 @@ var Business = class {
|
|
|
2844
2985
|
}
|
|
2845
2986
|
routeValueMeterKeys() {
|
|
2846
2987
|
const keys = /* @__PURE__ */ new Set();
|
|
2988
|
+
for (const cost of this.defaultMeterCosts) {
|
|
2989
|
+
keys.add(cost.meter);
|
|
2990
|
+
}
|
|
2847
2991
|
for (const file of this.graph.values("feature")) {
|
|
2848
2992
|
for (const route of file.routes) {
|
|
2849
2993
|
for (const meter of Object.keys(route.metering?.defaults ?? {})) {
|
|
@@ -2854,6 +2998,11 @@ var Business = class {
|
|
|
2854
2998
|
}
|
|
2855
2999
|
}
|
|
2856
3000
|
}
|
|
3001
|
+
for (const workflow of this.graph.values("workflow")) {
|
|
3002
|
+
for (const meter of workflow.meters ?? []) keys.add(meter);
|
|
3003
|
+
for (const meter of Object.keys(workflow.estimates ?? {}))
|
|
3004
|
+
keys.add(meter);
|
|
3005
|
+
}
|
|
2857
3006
|
return keys;
|
|
2858
3007
|
}
|
|
2859
3008
|
buildRoute(match, options) {
|
|
@@ -2878,11 +3027,6 @@ var Business = class {
|
|
|
2878
3027
|
buildRouteMetering(options) {
|
|
2879
3028
|
if (options.unmetered === true) return void 0;
|
|
2880
3029
|
const defaults = {};
|
|
2881
|
-
if (options.inheritDefaultMeters !== false) {
|
|
2882
|
-
for (const cost of this.defaultMeterCosts) {
|
|
2883
|
-
defaults[cost.meter] = (defaults[cost.meter] ?? 0) + cost.value;
|
|
2884
|
-
}
|
|
2885
|
-
}
|
|
2886
3030
|
for (const cost of this.normalizeMeterCosts(options.costs)) {
|
|
2887
3031
|
defaults[cost.meter] = (defaults[cost.meter] ?? 0) + cost.value;
|
|
2888
3032
|
}
|
|
@@ -2910,6 +3054,32 @@ var Business = class {
|
|
|
2910
3054
|
out.onStatusCodes = options.onStatusCodes;
|
|
2911
3055
|
return Object.keys(out).length ? out : void 0;
|
|
2912
3056
|
}
|
|
3057
|
+
materializeFeatureFiles() {
|
|
3058
|
+
return this.graph.sortedValues("feature", (file) => file.feature).map((file) => ({
|
|
3059
|
+
...file,
|
|
3060
|
+
routes: file.routes.map((route) => this.materializeRoute(route))
|
|
3061
|
+
}));
|
|
3062
|
+
}
|
|
3063
|
+
materializeRoute(route) {
|
|
3064
|
+
if (route.unmetered === true) return route;
|
|
3065
|
+
const defaults = {
|
|
3066
|
+
...route.metering?.defaults ?? {}
|
|
3067
|
+
};
|
|
3068
|
+
if (route.inheritDefaultMeters !== false) {
|
|
3069
|
+
for (const cost of this.defaultMeterCosts) {
|
|
3070
|
+
defaults[cost.meter] = (defaults[cost.meter] ?? 0) + cost.value;
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
const hasMetering = route.metering !== void 0 || Object.keys(defaults).length > 0;
|
|
3074
|
+
const metering = hasMetering ? {
|
|
3075
|
+
...route.metering ?? {},
|
|
3076
|
+
...Object.keys(defaults).length ? { defaults } : {}
|
|
3077
|
+
} : void 0;
|
|
3078
|
+
return {
|
|
3079
|
+
...route,
|
|
3080
|
+
...metering ? { metering } : {}
|
|
3081
|
+
};
|
|
3082
|
+
}
|
|
2913
3083
|
normalizeMeterCost(cost) {
|
|
2914
3084
|
if (!cost || cost.kind !== "meter_cost") {
|
|
2915
3085
|
throw new ManifestBuilderError(
|
|
@@ -3034,6 +3204,27 @@ var Business = class {
|
|
|
3034
3204
|
policyDependsOn(file) {
|
|
3035
3205
|
return this.dependenciesFor("meter", file.compatible_with?.meters);
|
|
3036
3206
|
}
|
|
3207
|
+
entitlementDependsOn(entitlement) {
|
|
3208
|
+
const limitDimensions = (entitlement.limits ?? []).map(
|
|
3209
|
+
(limit) => limit.dimension
|
|
3210
|
+
);
|
|
3211
|
+
return [
|
|
3212
|
+
...this.dependenciesFor("capability", entitlement.capabilities),
|
|
3213
|
+
...this.existingDependenciesFor("meter", [
|
|
3214
|
+
...entitlement.meters ?? [],
|
|
3215
|
+
...limitDimensions
|
|
3216
|
+
])
|
|
3217
|
+
];
|
|
3218
|
+
}
|
|
3219
|
+
workflowDependsOn(workflow) {
|
|
3220
|
+
return [
|
|
3221
|
+
...this.dependenciesFor("capability", workflow.capabilities),
|
|
3222
|
+
...this.dependenciesFor("meter", [
|
|
3223
|
+
...workflow.meters ?? [],
|
|
3224
|
+
...Object.keys(workflow.estimates ?? {})
|
|
3225
|
+
])
|
|
3226
|
+
];
|
|
3227
|
+
}
|
|
3037
3228
|
featureDependsOn(file) {
|
|
3038
3229
|
const meterKeys = file.routes.flatMap(
|
|
3039
3230
|
(route) => this.routeMeterDependencyKeys(route)
|
|
@@ -3056,12 +3247,26 @@ var Business = class {
|
|
|
3056
3247
|
}
|
|
3057
3248
|
return [...keys];
|
|
3058
3249
|
}
|
|
3059
|
-
assertRouteMeteringValid() {
|
|
3060
|
-
|
|
3250
|
+
assertRouteMeteringValid(files) {
|
|
3251
|
+
const declaredMeters = new Set(
|
|
3252
|
+
this.graph.values("meter").map((meter) => meter.key)
|
|
3253
|
+
);
|
|
3254
|
+
for (const file of files) {
|
|
3061
3255
|
file.routes.forEach((route, routeIndex) => {
|
|
3062
3256
|
if (route.unmetered === true) return;
|
|
3063
3257
|
const defaults = new Set(Object.keys(route.metering?.defaults ?? {}));
|
|
3258
|
+
for (const meter of defaults) {
|
|
3259
|
+
if (declaredMeters.has(meter)) continue;
|
|
3260
|
+
throw new ManifestBuilderError(
|
|
3261
|
+
`feature "${file.feature}" route ${routeIndex}: fixed meter "${meter}" is not declared \u2014 declare it with product.meter("${meter}", ...) first`
|
|
3262
|
+
);
|
|
3263
|
+
}
|
|
3064
3264
|
for (const meter of route.metering?.reports ?? []) {
|
|
3265
|
+
if (!declaredMeters.has(meter)) {
|
|
3266
|
+
throw new ManifestBuilderError(
|
|
3267
|
+
`feature "${file.feature}" route ${routeIndex}: reported meter "${meter}" is not declared \u2014 declare it with product.meter("${meter}", ...) first`
|
|
3268
|
+
);
|
|
3269
|
+
}
|
|
3065
3270
|
if (defaults.has(meter)) {
|
|
3066
3271
|
throw new ManifestBuilderError(
|
|
3067
3272
|
`feature "${file.feature}" route ${routeIndex}: meter "${meter}" cannot be both a fixed route cost and a dynamic report`
|
|
@@ -3084,6 +3289,12 @@ var Business = class {
|
|
|
3084
3289
|
);
|
|
3085
3290
|
}
|
|
3086
3291
|
}
|
|
3292
|
+
for (const meter of Object.keys(route.metering?.estimates ?? {})) {
|
|
3293
|
+
if (declaredMeters.has(meter)) continue;
|
|
3294
|
+
throw new ManifestBuilderError(
|
|
3295
|
+
`feature "${file.feature}" route ${routeIndex}: estimate meter "${meter}" is not declared \u2014 declare it with product.meter("${meter}", ...) first`
|
|
3296
|
+
);
|
|
3297
|
+
}
|
|
3087
3298
|
});
|
|
3088
3299
|
}
|
|
3089
3300
|
}
|
|
@@ -3139,13 +3350,13 @@ var Business = class {
|
|
|
3139
3350
|
return [...new Set(keys)].filter((key) => this.graph.has(kind, key)).map((key) => resourceDependency(kind, key));
|
|
3140
3351
|
}
|
|
3141
3352
|
};
|
|
3142
|
-
function
|
|
3143
|
-
return typeof value === "object" && value !== null && value[
|
|
3353
|
+
function isProduct(value) {
|
|
3354
|
+
return typeof value === "object" && value !== null && value[PRODUCT_BRAND] === true;
|
|
3144
3355
|
}
|
|
3145
|
-
function
|
|
3146
|
-
const
|
|
3147
|
-
if (configure)
|
|
3148
|
-
return
|
|
3356
|
+
function product(name, options, configure) {
|
|
3357
|
+
const instance = new Product(name, options);
|
|
3358
|
+
if (configure) instance.use(configure);
|
|
3359
|
+
return instance;
|
|
3149
3360
|
}
|
|
3150
3361
|
function isPlainObject(value) {
|
|
3151
3362
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -3154,7 +3365,7 @@ function buildCustomerContext(options) {
|
|
|
3154
3365
|
return {
|
|
3155
3366
|
...options.identityRequirement !== void 0 ? { identity_requirement: options.identityRequirement } : {},
|
|
3156
3367
|
...options.contextTokens !== void 0 ? { context_tokens: options.contextTokens } : {},
|
|
3157
|
-
...options.
|
|
3368
|
+
...options.customerAuth !== void 0 ? { portal_auth: options.customerAuth } : {}
|
|
3158
3369
|
};
|
|
3159
3370
|
}
|
|
3160
3371
|
function deepMerge(base, patch) {
|
|
@@ -3204,19 +3415,17 @@ var price = {
|
|
|
3204
3415
|
};
|
|
3205
3416
|
|
|
3206
3417
|
// src/index.ts
|
|
3207
|
-
var
|
|
3208
|
-
var fs = { business, product, price };
|
|
3418
|
+
var fs = { product, price };
|
|
3209
3419
|
export {
|
|
3210
|
-
BUSINESS_BRAND,
|
|
3211
|
-
Business,
|
|
3212
3420
|
ManifestBuilderError,
|
|
3213
3421
|
ManifestValidationError,
|
|
3422
|
+
PRODUCT_BRAND,
|
|
3423
|
+
Product,
|
|
3214
3424
|
SDK_VERSION,
|
|
3215
|
-
business,
|
|
3216
3425
|
canonicalIrJson,
|
|
3217
3426
|
fs,
|
|
3218
3427
|
hashIr,
|
|
3219
|
-
|
|
3428
|
+
isProduct,
|
|
3220
3429
|
price,
|
|
3221
3430
|
product,
|
|
3222
3431
|
validateManifestIr
|
package/dist/types/errors.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export type ManifestIssue = {
|
|
|
5
5
|
message: string;
|
|
6
6
|
};
|
|
7
7
|
/**
|
|
8
|
-
* Thrown by `
|
|
8
|
+
* Thrown by `product.toIR()` (and the manifest-build bin) when the
|
|
9
9
|
* assembled manifest fails schema validation. Plain-data `issues` so the
|
|
10
10
|
* error survives serialization across the runner callback boundary.
|
|
11
11
|
*/
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare const product: typeof business;
|
|
1
|
+
import { product } from "./product.js";
|
|
3
2
|
export declare const fs: {
|
|
4
|
-
|
|
5
|
-
product: typeof business;
|
|
3
|
+
product: typeof product;
|
|
6
4
|
price: {
|
|
7
5
|
monthly(dollars: number): import("./price.js").PriceSpec;
|
|
8
6
|
yearly(dollars: number): import("./price.js").PriceSpec;
|
|
9
7
|
free(): import("./price.js").PriceSpec;
|
|
10
8
|
};
|
|
11
9
|
};
|
|
12
|
-
export {
|
|
10
|
+
export { product, Product, isProduct, PRODUCT_BRAND } from "./product.js";
|
|
13
11
|
export { price } from "./price.js";
|
|
14
12
|
export { validateManifestIr, hashIr, canonicalIrJson } from "./validate.js";
|
|
15
13
|
export { ManifestValidationError, ManifestBuilderError } from "./errors.js";
|
|
16
14
|
export { SDK_VERSION } from "./version.js";
|
|
17
|
-
export type {
|
|
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";
|
|
18
16
|
export type { ManifestResourceGraphSnapshot, ManifestResourceKind, ManifestResourceUrn, } from "./resource-graph.js";
|
|
19
17
|
export type { PriceSpec } from "./price.js";
|
|
20
18
|
export type { ManifestIssue } from "./errors.js";
|