@stapel/attributes-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/README.md +123 -0
- package/dist/default/FeatureBadges.d.ts +33 -0
- package/dist/default/FeatureBadges.d.ts.map +1 -0
- package/dist/default/FeatureBadges.js +56 -0
- package/dist/default/FeatureBadges.js.map +1 -0
- package/dist/default/FeatureFields.d.ts +52 -0
- package/dist/default/FeatureFields.d.ts.map +1 -0
- package/dist/default/FeatureFields.js +60 -0
- package/dist/default/FeatureFields.js.map +1 -0
- package/dist/default/editors.d.ts +24 -0
- package/dist/default/editors.d.ts.map +1 -0
- package/dist/default/editors.js +354 -0
- package/dist/default/editors.js.map +1 -0
- package/dist/default/index.d.ts +27 -0
- package/dist/default/index.d.ts.map +1 -0
- package/dist/default/index.js +25 -0
- package/dist/default/index.js.map +1 -0
- package/dist/dto.d.ts +40 -0
- package/dist/dto.d.ts.map +1 -0
- package/dist/dto.js +64 -0
- package/dist/dto.js.map +1 -0
- package/dist/errors.d.ts +57 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +98 -0
- package/dist/errors.js.map +1 -0
- package/dist/format.d.ts +46 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +159 -0
- package/dist/format.js.map +1 -0
- package/dist/i18n/es.d.ts +13 -0
- package/dist/i18n/es.d.ts.map +1 -0
- package/dist/i18n/es.js +36 -0
- package/dist/i18n/es.js.map +1 -0
- package/dist/i18n/keys.d.ts +56 -0
- package/dist/i18n/keys.d.ts.map +1 -0
- package/dist/i18n/keys.js +80 -0
- package/dist/i18n/keys.js.map +1 -0
- package/dist/i18n/ru.d.ts +19 -0
- package/dist/i18n/ru.d.ts.map +1 -0
- package/dist/i18n/ru.js +42 -0
- package/dist/i18n/ru.js.map +1 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +54 -0
- package/dist/index.js.map +1 -0
- package/dist/registry.d.ts +122 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +82 -0
- package/dist/registry.js.map +1 -0
- package/dist/types.d.ts +134 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +35 -0
- package/dist/types.js.map +1 -0
- package/dist/validate.d.ts +88 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +410 -0
- package/dist/validate.js.map +1 -0
- package/manifest.json +96 -0
- package/package.json +107 -0
- package/src/default/FeatureBadges.tsx +123 -0
- package/src/default/FeatureFields.tsx +140 -0
- package/src/default/editors.tsx +578 -0
- package/src/default/index.ts +34 -0
- package/src/dto.ts +78 -0
- package/src/errors.ts +127 -0
- package/src/format.ts +210 -0
- package/src/i18n/es.ts +41 -0
- package/src/i18n/keys.ts +89 -0
- package/src/i18n/ru.ts +48 -0
- package/src/index.ts +98 -0
- package/src/registry.ts +167 -0
- package/src/types.ts +166 -0
- package/src/validate.ts +507 -0
- package/tsconfig.json +26 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@stapel/attributes-react` — the React value layer for `stapel-attributes`'
|
|
3
|
+
* dynamic feature types.
|
|
4
|
+
*
|
|
5
|
+
* ── What this package is, and what it deliberately is not ──────────────────
|
|
6
|
+
*
|
|
7
|
+
* It is an **L0 package, not a pair**, modelled on `@stapel/image`: no client,
|
|
8
|
+
* no queries, no `docs/schema.json`. Its backend counterpart is an L1 library
|
|
9
|
+
* with no HTTP surface whatsoever (`stapel-attributes/docs/readme.md`) — there
|
|
10
|
+
* is no `/attributes/api/v1/` to pair with. Feature definitions and validation
|
|
11
|
+
* verdicts reach a browser inside the responses of the modules that OWN them
|
|
12
|
+
* (categories, listings), and those pairs depend on this one to draw and check
|
|
13
|
+
* what they carry.
|
|
14
|
+
*
|
|
15
|
+
* It switches on **`config.type`** — the value type (`string`, `int`,
|
|
16
|
+
* `select`, `date`, `hex_color`, `hierarchical_select`, `convertible_unit`,
|
|
17
|
+
* `bool`, `float`, `header`). That is a DIFFERENT axis from the one
|
|
18
|
+
* `@stapel/forms-react` works on, which is `FormField.kind`: the field kinds
|
|
19
|
+
* of the admin form that CONFIGURES a type. Same upstream library, two
|
|
20
|
+
* vocabularies, and the storefront needs this one. A consequence worth
|
|
21
|
+
* stating because it removes an upstream ask: a storefront needs **no
|
|
22
|
+
* catalogue endpoint** for these types — the type arrives in the data, on
|
|
23
|
+
* every feature.
|
|
24
|
+
*
|
|
25
|
+
* ── The three-rung ladder, verbatim from forms-react ───────────────────────
|
|
26
|
+
*
|
|
27
|
+
* explicit `registerValueEditor(type, …)` ← a host's, always wins
|
|
28
|
+
* → the skin's `BUILTIN_VALUE_EDITORS` ← `/default`, ten types
|
|
29
|
+
* → `<UnsupportedValueEditor/>` ← loud, never a skipped field
|
|
30
|
+
*
|
|
31
|
+
* and, while an undrawable feature is on screen, `unsupportedTypeGate` blocks
|
|
32
|
+
* the submit with the reason NAMED. A category can legally carry a type this
|
|
33
|
+
* build has no editor for; drawing nothing would silently drop a feature that
|
|
34
|
+
* may be MANDATORY, and the person would submit a listing they could not
|
|
35
|
+
* complete and be told, by the server, that an attribute they never saw is
|
|
36
|
+
* missing.
|
|
37
|
+
*
|
|
38
|
+
* ── Layout ─────────────────────────────────────────────────────────────────
|
|
39
|
+
*
|
|
40
|
+
* `.` registry + mirror + DTO helpers + display formatting.
|
|
41
|
+
* React types only; no antd, no react-router, no fetch.
|
|
42
|
+
* `./default` the antd skin: ten builtin editors, `<FeatureFields>`,
|
|
43
|
+
* `<FeatureBadges>`, `<FeatureValueList>`.
|
|
44
|
+
* `./i18n/ru` opt-in locale bundles.
|
|
45
|
+
* `./i18n/es`
|
|
46
|
+
*/
|
|
47
|
+
export type { FeatureConfig, FeatureDef, FeatureValueDto, FeatureValidationResult, FeaturesDto, ValidationBatchResult, ValidationErrorCode, ValidationStatus, } from "./types.js";
|
|
48
|
+
export { featureConfig, featureName, featureType } from "./types.js";
|
|
49
|
+
export { UNTYPED_FEATURE, registerValueEditor, registeredValueEditorTypes, resolveValueEditor, unregisterValueEditor, unsupportedTypeGate, unsupportedTypes, } from "./registry.js";
|
|
50
|
+
export type { ValueEditor, ValueEditorProps } from "./registry.js";
|
|
51
|
+
export { ERROR_CODE_TO_KEY, VALIDATION_ERROR_CODES, featureErrorsBySlug, resultErrorKey, } from "./errors.js";
|
|
52
|
+
export { SIMPLE_COLORS, codePointLength, isBlank, mirrorValidate, patternFullMatch, validateFeatureValue, } from "./validate.js";
|
|
53
|
+
export { fromFeaturesDto, toFeaturesDto } from "./dto.js";
|
|
54
|
+
export { FORMATTABLE_TYPES, formatFeatureValue, hexColorSwatch } from "./format.js";
|
|
55
|
+
export type { FormatOptions } from "./format.js";
|
|
56
|
+
export { ATTRIBUTES_ERROR_BUNDLE_EN, ATTRIBUTES_I18N_KEYS, attributesI18nBundleEn, registerAttributesI18n, } from "./i18n/keys.js";
|
|
57
|
+
export type { AttributesI18nKey } from "./i18n/keys.js";
|
|
58
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAEH,YAAY,EACV,aAAa,EACb,UAAU,EACV,eAAe,EACf,uBAAuB,EACvB,WAAW,EACX,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAErE,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,0BAA0B,EAC1B,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEnE,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,mBAAmB,EACnB,cAAc,GACf,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,aAAa,EACb,eAAe,EACf,OAAO,EACP,cAAc,EACd,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE1D,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACpF,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@stapel/attributes-react` — the React value layer for `stapel-attributes`'
|
|
3
|
+
* dynamic feature types.
|
|
4
|
+
*
|
|
5
|
+
* ── What this package is, and what it deliberately is not ──────────────────
|
|
6
|
+
*
|
|
7
|
+
* It is an **L0 package, not a pair**, modelled on `@stapel/image`: no client,
|
|
8
|
+
* no queries, no `docs/schema.json`. Its backend counterpart is an L1 library
|
|
9
|
+
* with no HTTP surface whatsoever (`stapel-attributes/docs/readme.md`) — there
|
|
10
|
+
* is no `/attributes/api/v1/` to pair with. Feature definitions and validation
|
|
11
|
+
* verdicts reach a browser inside the responses of the modules that OWN them
|
|
12
|
+
* (categories, listings), and those pairs depend on this one to draw and check
|
|
13
|
+
* what they carry.
|
|
14
|
+
*
|
|
15
|
+
* It switches on **`config.type`** — the value type (`string`, `int`,
|
|
16
|
+
* `select`, `date`, `hex_color`, `hierarchical_select`, `convertible_unit`,
|
|
17
|
+
* `bool`, `float`, `header`). That is a DIFFERENT axis from the one
|
|
18
|
+
* `@stapel/forms-react` works on, which is `FormField.kind`: the field kinds
|
|
19
|
+
* of the admin form that CONFIGURES a type. Same upstream library, two
|
|
20
|
+
* vocabularies, and the storefront needs this one. A consequence worth
|
|
21
|
+
* stating because it removes an upstream ask: a storefront needs **no
|
|
22
|
+
* catalogue endpoint** for these types — the type arrives in the data, on
|
|
23
|
+
* every feature.
|
|
24
|
+
*
|
|
25
|
+
* ── The three-rung ladder, verbatim from forms-react ───────────────────────
|
|
26
|
+
*
|
|
27
|
+
* explicit `registerValueEditor(type, …)` ← a host's, always wins
|
|
28
|
+
* → the skin's `BUILTIN_VALUE_EDITORS` ← `/default`, ten types
|
|
29
|
+
* → `<UnsupportedValueEditor/>` ← loud, never a skipped field
|
|
30
|
+
*
|
|
31
|
+
* and, while an undrawable feature is on screen, `unsupportedTypeGate` blocks
|
|
32
|
+
* the submit with the reason NAMED. A category can legally carry a type this
|
|
33
|
+
* build has no editor for; drawing nothing would silently drop a feature that
|
|
34
|
+
* may be MANDATORY, and the person would submit a listing they could not
|
|
35
|
+
* complete and be told, by the server, that an attribute they never saw is
|
|
36
|
+
* missing.
|
|
37
|
+
*
|
|
38
|
+
* ── Layout ─────────────────────────────────────────────────────────────────
|
|
39
|
+
*
|
|
40
|
+
* `.` registry + mirror + DTO helpers + display formatting.
|
|
41
|
+
* React types only; no antd, no react-router, no fetch.
|
|
42
|
+
* `./default` the antd skin: ten builtin editors, `<FeatureFields>`,
|
|
43
|
+
* `<FeatureBadges>`, `<FeatureValueList>`.
|
|
44
|
+
* `./i18n/ru` opt-in locale bundles.
|
|
45
|
+
* `./i18n/es`
|
|
46
|
+
*/
|
|
47
|
+
export { featureConfig, featureName, featureType } from "./types.js";
|
|
48
|
+
export { UNTYPED_FEATURE, registerValueEditor, registeredValueEditorTypes, resolveValueEditor, unregisterValueEditor, unsupportedTypeGate, unsupportedTypes, } from "./registry.js";
|
|
49
|
+
export { ERROR_CODE_TO_KEY, VALIDATION_ERROR_CODES, featureErrorsBySlug, resultErrorKey, } from "./errors.js";
|
|
50
|
+
export { SIMPLE_COLORS, codePointLength, isBlank, mirrorValidate, patternFullMatch, validateFeatureValue, } from "./validate.js";
|
|
51
|
+
export { fromFeaturesDto, toFeaturesDto } from "./dto.js";
|
|
52
|
+
export { FORMATTABLE_TYPES, formatFeatureValue, hexColorSwatch } from "./format.js";
|
|
53
|
+
export { ATTRIBUTES_ERROR_BUNDLE_EN, ATTRIBUTES_I18N_KEYS, attributesI18nBundleEn, registerAttributesI18n, } from "./i18n/keys.js";
|
|
54
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAYH,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAErE,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,0BAA0B,EAC1B,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,mBAAmB,EACnB,cAAc,GACf,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,aAAa,EACb,eAAe,EACf,OAAO,EACP,cAAc,EACd,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE1D,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGpF,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The value-editor registry — THE customer seam of this package, and the
|
|
3
|
+
* direct descendant of forms-react's field-widget registry.
|
|
4
|
+
*
|
|
5
|
+
* It keys on `config.type` (the VALUE type: `string`, `int`, `select`, …),
|
|
6
|
+
* not on `FormField.kind` (the admin config form's field kinds). That is the
|
|
7
|
+
* whole reason this package exists next to `@stapel/forms-react` rather than
|
|
8
|
+
* inside it: two different vocabularies, and L2 pairs may not import each
|
|
9
|
+
* other anyway (`stapel-react/README.md` — dependency direction is strictly
|
|
10
|
+
* downward).
|
|
11
|
+
*
|
|
12
|
+
* The type vocabulary is an OPEN registry server-side (builtins →
|
|
13
|
+
* `STAPEL_ATTRIBUTES["EXTRA_TYPES"]` → runtime `register_feature_type`), so a
|
|
14
|
+
* host that adds a type on the backend must be able to draw it on the front
|
|
15
|
+
* without forking the skin:
|
|
16
|
+
*
|
|
17
|
+
* ```tsx
|
|
18
|
+
* registerValueEditor("size_grid", SizeGridEditor); // at startup
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* ── Resolution, and what happens when nothing matches ──────────────────────
|
|
22
|
+
*
|
|
23
|
+
* This module answers only the FIRST rung: an explicit registration, or
|
|
24
|
+
* `null`. The `/default` skin completes the ladder — explicit registration >
|
|
25
|
+
* skin builtin (antd) > **loud unsupported notice** — verbatim the ladder
|
|
26
|
+
* forms-react's `<StapelForm>` runs.
|
|
27
|
+
*
|
|
28
|
+
* The last rung is a notice, deliberately, and never a skipped field. A
|
|
29
|
+
* category can legally carry a type this build has no editor for; rendering
|
|
30
|
+
* nothing would silently drop a feature that may be MANDATORY, and the person
|
|
31
|
+
* would submit a listing they could not complete and be told, by the server,
|
|
32
|
+
* that an attribute they never saw is missing. `unsupportedTypeGate` blocks
|
|
33
|
+
* the submit with the reason NAMED while such a feature is present — degrade
|
|
34
|
+
* loudly.
|
|
35
|
+
*
|
|
36
|
+
* The registry lives in the main entry, not in `/default`, so a host building
|
|
37
|
+
* its own renderer uses the same seam the skin does rather than a parallel
|
|
38
|
+
* one.
|
|
39
|
+
*/
|
|
40
|
+
import type { ComponentType } from "react";
|
|
41
|
+
import type { FlowError } from "@stapel/core";
|
|
42
|
+
import type { ActionAvailability } from "@stapel/core";
|
|
43
|
+
import type { FeatureDef } from "./types.js";
|
|
44
|
+
/**
|
|
45
|
+
* What every value editor receives. One feature, one value, one setter, one
|
|
46
|
+
* error — the editor never touches an API layer or the rest of the form.
|
|
47
|
+
*/
|
|
48
|
+
export interface ValueEditorProps<T = unknown> {
|
|
49
|
+
/** The feature being edited. `feature.config` carries the type's camelCase
|
|
50
|
+
* options (`maxLength`, `minSelected`, `precision`, `multiline`, …). */
|
|
51
|
+
readonly feature: FeatureDef;
|
|
52
|
+
/** The current answer, or `undefined` while unanswered. */
|
|
53
|
+
readonly value: T | undefined;
|
|
54
|
+
/** Report a new answer. Pass the type's own DTO `value` — bare for the
|
|
55
|
+
* scalar types, an array for `select`/`hierarchical_select`, an object for
|
|
56
|
+
* `hex_color` and `convertible_unit` (see `toFeaturesDto`). */
|
|
57
|
+
onChange(value: T | undefined): void;
|
|
58
|
+
/** The feature's current refusal (client mirror or server verdict), or
|
|
59
|
+
* `undefined`. */
|
|
60
|
+
readonly error?: FlowError | undefined;
|
|
61
|
+
/** True while a submit is in flight — editors should go read-only. */
|
|
62
|
+
readonly disabled?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* DOM id the editor MUST put on its primary control. The field row points
|
|
65
|
+
* its `<label for>` at this, so the label actually names the input for a
|
|
66
|
+
* screen reader (and for a click). An editor that drops it renders an
|
|
67
|
+
* unlabelled control — which antd's `Form.Item` cannot detect and will
|
|
68
|
+
* happily draw a label beside.
|
|
69
|
+
*/
|
|
70
|
+
readonly id: string;
|
|
71
|
+
}
|
|
72
|
+
export type ValueEditor = ComponentType<ValueEditorProps>;
|
|
73
|
+
/**
|
|
74
|
+
* Register (or override) the editor for a value type. Call at startup, before
|
|
75
|
+
* the first render — the registry is module-global, like the i18n bundle
|
|
76
|
+
* registration and forms-react's widget registry.
|
|
77
|
+
*/
|
|
78
|
+
export declare function registerValueEditor(type: string, editor: ValueEditor): void;
|
|
79
|
+
/** Remove an explicit registration (the skin's builtin, if any, resolves
|
|
80
|
+
* again). */
|
|
81
|
+
export declare function unregisterValueEditor(type: string): void;
|
|
82
|
+
/**
|
|
83
|
+
* The EXPLICIT registration for a type, or `null`. Named for what it is: the
|
|
84
|
+
* skin calls this FIRST and only falls back to its own builtin when it
|
|
85
|
+
* returns `null`, so a host registration always wins.
|
|
86
|
+
*/
|
|
87
|
+
export declare function resolveValueEditor(type: string): ValueEditor | null;
|
|
88
|
+
/** Every type with an explicit registration, sorted. */
|
|
89
|
+
export declare function registeredValueEditorTypes(): readonly string[];
|
|
90
|
+
/**
|
|
91
|
+
* The value types present in `features` that NOTHING can draw — neither an
|
|
92
|
+
* explicit registration nor the caller's builtin set.
|
|
93
|
+
*
|
|
94
|
+
* Pure and React-free on purpose: the headless half must be able to judge
|
|
95
|
+
* renderability without importing the skin, which is how forms-react's
|
|
96
|
+
* `<FormFill>` avoids pulling antd into a headless bundle. `builtinTypes` is
|
|
97
|
+
* therefore passed IN (the skin exports `BUILTIN_VALUE_EDITOR_TYPES`), not
|
|
98
|
+
* imported from here.
|
|
99
|
+
*
|
|
100
|
+
* A feature whose config declares no `type` at all counts as unsupported and
|
|
101
|
+
* is reported under `"(none)"` — an unnamed hole is still a hole, and a
|
|
102
|
+
* silently dropped mandatory attribute is the exact failure this returns
|
|
103
|
+
* data to prevent.
|
|
104
|
+
*/
|
|
105
|
+
export declare function unsupportedTypes(features: readonly FeatureDef[], builtinTypes: readonly string[]): readonly string[];
|
|
106
|
+
/** What `unsupportedTypes` reports for a feature whose config names no type
|
|
107
|
+
* at all. Exported so a caller can tell "we cannot draw `size_grid`" from
|
|
108
|
+
* "this row has no type" without string-matching a message. */
|
|
109
|
+
export declare const UNTYPED_FEATURE = "(none)";
|
|
110
|
+
/**
|
|
111
|
+
* The submit gate for an unsupported type — blocked with the reason named,
|
|
112
|
+
* never a disabled button with no explanation.
|
|
113
|
+
*
|
|
114
|
+
* This package owns its own key (`attributes.submit.blocked.unsupported_type`)
|
|
115
|
+
* because it owns the fact. A pair with its own submit — listings-react's
|
|
116
|
+
* composer, say — raises its own `listings.compose.blocked.unsupported_type`
|
|
117
|
+
* from the SAME `unsupportedTypes` call and never re-derives the fact; both
|
|
118
|
+
* spellings say the same thing to the same person, and neither invents a
|
|
119
|
+
* silent third behaviour.
|
|
120
|
+
*/
|
|
121
|
+
export declare function unsupportedTypeGate(features: readonly FeatureDef[], builtinTypes: readonly string[]): ActionAvailability;
|
|
122
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAI7C;;;GAGG;AACH,MAAM,WAAW,gBAAgB,CAAC,CAAC,GAAG,OAAO;IAC3C;4EACwE;IACxE,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,SAAS,CAAC;IAC9B;;mEAE+D;IAC/D,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC;IACrC;sBACkB;IAClB,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IACvC,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,WAAW,GAAG,aAAa,CAAC,gBAAgB,CAAC,CAAC;AAI1D;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI,CAE3E;AAED;aACa;AACb,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAExD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAEnE;AAED,wDAAwD;AACxD,wBAAgB,0BAA0B,IAAI,SAAS,MAAM,EAAE,CAE9D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,SAAS,UAAU,EAAE,EAC/B,YAAY,EAAE,SAAS,MAAM,EAAE,GAC9B,SAAS,MAAM,EAAE,CAYnB;AAED;;+DAE+D;AAC/D,eAAO,MAAM,eAAe,WAAW,CAAC;AAExC;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,SAAS,UAAU,EAAE,EAC/B,YAAY,EAAE,SAAS,MAAM,EAAE,GAC9B,kBAAkB,CAMpB"}
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { actionAvailable, actionBlocked } from "@stapel/core";
|
|
2
|
+
import { featureType } from "./types.js";
|
|
3
|
+
import { ATTRIBUTES_I18N_KEYS } from "./i18n/keys.js";
|
|
4
|
+
const registered = new Map();
|
|
5
|
+
/**
|
|
6
|
+
* Register (or override) the editor for a value type. Call at startup, before
|
|
7
|
+
* the first render — the registry is module-global, like the i18n bundle
|
|
8
|
+
* registration and forms-react's widget registry.
|
|
9
|
+
*/
|
|
10
|
+
export function registerValueEditor(type, editor) {
|
|
11
|
+
registered.set(type, editor);
|
|
12
|
+
}
|
|
13
|
+
/** Remove an explicit registration (the skin's builtin, if any, resolves
|
|
14
|
+
* again). */
|
|
15
|
+
export function unregisterValueEditor(type) {
|
|
16
|
+
registered.delete(type);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The EXPLICIT registration for a type, or `null`. Named for what it is: the
|
|
20
|
+
* skin calls this FIRST and only falls back to its own builtin when it
|
|
21
|
+
* returns `null`, so a host registration always wins.
|
|
22
|
+
*/
|
|
23
|
+
export function resolveValueEditor(type) {
|
|
24
|
+
return registered.get(type) ?? null;
|
|
25
|
+
}
|
|
26
|
+
/** Every type with an explicit registration, sorted. */
|
|
27
|
+
export function registeredValueEditorTypes() {
|
|
28
|
+
return [...registered.keys()].sort();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The value types present in `features` that NOTHING can draw — neither an
|
|
32
|
+
* explicit registration nor the caller's builtin set.
|
|
33
|
+
*
|
|
34
|
+
* Pure and React-free on purpose: the headless half must be able to judge
|
|
35
|
+
* renderability without importing the skin, which is how forms-react's
|
|
36
|
+
* `<FormFill>` avoids pulling antd into a headless bundle. `builtinTypes` is
|
|
37
|
+
* therefore passed IN (the skin exports `BUILTIN_VALUE_EDITOR_TYPES`), not
|
|
38
|
+
* imported from here.
|
|
39
|
+
*
|
|
40
|
+
* A feature whose config declares no `type` at all counts as unsupported and
|
|
41
|
+
* is reported under `"(none)"` — an unnamed hole is still a hole, and a
|
|
42
|
+
* silently dropped mandatory attribute is the exact failure this returns
|
|
43
|
+
* data to prevent.
|
|
44
|
+
*/
|
|
45
|
+
export function unsupportedTypes(features, builtinTypes) {
|
|
46
|
+
const builtin = new Set(builtinTypes);
|
|
47
|
+
const out = new Set();
|
|
48
|
+
for (const feature of features) {
|
|
49
|
+
const type = featureType(feature);
|
|
50
|
+
if (type === undefined) {
|
|
51
|
+
out.add(UNTYPED_FEATURE);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (resolveValueEditor(type) === null && !builtin.has(type))
|
|
55
|
+
out.add(type);
|
|
56
|
+
}
|
|
57
|
+
return [...out].sort();
|
|
58
|
+
}
|
|
59
|
+
/** What `unsupportedTypes` reports for a feature whose config names no type
|
|
60
|
+
* at all. Exported so a caller can tell "we cannot draw `size_grid`" from
|
|
61
|
+
* "this row has no type" without string-matching a message. */
|
|
62
|
+
export const UNTYPED_FEATURE = "(none)";
|
|
63
|
+
/**
|
|
64
|
+
* The submit gate for an unsupported type — blocked with the reason named,
|
|
65
|
+
* never a disabled button with no explanation.
|
|
66
|
+
*
|
|
67
|
+
* This package owns its own key (`attributes.submit.blocked.unsupported_type`)
|
|
68
|
+
* because it owns the fact. A pair with its own submit — listings-react's
|
|
69
|
+
* composer, say — raises its own `listings.compose.blocked.unsupported_type`
|
|
70
|
+
* from the SAME `unsupportedTypes` call and never re-derives the fact; both
|
|
71
|
+
* spellings say the same thing to the same person, and neither invents a
|
|
72
|
+
* silent third behaviour.
|
|
73
|
+
*/
|
|
74
|
+
export function unsupportedTypeGate(features, builtinTypes) {
|
|
75
|
+
const types = unsupportedTypes(features, builtinTypes);
|
|
76
|
+
if (types.length === 0)
|
|
77
|
+
return actionAvailable();
|
|
78
|
+
return actionBlocked(ATTRIBUTES_I18N_KEYS.submitBlockedUnsupportedType, {
|
|
79
|
+
types: types.join(", "),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAyCA,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAG9D,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAiCtD,MAAM,UAAU,GAAG,IAAI,GAAG,EAAuB,CAAC;AAElD;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,MAAmB;IACnE,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC/B,CAAC;AAED;aACa;AACb,MAAM,UAAU,qBAAqB,CAAC,IAAY;IAChD,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,OAAO,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;AACtC,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,0BAA0B;IACxC,OAAO,CAAC,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AACvC,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAA+B,EAC/B,YAA+B;IAE/B,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC;IACtC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;QAClC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,GAAG,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;YACzB,SAAS;QACX,CAAC;QACD,IAAI,kBAAkB,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC7E,CAAC;IACD,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AACzB,CAAC;AAED;;+DAE+D;AAC/D,MAAM,CAAC,MAAM,eAAe,GAAG,QAAQ,CAAC;AAExC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CACjC,QAA+B,EAC/B,YAA+B;IAE/B,MAAM,KAAK,GAAG,gBAAgB,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IACvD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,eAAe,EAAE,CAAC;IACjD,OAAO,aAAa,CAAC,oBAAoB,CAAC,4BAA4B,EAAE;QACtE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;KACxB,CAAC,CAAC;AACL,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The wire shapes of `stapel_attributes`, as this package sees them.
|
|
3
|
+
*
|
|
4
|
+
* There is no generated `schema.ts` here and there never will be:
|
|
5
|
+
* stapel-attributes is an **L1 library with no HTTP surface at all** — no
|
|
6
|
+
* models, no views, no urls (`stapel-attributes/docs/readme.md`), so it emits
|
|
7
|
+
* no `docs/schema.json` for `pnpm gen:api` to read. Its shapes reach a browser
|
|
8
|
+
* embedded in someone ELSE's response: a category's features arrive from
|
|
9
|
+
* `GET /categories/api/v1/categories/{id}/features/`, a validation verdict
|
|
10
|
+
* from `POST /categories/{pk}/validate-dto/` and from
|
|
11
|
+
* `POST /listings/{pk}/publish/`. The owning pair generates those; this
|
|
12
|
+
* package types the payload they carry.
|
|
13
|
+
*
|
|
14
|
+
* Every field here was read off the Python it mirrors, and the mirrors are
|
|
15
|
+
* named in the doc comments so the next person can check rather than trust.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* A feature's type-specific configuration. `type` is the discriminator — the
|
|
19
|
+
* **value type's slug**, and the axis this whole package switches on.
|
|
20
|
+
*
|
|
21
|
+
* ── The distinction that must not be swallowed ─────────────────────────────
|
|
22
|
+
*
|
|
23
|
+
* stapel-attributes has TWO field vocabularies, and `@stapel/forms-react`
|
|
24
|
+
* works with the other one:
|
|
25
|
+
*
|
|
26
|
+
* - `FormField.kind` (`stapel_attributes/config_form.py`) — the field kinds
|
|
27
|
+
* of the ADMIN form that configures a type. That is what
|
|
28
|
+
* `GET /forms/api/v1/field-kinds` enumerates and what forms-react's widget
|
|
29
|
+
* registry keys on.
|
|
30
|
+
* - `config["type"]` — the VALUE type: `int`, `float`, `string`, `bool`,
|
|
31
|
+
* `hex_color`, `select`, `date`, `header`, `hierarchical_select`,
|
|
32
|
+
* `convertible_unit`. That is what a person filling in a listing actually
|
|
33
|
+
* edits, and it is what this package keys on.
|
|
34
|
+
*
|
|
35
|
+
* A storefront therefore needs **no catalogue endpoint**: the type arrives in
|
|
36
|
+
* the data, on every feature.
|
|
37
|
+
*
|
|
38
|
+
* `type` is optional here because the wire can omit it. The features endpoint
|
|
39
|
+
* serializes `obj.config` verbatim (`stapel-categories`
|
|
40
|
+
* `FeatureCompactSerializer.get_config`), NOT `get_config_with_defaults()`, so
|
|
41
|
+
* a config saved without its defaults arrives without them — including,
|
|
42
|
+
* for a malformed row, without a `type`. A missing type is an unsupported
|
|
43
|
+
* type, loudly, rather than a crash.
|
|
44
|
+
*/
|
|
45
|
+
export interface FeatureConfig {
|
|
46
|
+
readonly type?: string;
|
|
47
|
+
readonly [key: string]: unknown;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* One feature of a category — the browser's view of `stapel_attributes.base
|
|
51
|
+
* .FeatureDef`, as `stapel-categories`' `FeatureCompactSerializer` sends it
|
|
52
|
+
* (`fields = [id, tn_parent, name, slug, icon, comment, config, mandatory,
|
|
53
|
+
* show_as_badge, show_at_title, translate]`).
|
|
54
|
+
*/
|
|
55
|
+
export interface FeatureDef {
|
|
56
|
+
/** Payload key this feature's value is submitted under. */
|
|
57
|
+
readonly slug: string;
|
|
58
|
+
readonly config?: FeatureConfig;
|
|
59
|
+
readonly id?: number | string | null;
|
|
60
|
+
/** Display name or translation key. Falls back to `slug` server-side. */
|
|
61
|
+
readonly name?: string | null;
|
|
62
|
+
readonly mandatory?: boolean;
|
|
63
|
+
readonly show_at_title?: boolean;
|
|
64
|
+
readonly show_as_badge?: boolean;
|
|
65
|
+
readonly translate?: string | null;
|
|
66
|
+
readonly icon?: string | null;
|
|
67
|
+
readonly comment?: string | null;
|
|
68
|
+
readonly tn_parent?: number | string | null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* One submitted value: `{type, value}` plus whatever else the type's DTO
|
|
72
|
+
* carries. Exactly one builtin adds a key — `convertible_unit`, whose DTO is
|
|
73
|
+
* `{type, value, unit}` because the number has to be tagged with the unit it
|
|
74
|
+
* was typed in before the server converts to the family's base unit.
|
|
75
|
+
*/
|
|
76
|
+
export interface FeatureValueDto {
|
|
77
|
+
readonly type: string;
|
|
78
|
+
readonly value: unknown;
|
|
79
|
+
readonly [key: string]: unknown;
|
|
80
|
+
}
|
|
81
|
+
/** The `features_draft`-shaped payload: `{slug: {type, value}}`. */
|
|
82
|
+
export type FeaturesDto = Readonly<Record<string, FeatureValueDto>>;
|
|
83
|
+
/** `stapel_attributes.results.ValidationStatus`. */
|
|
84
|
+
export type ValidationStatus = "ok" | "validation_failed";
|
|
85
|
+
/**
|
|
86
|
+
* `stapel_attributes.results.ValidationErrorCode`, mirrored.
|
|
87
|
+
*
|
|
88
|
+
* Pinned against the engine's own generated corpus
|
|
89
|
+
* (`stapel-attributes/tests/golden/error_codes.json`, itself generated from
|
|
90
|
+
* the enum and asserted by both the Python and the TypeScript half of the
|
|
91
|
+
* cross-language bridge) — see `test/contract.test.ts`.
|
|
92
|
+
*/
|
|
93
|
+
export type ValidationErrorCode = "above_maximum" | "below_minimum" | "description_too_long" | "description_too_short" | "duplicate_slug" | "empty_options" | "invalid_config" | "invalid_format" | "invalid_type" | "mandatory_missing" | "min_greater_than_max" | "not_allowed" | "not_in_options" | "unknown_feature" | "unknown_feature_type";
|
|
94
|
+
/**
|
|
95
|
+
* One row of a batch verdict — `stapel_attributes.results
|
|
96
|
+
* .FeatureValidationResult` as its DRF serializer sends it.
|
|
97
|
+
*
|
|
98
|
+
* `slug` is the routing key: it is what puts a refusal on a control. The
|
|
99
|
+
* server's `params` carry `{feature, slug}` (never `field`), so
|
|
100
|
+
* `featureErrorsBySlug` adds `field` when folding a row into a `FlowError`,
|
|
101
|
+
* which is what the fleet's `useFieldError` convention reads.
|
|
102
|
+
*/
|
|
103
|
+
export interface FeatureValidationResult {
|
|
104
|
+
readonly slug: string;
|
|
105
|
+
readonly status: ValidationStatus;
|
|
106
|
+
readonly id?: number | string | null;
|
|
107
|
+
readonly error?: ValidationErrorCode | null;
|
|
108
|
+
/** The constraint that was violated (a limit, a list of options). */
|
|
109
|
+
readonly ref_value?: unknown;
|
|
110
|
+
readonly message?: string | null;
|
|
111
|
+
/** `error.400.feature_*` — the key a person's sentence comes from. */
|
|
112
|
+
readonly localizable_error?: string | null;
|
|
113
|
+
readonly params?: Readonly<Record<string, unknown>> | null;
|
|
114
|
+
/** Non-blocking findings (e.g. config keys the parser dropped). Never
|
|
115
|
+
* flips `valid` — a warning is not a refusal. */
|
|
116
|
+
readonly warnings?: readonly string[] | null;
|
|
117
|
+
}
|
|
118
|
+
/** `stapel_attributes.results.ValidationBatchResult`. */
|
|
119
|
+
export interface ValidationBatchResult {
|
|
120
|
+
readonly valid: boolean;
|
|
121
|
+
readonly results: readonly FeatureValidationResult[];
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* A feature's declared value type, or `undefined` when the config carries
|
|
125
|
+
* none. The ONE place `config.type` is read, so "which axis is this?" has a
|
|
126
|
+
* single answer in this package.
|
|
127
|
+
*/
|
|
128
|
+
export declare function featureType(feature: FeatureDef): string | undefined;
|
|
129
|
+
/** A feature's config, never `undefined` — saves every reader a `?? {}`. */
|
|
130
|
+
export declare function featureConfig(feature: FeatureDef): FeatureConfig;
|
|
131
|
+
/** A feature's display name, falling back to its slug exactly as
|
|
132
|
+
* `FeatureDef.__post_init__` does server-side. */
|
|
133
|
+
export declare function featureName(feature: FeatureDef): string;
|
|
134
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,2DAA2D;IAC3D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACrC,yEAAyE;IACzE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;CAC7C;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED,oEAAoE;AACpE,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC;AAEpE,oDAAoD;AACpD,MAAM,MAAM,gBAAgB,GAAG,IAAI,GAAG,mBAAmB,CAAC;AAE1D;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAC3B,eAAe,GACf,eAAe,GACf,sBAAsB,GACtB,uBAAuB,GACvB,gBAAgB,GAChB,eAAe,GACf,gBAAgB,GAChB,gBAAgB,GAChB,cAAc,GACd,mBAAmB,GACnB,sBAAsB,GACtB,aAAa,GACb,gBAAgB,GAChB,iBAAiB,GACjB,sBAAsB,CAAC;AAE3B;;;;;;;;GAQG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,KAAK,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC5C,qEAAqE;IACrE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,sEAAsE;IACtE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAC3D;qDACiD;IACjD,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;CAC9C;AAED,yDAAyD;AACzD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,SAAS,uBAAuB,EAAE,CAAC;CACtD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CAGnE;AAED,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,OAAO,EAAE,UAAU,GAAG,aAAa,CAEhE;AAED;kDACkD;AAClD,wBAAgB,WAAW,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,CAEvD"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The wire shapes of `stapel_attributes`, as this package sees them.
|
|
3
|
+
*
|
|
4
|
+
* There is no generated `schema.ts` here and there never will be:
|
|
5
|
+
* stapel-attributes is an **L1 library with no HTTP surface at all** — no
|
|
6
|
+
* models, no views, no urls (`stapel-attributes/docs/readme.md`), so it emits
|
|
7
|
+
* no `docs/schema.json` for `pnpm gen:api` to read. Its shapes reach a browser
|
|
8
|
+
* embedded in someone ELSE's response: a category's features arrive from
|
|
9
|
+
* `GET /categories/api/v1/categories/{id}/features/`, a validation verdict
|
|
10
|
+
* from `POST /categories/{pk}/validate-dto/` and from
|
|
11
|
+
* `POST /listings/{pk}/publish/`. The owning pair generates those; this
|
|
12
|
+
* package types the payload they carry.
|
|
13
|
+
*
|
|
14
|
+
* Every field here was read off the Python it mirrors, and the mirrors are
|
|
15
|
+
* named in the doc comments so the next person can check rather than trust.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* A feature's declared value type, or `undefined` when the config carries
|
|
19
|
+
* none. The ONE place `config.type` is read, so "which axis is this?" has a
|
|
20
|
+
* single answer in this package.
|
|
21
|
+
*/
|
|
22
|
+
export function featureType(feature) {
|
|
23
|
+
const type = feature.config?.type;
|
|
24
|
+
return typeof type === "string" && type.length > 0 ? type : undefined;
|
|
25
|
+
}
|
|
26
|
+
/** A feature's config, never `undefined` — saves every reader a `?? {}`. */
|
|
27
|
+
export function featureConfig(feature) {
|
|
28
|
+
return feature.config ?? {};
|
|
29
|
+
}
|
|
30
|
+
/** A feature's display name, falling back to its slug exactly as
|
|
31
|
+
* `FeatureDef.__post_init__` does server-side. */
|
|
32
|
+
export function featureName(feature) {
|
|
33
|
+
return feature.name ?? feature.slug;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAmIH;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,OAAmB;IAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC;IAClC,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AACxE,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,aAAa,CAAC,OAAmB;IAC/C,OAAO,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;AAC9B,CAAC;AAED;kDACkD;AAClD,MAAM,UAAU,WAAW,CAAC,OAAmB;IAC7C,OAAO,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;AACtC,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The client-side validation MIRROR — instant feedback, never a verdict.
|
|
3
|
+
*
|
|
4
|
+
* "Client-side validation mirrors, server decides." Every rule here is
|
|
5
|
+
* derived from the feature's own `config` so a person sees a problem as they
|
|
6
|
+
* type instead of after a round trip; none of it is trusted. The server
|
|
7
|
+
* re-runs `stapel_attributes.validate_dto_structured` on
|
|
8
|
+
* `POST /categories/{pk}/validate-dto/` and again on
|
|
9
|
+
* `POST /listings/{pk}/publish/`, and its `ValidationBatchResult` is the one
|
|
10
|
+
* that counts. This function returns the SAME shape so a server answer can
|
|
11
|
+
* replace a mirrored one with no translation step.
|
|
12
|
+
*
|
|
13
|
+
* ── Two contract details that are easy to get subtly wrong ─────────────────
|
|
14
|
+
*
|
|
15
|
+
* 1. **`pattern` matches the WHOLE value.** The engine uses `re.fullmatch`,
|
|
16
|
+
* and the admin JS mirrors it as `^(?:<pattern>)$`
|
|
17
|
+
* (`stapel-attributes/MODULE.md`, "Pattern contract"). A bare
|
|
18
|
+
* `RegExp.test` is a PREFIX match, so `^\d{4}$`-less patterns would pass
|
|
19
|
+
* here and fail there — the mirror telling a person their input is fine
|
|
20
|
+
* right before the server refuses it.
|
|
21
|
+
*
|
|
22
|
+
* 2. **String length is counted in Unicode CODE POINTS**, on both sides —
|
|
23
|
+
* same source. JavaScript's `String.length` counts UTF-16 code units, so
|
|
24
|
+
* one emoji is 2 and one astral CJK ideograph is 2. A `maxLength: 10`
|
|
25
|
+
* field would refuse ten emoji locally and accept them server-side.
|
|
26
|
+
*
|
|
27
|
+
* ── What the mirror deliberately does NOT judge ────────────────────────────
|
|
28
|
+
*
|
|
29
|
+
* - **A value type it does not know.** An unknown `config.type` may be a
|
|
30
|
+
* perfectly valid `EXTRA_TYPES` registration whose rules live only in
|
|
31
|
+
* Python. The mirror runs the type-independent checks (mandatory/empty) and
|
|
32
|
+
* leaves the rest to the server — a mirror that invented a refusal for a
|
|
33
|
+
* type it cannot read would block a submit the backend would have accepted.
|
|
34
|
+
* Whether such a type can be DRAWN is a separate question, answered loudly
|
|
35
|
+
* by `unsupportedTypes`/`unsupportedTypeGate`.
|
|
36
|
+
*
|
|
37
|
+
* - **`convertible_unit` range.** `min`/`max` are expressed in the unit
|
|
38
|
+
* family's canonical base unit and the value is converted before comparison
|
|
39
|
+
* (`stapel_attributes.types.convertible_unit`), and the family table is
|
|
40
|
+
* Python-side. Mirroring the comparison without the conversion would report
|
|
41
|
+
* "too large" for a perfectly good number in the other unit system. The
|
|
42
|
+
* number and the unit code ARE checked; the range is the server's.
|
|
43
|
+
*/
|
|
44
|
+
import type { FeatureDef, FeatureValidationResult, FeaturesDto, FeatureValueDto, ValidationBatchResult } from "./types.js";
|
|
45
|
+
/** `stapel_attributes.types.hex_color.constants.SIMPLE_COLORS` — the closed
|
|
46
|
+
* set of colour categories a `hex_color` value must name. */
|
|
47
|
+
export declare const SIMPLE_COLORS: readonly string[];
|
|
48
|
+
/** Length in Unicode code points — the unit BOTH sides count in. */
|
|
49
|
+
export declare function codePointLength(text: string): number;
|
|
50
|
+
/**
|
|
51
|
+
* Does `pattern` match the WHOLE of `text`? `re.fullmatch`, in JavaScript.
|
|
52
|
+
*
|
|
53
|
+
* A pattern JS cannot compile (a Python-only construct) is NOT the person's
|
|
54
|
+
* problem: the mirror stands down and lets the server, which compiled it, be
|
|
55
|
+
* the one to refuse. The `u` flag is tried first because it is what makes `.`
|
|
56
|
+
* and quantifiers count code points rather than surrogate halves, and dropped
|
|
57
|
+
* when a pattern is only valid without it.
|
|
58
|
+
*/
|
|
59
|
+
export declare function patternFullMatch(pattern: string, text: string): boolean | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* "Nothing was entered" — the one shape the mandatory rule fires on, and the
|
|
62
|
+
* engine's own predicate: `raw_value is None or raw_value == '' or
|
|
63
|
+
* raw_value == []` (`validation.py`). An explicit `false` is an ANSWER for a
|
|
64
|
+
* `bool`, and `0` is an answer for an `int`, which is why this is not a
|
|
65
|
+
* falsiness check.
|
|
66
|
+
*/
|
|
67
|
+
export declare function isBlank(value: unknown): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Validate one submitted value against its feature's config. Returns the
|
|
70
|
+
* mirrored refusal, or `undefined` when this side of the wire is satisfied.
|
|
71
|
+
*
|
|
72
|
+
* `header` is never validated: the engine regenerates a header's DAO from its
|
|
73
|
+
* config and skips it outright in the batch validator, so a header has
|
|
74
|
+
* nothing to check and must never carry a value.
|
|
75
|
+
*/
|
|
76
|
+
export declare function validateFeatureValue(feature: FeatureDef, dto: FeatureValueDto): FeatureValidationResult | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* Validate a whole answer set against a category's features — the client-side
|
|
79
|
+
* twin of `POST /categories/{pk}/validate-dto/`.
|
|
80
|
+
*
|
|
81
|
+
* Mirrors the engine's two passes and their order, because the order is what
|
|
82
|
+
* a caller sees: first every SUBMITTED entry whose slug the category allows
|
|
83
|
+
* (an unknown slug is ignored, not refused — the engine's documented
|
|
84
|
+
* behaviour), then every allowed feature that was never submitted, of which
|
|
85
|
+
* only a mandatory non-header one produces a row.
|
|
86
|
+
*/
|
|
87
|
+
export declare function mirrorValidate(features: readonly FeatureDef[], dto: FeaturesDto): ValidationBatchResult;
|
|
88
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,OAAO,KAAK,EACV,UAAU,EACV,uBAAuB,EACvB,WAAW,EACX,eAAe,EACf,qBAAqB,EAEtB,MAAM,YAAY,CAAC;AAIpB;6DAC6D;AAC7D,eAAO,MAAM,aAAa,EAAE,SAAS,MAAM,EAmB1C,CAAC;AAaF,oEAAoE;AACpE,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAUnF;AAqBD;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAK/C;AAmPD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,UAAU,EACnB,GAAG,EAAE,eAAe,GACnB,uBAAuB,GAAG,SAAS,CAsCrC;AAyBD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,SAAS,UAAU,EAAE,EAC/B,GAAG,EAAE,WAAW,GACf,qBAAqB,CA+BvB"}
|