@onabl/js 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 +25 -0
- package/dist/index.d.mts +146 -0
- package/dist/index.d.ts +146 -0
- package/dist/index.js +1259 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1246 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# `@onabl/js`
|
|
2
|
+
|
|
3
|
+
Framework-agnostic core of the onabl SDK. No React, no DOM assumptions —
|
|
4
|
+
`@onabl/react` and `@onabl/next` build on top of this.
|
|
5
|
+
|
|
6
|
+
**Status:** skeleton only (Phase 14 §14.5.2, in progress). See
|
|
7
|
+
`docs/SDK-DESIGN.md` for the full intended API surface before adding to this
|
|
8
|
+
package, and `docs/INTEGRATION-LAYER-TODO.md` for current build status.
|
|
9
|
+
|
|
10
|
+
## What belongs here
|
|
11
|
+
|
|
12
|
+
- `createOnablClient()` — the typed API client
|
|
13
|
+
- `OnablError` and its subclasses (`OnablNotFound`, `OnablUnauthorized`,
|
|
14
|
+
`OnablValidation`, `OnablNetwork`)
|
|
15
|
+
- The condition evaluator and form state machine, extracted from
|
|
16
|
+
`apps/web/components/form/MultiStepForm.tsx`
|
|
17
|
+
- `presentQuote` / `groupLineItems`, re-exported (and bundled — see
|
|
18
|
+
`tsup.config.ts`) from `@onabl/engine`, which is never published on its own
|
|
19
|
+
|
|
20
|
+
## Build
|
|
21
|
+
|
|
22
|
+
Built with `tsup`, not plain `tsc` — `@onabl/engine`'s presentation code has
|
|
23
|
+
to be bundled into this package's output since `@onabl/engine` itself is
|
|
24
|
+
private and never installable on its own. See the comment in
|
|
25
|
+
`tsup.config.ts` before changing the dependency/build setup.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { PublicForm, ValidateCodeResponse, Quote, Invoice, PresignResponse, PublicFormField } from '@onabl/contracts/public-api';
|
|
2
|
+
export { Invoice, PublicForm, PublicFormConfig, PublicFormField, Quote, ValidateCodeResponse } from '@onabl/contracts/public-api';
|
|
3
|
+
import { AnswerMap, FormStep } from '@onabl/types/config';
|
|
4
|
+
export { GroupedItems, PresentQuoteInput, QuotePresentation, QuoteRow, SeparateSection, groupLineItems, presentQuote } from '@onabl/engine/presentation';
|
|
5
|
+
|
|
6
|
+
interface OnablClientConfig {
|
|
7
|
+
handle: string;
|
|
8
|
+
secretKey: string;
|
|
9
|
+
baseUrl?: string;
|
|
10
|
+
apiVersion?: string;
|
|
11
|
+
/** Path template for the quote view page on the integrator's own site — see submissions.create()'s returned quoteUrl. */
|
|
12
|
+
quotePath?: string;
|
|
13
|
+
}
|
|
14
|
+
interface CallOpts {
|
|
15
|
+
/** Slug of a specific form, for tenants running more than one. Omitted, resolves to the tenant's dashboard-configured default. */
|
|
16
|
+
slug?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Customer-token-scoped calls need a bearer token — @onabl/next supplies one
|
|
20
|
+
* automatically from its managed cookie (SDK-DESIGN.md §3, "Access cookie").
|
|
21
|
+
* Used directly (e.g. a Vite/plain-React app per SDK-DESIGN.md §4, "Route
|
|
22
|
+
* handler only" decision), the caller supplies it explicitly.
|
|
23
|
+
*/
|
|
24
|
+
interface TokenOpts {
|
|
25
|
+
token: string;
|
|
26
|
+
}
|
|
27
|
+
interface OnablClient {
|
|
28
|
+
forms: {
|
|
29
|
+
get(opts?: CallOpts): Promise<PublicForm>;
|
|
30
|
+
validateDiscountCode(code: string, opts?: CallOpts): Promise<ValidateCodeResponse>;
|
|
31
|
+
};
|
|
32
|
+
quotes: {
|
|
33
|
+
get(uuid: string, opts: TokenOpts): Promise<Quote>;
|
|
34
|
+
accept(uuid: string, opts: TokenOpts): Promise<void>;
|
|
35
|
+
decline(uuid: string, reason: string, note: string | undefined, opts: TokenOpts): Promise<void>;
|
|
36
|
+
};
|
|
37
|
+
invoices: {
|
|
38
|
+
get(uuid: string, opts: TokenOpts): Promise<Invoice>;
|
|
39
|
+
/** milestone "deposit" saves the deposit POP; "balance"/"full" save the balance/full-payment POP. */
|
|
40
|
+
savePopUrl(uuid: string, key: string, milestone: "deposit" | "balance" | "full", opts: TokenOpts): Promise<void>;
|
|
41
|
+
};
|
|
42
|
+
storage: {
|
|
43
|
+
/** Presigns an upload for a customer-facing flow (currently only invoice POP uploads — pathPrefix is always "pop"). */
|
|
44
|
+
presignPop(resourceId: string, filename: string, contentType: string, fileSize: number, opts: TokenOpts): Promise<PresignResponse>;
|
|
45
|
+
};
|
|
46
|
+
submissions: {
|
|
47
|
+
/** isTenantSource marks a submission taken by the tenant on the customer's behalf (the on-site quote tool) rather than the customer directly — see docs/ROADMAP.md Phase 2.11. */
|
|
48
|
+
create(answers: AnswerMap, opts?: CallOpts & {
|
|
49
|
+
consentGiven?: boolean;
|
|
50
|
+
discountCode?: string;
|
|
51
|
+
isTenantSource?: boolean;
|
|
52
|
+
}): Promise<{
|
|
53
|
+
quoteId: string;
|
|
54
|
+
quoteUrl: string;
|
|
55
|
+
token: string;
|
|
56
|
+
}>;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
declare function createOnablClient(config: OnablClientConfig): OnablClient;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Typed error classes so a catch block can discriminate without matching on
|
|
63
|
+
* HTTP status numbers or string messages. See docs/SDK-DESIGN.md §3.
|
|
64
|
+
*/
|
|
65
|
+
declare class OnablError extends Error {
|
|
66
|
+
readonly status?: number | undefined;
|
|
67
|
+
constructor(message: string, status?: number | undefined);
|
|
68
|
+
}
|
|
69
|
+
/** 404, or a customer token that doesn't grant access to this resource. */
|
|
70
|
+
declare class OnablNotFound extends OnablError {
|
|
71
|
+
constructor(message?: string);
|
|
72
|
+
}
|
|
73
|
+
/** Missing or invalid secret key / customer token. */
|
|
74
|
+
declare class OnablUnauthorized extends OnablError {
|
|
75
|
+
constructor(message?: string);
|
|
76
|
+
}
|
|
77
|
+
/** 400 — carries field-level detail from the API's Zod validation errors. */
|
|
78
|
+
declare class OnablValidation extends OnablError {
|
|
79
|
+
readonly fieldErrors?: Record<string, string[]> | undefined;
|
|
80
|
+
constructor(message: string, fieldErrors?: Record<string, string[]> | undefined);
|
|
81
|
+
}
|
|
82
|
+
/** Transport failure — network error, timeout, or an unparseable response. */
|
|
83
|
+
declare class OnablNetwork extends OnablError {
|
|
84
|
+
constructor(message?: string, cause?: unknown);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The form state machine, extracted from apps/web/components/form/MultiStepForm.tsx
|
|
89
|
+
* (§14.5.2, part 2). Pure functions only — no React, no framework of any kind —
|
|
90
|
+
* so any UI layer (a React hook in @onabl/react, or something else entirely)
|
|
91
|
+
* can drive the same tested logic. Renders nothing; every function here takes
|
|
92
|
+
* a snapshot of state and returns a value, never mutates.
|
|
93
|
+
*
|
|
94
|
+
* Deliberately excludes three things apps/web's current implementation
|
|
95
|
+
* handles that the documented useOnablForm surface (docs/SDK-DESIGN.md §3)
|
|
96
|
+
* does not — decided 2026-08-24, see docs/INTEGRATION-LAYER-TODO.md:
|
|
97
|
+
*
|
|
98
|
+
* - Intro screen: not modeled as a step here at all. It's pure presentation
|
|
99
|
+
* (config.meta.intro) with no fields to collect or validate — the
|
|
100
|
+
* integrator reads it directly and renders it before ever touching this
|
|
101
|
+
* module's step navigation.
|
|
102
|
+
* - Auto-advance (single radio/toggle field steps skipping the Continue
|
|
103
|
+
* click): not built in here. It's a UI/interaction choice, not form logic —
|
|
104
|
+
* the render layer calls next() itself after setAnswer() when it wants
|
|
105
|
+
* that behavior, using visibleFields to detect the single-field case.
|
|
106
|
+
* - Discount code: folded in as ordinary state here (getDiscountCode-style
|
|
107
|
+
* handling lives in @onabl/react's hook, not this module) rather than
|
|
108
|
+
* modeled as a synthetic step the way apps/web's current buildSteps() does.
|
|
109
|
+
* Where to render the discount input within the step flow (inline on the
|
|
110
|
+
* last step, or as a separate screen before submit) is left to the
|
|
111
|
+
* integrator — this module has no opinion on it.
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
/** A step that currently has at least one visible field — steps a conditional field emptied out entirely are skipped. */
|
|
115
|
+
interface VisibleStep extends FormStep {
|
|
116
|
+
fields: PublicFormField[];
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Every step the visitor can currently navigate to, in order, filtered to
|
|
120
|
+
* only those with ≥1 visible field. Ungrouped fields (stepId: null) are
|
|
121
|
+
* collected into a synthetic trailing step, matching apps/web's current
|
|
122
|
+
* behavior — labeled "Details" there today; integrators using this directly
|
|
123
|
+
* can relabel as they see fit since this is just a FormStep-shaped value.
|
|
124
|
+
*/
|
|
125
|
+
declare function getVisibleSteps(form: PublicForm, answers: AnswerMap): VisibleStep[];
|
|
126
|
+
/** Every field on the form, regardless of visibility — for debugging a field that won't appear. */
|
|
127
|
+
declare function getAllFields(form: PublicForm): PublicFormField[];
|
|
128
|
+
/**
|
|
129
|
+
* Seeds the initial AnswerMap from field defaults, using @onabl/engine's
|
|
130
|
+
* canonical applyDefaults — including its type-based fallback (unset number
|
|
131
|
+
* fields → 0, unset toggle → false). This is a deliberate choice for this
|
|
132
|
+
* new implementation, not a port of apps/web's current (leaner) local
|
|
133
|
+
* version — see docs/INTEGRATION-LAYER-TODO.md's Stage 3 pre-work note for
|
|
134
|
+
* why apps/web wasn't swapped onto this same behavior directly. Verify this
|
|
135
|
+
* during apps/web's own SDK migration (§14.5.5), since it is a real,
|
|
136
|
+
* intentional behavior difference from what's live today.
|
|
137
|
+
*/
|
|
138
|
+
declare function getInitialAnswers(form: PublicForm): AnswerMap;
|
|
139
|
+
/**
|
|
140
|
+
* Validates one step's fields against the current answers. Same rules as
|
|
141
|
+
* apps/web's current validateStep(), generalized to a plain field list
|
|
142
|
+
* rather than coupled to a Step union type.
|
|
143
|
+
*/
|
|
144
|
+
declare function validateFields(fields: PublicFormField[], answers: AnswerMap): Record<string, string>;
|
|
145
|
+
|
|
146
|
+
export { type CallOpts, type OnablClient, type OnablClientConfig, OnablError, OnablNetwork, OnablNotFound, OnablUnauthorized, OnablValidation, type TokenOpts, type VisibleStep, createOnablClient, getAllFields, getInitialAnswers, getVisibleSteps, validateFields };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { PublicForm, ValidateCodeResponse, Quote, Invoice, PresignResponse, PublicFormField } from '@onabl/contracts/public-api';
|
|
2
|
+
export { Invoice, PublicForm, PublicFormConfig, PublicFormField, Quote, ValidateCodeResponse } from '@onabl/contracts/public-api';
|
|
3
|
+
import { AnswerMap, FormStep } from '@onabl/types/config';
|
|
4
|
+
export { GroupedItems, PresentQuoteInput, QuotePresentation, QuoteRow, SeparateSection, groupLineItems, presentQuote } from '@onabl/engine/presentation';
|
|
5
|
+
|
|
6
|
+
interface OnablClientConfig {
|
|
7
|
+
handle: string;
|
|
8
|
+
secretKey: string;
|
|
9
|
+
baseUrl?: string;
|
|
10
|
+
apiVersion?: string;
|
|
11
|
+
/** Path template for the quote view page on the integrator's own site — see submissions.create()'s returned quoteUrl. */
|
|
12
|
+
quotePath?: string;
|
|
13
|
+
}
|
|
14
|
+
interface CallOpts {
|
|
15
|
+
/** Slug of a specific form, for tenants running more than one. Omitted, resolves to the tenant's dashboard-configured default. */
|
|
16
|
+
slug?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Customer-token-scoped calls need a bearer token — @onabl/next supplies one
|
|
20
|
+
* automatically from its managed cookie (SDK-DESIGN.md §3, "Access cookie").
|
|
21
|
+
* Used directly (e.g. a Vite/plain-React app per SDK-DESIGN.md §4, "Route
|
|
22
|
+
* handler only" decision), the caller supplies it explicitly.
|
|
23
|
+
*/
|
|
24
|
+
interface TokenOpts {
|
|
25
|
+
token: string;
|
|
26
|
+
}
|
|
27
|
+
interface OnablClient {
|
|
28
|
+
forms: {
|
|
29
|
+
get(opts?: CallOpts): Promise<PublicForm>;
|
|
30
|
+
validateDiscountCode(code: string, opts?: CallOpts): Promise<ValidateCodeResponse>;
|
|
31
|
+
};
|
|
32
|
+
quotes: {
|
|
33
|
+
get(uuid: string, opts: TokenOpts): Promise<Quote>;
|
|
34
|
+
accept(uuid: string, opts: TokenOpts): Promise<void>;
|
|
35
|
+
decline(uuid: string, reason: string, note: string | undefined, opts: TokenOpts): Promise<void>;
|
|
36
|
+
};
|
|
37
|
+
invoices: {
|
|
38
|
+
get(uuid: string, opts: TokenOpts): Promise<Invoice>;
|
|
39
|
+
/** milestone "deposit" saves the deposit POP; "balance"/"full" save the balance/full-payment POP. */
|
|
40
|
+
savePopUrl(uuid: string, key: string, milestone: "deposit" | "balance" | "full", opts: TokenOpts): Promise<void>;
|
|
41
|
+
};
|
|
42
|
+
storage: {
|
|
43
|
+
/** Presigns an upload for a customer-facing flow (currently only invoice POP uploads — pathPrefix is always "pop"). */
|
|
44
|
+
presignPop(resourceId: string, filename: string, contentType: string, fileSize: number, opts: TokenOpts): Promise<PresignResponse>;
|
|
45
|
+
};
|
|
46
|
+
submissions: {
|
|
47
|
+
/** isTenantSource marks a submission taken by the tenant on the customer's behalf (the on-site quote tool) rather than the customer directly — see docs/ROADMAP.md Phase 2.11. */
|
|
48
|
+
create(answers: AnswerMap, opts?: CallOpts & {
|
|
49
|
+
consentGiven?: boolean;
|
|
50
|
+
discountCode?: string;
|
|
51
|
+
isTenantSource?: boolean;
|
|
52
|
+
}): Promise<{
|
|
53
|
+
quoteId: string;
|
|
54
|
+
quoteUrl: string;
|
|
55
|
+
token: string;
|
|
56
|
+
}>;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
declare function createOnablClient(config: OnablClientConfig): OnablClient;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Typed error classes so a catch block can discriminate without matching on
|
|
63
|
+
* HTTP status numbers or string messages. See docs/SDK-DESIGN.md §3.
|
|
64
|
+
*/
|
|
65
|
+
declare class OnablError extends Error {
|
|
66
|
+
readonly status?: number | undefined;
|
|
67
|
+
constructor(message: string, status?: number | undefined);
|
|
68
|
+
}
|
|
69
|
+
/** 404, or a customer token that doesn't grant access to this resource. */
|
|
70
|
+
declare class OnablNotFound extends OnablError {
|
|
71
|
+
constructor(message?: string);
|
|
72
|
+
}
|
|
73
|
+
/** Missing or invalid secret key / customer token. */
|
|
74
|
+
declare class OnablUnauthorized extends OnablError {
|
|
75
|
+
constructor(message?: string);
|
|
76
|
+
}
|
|
77
|
+
/** 400 — carries field-level detail from the API's Zod validation errors. */
|
|
78
|
+
declare class OnablValidation extends OnablError {
|
|
79
|
+
readonly fieldErrors?: Record<string, string[]> | undefined;
|
|
80
|
+
constructor(message: string, fieldErrors?: Record<string, string[]> | undefined);
|
|
81
|
+
}
|
|
82
|
+
/** Transport failure — network error, timeout, or an unparseable response. */
|
|
83
|
+
declare class OnablNetwork extends OnablError {
|
|
84
|
+
constructor(message?: string, cause?: unknown);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The form state machine, extracted from apps/web/components/form/MultiStepForm.tsx
|
|
89
|
+
* (§14.5.2, part 2). Pure functions only — no React, no framework of any kind —
|
|
90
|
+
* so any UI layer (a React hook in @onabl/react, or something else entirely)
|
|
91
|
+
* can drive the same tested logic. Renders nothing; every function here takes
|
|
92
|
+
* a snapshot of state and returns a value, never mutates.
|
|
93
|
+
*
|
|
94
|
+
* Deliberately excludes three things apps/web's current implementation
|
|
95
|
+
* handles that the documented useOnablForm surface (docs/SDK-DESIGN.md §3)
|
|
96
|
+
* does not — decided 2026-08-24, see docs/INTEGRATION-LAYER-TODO.md:
|
|
97
|
+
*
|
|
98
|
+
* - Intro screen: not modeled as a step here at all. It's pure presentation
|
|
99
|
+
* (config.meta.intro) with no fields to collect or validate — the
|
|
100
|
+
* integrator reads it directly and renders it before ever touching this
|
|
101
|
+
* module's step navigation.
|
|
102
|
+
* - Auto-advance (single radio/toggle field steps skipping the Continue
|
|
103
|
+
* click): not built in here. It's a UI/interaction choice, not form logic —
|
|
104
|
+
* the render layer calls next() itself after setAnswer() when it wants
|
|
105
|
+
* that behavior, using visibleFields to detect the single-field case.
|
|
106
|
+
* - Discount code: folded in as ordinary state here (getDiscountCode-style
|
|
107
|
+
* handling lives in @onabl/react's hook, not this module) rather than
|
|
108
|
+
* modeled as a synthetic step the way apps/web's current buildSteps() does.
|
|
109
|
+
* Where to render the discount input within the step flow (inline on the
|
|
110
|
+
* last step, or as a separate screen before submit) is left to the
|
|
111
|
+
* integrator — this module has no opinion on it.
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
/** A step that currently has at least one visible field — steps a conditional field emptied out entirely are skipped. */
|
|
115
|
+
interface VisibleStep extends FormStep {
|
|
116
|
+
fields: PublicFormField[];
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Every step the visitor can currently navigate to, in order, filtered to
|
|
120
|
+
* only those with ≥1 visible field. Ungrouped fields (stepId: null) are
|
|
121
|
+
* collected into a synthetic trailing step, matching apps/web's current
|
|
122
|
+
* behavior — labeled "Details" there today; integrators using this directly
|
|
123
|
+
* can relabel as they see fit since this is just a FormStep-shaped value.
|
|
124
|
+
*/
|
|
125
|
+
declare function getVisibleSteps(form: PublicForm, answers: AnswerMap): VisibleStep[];
|
|
126
|
+
/** Every field on the form, regardless of visibility — for debugging a field that won't appear. */
|
|
127
|
+
declare function getAllFields(form: PublicForm): PublicFormField[];
|
|
128
|
+
/**
|
|
129
|
+
* Seeds the initial AnswerMap from field defaults, using @onabl/engine's
|
|
130
|
+
* canonical applyDefaults — including its type-based fallback (unset number
|
|
131
|
+
* fields → 0, unset toggle → false). This is a deliberate choice for this
|
|
132
|
+
* new implementation, not a port of apps/web's current (leaner) local
|
|
133
|
+
* version — see docs/INTEGRATION-LAYER-TODO.md's Stage 3 pre-work note for
|
|
134
|
+
* why apps/web wasn't swapped onto this same behavior directly. Verify this
|
|
135
|
+
* during apps/web's own SDK migration (§14.5.5), since it is a real,
|
|
136
|
+
* intentional behavior difference from what's live today.
|
|
137
|
+
*/
|
|
138
|
+
declare function getInitialAnswers(form: PublicForm): AnswerMap;
|
|
139
|
+
/**
|
|
140
|
+
* Validates one step's fields against the current answers. Same rules as
|
|
141
|
+
* apps/web's current validateStep(), generalized to a plain field list
|
|
142
|
+
* rather than coupled to a Step union type.
|
|
143
|
+
*/
|
|
144
|
+
declare function validateFields(fields: PublicFormField[], answers: AnswerMap): Record<string, string>;
|
|
145
|
+
|
|
146
|
+
export { type CallOpts, type OnablClient, type OnablClientConfig, OnablError, OnablNetwork, OnablNotFound, OnablUnauthorized, OnablValidation, type TokenOpts, type VisibleStep, createOnablClient, getAllFields, getInitialAnswers, getVisibleSteps, validateFields };
|