@hanzo/ui 8.0.22 → 8.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,44 @@
1
+ /**
2
+ * CollectionsBrowser — the app-lane home: the DocTypes of a `module` (a CMS
3
+ * "collection" IS a framework DocType tagged with the lane's module). It lists
4
+ * them as cards, offers a first-run "Set up" that installs the lane's fixtures
5
+ * (POST /v1/framework/modules/:module/install), and a "New collection" that opens
6
+ * the dynamic content-type builder (name + typed fields, on-page). Everything is
7
+ * per-org and honest — an org with the lane not yet installed sees the setup CTA,
8
+ * never a fabricated collection.
9
+ *
10
+ * This is generic over `module`, so CMS (`cms`), ERP (`erp`), and Helpdesk
11
+ * (`help`) all reuse it — the ONE collections home for every lane.
12
+ */
13
+ import { type ReactNode } from 'react';
14
+ import type { FrameworkClient } from './client';
15
+ export interface CollectionsBrowserProps {
16
+ client: FrameworkClient;
17
+ /** The app lane: 'cms' | 'erp' | 'help' | … */
18
+ module: string;
19
+ /** Human label for the lane (e.g. "Content"). */
20
+ label: string;
21
+ subtitle: string;
22
+ /** Open a collection's records. */
23
+ onOpen: (doctype: string) => void;
24
+ /**
25
+ * Lane-appropriate copy for the first-run (pre-install) empty state. Optional and
26
+ * defaulted to the CMS wording, so this component stays generic over the lane: a
27
+ * CMS caller renders identically, while ERP/Help pass their own description +
28
+ * bullets. Additive only — no behavior/permission/proxy change.
29
+ */
30
+ setupDescription?: string;
31
+ setupBullets?: string[];
32
+ /**
33
+ * Schema AUTHORING is a separate concern from schema USE, so it is injected
34
+ * rather than imported: a host that can define collections (the console's
35
+ * CollectionBuilder) supplies this; a host that only consumes them (a site
36
+ * shell) omits it and the "New collection" affordance is simply not offered.
37
+ * Nothing is faked either way.
38
+ */
39
+ renderBuilder?: (p: {
40
+ onSaved: (doctype: string) => void;
41
+ onCancel: () => void;
42
+ }) => ReactNode;
43
+ }
44
+ export declare function CollectionsBrowser({ client, module, label, subtitle, onOpen, setupDescription, setupBullets, renderBuilder }: CollectionsBrowserProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import type { FrameworkClient } from './client';
2
+ export interface DocTypeDetailProps {
3
+ client: FrameworkClient;
4
+ doctype: string;
5
+ /** Document name, or `'new'` to open the create form. */
6
+ name: string;
7
+ /** Active project scope — stamped onto a NEW record when the collection has a `project` field. */
8
+ project?: string;
9
+ onBack: () => void;
10
+ /** Land on a document (after create). */
11
+ onView: (name: string) => void;
12
+ }
13
+ export declare function DocTypeDetail({ client, doctype, name, project, onBack, onView }: DocTypeDetailProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * DocTypeRecords — the Twenty-grade records surface for ONE framework DocType,
3
+ * driven ENTIRELY by DocType metadata. It loads the schema (→ @hanzo/data
4
+ * `FieldDefinition[]` via `docTypeToFields`), the documents, and each relation's
5
+ * candidate records, then renders through @hanzo/data's `RecordsView` — the shared
6
+ * table ⇆ board view with filter/sort/group, inline cell editing, and a detail
7
+ * hand-off. Every field type shows the right Display/Input with ZERO per-doctype
8
+ * code, so a CMS Page, an ERP Invoice, or a Helpdesk Ticket all render here.
9
+ *
10
+ * Persistence is REAL: an inline cell edit sends the FULL record (the engine
11
+ * validates the whole document on update) via `savePayload`, and reflects the
12
+ * server's row. States are honest — loading / empty / backend-error, never a
13
+ * fabricated row.
14
+ */
15
+ import { type ReactNode } from 'react';
16
+ import type { FrameworkClient } from './client';
17
+ import type { DocType, FrameworkDoc } from './types';
18
+ export interface DocTypeRecordsProps {
19
+ client: FrameworkClient;
20
+ /** DocType name to render. */
21
+ doctype: string;
22
+ /** Open a document's detail (by document name). */
23
+ onOpen: (name: string) => void;
24
+ /** Start creating a document. */
25
+ onCreate: () => void;
26
+ /** Active project scope — filters the list when the collection has a `project` field. */
27
+ project?: string;
28
+ title?: ReactNode;
29
+ /**
30
+ * An asset library (a DocType whose required field is an Attach) is better
31
+ * shown as a gallery than a table, but uploading bytes needs the HOST's object
32
+ * store — not something this layer can know. So the gallery is injected: a host
33
+ * with a DAM supplies it, a host without one gets the honest generic table.
34
+ */
35
+ renderMedia?: (p: {
36
+ dt: DocType;
37
+ docs: FrameworkDoc[];
38
+ onOpen: (name: string) => void;
39
+ onChanged: () => void;
40
+ toolbarExtra: ReactNode;
41
+ }) => ReactNode;
42
+ }
43
+ export declare function DocTypeRecords({ client, doctype, onOpen, onCreate, project, title, renderMedia }: DocTypeRecordsProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,4 @@
1
+ export declare function Loader({ label, size }: {
2
+ label?: string;
3
+ size?: number;
4
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * FrameworkApi — the ONE generic client for the Hanzo Framework DocType engine
3
+ * (cloud `clients/framework`, live at /v1/framework/*). It is metadata-driven and
4
+ * doctype-agnostic: the SAME `records` CRUD serves a CMS Page, an ERP Invoice, or
5
+ * a Helpdesk Ticket — an app lane is just a `module` filter over `doctypes`.
6
+ *
7
+ * TRANSPORT IS INJECTED. This layer knows the framework's *shape*, never how a
8
+ * given host reaches it: the console proxies through its own user-bearer BFF, the
9
+ * site shells (erp/crm/cms/help.<brand>) call the API origin with the IAM bearer
10
+ * directly. Both hand a `Transport` to `createFrameworkClient` and get the SAME
11
+ * client, so there is exactly one definition of the wire surface.
12
+ *
13
+ * Names (DocType + document) are used verbatim in the URL path, so a host keeps
14
+ * them slug-style (no spaces/`%`) and this encodes each segment. Payloads are read
15
+ * defensively from `{ data }` (or a bare array), so a shape drift degrades a list
16
+ * rather than throwing.
17
+ */
18
+ import type { DocType, FrameworkDoc, ListQuery, ModuleInfo, InstallResult } from './types';
19
+ /**
20
+ * The four verbs the framework surface needs, relative to the caller's
21
+ * `/v1/framework` root. A host implements this over whatever credential path it
22
+ * already owns — this layer never touches auth, cookies, or origins.
23
+ */
24
+ export interface Transport {
25
+ get: <T>(path: string) => Promise<T>;
26
+ post: <T>(path: string, body?: unknown) => Promise<T>;
27
+ put: <T>(path: string, body?: unknown) => Promise<T>;
28
+ del: (path: string) => Promise<void>;
29
+ }
30
+ /** Build the generic document list querystring from a typed ListQuery. */
31
+ export declare function listQuery(q?: ListQuery): string;
32
+ /** Build the framework client over a host's transport. */
33
+ export declare function createFrameworkClient(t: Transport): {
34
+ /** The DocType registry (schemas) — the collection definitions of every app lane. */
35
+ doctypes: {
36
+ list: () => Promise<DocType[]>;
37
+ get: (name: string) => Promise<DocType>;
38
+ create: (dt: DocType) => Promise<DocType>;
39
+ update: (name: string, dt: DocType) => Promise<DocType>;
40
+ remove: (name: string) => Promise<void>;
41
+ };
42
+ /** Generic, metadata-driven document CRUD — the SAME calls for ANY doctype. */
43
+ records: {
44
+ list: (doctype: string, q?: ListQuery) => Promise<FrameworkDoc[]>;
45
+ get: (doctype: string, name: string) => Promise<FrameworkDoc>;
46
+ create: (doctype: string, data: Record<string, unknown>) => Promise<FrameworkDoc>;
47
+ update: (doctype: string, name: string, data: Record<string, unknown>) => Promise<FrameworkDoc>;
48
+ remove: (doctype: string, name: string) => Promise<void>;
49
+ submit: (doctype: string, name: string) => Promise<FrameworkDoc>;
50
+ cancel: (doctype: string, name: string) => Promise<FrameworkDoc>;
51
+ };
52
+ /** App-lane fixtures: list the registered lanes, inspect one, install into the org. */
53
+ modules: {
54
+ list: () => Promise<ModuleInfo[]>;
55
+ get: (module: string) => Promise<ModuleInfo>;
56
+ install: (module: string) => Promise<InstallResult>;
57
+ };
58
+ /** Per-org role assignments (grant editors; the owner is seeded System Manager). */
59
+ roles: {
60
+ list: () => Promise<{
61
+ user: string;
62
+ role: string;
63
+ }[]>;
64
+ assign: (user: string, role: string) => Promise<void>;
65
+ revoke: (user: string, role: string) => Promise<void>;
66
+ };
67
+ };
68
+ export type FrameworkClient = ReturnType<typeof createFrameworkClient>;
69
+ /** An HTTP failure carrying the status, so `classifyBackend` can tell 401 from 403 from 404. */
70
+ export declare class FrameworkHttpError extends Error {
71
+ readonly status: number;
72
+ constructor(status: number, message: string);
73
+ }
74
+ export interface FetchTransportOptions {
75
+ /** Absolute `/v1/framework` root, e.g. `https://api.hanzo.ai/v1/framework`. */
76
+ baseUrl: string;
77
+ /** Called per request — a bearer token, or null when signed out. */
78
+ token?: () => string | null | undefined;
79
+ /** Injectable for tests; defaults to the global `fetch`. */
80
+ fetchImpl?: typeof fetch;
81
+ }
82
+ /**
83
+ * The bearer transport every site shell uses: the caller's IAM access token on
84
+ * the Authorization header, straight to the API origin. No cookie, no second
85
+ * credential path — the engine resolves the org from the token's owner claim.
86
+ */
87
+ export declare function fetchTransport(opts: FetchTransportOptions): Transport;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Shared, non-React data helpers for the generic DocType renderer. A relation
3
+ * (Link) field is a picker over the TARGET doctype's records, so before a view
4
+ * can render or edit one it must load those records (+ the target's schema for
5
+ * the human label). This is the ONE place that does it, used by both the records
6
+ * list and the record detail — no per-view duplication.
7
+ */
8
+ import type { SelectOption, FieldDefinition } from '@hanzo/data';
9
+ import type { DocType } from './types';
10
+ import type { FrameworkClient } from './client';
11
+ /** The picker options for every Link field of `dt`: value = target id, label = its title. */
12
+ export declare function loadLinkOptions(client: FrameworkClient, dt: DocType): Promise<Record<string, SelectOption[]>>;
13
+ /**
14
+ * The `fieldOptions` callback @hanzo/data's views take: relation fields get their
15
+ * loaded target records; select fields get the choices declared in metadata (so
16
+ * the table cell and the form input agree). Everything else → undefined.
17
+ */
18
+ export declare function makeFieldOptions(linkOptions: Record<string, SelectOption[]>): (field: FieldDefinition) => SelectOption[] | undefined;
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Pure mapping between the Hanzo Framework DocType metadata and @hanzo/data's
3
+ * render model — the brain of the generic DocType renderer. It is the ONE place
4
+ * that translates the engine's Frappe field vocabulary into @hanzo/data's, so
5
+ * EVERY app lane (CMS/ERP/CRM/Helpdesk) renders list/form/detail identically with
6
+ * zero per-doctype code. A new DocType — or a whole new lane — "just works".
7
+ *
8
+ * No I/O, no React — data in, data out, trivially testable (the @hanzo/data
9
+ * imports are `import type`, erased at build, so the unit test runs in plain Node).
10
+ */
11
+ import type { FieldDefinition, SelectOption } from '@hanzo/data';
12
+ import type { DocField, DocType, FrameworkDoc } from './types';
13
+ /** `first_name` / `firstName` → "First Name"; known acronyms upper-cased. */
14
+ export declare function humanize(name: string): string;
15
+ /** The autoname source field for a `field:xxx` rule (`""` otherwise). */
16
+ export declare function autonameSource(dt: DocType): string;
17
+ export interface FieldMapOpts {
18
+ /** Editing an existing record: the autoname-source field is fixed (the URL key). */
19
+ editing?: boolean;
20
+ }
21
+ /**
22
+ * Map a DocType's fields to the ordered `FieldDefinition[]` every @hanzo/data view
23
+ * renders from. Hidden fields are dropped; the engine's readOnly is honored; and
24
+ * when editing an existing record the autoname source (e.g. `slug`) is made
25
+ * read-only because it IS the document's immutable URL key.
26
+ */
27
+ export declare function docTypeToFields(dt: DocType, opts?: FieldMapOpts): FieldDefinition[];
28
+ /** The status field a publishable content type uses (a Select including "Published"). */
29
+ export declare function statusField(dt: DocType): string;
30
+ /** Fields shown as table columns: the `inListView` subset (all, if none flagged). */
31
+ export declare function listHiddenFields(dt: DocType): string[];
32
+ /** True when a DocType is a media/asset library (a required Attach) → grid view. */
33
+ export declare function isMediaDoctype(dt: DocType): boolean;
34
+ /** The primary Attach field name (the asset URL) of a media doctype. */
35
+ export declare function mediaFileField(dt: DocType): string;
36
+ /** DocTypes belonging to an app lane (module) — a CMS "collection" is one of these. */
37
+ export declare function moduleDoctypes(dts: DocType[], module: string): DocType[];
38
+ /** The conventional field a collection uses to scope documents to a project. */
39
+ export declare const PROJECT_FIELD = "project";
40
+ /** True when a DocType declares the `project` scope field (→ project filtering works). */
41
+ export declare function hasProjectField(dt: DocType): boolean;
42
+ /** A record's human title — the DocType's titleField, else its name. */
43
+ export declare function titleOf(doc: Record<string, unknown>, dt: DocType): string;
44
+ /**
45
+ * URL-safe slug from arbitrary text: lower-case, any run of non-alphanumerics
46
+ * collapsed to a single dash, ends trimmed. Guarantees a no-space, `%`-free
47
+ * document name (the engine and the console's own proxy path both require it).
48
+ */
49
+ export declare function slugify(s: unknown): string;
50
+ /** Valid Pascal/slug DocType name (letters/digits/dash/underscore, alnum-led, no space). */
51
+ export declare function isValidDoctypeName(name: string): boolean;
52
+ /**
53
+ * A framework document → a flat @hanzo/data record: the field values coerced to
54
+ * their display shapes, plus `id` (the record key every view uses) and the managed
55
+ * envelope. The ONE read mapping every DocType view shares.
56
+ */
57
+ export declare function toRecord(doc: FrameworkDoc, dt: DocType): Record<string, unknown>;
58
+ /**
59
+ * Enrich a record's Link values with the target's human label ({id,label}), so a
60
+ * relation renders as its title (not a raw id) in BOTH the list cell and the
61
+ * detail — @hanzo/data's relation display reads `.label`, its input reads `.id`.
62
+ * `optionsByField` is the loaded picker options per Link field (value=id, label=title).
63
+ */
64
+ export declare function enrichLinks(record: Record<string, unknown>, dt: DocType, optionsByField: Record<string, SelectOption[]>): Record<string, unknown>;
65
+ /** The Link fields of a DocType (their options must be loaded for the picker). */
66
+ export declare function linkFields(dt: DocType): DocField[];
67
+ /** A blank create-form draft seeded with each field's declared default (Check → bool). */
68
+ export declare function newDraft(dt: DocType): Record<string, unknown>;
69
+ /**
70
+ * A draft record → the framework write body: editable field values only (the
71
+ * engine owns `name`/`docstatus`/timestamps), each coerced to its wire shape, and
72
+ * the autoname source (e.g. `slug`) slugified so the document name is always URL-
73
+ * safe. Fields left `undefined` are dropped — a partial edit never nulls a value.
74
+ */
75
+ export declare function savePayload(draft: Record<string, unknown>, dt: DocType): Record<string, unknown>;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * `@hanzo/ui/framework` — the generic renderer for the Hanzo Framework DocType
3
+ * engine (cloud `clients/framework`, /v1/framework/*).
4
+ *
5
+ * The engine's thesis is that a business app IS a set of DocTypes: an ERP Sales
6
+ * Order, a CRM Deal, a CMS Page and a Helpdesk Ticket differ only in metadata.
7
+ * This module is the other half of that thesis — ONE list, ONE detail, ONE form,
8
+ * driven entirely by that metadata, so every lane renders with zero per-app UI.
9
+ *
10
+ * Three seams keep it host-agnostic:
11
+ * • `Transport` — how a host reaches /v1/framework (BFF proxy vs IAM bearer).
12
+ * • `renderBuilder` — schema AUTHORING, which only an admin surface offers.
13
+ * • `renderMedia` — an asset gallery, which needs the host's object store.
14
+ * Anything a host does not supply is simply not offered; nothing is faked.
15
+ */
16
+ export * from './types';
17
+ export * from './fields';
18
+ export * from './client';
19
+ export * from './data';
20
+ export { Loader } from './Loader';
21
+ export { CollectionsBrowser, type CollectionsBrowserProps } from './CollectionsBrowser';
22
+ export { DocTypeRecords, type DocTypeRecordsProps } from './DocTypeRecords';
23
+ export { DocTypeDetail, type DocTypeDetailProps } from './DocTypeDetail';
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The Hanzo Framework wire types — the DocType/DocField/document contract the
3
+ * cloud `clients/framework` engine serves at /v1/framework/* (Frappe's DocType
4
+ * metadata core, rebuilt native in Go). These mirror the Go JSON tags exactly.
5
+ *
6
+ * This is the ONE metadata vocabulary the generic @hanzo/data DocType renderer
7
+ * builds from, so CMS/ERP/CRM/Helpdesk — all "just DocTypes" on the engine —
8
+ * render through the SAME components. Types only; no runtime.
9
+ */
10
+ /** The engine's closed fieldtype set (mirrors clients/framework/doctype.go). */
11
+ export type Fieldtype = 'Data' | 'Int' | 'Float' | 'Currency' | 'Check' | 'Date' | 'Datetime' | 'Text' | 'SmallText' | 'LongText' | 'RichText' | 'Select' | 'Link' | 'Table' | 'Attach' | 'JSON' | 'Password';
12
+ /** One field of a DocType (Frappe's DocField). */
13
+ export interface DocField {
14
+ fieldname: string;
15
+ fieldtype: Fieldtype;
16
+ label?: string;
17
+ reqd?: boolean;
18
+ /** Select → newline-separated choices; Link → target DocType; Table → child DocType. */
19
+ options?: string;
20
+ default?: string;
21
+ unique?: boolean;
22
+ readOnly?: boolean;
23
+ hidden?: boolean;
24
+ inListView?: boolean;
25
+ fetchFrom?: string;
26
+ }
27
+ /** A role's rights on a DocType (Frappe's DocPerm). */
28
+ export interface DocPerm {
29
+ role: string;
30
+ read?: boolean;
31
+ write?: boolean;
32
+ create?: boolean;
33
+ delete?: boolean;
34
+ submit?: boolean;
35
+ cancel?: boolean;
36
+ }
37
+ /** A DocType definition — per-org metadata. */
38
+ export interface DocType {
39
+ name: string;
40
+ module?: string;
41
+ isSingle?: boolean;
42
+ isSubmittable?: boolean;
43
+ autoname?: string;
44
+ titleField?: string;
45
+ fields: DocField[];
46
+ permissions?: DocPerm[];
47
+ createdAt?: number;
48
+ updatedAt?: number;
49
+ }
50
+ /** The managed document envelope keys the engine always returns. */
51
+ export interface DocEnvelope {
52
+ name: string;
53
+ doctype: string;
54
+ /** 0 = draft, 1 = submitted, 2 = cancelled. */
55
+ docstatus: number;
56
+ createdAt: number;
57
+ updatedAt: number;
58
+ }
59
+ /** A document: the field data overlaid with the managed envelope keys. */
60
+ export type FrameworkDoc = Record<string, unknown> & DocEnvelope;
61
+ /** A registered app lane and (optionally, per-org) which fixtures are installed. */
62
+ export interface ModuleInfo {
63
+ module: string;
64
+ doctypes: string[];
65
+ installed?: string[];
66
+ }
67
+ /** The result of installing a module into the caller's org. */
68
+ export interface InstallResult {
69
+ module: string;
70
+ created: string[];
71
+ existing: string[];
72
+ }
73
+ /** List query for the generic document surface. */
74
+ export interface ListQuery {
75
+ filters?: Record<string, string | number | boolean>;
76
+ fields?: string[];
77
+ orderBy?: string;
78
+ limit?: number;
79
+ }
80
+ /** The redacted-Password marker the engine returns for a set secret. */
81
+ export declare const REDACTED_MARKER = "__set__";