@nodii/onboarding 0.0.1 → 0.1.0-dev.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.
Files changed (50) hide show
  1. package/dist/chunk-34BQX2KR.js +164 -0
  2. package/dist/chunk-34BQX2KR.js.map +1 -0
  3. package/dist/chunk-4SWOVHZ2.js +434 -0
  4. package/dist/chunk-4SWOVHZ2.js.map +1 -0
  5. package/dist/chunk-7NS7ENJV.js +232 -0
  6. package/dist/chunk-7NS7ENJV.js.map +1 -0
  7. package/dist/chunk-AFCJLUPK.js +92 -0
  8. package/dist/chunk-AFCJLUPK.js.map +1 -0
  9. package/dist/chunk-AW7W3HAC.js +154 -0
  10. package/dist/chunk-AW7W3HAC.js.map +1 -0
  11. package/dist/chunk-BIFANV7M.js +22 -0
  12. package/dist/chunk-BIFANV7M.js.map +1 -0
  13. package/dist/chunk-BQCTDU5V.js +327 -0
  14. package/dist/chunk-BQCTDU5V.js.map +1 -0
  15. package/dist/chunk-JFIONONR.js +3 -0
  16. package/dist/chunk-JFIONONR.js.map +1 -0
  17. package/dist/chunk-SWMIIUXR.js +1162 -0
  18. package/dist/chunk-SWMIIUXR.js.map +1 -0
  19. package/dist/chunk-VH6FWDWI.js +3 -0
  20. package/dist/chunk-VH6FWDWI.js.map +1 -0
  21. package/dist/chunk-Z4PA6C2Z.js +3 -0
  22. package/dist/chunk-Z4PA6C2Z.js.map +1 -0
  23. package/dist/chunk-ZDY2I6KY.js +3 -0
  24. package/dist/chunk-ZDY2I6KY.js.map +1 -0
  25. package/dist/dynamic/index.d.ts +184 -0
  26. package/dist/dynamic/index.js +4 -0
  27. package/dist/dynamic/index.js.map +1 -0
  28. package/dist/form/index.d.ts +64 -0
  29. package/dist/form/index.js +5 -0
  30. package/dist/form/index.js.map +1 -0
  31. package/dist/index.d.ts +10 -2
  32. package/dist/index.js +14 -2
  33. package/dist/index.js.map +1 -0
  34. package/dist/model-IEyDI54w.d.ts +487 -0
  35. package/dist/recaptcha/index.d.ts +102 -0
  36. package/dist/recaptcha/index.js +4 -0
  37. package/dist/recaptcha/index.js.map +1 -0
  38. package/dist/schema-TfyBU6JW.d.ts +107 -0
  39. package/dist/signup-client-Bl6eUrxR.d.ts +259 -0
  40. package/dist/spec/index.d.ts +88 -0
  41. package/dist/spec/index.js +4 -0
  42. package/dist/spec/index.js.map +1 -0
  43. package/dist/submit/index.d.ts +106 -0
  44. package/dist/submit/index.js +6 -0
  45. package/dist/submit/index.js.map +1 -0
  46. package/dist/widget/index.d.ts +219 -0
  47. package/dist/widget/index.js +10 -0
  48. package/dist/widget/index.js.map +1 -0
  49. package/package.json +77 -6
  50. package/README.md +0 -5
@@ -0,0 +1,219 @@
1
+ import { a as OnboardingSpec } from '../model-IEyDI54w.js';
2
+ import { OnboardingSpecCache } from '../spec/index.js';
3
+ import { c as SignupSubmitResult } from '../signup-client-Bl6eUrxR.js';
4
+ import 'zod/v3';
5
+ import '../schema-TfyBU6JW.js';
6
+
7
+ /**
8
+ * Default vanilla-TS contact form widget.
9
+ *
10
+ * Renders a form into a host element and handles the full lifecycle:
11
+ * 1. Render fields (name, email, phone, message)
12
+ * 2. Client-side validation on submit
13
+ * 3. reCAPTCHA token acquisition (D537)
14
+ * 4. POST to /api/v1/public/onboarding/leads via submit client
15
+ * 5. Retry on 5xx with SAME idempotency key
16
+ * 6. Show success or error state
17
+ *
18
+ * No React or framework required. CSS via injectWidgetStyles().
19
+ */
20
+ interface ContactFormMountOptions {
21
+ /**
22
+ * The onboarder identifier (slug or UUID).
23
+ *
24
+ * NOT transmitted on the wire — by design. Per LOCKED D536, onboarder
25
+ * trust/scoping is derived server-side from the request Host at the edge
26
+ * ("onboarder derived from the bundle, never client-supplied", D540 §1);
27
+ * a client-supplied identifier is never trusted and therefore never sent.
28
+ *
29
+ * This option is reserved for Phase 2, where it becomes the client-side
30
+ * cache key for the spec-fetch (`(onboarder, specVersion)` per D540 §3).
31
+ * In Phase 1 it has no runtime effect.
32
+ */
33
+ onboarder: string;
34
+ /**
35
+ * Base URL for the intake service.
36
+ * e.g. "https://api.acme.nucleus-cloud.in"
37
+ */
38
+ baseUrl: string;
39
+ /**
40
+ * reCAPTCHA v3 site key. Obtained from the spec endpoint (D540 §1)
41
+ * or passed directly in Phase 1 before spec-fetch is wired.
42
+ */
43
+ siteKey: string;
44
+ /**
45
+ * Which form to render. Phase 1 supports "contact" only.
46
+ * Phase 2 will add "signup" (dynamic stepper from spec).
47
+ */
48
+ form: "contact";
49
+ /** Called after a successful lead submission. */
50
+ onSuccess?: () => void;
51
+ /** Called on non-retryable errors (bot challenge, rate limit). */
52
+ onError?: (error: unknown) => void;
53
+ /** Custom fetch implementation (defaults to globalThis.fetch). */
54
+ fetch?: typeof globalThis.fetch;
55
+ /**
56
+ * Per-request timeout in ms (default 15s), applied to BOTH the reCAPTCHA
57
+ * execute() and the submit POST so a hung dependency re-enables the form.
58
+ */
59
+ timeoutMs?: number;
60
+ /** Success message override. */
61
+ successMessage?: string;
62
+ /** Submit button label override. */
63
+ submitLabel?: string;
64
+ }
65
+ interface MountedWidget {
66
+ /** Unmount and clean up the widget from the host element. */
67
+ unmount(): void;
68
+ /** Reset the form to its initial state. */
69
+ reset(): void;
70
+ }
71
+ /**
72
+ * Mount the contact form widget into a host element.
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * import { OnboardingWidget } from "@nodii/onboarding/widget";
77
+ * const widget = OnboardingWidget.mount(document.getElementById("signup"), {
78
+ * onboarder: "acme-corp",
79
+ * form: "contact",
80
+ * baseUrl: "https://api.acme.nucleus-cloud.in",
81
+ * siteKey: "6Lc...",
82
+ * });
83
+ * ```
84
+ */
85
+ declare function mountContactForm(host: HTMLElement, options: ContactFormMountOptions): MountedWidget;
86
+
87
+ /**
88
+ * Default vanilla-TS dynamic signup stepper widget (D540 Phase 2).
89
+ *
90
+ * Mount flow:
91
+ * 1. Fetch + validate the spec (GET /api/v1/public/onboarding/spec);
92
+ * the reCAPTCHA siteKey comes FROM THE SPEC (botChallenge.siteKey) —
93
+ * the mount-option siteKey is only an override/fallback.
94
+ * 2. Build the headless stepper from spec.forms.signup.steps.
95
+ * 3. Render the current step's fields per descriptor type; Back/Next
96
+ * navigate with per-step validation.
97
+ * 4. The FINAL step carries the account section (credentials + consent
98
+ * clickwrap) and the submit button.
99
+ * 5. Submit: validateAll → payload-fingerprint key management (same
100
+ * D538 discipline as the contact widget) → reCAPTCHA token →
101
+ * submitSignup.
102
+ * 6. 409 spec_stale: refetch the spec, applyNewSpec (drops removed
103
+ * fields, re-validates), show a "form updated — please review"
104
+ * banner. The SDK NEVER auto-resubmits a stale submission.
105
+ */
106
+
107
+ interface SignupFormMountOptions {
108
+ /**
109
+ * The onboarder identifier — the CLIENT-side spec-cache key
110
+ * (D540 §3 `(onboarder, specVersion)`). NOT transmitted: per LOCKED
111
+ * D536, onboarder trust/scoping is host-derived at the edge.
112
+ */
113
+ onboarder: string;
114
+ baseUrl: string;
115
+ /**
116
+ * OPTIONAL siteKey override. Normally the widget uses the siteKey the
117
+ * spec serves (botChallenge.siteKey, D537/D540); pass this only for
118
+ * environments where the spec cannot carry it yet.
119
+ */
120
+ siteKey?: string;
121
+ form: "signup";
122
+ onSuccess?: (result: SignupSubmitResult) => void;
123
+ onError?: (error: unknown) => void;
124
+ /** Called after the spec loads and the stepper renders. */
125
+ onReady?: (spec: OnboardingSpec) => void;
126
+ fetch?: typeof globalThis.fetch;
127
+ /**
128
+ * Per-request timeout in ms (default 15s), applied to the boot spec fetch,
129
+ * the reCAPTCHA execute(), the submit POST, and the 409 refetch — so a hung
130
+ * dependency never leaves "Loading signup form…"/"Sending…" stuck.
131
+ */
132
+ timeoutMs?: number;
133
+ submitLabel?: string;
134
+ tosUrl?: string;
135
+ privacyUrl?: string;
136
+ /** Spec cache override (defaults to the module-level cache). */
137
+ cache?: OnboardingSpecCache;
138
+ }
139
+ /**
140
+ * Mount the dynamic signup stepper into a host element.
141
+ * Returns synchronously; the spec loads in the background (loading state
142
+ * rendered meanwhile; onReady fires when interactive).
143
+ */
144
+ declare function mountSignupForm(host: HTMLElement, options: SignupFormMountOptions): MountedWidget;
145
+
146
+ /**
147
+ * Default CSS for the onboarding contact form widget.
148
+ *
149
+ * Uses CSS custom properties (variables) for theming. Consumers override
150
+ * these on the host element or globally:
151
+ *
152
+ * --nodii-onboarding-font-family
153
+ * --nodii-onboarding-primary-color
154
+ * --nodii-onboarding-primary-hover
155
+ * --nodii-onboarding-error-color
156
+ * --nodii-onboarding-border-color
157
+ * --nodii-onboarding-input-bg
158
+ * --nodii-onboarding-label-color
159
+ * --nodii-onboarding-radius
160
+ * --nodii-onboarding-spacing
161
+ *
162
+ * The widget scopes all styles under `.nodii-onboarding-widget` to avoid
163
+ * leaking into the host page.
164
+ */
165
+ declare const DEFAULT_WIDGET_CSS = "\n.nodii-onboarding-widget {\n --_font: var(--nodii-onboarding-font-family, system-ui, -apple-system, sans-serif);\n --_primary: var(--nodii-onboarding-primary-color, #2563eb);\n --_primary-hover: var(--nodii-onboarding-primary-hover, #1d4ed8);\n --_error: var(--nodii-onboarding-error-color, #dc2626);\n --_border: var(--nodii-onboarding-border-color, #d1d5db);\n --_input-bg: var(--nodii-onboarding-input-bg, #fff);\n --_label: var(--nodii-onboarding-label-color, #374151);\n --_radius: var(--nodii-onboarding-radius, 6px);\n --_spacing: var(--nodii-onboarding-spacing, 16px);\n\n font-family: var(--_font);\n box-sizing: border-box;\n}\n\n.nodii-onboarding-widget *,\n.nodii-onboarding-widget *::before,\n.nodii-onboarding-widget *::after {\n box-sizing: inherit;\n}\n\n.nodii-onboarding-field {\n display: flex;\n flex-direction: column;\n gap: calc(var(--_spacing) * 0.25);\n margin-bottom: var(--_spacing);\n}\n\n.nodii-onboarding-label {\n font-size: 0.875rem;\n font-weight: 500;\n color: var(--_label);\n}\n\n.nodii-onboarding-label.required::after {\n content: \" *\";\n color: var(--_error);\n}\n\n.nodii-onboarding-input,\n.nodii-onboarding-textarea {\n width: 100%;\n padding: calc(var(--_spacing) * 0.5) calc(var(--_spacing) * 0.75);\n border: 1px solid var(--_border);\n border-radius: var(--_radius);\n background: var(--_input-bg);\n font-family: var(--_font);\n font-size: 1rem;\n line-height: 1.5;\n outline: none;\n transition: border-color 0.15s ease;\n}\n\n.nodii-onboarding-input:focus,\n.nodii-onboarding-textarea:focus {\n border-color: var(--_primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--_primary) 20%, transparent);\n}\n\n.nodii-onboarding-input.has-error,\n.nodii-onboarding-textarea.has-error {\n border-color: var(--_error);\n}\n\n.nodii-onboarding-textarea {\n min-height: 100px;\n resize: vertical;\n}\n\n.nodii-onboarding-field-error {\n font-size: 0.75rem;\n color: var(--_error);\n margin-top: 2px;\n}\n\n.nodii-onboarding-server-error {\n padding: calc(var(--_spacing) * 0.75);\n border-radius: var(--_radius);\n background: color-mix(in srgb, var(--_error) 8%, transparent);\n border: 1px solid color-mix(in srgb, var(--_error) 30%, transparent);\n color: var(--_error);\n font-size: 0.875rem;\n margin-bottom: var(--_spacing);\n}\n\n.nodii-onboarding-success {\n padding: var(--_spacing);\n border-radius: var(--_radius);\n background: #f0fdf4;\n border: 1px solid #bbf7d0;\n color: #15803d;\n font-size: 0.9375rem;\n text-align: center;\n}\n\n.nodii-onboarding-submit-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n padding: calc(var(--_spacing) * 0.625) var(--_spacing);\n background: var(--_primary);\n color: #fff;\n border: none;\n border-radius: var(--_radius);\n font-family: var(--_font);\n font-size: 1rem;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s ease;\n}\n\n.nodii-onboarding-submit-btn:hover:not(:disabled) {\n background: var(--_primary-hover);\n}\n\n.nodii-onboarding-submit-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.nodii-onboarding-spinner {\n display: inline-block;\n width: 1em;\n height: 1em;\n border: 2px solid rgba(255,255,255,0.4);\n border-top-color: #fff;\n border-radius: 50%;\n animation: nodii-spin 0.6s linear infinite;\n margin-right: 0.5em;\n}\n\n@keyframes nodii-spin {\n to { transform: rotate(360deg); }\n}\n\n.nodii-onboarding-recaptcha-notice {\n font-size: 0.7rem;\n color: #6b7280;\n margin-top: calc(var(--_spacing) * 0.5);\n text-align: center;\n}\n\n.nodii-onboarding-recaptcha-notice a {\n color: inherit;\n}\n\n/* \u2500\u2500 dynamic stepper (signup form) \u2500\u2500 */\n\n.nodii-onboarding-steps {\n display: flex;\n gap: calc(var(--_spacing) * 0.5);\n margin-bottom: var(--_spacing);\n}\n\n.nodii-onboarding-step-dot {\n flex: 1;\n height: 4px;\n border-radius: 2px;\n background: var(--_border);\n}\n\n.nodii-onboarding-step-dot.active {\n background: var(--_primary);\n}\n\n.nodii-onboarding-stage-title {\n font-size: 1.0625rem;\n font-weight: 600;\n color: var(--_label);\n margin: 0 0 var(--_spacing) 0;\n}\n\n.nodii-onboarding-nav {\n display: flex;\n gap: calc(var(--_spacing) * 0.5);\n margin-top: var(--_spacing);\n}\n\n.nodii-onboarding-back-btn {\n padding: calc(var(--_spacing) * 0.625) var(--_spacing);\n background: transparent;\n color: var(--_label);\n border: 1px solid var(--_border);\n border-radius: var(--_radius);\n font-family: var(--_font);\n font-size: 1rem;\n cursor: pointer;\n}\n\n.nodii-onboarding-stale-banner {\n padding: calc(var(--_spacing) * 0.75);\n border-radius: var(--_radius);\n background: #fffbeb;\n border: 1px solid #fcd34d;\n color: #92400e;\n font-size: 0.875rem;\n margin-bottom: var(--_spacing);\n}\n\n.nodii-onboarding-consent-row {\n display: flex;\n align-items: flex-start;\n gap: calc(var(--_spacing) * 0.5);\n margin-bottom: calc(var(--_spacing) * 0.5);\n font-size: 0.875rem;\n color: var(--_label);\n}\n\n.nodii-onboarding-consent-row input[type=\"checkbox\"] {\n margin-top: 3px;\n}\n\n.nodii-onboarding-account-section {\n border-top: 1px solid var(--_border);\n margin-top: var(--_spacing);\n padding-top: var(--_spacing);\n}\n\n.nodii-onboarding-loading {\n padding: var(--_spacing);\n text-align: center;\n color: var(--_label);\n}\n";
166
+ /**
167
+ * Inject the default widget CSS once into the document head.
168
+ * Idempotent — will not add a duplicate style tag.
169
+ */
170
+ declare function injectWidgetStyles(): void;
171
+
172
+ /**
173
+ * Shared DOM helpers for the widget layer.
174
+ *
175
+ * `idSelector` builds a `#id` CSS selector with the id CSS-escaped, so a field
176
+ * key (which originates in the fetched onboarder spec) can never break out of
177
+ * the selector or match unintended nodes. This is defense-in-depth: the spec
178
+ * model already constrains field keys to `[A-Za-z0-9_-]+` (FIELD_KEY_PATTERN),
179
+ * but the escaping guarantees selector safety even for the widget's own
180
+ * internally-composed ids and for any future relaxation of that alphabet.
181
+ */
182
+ /**
183
+ * CSS-escape a string for safe use as an identifier in a selector.
184
+ * Uses the standard `CSS.escape` when available (all modern browsers), with a
185
+ * spec-compliant-enough fallback for non-DOM/test runtimes: escape everything
186
+ * that is not an ASCII identifier char.
187
+ */
188
+ declare function cssEscape(value: string): string;
189
+ /** Build a safe `#id` selector with the id CSS-escaped. */
190
+ declare function idSelector(id: string): string;
191
+
192
+ type WidgetMountOptions = ContactFormMountOptions | SignupFormMountOptions;
193
+ declare const OnboardingWidget: {
194
+ /**
195
+ * Mount the onboarding widget into a host element.
196
+ *
197
+ * @param el - The DOM element to render into.
198
+ * @param options - Widget configuration. `form: "contact"` renders the
199
+ * fixed contact form (Phase 1); `form: "signup"` fetches the D540 spec
200
+ * and renders the dynamic stepper (Phase 2 — siteKey comes from the
201
+ * spec's botChallenge unless overridden).
202
+ * @returns A mounted widget control (unmount / reset).
203
+ *
204
+ * @example
205
+ * ```ts
206
+ * import { OnboardingWidget } from "@nodii/onboarding/widget";
207
+ *
208
+ * OnboardingWidget.mount(document.getElementById("signup-form")!, {
209
+ * onboarder: "acme-corp",
210
+ * form: "signup",
211
+ * baseUrl: "https://api.acme.nucleus-cloud.in",
212
+ * onSuccess: (r) => console.log("landed at", r.terminalStage),
213
+ * });
214
+ * ```
215
+ */
216
+ readonly mount: (el: HTMLElement, options: WidgetMountOptions) => MountedWidget;
217
+ };
218
+
219
+ export { type ContactFormMountOptions, DEFAULT_WIDGET_CSS, type MountedWidget, OnboardingWidget, type SignupFormMountOptions, type WidgetMountOptions, cssEscape, idSelector, injectWidgetStyles, mountContactForm, mountSignupForm };
@@ -0,0 +1,10 @@
1
+ export { DEFAULT_WIDGET_CSS, OnboardingWidget, cssEscape, idSelector, injectWidgetStyles, mountContactForm, mountSignupForm } from '../chunk-SWMIIUXR.js';
2
+ import '../chunk-AFCJLUPK.js';
3
+ import '../chunk-34BQX2KR.js';
4
+ import '../chunk-4SWOVHZ2.js';
5
+ import '../chunk-BQCTDU5V.js';
6
+ import '../chunk-BIFANV7M.js';
7
+ import '../chunk-AW7W3HAC.js';
8
+ import '../chunk-7NS7ENJV.js';
9
+ //# sourceMappingURL=index.js.map
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
package/package.json CHANGED
@@ -1,24 +1,95 @@
1
1
  {
2
2
  "name": "@nodii/onboarding",
3
- "version": "0.0.1",
4
- "description": "Placeholder for @nodii/onboarding — implementation lands at v0.1.0",
3
+ "version": "0.1.0-dev.0",
4
+ "description": "Browser SDK for the Nodii self-signup onboarding flow. Headless form state, reCAPTCHA integration, and a default themeable vanilla-TS widget. D540/D537 contract client.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
+ "browser": true,
7
8
  "main": "./dist/index.js",
9
+ "module": "./dist/index.js",
8
10
  "types": "./dist/index.d.ts",
9
11
  "exports": {
10
12
  ".": {
11
- "types": "./dist/index.d.ts",
12
- "default": "./dist/index.js"
13
+ "import": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ }
17
+ },
18
+ "./form": {
19
+ "import": {
20
+ "types": "./dist/form/index.d.ts",
21
+ "default": "./dist/form/index.js"
22
+ }
23
+ },
24
+ "./spec": {
25
+ "import": {
26
+ "types": "./dist/spec/index.d.ts",
27
+ "default": "./dist/spec/index.js"
28
+ }
29
+ },
30
+ "./dynamic": {
31
+ "import": {
32
+ "types": "./dist/dynamic/index.d.ts",
33
+ "default": "./dist/dynamic/index.js"
34
+ }
35
+ },
36
+ "./submit": {
37
+ "import": {
38
+ "types": "./dist/submit/index.d.ts",
39
+ "default": "./dist/submit/index.js"
40
+ }
41
+ },
42
+ "./recaptcha": {
43
+ "import": {
44
+ "types": "./dist/recaptcha/index.d.ts",
45
+ "default": "./dist/recaptcha/index.js"
46
+ }
47
+ },
48
+ "./widget": {
49
+ "import": {
50
+ "types": "./dist/widget/index.d.ts",
51
+ "default": "./dist/widget/index.js"
52
+ }
13
53
  }
14
54
  },
15
- "files": ["dist"],
55
+ "files": [
56
+ "dist",
57
+ "README.md",
58
+ "LICENSE"
59
+ ],
60
+ "scripts": {
61
+ "build": "tsup",
62
+ "dev": "tsup --watch",
63
+ "typecheck": "tsc --noEmit",
64
+ "test": "bun test",
65
+ "clean": "rm -rf dist"
66
+ },
67
+ "dependencies": {
68
+ "zod": "^3.25.76"
69
+ },
70
+ "devDependencies": {
71
+ "@happy-dom/global-registrator": "^20.10.6",
72
+ "tsup": "^8.3.5",
73
+ "typescript": "^5.9.3"
74
+ },
16
75
  "publishConfig": {
17
- "access": "public"
76
+ "access": "public",
77
+ "registry": "https://registry.npmjs.org"
18
78
  },
19
79
  "repository": {
20
80
  "type": "git",
21
81
  "url": "git+https://github.com/cognion-nucleus/nodii-libs.git",
22
82
  "directory": "ts/onboarding"
83
+ },
84
+ "keywords": [
85
+ "onboarding",
86
+ "self-signup",
87
+ "recaptcha",
88
+ "browser",
89
+ "sdk",
90
+ "nodii"
91
+ ],
92
+ "engines": {
93
+ "node": ">=20"
23
94
  }
24
95
  }
package/README.md DELETED
@@ -1,5 +0,0 @@
1
- # @nodii/onboarding
2
-
3
- Placeholder package at v0.0.1. Real implementation ships at v0.1.0 per the locked feature_doc.
4
-
5
- **Spec**: `https://planning.dev.nucleus-cloud.in/api/v1/feature-docs?serviceId=nodii-libs&docKey=onboarding`