@stapel/categories-react 0.1.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/CHANGELOG.md +1 -0
- package/MODULE.md +194 -0
- package/README.md +191 -0
- package/dist/api/categoriesApi.d.ts +121 -0
- package/dist/api/categoriesApi.d.ts.map +1 -0
- package/dist/api/categoriesApi.js +48 -0
- package/dist/api/categoriesApi.js.map +1 -0
- package/dist/api/generated/schema.d.ts +2321 -0
- package/dist/api/generated/schema.d.ts.map +1 -0
- package/dist/api/generated/schema.js +2 -0
- package/dist/api/generated/schema.js.map +1 -0
- package/dist/api/types.d.ts +92 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +2 -0
- package/dist/api/types.js.map +1 -0
- package/dist/catalog/labels.d.ts +103 -0
- package/dist/catalog/labels.d.ts.map +1 -0
- package/dist/catalog/labels.js +56 -0
- package/dist/catalog/labels.js.map +1 -0
- package/dist/catalog/pks.d.ts +25 -0
- package/dist/catalog/pks.d.ts.map +1 -0
- package/dist/catalog/pks.js +38 -0
- package/dist/catalog/pks.js.map +1 -0
- package/dist/catalog/sync.d.ts +103 -0
- package/dist/catalog/sync.d.ts.map +1 -0
- package/dist/catalog/sync.js +114 -0
- package/dist/catalog/sync.js.map +1 -0
- package/dist/catalog/tree.d.ts +119 -0
- package/dist/catalog/tree.d.ts.map +1 -0
- package/dist/catalog/tree.js +166 -0
- package/dist/catalog/tree.js.map +1 -0
- package/dist/default/CatalogPage.d.ts +16 -0
- package/dist/default/CatalogPage.d.ts.map +1 -0
- package/dist/default/CatalogPage.js +13 -0
- package/dist/default/CatalogPage.js.map +1 -0
- package/dist/default/CategoryBreadcrumbsBar.d.ts +20 -0
- package/dist/default/CategoryBreadcrumbsBar.d.ts.map +1 -0
- package/dist/default/CategoryBreadcrumbsBar.js +35 -0
- package/dist/default/CategoryBreadcrumbsBar.js.map +1 -0
- package/dist/default/CategoryCarouselStrip.d.ts +22 -0
- package/dist/default/CategoryCarouselStrip.d.ts.map +1 -0
- package/dist/default/CategoryCarouselStrip.js +24 -0
- package/dist/default/CategoryCarouselStrip.js.map +1 -0
- package/dist/default/CategoryFeatureList.d.ts +23 -0
- package/dist/default/CategoryFeatureList.d.ts.map +1 -0
- package/dist/default/CategoryFeatureList.js +28 -0
- package/dist/default/CategoryFeatureList.js.map +1 -0
- package/dist/default/CategoryPage.d.ts +43 -0
- package/dist/default/CategoryPage.d.ts.map +1 -0
- package/dist/default/CategoryPage.js +21 -0
- package/dist/default/CategoryPage.js.map +1 -0
- package/dist/default/CategoryPickerField.d.ts +26 -0
- package/dist/default/CategoryPickerField.d.ts.map +1 -0
- package/dist/default/CategoryPickerField.js +40 -0
- package/dist/default/CategoryPickerField.js.map +1 -0
- package/dist/default/CategoryTreePane.d.ts +28 -0
- package/dist/default/CategoryTreePane.d.ts.map +1 -0
- package/dist/default/CategoryTreePane.js +25 -0
- package/dist/default/CategoryTreePane.js.map +1 -0
- package/dist/default/ErrorAlert.d.ts +9 -0
- package/dist/default/ErrorAlert.d.ts.map +1 -0
- package/dist/default/ErrorAlert.js +27 -0
- package/dist/default/ErrorAlert.js.map +1 -0
- package/dist/default/index.d.ts +37 -0
- package/dist/default/index.d.ts.map +1 -0
- package/dist/default/index.js +28 -0
- package/dist/default/index.js.map +1 -0
- package/dist/default/theme.d.ts +25 -0
- package/dist/default/theme.d.ts.map +1 -0
- package/dist/default/theme.js +40 -0
- package/dist/default/theme.js.map +1 -0
- package/dist/default/types.d.ts +12 -0
- package/dist/default/types.d.ts.map +1 -0
- package/dist/default/types.js +2 -0
- package/dist/default/types.js.map +1 -0
- package/dist/headless/CategoriesProvider.d.ts +19 -0
- package/dist/headless/CategoriesProvider.d.ts.map +1 -0
- package/dist/headless/CategoriesProvider.js +15 -0
- package/dist/headless/CategoriesProvider.js.map +1 -0
- package/dist/headless/CategoryBreadcrumbs.d.ts +40 -0
- package/dist/headless/CategoryBreadcrumbs.d.ts.map +1 -0
- package/dist/headless/CategoryBreadcrumbs.js +42 -0
- package/dist/headless/CategoryBreadcrumbs.js.map +1 -0
- package/dist/headless/CategoryCarousel.d.ts +48 -0
- package/dist/headless/CategoryCarousel.d.ts.map +1 -0
- package/dist/headless/CategoryCarousel.js +34 -0
- package/dist/headless/CategoryCarousel.js.map +1 -0
- package/dist/headless/CategoryFeatures.d.ts +67 -0
- package/dist/headless/CategoryFeatures.d.ts.map +1 -0
- package/dist/headless/CategoryFeatures.js +46 -0
- package/dist/headless/CategoryFeatures.js.map +1 -0
- package/dist/headless/CategoryPicker.d.ts +69 -0
- package/dist/headless/CategoryPicker.d.ts.map +1 -0
- package/dist/headless/CategoryPicker.js +86 -0
- package/dist/headless/CategoryPicker.js.map +1 -0
- package/dist/headless/CategoryTree.d.ts +55 -0
- package/dist/headless/CategoryTree.d.ts.map +1 -0
- package/dist/headless/CategoryTree.js +51 -0
- package/dist/headless/CategoryTree.js.map +1 -0
- package/dist/i18n/errorsMap.d.ts +22 -0
- package/dist/i18n/errorsMap.d.ts.map +1 -0
- package/dist/i18n/errorsMap.js +31 -0
- package/dist/i18n/errorsMap.js.map +1 -0
- package/dist/i18n/es.d.ts +25 -0
- package/dist/i18n/es.d.ts.map +1 -0
- package/dist/i18n/es.js +74 -0
- package/dist/i18n/es.js.map +1 -0
- package/dist/i18n/generated/errors.es.gen.d.ts +16 -0
- package/dist/i18n/generated/errors.es.gen.d.ts.map +1 -0
- package/dist/i18n/generated/errors.es.gen.js +58 -0
- package/dist/i18n/generated/errors.es.gen.js.map +1 -0
- package/dist/i18n/generated/errors.gen.d.ts +407 -0
- package/dist/i18n/generated/errors.gen.d.ts.map +1 -0
- package/dist/i18n/generated/errors.gen.js +207 -0
- package/dist/i18n/generated/errors.gen.js.map +1 -0
- package/dist/i18n/generated/errors.ru.gen.d.ts +16 -0
- package/dist/i18n/generated/errors.ru.gen.d.ts.map +1 -0
- package/dist/i18n/generated/errors.ru.gen.js +58 -0
- package/dist/i18n/generated/errors.ru.gen.js.map +1 -0
- package/dist/i18n/keys.d.ts +89 -0
- package/dist/i18n/keys.d.ts.map +1 -0
- package/dist/i18n/keys.js +134 -0
- package/dist/i18n/keys.js.map +1 -0
- package/dist/i18n/ru.d.ts +41 -0
- package/dist/i18n/ru.d.ts.map +1 -0
- package/dist/i18n/ru.js +90 -0
- package/dist/i18n/ru.js.map +1 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +78 -0
- package/dist/index.js.map +1 -0
- package/dist/model/catalogStore.d.ts +23 -0
- package/dist/model/catalogStore.d.ts.map +1 -0
- package/dist/model/catalogStore.js +85 -0
- package/dist/model/catalogStore.js.map +1 -0
- package/dist/model/catalogSync.d.ts +49 -0
- package/dist/model/catalogSync.d.ts.map +1 -0
- package/dist/model/catalogSync.js +64 -0
- package/dist/model/catalogSync.js.map +1 -0
- package/dist/model/context.d.ts +11 -0
- package/dist/model/context.d.ts.map +1 -0
- package/dist/model/context.js +16 -0
- package/dist/model/context.js.map +1 -0
- package/dist/model/queries.d.ts +124 -0
- package/dist/model/queries.d.ts.map +1 -0
- package/dist/model/queries.js +147 -0
- package/dist/model/queries.js.map +1 -0
- package/dist/model/queryKeys.d.ts +30 -0
- package/dist/model/queryKeys.d.ts.map +1 -0
- package/dist/model/queryKeys.js +16 -0
- package/dist/model/queryKeys.js.map +1 -0
- package/dist/model/runtime.d.ts +31 -0
- package/dist/model/runtime.d.ts.map +1 -0
- package/dist/model/runtime.js +6 -0
- package/dist/model/runtime.js.map +1 -0
- package/dist/nav/manifest.d.ts +31 -0
- package/dist/nav/manifest.d.ts.map +1 -0
- package/dist/nav/manifest.js +37 -0
- package/dist/nav/manifest.js.map +1 -0
- package/llms.txt +125 -0
- package/manifest.json +918 -0
- package/nav-manifest.json +44 -0
- package/package.json +119 -0
- package/src/analytics/generated/events.json +7 -0
- package/src/api/categoriesApi.ts +179 -0
- package/src/api/generated/schema.ts +2323 -0
- package/src/api/types.ts +99 -0
- package/src/catalog/labels.ts +129 -0
- package/src/catalog/pks.ts +35 -0
- package/src/catalog/sync.ts +169 -0
- package/src/catalog/tree.ts +242 -0
- package/src/default/CatalogPage.tsx +40 -0
- package/src/default/CategoryBreadcrumbsBar.tsx +93 -0
- package/src/default/CategoryCarouselStrip.tsx +102 -0
- package/src/default/CategoryFeatureList.tsx +125 -0
- package/src/default/CategoryPage.tsx +114 -0
- package/src/default/CategoryPickerField.tsx +173 -0
- package/src/default/CategoryTreePane.tsx +134 -0
- package/src/default/ErrorAlert.tsx +46 -0
- package/src/default/index.ts +36 -0
- package/src/default/theme.tsx +67 -0
- package/src/default/types.ts +12 -0
- package/src/headless/CategoriesProvider.tsx +20 -0
- package/src/headless/CategoryBreadcrumbs.tsx +78 -0
- package/src/headless/CategoryCarousel.tsx +80 -0
- package/src/headless/CategoryFeatures.tsx +100 -0
- package/src/headless/CategoryPicker.tsx +173 -0
- package/src/headless/CategoryTree.tsx +115 -0
- package/src/i18n/errorsMap.ts +46 -0
- package/src/i18n/es.ts +92 -0
- package/src/i18n/generated/errors.es.gen.ts +64 -0
- package/src/i18n/generated/errors.gen.ts +238 -0
- package/src/i18n/generated/errors.json +495 -0
- package/src/i18n/generated/errors.ru.gen.ts +64 -0
- package/src/i18n/keys.ts +156 -0
- package/src/i18n/ru.ts +110 -0
- package/src/index.ts +191 -0
- package/src/model/catalogStore.ts +102 -0
- package/src/model/catalogSync.ts +115 -0
- package/src/model/context.tsx +28 -0
- package/src/model/queries.ts +226 -0
- package/src/model/queryKeys.ts +50 -0
- package/src/model/runtime.ts +39 -0
- package/src/nav/manifest.ts +66 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"package": "@stapel/categories-react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"entries": [
|
|
5
|
+
{
|
|
6
|
+
"id": "categories.catalog",
|
|
7
|
+
"labelKey": "categories.catalog.title",
|
|
8
|
+
"icon": "AppstoreOutlined",
|
|
9
|
+
"route": {
|
|
10
|
+
"path": "/c"
|
|
11
|
+
},
|
|
12
|
+
"component": {
|
|
13
|
+
"export": "CatalogPage",
|
|
14
|
+
"subpath": "default"
|
|
15
|
+
},
|
|
16
|
+
"placement": {
|
|
17
|
+
"level": "top"
|
|
18
|
+
},
|
|
19
|
+
"menuVisibleDefault": true,
|
|
20
|
+
"requiresAuth": false,
|
|
21
|
+
"surface": "public",
|
|
22
|
+
"order": 5
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "categories.category",
|
|
26
|
+
"labelKey": "categories.category.title",
|
|
27
|
+
"icon": "FolderOpenOutlined",
|
|
28
|
+
"route": {
|
|
29
|
+
"path": "/c/:slug"
|
|
30
|
+
},
|
|
31
|
+
"component": {
|
|
32
|
+
"export": "CategoryPage",
|
|
33
|
+
"subpath": "default"
|
|
34
|
+
},
|
|
35
|
+
"placement": {
|
|
36
|
+
"level": "top"
|
|
37
|
+
},
|
|
38
|
+
"menuVisibleDefault": false,
|
|
39
|
+
"requiresAuth": false,
|
|
40
|
+
"surface": "public",
|
|
41
|
+
"order": 6
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stapel/categories-react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Headless React pair for stapel-categories: a typed client, a client-assembled category tree, and the revision-delta sync the module documents. The list endpoint returns FLAT rows with treenode ancestry as comma-joined pk strings — this package parses them, builds the tree, resolves a slug to a category (the server has no slug lookup), walks breadcrumbs, and keeps the whole catalogue in an app-scoped repository refreshed by `?min_revision=` deltas instead of refetching it on every page. Category and feature names are TRANSLATION KEYS on the wire, never resolved labels, and the pair says so in its types. A category's feature schema is served typed for @stapel/attributes-react to draw and for search facets to label. Zero visual opinion in the main entry; an opt-in /default subpath ships the antd skin.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/usestapel/stapel-react.git",
|
|
9
|
+
"directory": "packages/categories-react"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./default": {
|
|
21
|
+
"types": "./dist/default/index.d.ts",
|
|
22
|
+
"default": "./dist/default/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./i18n/ru": {
|
|
25
|
+
"types": "./dist/i18n/ru.d.ts",
|
|
26
|
+
"default": "./dist/i18n/ru.js"
|
|
27
|
+
},
|
|
28
|
+
"./i18n/es": {
|
|
29
|
+
"types": "./dist/i18n/es.d.ts",
|
|
30
|
+
"default": "./dist/i18n/es.js"
|
|
31
|
+
},
|
|
32
|
+
"./manifest": "./manifest.json",
|
|
33
|
+
"./manifest.json": "./manifest.json",
|
|
34
|
+
"./nav-manifest.json": "./nav-manifest.json",
|
|
35
|
+
"./llms.txt": "./llms.txt",
|
|
36
|
+
"./package.json": "./package.json"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"src",
|
|
41
|
+
"tsconfig.json",
|
|
42
|
+
"README.md",
|
|
43
|
+
"MODULE.md",
|
|
44
|
+
"CHANGELOG.md",
|
|
45
|
+
"manifest.json",
|
|
46
|
+
"nav-manifest.json",
|
|
47
|
+
"llms.txt"
|
|
48
|
+
],
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsc -p tsconfig.json",
|
|
51
|
+
"test": "tsc -p tsconfig.test.json && vitest run --exclude test/prodBundlePurity.test.ts",
|
|
52
|
+
"lint": "eslint .",
|
|
53
|
+
"size": "size-limit",
|
|
54
|
+
"test:pack": "vitest run test/prodBundlePurity.test.ts"
|
|
55
|
+
},
|
|
56
|
+
"size-limit": [
|
|
57
|
+
{
|
|
58
|
+
"name": "index — the headless pair (client + tree assembly + delta sync + hooks): no antd",
|
|
59
|
+
"path": "dist/index.js",
|
|
60
|
+
"limit": "9 KB"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "default — the antd skin (catalog, category page, carousel, picker, breadcrumbs) must stay out of the main bundle",
|
|
64
|
+
"path": "dist/default/index.js",
|
|
65
|
+
"limit": "13 KB"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "i18n/ru — the Russian bundle is opt-in, not carried by the main entry",
|
|
69
|
+
"path": "dist/i18n/ru.js",
|
|
70
|
+
"limit": "4 KB"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"name": "i18n/es — the Spanish bundle is opt-in, not carried by the main entry",
|
|
74
|
+
"path": "dist/i18n/es.js",
|
|
75
|
+
"limit": "3.5 KB"
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
"peerDependencies": {
|
|
79
|
+
"@stapel/attributes-react": ">=0.1.0",
|
|
80
|
+
"@stapel/core": ">=0.15.0 <1.0.0",
|
|
81
|
+
"@stapel/tokens-antd": ">=0.5.0",
|
|
82
|
+
"@tanstack/react-query": "^5.0.0",
|
|
83
|
+
"antd": ">=5.20.0 <7",
|
|
84
|
+
"react": ">=19"
|
|
85
|
+
},
|
|
86
|
+
"peerDependenciesMeta": {
|
|
87
|
+
"@stapel/tokens-antd": {
|
|
88
|
+
"optional": true
|
|
89
|
+
},
|
|
90
|
+
"antd": {
|
|
91
|
+
"optional": true
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"devDependencies": {
|
|
95
|
+
"@size-limit/preset-small-lib": "^11.2.0",
|
|
96
|
+
"@stapel/attributes-react": "workspace:^",
|
|
97
|
+
"@stapel/core": "workspace:^",
|
|
98
|
+
"@stapel/showcase": "workspace:^",
|
|
99
|
+
"@stapel/tokens": "workspace:^",
|
|
100
|
+
"@stapel/tokens-antd": "workspace:^",
|
|
101
|
+
"@tanstack/react-query": "^5.81.0",
|
|
102
|
+
"@testing-library/react": "^16.3.0",
|
|
103
|
+
"@types/react": "^19.1.0",
|
|
104
|
+
"@types/react-dom": "^19.1.0",
|
|
105
|
+
"antd": "^6.5.0",
|
|
106
|
+
"jsdom": "^26.1.0",
|
|
107
|
+
"react": "^19.1.0",
|
|
108
|
+
"react-dom": "^19.1.0",
|
|
109
|
+
"size-limit": "^11.2.0",
|
|
110
|
+
"typescript": "^5.8.3",
|
|
111
|
+
"vitest": "^3.2.4"
|
|
112
|
+
},
|
|
113
|
+
"engines": {
|
|
114
|
+
"node": ">=22"
|
|
115
|
+
},
|
|
116
|
+
"publishConfig": {
|
|
117
|
+
"access": "public"
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { StapelClient } from "@stapel/core";
|
|
2
|
+
import type {
|
|
3
|
+
Category,
|
|
4
|
+
CategoryFeature,
|
|
5
|
+
CategoryListParams,
|
|
6
|
+
CategoryPage,
|
|
7
|
+
MaxRevision,
|
|
8
|
+
} from "./types.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The pair's typed operation surface — one method per stapel-categories
|
|
12
|
+
* endpoint a storefront may call, bound to the injected {@link StapelClient}
|
|
13
|
+
* (the per-module override seam of frontend-standard §7.2). Paths are relative
|
|
14
|
+
* to the runtime's `baseUrl` (`/categories/api/v1/`).
|
|
15
|
+
*
|
|
16
|
+
* ── Five reads, and the eighteen operations that are NOT here ──────────────
|
|
17
|
+
*
|
|
18
|
+
* The contract has 23 paths. Every one of them is on `ReadOnlyOrStaff`, which
|
|
19
|
+
* means SAFE methods are open to anonymous callers and everything else needs
|
|
20
|
+
* `is_staff` — so the split below is not a taste call, it is the permission
|
|
21
|
+
* boundary drawn on the surface:
|
|
22
|
+
*
|
|
23
|
+
* - **Public reads, and this pair's whole surface**: the category list (the
|
|
24
|
+
* delta-sync source), `children`, `carousel`, `{id}/features`, and
|
|
25
|
+
* `revision`.
|
|
26
|
+
* - **Staff writes**: create / update / delete, `bulk_add`,
|
|
27
|
+
* `bulk-commands`, `undelete`, `convert-type`, and the four
|
|
28
|
+
* `feature-editor*` operations. These are the catalogue admin, which the
|
|
29
|
+
* storefront wave leaves to Django admin (spec §4.3) — a pair that exposed
|
|
30
|
+
* them would invite a public screen to call something only staff may.
|
|
31
|
+
* - **`POST {id}/validate-dto/` is a WRITE in DRF's eyes**, so despite
|
|
32
|
+
* reading like a public helper it answers 403 to a visitor. The listing
|
|
33
|
+
* compose flow validates against the same rules with
|
|
34
|
+
* `@stapel/attributes-react`'s client-side mirror plus the server's verdict
|
|
35
|
+
* on publish; asking this endpoint would fail for exactly the people who
|
|
36
|
+
* need it.
|
|
37
|
+
* - **`GET {id}/validate-configs/`** is a catalogue-health read for an
|
|
38
|
+
* operator, not a storefront read.
|
|
39
|
+
* - **`GET translation-keys/`** is `IsServiceRequest` (service-to-service):
|
|
40
|
+
* it is the extraction feed for translators, and it returns KEYS with
|
|
41
|
+
* admin refs — never resolved labels. See `catalog/labels.ts`.
|
|
42
|
+
* - **`GET data.json/`** requires a `?revision=` cache-buster and hands back
|
|
43
|
+
* the whole table in one response. It is a legitimate alternative to the
|
|
44
|
+
* paged walk, but it is a *second* sync protocol, and the module documents
|
|
45
|
+
* the paged one. One protocol, tested, beats two, half-tested.
|
|
46
|
+
* - The parallel `/features/` collection (list, retrieve, create, …) is the
|
|
47
|
+
* feature tree in its own right — an admin axis. A storefront reads
|
|
48
|
+
* features THROUGH a category, which is the only place inheritance and
|
|
49
|
+
* order are resolved.
|
|
50
|
+
*
|
|
51
|
+
* Nothing is hidden: `manifest.json` lists the whole contract.
|
|
52
|
+
*
|
|
53
|
+
* These operations will be GENERATED from schema.json operationIds by gen-api
|
|
54
|
+
* v2 (task `core-typed-ops`); until then they are hand-authored here (the ONE
|
|
55
|
+
* legal home of path strings — `stapel/no-string-paths` §2.3 carve-out).
|
|
56
|
+
*/
|
|
57
|
+
export interface CategoriesApi {
|
|
58
|
+
readonly client: StapelClient;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* One page of the revision-paginated category list — the source the whole
|
|
62
|
+
* tree is assembled from, and the only endpoint that reports deletions.
|
|
63
|
+
*
|
|
64
|
+
* Rows are ordered by `revision`, NOT by tree position: this is a sync feed,
|
|
65
|
+
* not a menu. `revisions.global_max` is the cursor to store;
|
|
66
|
+
* `revisions.deleted_ids` is the authoritative tombstone list for a delta
|
|
67
|
+
* (see `catalog/sync.ts` for why it beats scanning `deleted: true` rows).
|
|
68
|
+
*/
|
|
69
|
+
list(
|
|
70
|
+
params?: CategoryListParams,
|
|
71
|
+
options?: { readonly signal?: AbortSignal }
|
|
72
|
+
): Promise<CategoryPage>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Non-deleted direct children of one category, `tn_priority` descending.
|
|
76
|
+
*
|
|
77
|
+
* Redundant with the synced tree by construction, and deliberately kept: a
|
|
78
|
+
* host that wants one branch without paying for the catalogue (an SSR
|
|
79
|
+
* category page, a lazily-expanded admin-ish picker) has an endpoint for it,
|
|
80
|
+
* and it is the one place the server does the `deleted` filtering.
|
|
81
|
+
*/
|
|
82
|
+
children(
|
|
83
|
+
id: number,
|
|
84
|
+
options?: { readonly signal?: AbortSignal }
|
|
85
|
+
): Promise<readonly Category[]>;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The carousel strip: `active` AND `carousel_enabled`, `tn_priority`
|
|
89
|
+
* descending, cached server-side and sent with
|
|
90
|
+
* `Cache-Control: public, max-age` — so this one is safe to call on every
|
|
91
|
+
* landing render.
|
|
92
|
+
*/
|
|
93
|
+
carousel(options?: {
|
|
94
|
+
readonly signal?: AbortSignal;
|
|
95
|
+
}): Promise<readonly Category[]>;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* A category's resolved feature schema: its own features plus every ancestor's,
|
|
99
|
+
* deduplicated by SLUG (an `inherit` override is a new row sharing the
|
|
100
|
+
* parent's slug, and the version nearest this category wins), ordered by the
|
|
101
|
+
* category's own `CategoryFeature.order` first.
|
|
102
|
+
*
|
|
103
|
+
* This is what the listing compose form draws with
|
|
104
|
+
* `@stapel/attributes-react` and what labels a search facet. `config`
|
|
105
|
+
* arrives verbatim — defaults are the reader's job, and attributes-react
|
|
106
|
+
* owns them.
|
|
107
|
+
*/
|
|
108
|
+
features(
|
|
109
|
+
id: number,
|
|
110
|
+
options?: { readonly signal?: AbortSignal }
|
|
111
|
+
): Promise<readonly CategoryFeature[]>;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The table's current maximum revision, without transferring any rows.
|
|
115
|
+
*
|
|
116
|
+
* The cheap freshness probe: a stored snapshot whose cursor already equals
|
|
117
|
+
* this needs no delta request at all.
|
|
118
|
+
*/
|
|
119
|
+
revision(options?: {
|
|
120
|
+
readonly signal?: AbortSignal;
|
|
121
|
+
}): Promise<MaxRevision>;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The wire query object for one list request.
|
|
126
|
+
*
|
|
127
|
+
* Exported because the query KEY is built from it, so the key and the request
|
|
128
|
+
* are the same value by construction.
|
|
129
|
+
*
|
|
130
|
+
* `include_deleted` is sent ONLY when the caller asked for a value, and the
|
|
131
|
+
* server's own default is `true`. Sending it always would be harmless; leaving
|
|
132
|
+
* it out when unasked keeps the cache key of a plain read from depending on
|
|
133
|
+
* this pair's opinion about a server default.
|
|
134
|
+
*/
|
|
135
|
+
export function categoryListParams(
|
|
136
|
+
params: CategoryListParams = {}
|
|
137
|
+
): Record<string, string | number | boolean | undefined> {
|
|
138
|
+
const query: Record<string, string | number | boolean | undefined> = {};
|
|
139
|
+
if (params.minRevision !== undefined) query["min_revision"] = params.minRevision;
|
|
140
|
+
if (params.maxRevision !== undefined) query["max_revision"] = params.maxRevision;
|
|
141
|
+
if (params.includeDeleted !== undefined) {
|
|
142
|
+
query["include_deleted"] = params.includeDeleted;
|
|
143
|
+
}
|
|
144
|
+
if (params.page !== undefined) query["page"] = params.page;
|
|
145
|
+
if (params.pageSize !== undefined) query["page_size"] = params.pageSize;
|
|
146
|
+
return query;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function createCategoriesApi(client: StapelClient): CategoriesApi {
|
|
150
|
+
return {
|
|
151
|
+
client,
|
|
152
|
+
|
|
153
|
+
list: (params, options) =>
|
|
154
|
+
client.get("/categories/", {
|
|
155
|
+
query: categoryListParams(params),
|
|
156
|
+
...(options?.signal !== undefined ? { signal: options.signal } : {}),
|
|
157
|
+
}),
|
|
158
|
+
|
|
159
|
+
children: (id, options) =>
|
|
160
|
+
client.get(`/categories/${String(id)}/children/`, {
|
|
161
|
+
...(options?.signal !== undefined ? { signal: options.signal } : {}),
|
|
162
|
+
}),
|
|
163
|
+
|
|
164
|
+
carousel: (options) =>
|
|
165
|
+
client.get("/categories/carousel/", {
|
|
166
|
+
...(options?.signal !== undefined ? { signal: options.signal } : {}),
|
|
167
|
+
}),
|
|
168
|
+
|
|
169
|
+
features: (id, options) =>
|
|
170
|
+
client.get(`/categories/${String(id)}/features/`, {
|
|
171
|
+
...(options?.signal !== undefined ? { signal: options.signal } : {}),
|
|
172
|
+
}),
|
|
173
|
+
|
|
174
|
+
revision: (options) =>
|
|
175
|
+
client.get("/categories/revision/", {
|
|
176
|
+
...(options?.signal !== undefined ? { signal: options.signal } : {}),
|
|
177
|
+
}),
|
|
178
|
+
};
|
|
179
|
+
}
|