@voyantjs/catalog-authoring 0.104.1 → 0.105.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.
@@ -0,0 +1,17 @@
1
+ import type { AuthoringIssue } from "./errors.js";
2
+ import type { ProductGraphSpec } from "./spec.js";
3
+ /**
4
+ * Category-aware shape validation. Runs before the builder (compose path) so a
5
+ * wrong-shape spec is rejected with descriptive, agent-recoverable issues
6
+ * instead of producing a malformed-but-bookable product.
7
+ *
8
+ * Keyed on `bookingMode` (the structural classifier that exists in the schema
9
+ * today; `supplyModel` is still proposed). See the voyantjs/platform authoring
10
+ * spec for the canonical per-category rules.
11
+ *
12
+ * Scope: the generic products graph — excursion (`date`/`date_time`), multi-day
13
+ * tour/package (`itinerary`), transfer (`transfer`). Other modes (`stay`,
14
+ * `open`, `other`) pass through leniently in v1.
15
+ */
16
+ export declare function validateProductGraph(spec: ProductGraphSpec): AuthoringIssue[];
17
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAEjD;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,gBAAgB,GAAG,cAAc,EAAE,CAyE7E"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Category-aware shape validation. Runs before the builder (compose path) so a
3
+ * wrong-shape spec is rejected with descriptive, agent-recoverable issues
4
+ * instead of producing a malformed-but-bookable product.
5
+ *
6
+ * Keyed on `bookingMode` (the structural classifier that exists in the schema
7
+ * today; `supplyModel` is still proposed). See the voyantjs/platform authoring
8
+ * spec for the canonical per-category rules.
9
+ *
10
+ * Scope: the generic products graph — excursion (`date`/`date_time`), multi-day
11
+ * tour/package (`itinerary`), transfer (`transfer`). Other modes (`stay`,
12
+ * `open`, `other`) pass through leniently in v1.
13
+ */
14
+ export function validateProductGraph(spec) {
15
+ const issues = [];
16
+ const mode = spec.product.bookingMode;
17
+ const totalDays = spec.itineraries.reduce((n, i) => n + i.days.length, 0);
18
+ const allUnits = spec.options.flatMap((o) => o.units);
19
+ // Every bookable product needs at least one option with a unit.
20
+ if (spec.options.length === 0) {
21
+ issues.push({
22
+ code: "no_options",
23
+ field: "options",
24
+ message: "Product has no options, so it cannot be booked.",
25
+ fix: "Add at least one option with at least one unit.",
26
+ });
27
+ }
28
+ else if (allUnits.length === 0) {
29
+ issues.push({
30
+ code: "no_units",
31
+ field: "options[].units",
32
+ message: "No option has any units, so there is nothing to price or book.",
33
+ fix: "Add at least one unit (e.g. an 'Adult' person unit) to an option.",
34
+ });
35
+ }
36
+ if (mode === "date" || mode === "date_time") {
37
+ if (totalDays > 1) {
38
+ issues.push({
39
+ code: "excursion_multi_day",
40
+ field: "itineraries",
41
+ message: `A '${mode}' (excursion) product is single-day, but the spec has ${totalDays} itinerary days.`,
42
+ fix: "Use bookingMode 'itinerary' for a multi-day product, or reduce the itinerary to a single day.",
43
+ });
44
+ }
45
+ const roomUnit = allUnits.find((u) => u.unitType === "room");
46
+ if (roomUnit) {
47
+ issues.push({
48
+ code: "excursion_room_unit",
49
+ field: "options[].units[].unitType",
50
+ message: `Excursions price per person, but unit '${roomUnit.name}' has unitType 'room'.`,
51
+ fix: "Set the unit's unitType to 'person', or switch bookingMode to 'stay'/'itinerary' for room-based products.",
52
+ });
53
+ }
54
+ }
55
+ if (mode === "itinerary" && totalDays < 2) {
56
+ issues.push({
57
+ code: "tour_needs_days",
58
+ field: "itineraries",
59
+ message: `A multi-day ('itinerary') product needs at least 2 itinerary days; found ${totalDays}.`,
60
+ fix: "Add itinerary days, or use bookingMode 'date' for a single-day excursion.",
61
+ });
62
+ }
63
+ if (mode === "transfer") {
64
+ if (totalDays > 0) {
65
+ issues.push({
66
+ code: "transfer_no_days",
67
+ field: "itineraries",
68
+ message: `Transfers are point-to-point and take no itinerary days; found ${totalDays}.`,
69
+ fix: "Remove the itinerary days; model the journey via pickup/dropoff pricing instead.",
70
+ });
71
+ }
72
+ const badUnit = allUnits.find((u) => u.unitType !== "vehicle" && u.unitType !== "person");
73
+ if (badUnit) {
74
+ issues.push({
75
+ code: "transfer_unit_type",
76
+ field: "options[].units[].unitType",
77
+ message: `Transfer unit '${badUnit.name}' has unitType '${badUnit.unitType}'; transfers sell per vehicle or per seat.`,
78
+ fix: "Set the unit's unitType to 'vehicle' or 'person'.",
79
+ });
80
+ }
81
+ }
82
+ return issues;
83
+ }
package/package.json CHANGED
@@ -1,66 +1,50 @@
1
1
  {
2
2
  "name": "@voyantjs/catalog-authoring",
3
- "version": "0.104.1",
3
+ "version": "0.105.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "exports": {
7
- ".": "./src/index.ts",
8
- "./schema": "./src/schema.ts",
9
- "./spec": "./src/spec.ts",
10
- "./extension": "./src/extension.ts"
11
- },
12
- "scripts": {
13
- "typecheck": "tsc --noEmit",
14
- "lint": "biome check src/",
15
- "test": "vitest run",
16
- "build": "tsc -p tsconfig.json",
17
- "clean": "rm -rf dist tsconfig.tsbuildinfo",
18
- "prepack": "pnpm run build"
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.js",
10
+ "default": "./dist/index.js"
11
+ },
12
+ "./schema": {
13
+ "types": "./dist/schema.d.ts",
14
+ "import": "./dist/schema.js",
15
+ "default": "./dist/schema.js"
16
+ },
17
+ "./spec": {
18
+ "types": "./dist/spec.d.ts",
19
+ "import": "./dist/spec.js",
20
+ "default": "./dist/spec.js"
21
+ },
22
+ "./extension": {
23
+ "types": "./dist/extension.d.ts",
24
+ "import": "./dist/extension.js",
25
+ "default": "./dist/extension.js"
26
+ }
19
27
  },
20
28
  "dependencies": {
21
- "@voyantjs/core": "workspace:^",
22
- "@voyantjs/db": "workspace:^",
23
- "@voyantjs/hono": "workspace:^",
24
- "@voyantjs/pricing": "workspace:^",
25
- "@voyantjs/products": "workspace:^",
26
29
  "drizzle-orm": "^0.45.2",
27
30
  "hono": "^4.12.10",
28
- "zod": "^4.3.6"
31
+ "zod": "^4.3.6",
32
+ "@voyantjs/core": "^0.104.1",
33
+ "@voyantjs/db": "^0.104.1",
34
+ "@voyantjs/hono": "^0.104.1",
35
+ "@voyantjs/pricing": "^0.104.1",
36
+ "@voyantjs/products": "^0.104.3"
29
37
  },
30
38
  "devDependencies": {
31
- "@voyantjs/voyant-test-utils": "workspace:^",
32
- "@voyantjs/voyant-typescript-config": "workspace:^",
33
- "typescript": "^6.0.2"
39
+ "typescript": "^6.0.2",
40
+ "@voyantjs/voyant-test-utils": "^0.1.0",
41
+ "@voyantjs/voyant-typescript-config": "^0.1.0"
34
42
  },
35
43
  "files": [
36
44
  "dist"
37
45
  ],
38
46
  "publishConfig": {
39
- "access": "public",
40
- "exports": {
41
- ".": {
42
- "types": "./dist/index.d.ts",
43
- "import": "./dist/index.js",
44
- "default": "./dist/index.js"
45
- },
46
- "./schema": {
47
- "types": "./dist/schema.d.ts",
48
- "import": "./dist/schema.js",
49
- "default": "./dist/schema.js"
50
- },
51
- "./spec": {
52
- "types": "./dist/spec.d.ts",
53
- "import": "./dist/spec.js",
54
- "default": "./dist/spec.js"
55
- },
56
- "./extension": {
57
- "types": "./dist/extension.d.ts",
58
- "import": "./dist/extension.js",
59
- "default": "./dist/extension.js"
60
- }
61
- },
62
- "main": "./dist/index.js",
63
- "types": "./dist/index.d.ts"
47
+ "access": "public"
64
48
  },
65
49
  "repository": {
66
50
  "type": "git",
@@ -74,5 +58,14 @@
74
58
  "@voyantjs/products",
75
59
  "@voyantjs/pricing"
76
60
  ]
77
- }
78
- }
61
+ },
62
+ "scripts": {
63
+ "typecheck": "tsc --noEmit",
64
+ "lint": "biome check src/",
65
+ "test": "vitest run",
66
+ "build": "tsc -p tsconfig.json",
67
+ "clean": "rm -rf dist tsconfig.tsbuildinfo"
68
+ },
69
+ "main": "./dist/index.js",
70
+ "types": "./dist/index.d.ts"
71
+ }