@betterportal/config-manager 10.1.53 → 10.1.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/lib/.bsb/clients/service-betterportal-config-manager.d.ts +1 -1
  2. package/lib/.bsb/clients/service-betterportal-config-manager.js +1 -1
  3. package/lib/bp-contracts/org.betterportal.config-manager.json +129 -1
  4. package/lib/plugins/service-betterportal-config-manager/adminApi.d.ts.map +1 -1
  5. package/lib/plugins/service-betterportal-config-manager/adminApi.js +120 -8
  6. package/lib/plugins/service-betterportal-config-manager/adminApi.js.map +1 -1
  7. package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/_renderer.bootstrap5/GET.d.ts.map +1 -1
  8. package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/_renderer.bootstrap5/GET.js +129 -1
  9. package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/_renderer.bootstrap5/GET.js.map +1 -1
  10. package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/route.impl.d.ts +8 -0
  11. package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/route.impl.d.ts.map +1 -1
  12. package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/route.impl.js +4 -0
  13. package/lib/plugins/service-betterportal-config-manager/bp-routes/routes/route.impl.js.map +1 -1
  14. package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/_renderer.bootstrap5/GET.d.ts.map +1 -1
  15. package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/_renderer.bootstrap5/GET.js +8 -1
  16. package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/_renderer.bootstrap5/GET.js.map +1 -1
  17. package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/route.impl.d.ts +30 -0
  18. package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/route.impl.d.ts.map +1 -1
  19. package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/route.impl.js +41 -0
  20. package/lib/plugins/service-betterportal-config-manager/bp-routes/tenants/route.impl.js.map +1 -1
  21. package/lib/plugins/service-betterportal-config-manager/index.d.ts.map +1 -1
  22. package/lib/plugins/service-betterportal-config-manager/index.js +4 -0
  23. package/lib/plugins/service-betterportal-config-manager/index.js.map +1 -1
  24. package/lib/plugins/service-betterportal-config-manager/storage/core.d.ts +1 -0
  25. package/lib/plugins/service-betterportal-config-manager/storage/core.d.ts.map +1 -1
  26. package/lib/plugins/service-betterportal-config-manager/storage/core.js +42 -4
  27. package/lib/plugins/service-betterportal-config-manager/storage/core.js.map +1 -1
  28. package/lib/plugins/service-betterportal-config-manager/storage/file.d.ts.map +1 -1
  29. package/lib/plugins/service-betterportal-config-manager/storage/file.js +2 -2
  30. package/lib/plugins/service-betterportal-config-manager/storage/file.js.map +1 -1
  31. package/lib/plugins/service-betterportal-config-manager/storage/postgres.d.ts.map +1 -1
  32. package/lib/plugins/service-betterportal-config-manager/storage/postgres.js +2 -2
  33. package/lib/plugins/service-betterportal-config-manager/storage/postgres.js.map +1 -1
  34. package/lib/plugins/service-betterportal-config-manager/syncApi.d.ts +14 -0
  35. package/lib/plugins/service-betterportal-config-manager/syncApi.d.ts.map +1 -1
  36. package/lib/plugins/service-betterportal-config-manager/syncApi.js +32 -5
  37. package/lib/plugins/service-betterportal-config-manager/syncApi.js.map +1 -1
  38. package/lib/schemas/service-betterportal-config-manager.json +1 -1
  39. package/lib/schemas/service-betterportal-config-manager.plugin.json +1 -1
  40. package/package.json +3 -3
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Auto-generated BSB virtual client for service-betterportal-config-manager
3
3
  * DO NOT EDIT - Regenerated on every build
4
- * @version 10.1.53
4
+ * @version 10.1.55
5
5
  */
6
6
  import { ServiceClient, BSBService } from "@bsb/base";
7
7
  import type { Observable, BSBServiceClientDefinition, EventInputType } from "@bsb/base";
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Auto-generated BSB virtual client for service-betterportal-config-manager
3
3
  * DO NOT EDIT - Regenerated on every build
4
- * @version 10.1.53
4
+ * @version 10.1.55
5
5
  */
6
6
  import { ServiceClient, bsb, createBroadcastEvent, createEventSchemas } from "@bsb/base";
7
7
  const _platformConfigChangedSchema = bsb.object({
@@ -4,7 +4,7 @@
4
4
  "pluginId": "org.betterportal.config-manager",
5
5
  "title": "BetterPortal Config Manager",
6
6
  "description": "Admin-facing BetterPortal service for discovering and managing service config surfaces.",
7
- "version": "10.1.53",
7
+ "version": "10.1.55",
8
8
  "category": "service",
9
9
  "deploymentModes": [
10
10
  "self-hosted"
@@ -29,6 +29,7 @@
29
29
  "title": "Permission Manager",
30
30
  "description": "Define role -> permission grants per app. Services advertise per-view permission requirements via their manifest.",
31
31
  "path": "/auth",
32
+ "pathVariants": [],
32
33
  "methods": [
33
34
  "GET"
34
35
  ],
@@ -404,6 +405,10 @@
404
405
  }
405
406
  ]
406
407
  },
408
+ "sitemap": {
409
+ "kind": "default"
410
+ },
411
+ "robots": [],
407
412
  "dependencies": [],
408
413
  "apiContracts": [],
409
414
  "demoScenarios": [],
@@ -420,6 +425,7 @@
420
425
  "title": "Config Manager",
421
426
  "description": "Admin service that discovers BetterPortal service config surfaces.",
422
427
  "path": "/config",
428
+ "pathVariants": [],
423
429
  "methods": [
424
430
  "GET"
425
431
  ],
@@ -603,6 +609,10 @@
603
609
  }
604
610
  ]
605
611
  },
612
+ "sitemap": {
613
+ "kind": "default"
614
+ },
615
+ "robots": [],
606
616
  "dependencies": [],
607
617
  "apiContracts": [],
608
618
  "demoScenarios": [
@@ -637,6 +647,7 @@
637
647
  "title": "Fragments",
638
648
  "description": "Manage topbar/footer fragments per app.",
639
649
  "path": "/fragments",
650
+ "pathVariants": [],
640
651
  "methods": [
641
652
  "GET"
642
653
  ],
@@ -750,6 +761,10 @@
750
761
  }
751
762
  ]
752
763
  },
764
+ "sitemap": {
765
+ "kind": "default"
766
+ },
767
+ "robots": [],
753
768
  "dependencies": [],
754
769
  "apiContracts": [],
755
770
  "demoScenarios": [
@@ -777,6 +792,7 @@
777
792
  "title": "Menu Designer",
778
793
  "description": "Design app navigation menu.",
779
794
  "path": "/menu",
795
+ "pathVariants": [],
780
796
  "methods": [
781
797
  "GET"
782
798
  ],
@@ -968,6 +984,10 @@
968
984
  }
969
985
  ]
970
986
  },
987
+ "sitemap": {
988
+ "kind": "default"
989
+ },
990
+ "robots": [],
971
991
  "dependencies": [],
972
992
  "apiContracts": [],
973
993
  "demoScenarios": [
@@ -997,6 +1017,7 @@
997
1017
  "title": "Component Preview",
998
1018
  "description": "Browse and preview service views, components, and fragments.",
999
1019
  "path": "/preview",
1020
+ "pathVariants": [],
1000
1021
  "methods": [
1001
1022
  "GET"
1002
1023
  ],
@@ -1151,6 +1172,10 @@
1151
1172
  }
1152
1173
  ]
1153
1174
  },
1175
+ "sitemap": {
1176
+ "kind": "default"
1177
+ },
1178
+ "robots": [],
1154
1179
  "dependencies": [],
1155
1180
  "apiContracts": [],
1156
1181
  "demoScenarios": [
@@ -1176,6 +1201,7 @@
1176
1201
  "title": "Route Designer",
1177
1202
  "description": "Design navigation routes for apps.",
1178
1203
  "path": "/routes",
1204
+ "pathVariants": [],
1179
1205
  "methods": [
1180
1206
  "GET"
1181
1207
  ],
@@ -1260,6 +1286,19 @@
1260
1286
  "kind": "string"
1261
1287
  }
1262
1288
  },
1289
+ "servicePathVariant": {
1290
+ "kind": "optional",
1291
+ "inner": {
1292
+ "kind": "string"
1293
+ }
1294
+ },
1295
+ "fixedParams": {
1296
+ "kind": "record",
1297
+ "valueSchema": {
1298
+ "kind": "string"
1299
+ },
1300
+ "default": {}
1301
+ },
1263
1302
  "methods": {
1264
1303
  "kind": "array",
1265
1304
  "items": {
@@ -1293,6 +1332,7 @@
1293
1332
  "path",
1294
1333
  "serviceId",
1295
1334
  "viewId",
1335
+ "fixedParams",
1296
1336
  "methods",
1297
1337
  "renderable",
1298
1338
  "enabled"
@@ -1341,6 +1381,22 @@
1341
1381
  "path": {
1342
1382
  "kind": "string"
1343
1383
  },
1384
+ "pathVariants": {
1385
+ "kind": "array",
1386
+ "items": {
1387
+ "kind": "string"
1388
+ },
1389
+ "default": []
1390
+ },
1391
+ "paramsSchema": {
1392
+ "kind": "optional",
1393
+ "inner": {
1394
+ "kind": "record",
1395
+ "valueSchema": {
1396
+ "kind": "any"
1397
+ }
1398
+ }
1399
+ },
1344
1400
  "methods": {
1345
1401
  "kind": "array",
1346
1402
  "items": {
@@ -1364,6 +1420,7 @@
1364
1420
  "viewId",
1365
1421
  "title",
1366
1422
  "path",
1423
+ "pathVariants",
1367
1424
  "methods",
1368
1425
  "renderable",
1369
1426
  "dependencies"
@@ -1450,6 +1507,10 @@
1450
1507
  }
1451
1508
  ]
1452
1509
  },
1510
+ "sitemap": {
1511
+ "kind": "default"
1512
+ },
1513
+ "robots": [],
1453
1514
  "dependencies": [],
1454
1515
  "apiContracts": [],
1455
1516
  "demoScenarios": [
@@ -1479,6 +1540,7 @@
1479
1540
  "title": "Service Registry",
1480
1541
  "description": "Manage registered service instances.",
1481
1542
  "path": "/services",
1543
+ "pathVariants": [],
1482
1544
  "methods": [
1483
1545
  "GET"
1484
1546
  ],
@@ -2126,6 +2188,10 @@
2126
2188
  }
2127
2189
  ]
2128
2190
  },
2191
+ "sitemap": {
2192
+ "kind": "default"
2193
+ },
2194
+ "robots": [],
2129
2195
  "dependencies": [],
2130
2196
  "apiContracts": [],
2131
2197
  "demoScenarios": [
@@ -2158,6 +2224,7 @@
2158
2224
  "title": "App Settings",
2159
2225
  "description": "Manage the current BetterPortal app through the user management API.",
2160
2226
  "path": "/settings",
2227
+ "pathVariants": [],
2161
2228
  "methods": [
2162
2229
  "GET"
2163
2230
  ],
@@ -2416,6 +2483,10 @@
2416
2483
  }
2417
2484
  ]
2418
2485
  },
2486
+ "sitemap": {
2487
+ "kind": "default"
2488
+ },
2489
+ "robots": [],
2419
2490
  "dependencies": [
2420
2491
  "services.index",
2421
2492
  "routes.index",
@@ -2472,6 +2543,7 @@
2472
2543
  "title": "Tenants & Apps",
2473
2544
  "description": "Manage tenants and applications.",
2474
2545
  "path": "/tenants",
2546
+ "pathVariants": [],
2475
2547
  "methods": [
2476
2548
  "DELETE",
2477
2549
  "GET",
@@ -2644,6 +2716,48 @@
2644
2716
  "unknownKeys": "strip"
2645
2717
  }
2646
2718
  },
2719
+ "seo": {
2720
+ "kind": "object",
2721
+ "properties": {
2722
+ "visibility": {
2723
+ "kind": "enum",
2724
+ "values": [
2725
+ "auto",
2726
+ "public",
2727
+ "private"
2728
+ ]
2729
+ },
2730
+ "serviceFailure": {
2731
+ "kind": "enum",
2732
+ "values": [
2733
+ "known-routes",
2734
+ "omit-service",
2735
+ "error"
2736
+ ]
2737
+ },
2738
+ "serviceCache": {
2739
+ "kind": "enum",
2740
+ "values": [
2741
+ "none",
2742
+ "1h",
2743
+ "24h",
2744
+ "7d"
2745
+ ]
2746
+ },
2747
+ "canonicalOrigin": {
2748
+ "kind": "optional",
2749
+ "inner": {
2750
+ "kind": "string"
2751
+ }
2752
+ }
2753
+ },
2754
+ "required": [
2755
+ "visibility",
2756
+ "serviceFailure",
2757
+ "serviceCache"
2758
+ ],
2759
+ "unknownKeys": "strip"
2760
+ },
2647
2761
  "pageViews": {
2648
2762
  "kind": "array",
2649
2763
  "items": {
@@ -2692,6 +2806,7 @@
2692
2806
  "slug",
2693
2807
  "title",
2694
2808
  "hostnames",
2809
+ "seo",
2695
2810
  "pageViews",
2696
2811
  "routeCount"
2697
2812
  ],
@@ -2888,6 +3003,10 @@
2888
3003
  }
2889
3004
  ]
2890
3005
  },
3006
+ "sitemap": {
3007
+ "kind": "default"
3008
+ },
3009
+ "robots": [],
2891
3010
  "dependencies": [],
2892
3011
  "apiContracts": [],
2893
3012
  "demoScenarios": [
@@ -2929,6 +3048,7 @@
2929
3048
  {
2930
3049
  "viewId": "auth.index",
2931
3050
  "path": "/auth",
3051
+ "pathVariants": [],
2932
3052
  "methods": [
2933
3053
  "GET"
2934
3054
  ],
@@ -2943,6 +3063,7 @@
2943
3063
  {
2944
3064
  "viewId": "config.index",
2945
3065
  "path": "/config",
3066
+ "pathVariants": [],
2946
3067
  "methods": [
2947
3068
  "GET"
2948
3069
  ],
@@ -2958,6 +3079,7 @@
2958
3079
  {
2959
3080
  "viewId": "fragments.index",
2960
3081
  "path": "/fragments",
3082
+ "pathVariants": [],
2961
3083
  "methods": [
2962
3084
  "GET"
2963
3085
  ],
@@ -2972,6 +3094,7 @@
2972
3094
  {
2973
3095
  "viewId": "menu.index",
2974
3096
  "path": "/menu",
3097
+ "pathVariants": [],
2975
3098
  "methods": [
2976
3099
  "GET"
2977
3100
  ],
@@ -2986,6 +3109,7 @@
2986
3109
  {
2987
3110
  "viewId": "preview.index",
2988
3111
  "path": "/preview",
3112
+ "pathVariants": [],
2989
3113
  "methods": [
2990
3114
  "GET"
2991
3115
  ],
@@ -3000,6 +3124,7 @@
3000
3124
  {
3001
3125
  "viewId": "routes.index",
3002
3126
  "path": "/routes",
3127
+ "pathVariants": [],
3003
3128
  "methods": [
3004
3129
  "GET"
3005
3130
  ],
@@ -3014,6 +3139,7 @@
3014
3139
  {
3015
3140
  "viewId": "services.index",
3016
3141
  "path": "/services",
3142
+ "pathVariants": [],
3017
3143
  "methods": [
3018
3144
  "GET"
3019
3145
  ],
@@ -3028,6 +3154,7 @@
3028
3154
  {
3029
3155
  "viewId": "settings.index",
3030
3156
  "path": "/settings",
3157
+ "pathVariants": [],
3031
3158
  "methods": [
3032
3159
  "GET"
3033
3160
  ],
@@ -3042,6 +3169,7 @@
3042
3169
  {
3043
3170
  "viewId": "tenants.index",
3044
3171
  "path": "/tenants",
3172
+ "pathVariants": [],
3045
3173
  "methods": [
3046
3174
  "DELETE",
3047
3175
  "GET",
@@ -1 +1 @@
1
- {"version":3,"file":"adminApi.d.ts","sourceRoot":"","sources":["../../../src/plugins/service-betterportal-config-manager/adminApi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EAEjB,mBAAmB,EACnB,SAAS,EACV,MAAM,yBAAyB,CAAC;AAYjC,OAAO,KAAK,EAAuE,kBAAkB,EAAE,eAAe,EAA0C,MAAM,yBAAyB,CAAC;AAChM,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AA0LzD,iBAAS,wBAAwB,CAAC,MAAM,EAAE,kBAAkB,EAAE,MAAM,EAAE,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiEpF;AAGD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,wBAAwB,CAAC,GAAG,MAAM,CA2CpG;AA8SD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG;IACvG,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;CACzB,CAwEA;AAiPD,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,mBAAmB,EAC1B,OAAO,EAAE,gBAAgB,GACxB,IAAI,CAm9CN;AA8VD,wBAAgB,uBAAuB,CAAC,CAAC,EAAE;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClD,GAAG,MAAM,CA0aT"}
1
+ {"version":3,"file":"adminApi.d.ts","sourceRoot":"","sources":["../../../src/plugins/service-betterportal-config-manager/adminApi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EAEjB,mBAAmB,EACnB,SAAS,EACV,MAAM,yBAAyB,CAAC;AAYjC,OAAO,KAAK,EAAuE,kBAAkB,EAAE,eAAe,EAA0C,MAAM,yBAAyB,CAAC;AAChM,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAoQzD,iBAAS,wBAAwB,CAAC,MAAM,EAAE,kBAAkB,EAAE,MAAM,EAAE,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiEpF;AAGD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,wBAAwB,CAAC,GAAG,MAAM,CA2CpG;AA0TD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG;IACvG,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;CACzB,CAwEA;AAiPD,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,mBAAmB,EAC1B,OAAO,EAAE,gBAAgB,GACxB,IAAI,CA2+CN;AA8VD,wBAAgB,uBAAuB,CAAC,CAAC,EAAE;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClD,GAAG,MAAM,CA0aT"}
@@ -90,6 +90,76 @@ function requiredRouteString(body, key, label) {
90
90
  return { error: `${label} is required.` };
91
91
  return { value };
92
92
  }
93
+ function routeParamNames(path) {
94
+ return [...new Set(path.split("/").flatMap((segment) => {
95
+ const match = segment.match(/^:([A-Za-z_][A-Za-z0-9_]*)$/);
96
+ return match ? [match[1]] : [];
97
+ }))];
98
+ }
99
+ function validateCanonicalRoutePath(path, label) {
100
+ if (!path.startsWith("/"))
101
+ return `${label} must start with /.`;
102
+ if (/\{[^}]+\}/.test(path))
103
+ return `${label} must use :param syntax; {param} is no longer supported.`;
104
+ for (const segment of path.split("/")) {
105
+ if (segment.startsWith(":") && !/^:[A-Za-z_][A-Za-z0-9_]*$/.test(segment)) {
106
+ return `${label} contains an invalid parameter segment: ${segment}.`;
107
+ }
108
+ }
109
+ return undefined;
110
+ }
111
+ function fixedParamsFromBody(body) {
112
+ const fixed = {};
113
+ for (const [key, value] of Object.entries(body)) {
114
+ if (!key.startsWith("fixedParam."))
115
+ continue;
116
+ const name = key.slice("fixedParam.".length);
117
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || typeof value !== "string")
118
+ continue;
119
+ const normalized = value.trim();
120
+ if (normalized)
121
+ fixed[name] = normalized;
122
+ }
123
+ return fixed;
124
+ }
125
+ function validateFixedParamValue(name, value, paramsSchema) {
126
+ const properties = paramsSchema?.properties;
127
+ const schema = properties && typeof properties === "object" && !Array.isArray(properties)
128
+ ? properties[name]
129
+ : undefined;
130
+ const rule = schema && typeof schema === "object" && !Array.isArray(schema)
131
+ ? schema
132
+ : {};
133
+ const minLength = typeof rule.minLength === "number" ? rule.minLength : 1;
134
+ const maxLength = Math.min(typeof rule.maxLength === "number" ? rule.maxLength : 100, 100);
135
+ if (value.length < minLength || value.length > maxLength) {
136
+ return `${name} must be between ${minLength} and ${maxLength} characters.`;
137
+ }
138
+ if (typeof rule.pattern === "string") {
139
+ try {
140
+ if (!new RegExp(rule.pattern).test(value))
141
+ return `${name} does not match its service validation pattern.`;
142
+ }
143
+ catch {
144
+ return `${name} has an invalid validation pattern in the service manifest.`;
145
+ }
146
+ }
147
+ return undefined;
148
+ }
149
+ function validateRouteParamMapping(appPath, servicePath, fixedParams, paramsSchema) {
150
+ const appParams = new Set(routeParamNames(appPath));
151
+ for (const name of routeParamNames(servicePath)) {
152
+ if (appParams.has(name))
153
+ continue;
154
+ const value = fixedParams[name];
155
+ if (!value)
156
+ return `Service parameter :${name} must appear in the app path or have a fixed value.`;
157
+ const valueError = validateFixedParamValue(name, value, paramsSchema);
158
+ if (valueError)
159
+ return valueError;
160
+ }
161
+ return undefined;
162
+ }
93
163
  function routeMethodsFromManifest(methods) {
94
164
  const normalized = (methods ?? []).filter((method) => method === "GET" || method === "POST" || method === "PUT" || method === "PATCH" || method === "DELETE" || method === "OPTIONS");
95
165
  return normalized.length ? normalized : ["GET"];
@@ -266,12 +336,24 @@ function parseRouteCreateBody(body) {
266
336
  const manifestView = getManifestCache().get(serviceId.value)?.viewIndex[viewId.value];
267
337
  const renderable = manifestView?.renderable !== false;
268
338
  const manifest = getManifestCache().get(serviceId.value);
269
- const serviceRoutePath = manifestView?.path ?? `/${viewId.value}`;
339
+ const servicePathVariant = trimmedString(body, "servicePathVariant") ?? manifestView?.path ?? `/${viewId.value}`;
340
+ if (manifestView && ![manifestView.path, ...manifestView.pathVariants].includes(servicePathVariant)) {
341
+ return { error: "Selected service path is not available for this view." };
342
+ }
343
+ const serviceRoutePath = servicePathVariant;
270
344
  const path = renderable ? requiredRouteString(body, "path", "Mount path") : { value: apiRoutePath(manifest?.serviceId ?? serviceId.value, serviceRoutePath) };
271
345
  if (path.error)
272
346
  return { error: path.error };
273
- if (!path.value.startsWith("/"))
274
- return { error: "Mount path must start with /." };
347
+ const pathError = validateCanonicalRoutePath(path.value, "Mount path");
348
+ if (pathError)
349
+ return { error: pathError };
350
+ const servicePathError = validateCanonicalRoutePath(serviceRoutePath, "Service path");
351
+ if (servicePathError)
352
+ return { error: servicePathError };
353
+ const fixedParams = fixedParamsFromBody(body);
354
+ const mappingError = validateRouteParamMapping(path.value, serviceRoutePath, fixedParams, manifestView?.paramsSchema);
355
+ if (mappingError)
356
+ return { error: mappingError };
275
357
  const title = renderable ? requiredRouteString(body, "title", "Display title") : { value: manifestView?.viewId ?? viewId.value };
276
358
  if (title.error)
277
359
  return { error: title.error };
@@ -280,12 +362,14 @@ function parseRouteCreateBody(body) {
280
362
  path: path.value,
281
363
  serviceId: serviceId.value,
282
364
  viewId: viewId.value,
365
+ ...(manifestView && servicePathVariant !== manifestView.path ? { servicePathVariant } : {}),
366
+ ...(Object.keys(fixedParams).length > 0 ? { fixedParams } : {}),
283
367
  title: title.value,
284
368
  enabled: true,
285
369
  methods: routeMethodsFromManifest(manifestView?.methods)
286
370
  };
287
371
  if (manifestView?.path)
288
- route.targetPath = manifestView.path;
372
+ route.targetPath = serviceRoutePath;
289
373
  const query = trimmedString(body, "query");
290
374
  if (query && renderable)
291
375
  route.query = query.replace(/^\?+/, "");
@@ -1845,8 +1929,9 @@ export function registerAdminApiRoutes(app, store, cpState) {
1845
1929
  const path = trimmedString(body, "path");
1846
1930
  if (!path)
1847
1931
  return validationError(event, "Mount path is required.");
1848
- if (!path.startsWith("/"))
1849
- return validationError(event, "Mount path must start with /.");
1932
+ const pathError = validateCanonicalRoutePath(path, "Mount path");
1933
+ if (pathError)
1934
+ return validationError(event, pathError);
1850
1935
  if (appDef.routes.some((candidate) => candidate.id !== route.id && appRoutePatternKey(candidate.path) === appRoutePatternKey(path))) {
1851
1936
  return validationError(event, `A route already exists at ${path}.`);
1852
1937
  }
@@ -1870,11 +1955,38 @@ export function registerAdminApiRoutes(app, store, cpState) {
1870
1955
  const manifest = getManifestCache().get(route.serviceId);
1871
1956
  const manifestView = manifest?.viewIndex[route.viewId];
1872
1957
  if (manifestView) {
1958
+ const routeIdentityChanged = body.serviceId !== undefined || body.viewId !== undefined;
1959
+ const requestedServicePath = trimmedString(body, "servicePathVariant")
1960
+ ?? (routeIdentityChanged ? undefined : route.servicePathVariant)
1961
+ ?? manifestView.path;
1962
+ if (![manifestView.path, ...manifestView.pathVariants].includes(requestedServicePath)) {
1963
+ return validationError(event, "Selected service path is not available for this view.");
1964
+ }
1965
+ const servicePathError = validateCanonicalRoutePath(requestedServicePath, "Service path");
1966
+ if (servicePathError)
1967
+ return validationError(event, servicePathError);
1968
+ const submittedFixed = fixedParamsFromBody(body);
1969
+ const mappedAppPath = manifestView.renderable === false
1970
+ ? apiRoutePath(manifest?.serviceId ?? route.serviceId, requestedServicePath)
1971
+ : route.path;
1972
+ const appParams = new Set(routeParamNames(mappedAppPath));
1973
+ const fixedParams = Object.fromEntries(Object.entries(submittedFixed).filter(([name]) => routeParamNames(requestedServicePath).includes(name) && !appParams.has(name)));
1974
+ const mappingError = validateRouteParamMapping(mappedAppPath, requestedServicePath, fixedParams, manifestView.paramsSchema);
1975
+ if (mappingError)
1976
+ return validationError(event, mappingError);
1977
+ if (requestedServicePath === manifestView.path)
1978
+ delete route.servicePathVariant;
1979
+ else
1980
+ route.servicePathVariant = requestedServicePath;
1981
+ if (Object.keys(fixedParams).length > 0)
1982
+ route.fixedParams = fixedParams;
1983
+ else
1984
+ delete route.fixedParams;
1873
1985
  route.methods = routeMethodsFromManifest(manifestView.methods);
1874
- route.targetPath = manifestView.path;
1986
+ route.targetPath = requestedServicePath;
1875
1987
  if (manifestView.renderable === false) {
1876
1988
  route.kind = "api";
1877
- route.path = apiRoutePath(manifest?.serviceId ?? route.serviceId, manifestView.path);
1989
+ route.path = apiRoutePath(manifest?.serviceId ?? route.serviceId, requestedServicePath);
1878
1990
  route.title = manifestView.viewId;
1879
1991
  delete route.query;
1880
1992
  }