@specbook/core 0.0.0-stage → 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ajai Shankar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,7 @@
1
- # Temporary Holding Version
1
+ # @specbook/core
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The runner, mock engine, template engine and shared proxy behind Specbook: Storybook for APIs.
4
+
5
+ Part of [Specbook](https://github.com/specbook-dev/specbook), Storybook for APIs. See the repository for documentation.
6
+
7
+ MIT licensed.
package/package.json CHANGED
@@ -1,6 +1,61 @@
1
1
  {
2
2
  "name": "@specbook/core",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "The runner, mock engine, template engine and shared proxy behind Specbook: Storybook for APIs.",
5
+ "keywords": [
6
+ "specbook",
7
+ "openapi",
8
+ "api-mocking",
9
+ "api-testing",
10
+ "mock-server"
11
+ ],
12
+ "license": "MIT",
13
+ "homepage": "https://ui.specbook.dev",
14
+ "bugs": {
15
+ "url": "https://github.com/specbook-dev/specbook/issues"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/specbook-dev/specbook.git",
20
+ "directory": "packages/core"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "type": "module",
26
+ "sideEffects": false,
27
+ "exports": {
28
+ ".": {
29
+ "types": "./src/index.ts",
30
+ "default": "./src/index.ts"
31
+ },
32
+ "./assert": {
33
+ "types": "./src/assert.ts",
34
+ "default": "./src/assert.ts"
35
+ },
36
+ "./runner": {
37
+ "types": "./src/runner/index.ts",
38
+ "default": "./src/runner/index.ts"
39
+ },
40
+ "./reporters": {
41
+ "types": "./src/reporters/index.ts",
42
+ "default": "./src/reporters/index.ts"
43
+ },
44
+ "./faker": {
45
+ "types": "./src/faker.ts",
46
+ "default": "./src/faker.ts"
47
+ }
48
+ },
49
+ "dependencies": {
50
+ "uvu": "0.5.6"
51
+ },
52
+ "files": [
53
+ "src",
54
+ "!**/*.test.*"
55
+ ],
56
+ "scripts": {
57
+ "build": "tsc --noEmit",
58
+ "typecheck": "tsc --noEmit",
59
+ "test": "vitest run"
60
+ }
6
61
  }
package/src/assert.ts ADDED
@@ -0,0 +1,81 @@
1
+ // Instrumented uvu/assert: every call is recorded as pass/fail. Soft mode swallows failures.
2
+ import * as uvu from 'uvu/assert'
3
+ import type { Assert, AssertionResult, AssertMode } from './types'
4
+
5
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: matching ANSI escape sequences
6
+ const ANSI = /\u001b\[[0-9;]*m/g
7
+ export const stripAnsi = (s: string) => s.replace(ANSI, '')
8
+
9
+ /** Plain uvu/assert, for use outside a run. */
10
+ export const assert = uvu as unknown as Assert
11
+
12
+ function locate(stack: string | undefined): AssertionResult['location'] {
13
+ if (!stack) return undefined
14
+ const lines = stack.split('\n').slice(1)
15
+ for (const line of lines) {
16
+ if (line.includes('/core/src/assert') || line.includes('node_modules')) continue
17
+ const m = /(?:\(|at )?([^\s()]+):(\d+):(\d+)\)?\s*$/.exec(line)
18
+ if (m) return { file: m[1] as string, line: Number(m[2]), column: Number(m[3]) }
19
+ }
20
+ return undefined
21
+ }
22
+
23
+ export interface Asserter {
24
+ assert: Assert
25
+ results: AssertionResult[]
26
+ }
27
+
28
+ export function createAsserter(mode: AssertMode = 'soft'): Asserter {
29
+ const results: AssertionResult[] = []
30
+
31
+ const wrap = (name: string, fn: (...a: any[]) => void) => {
32
+ const msgIndex = fn.length - 1
33
+ return (...args: any[]) => {
34
+ const given = typeof args[msgIndex] === 'string' ? (args[msgIndex] as string) : undefined
35
+ try {
36
+ fn(...args)
37
+ results.push({
38
+ label: given ?? name,
39
+ operator: name,
40
+ pass: true,
41
+ location: locate(new Error().stack),
42
+ })
43
+ } catch (e: any) {
44
+ if (e?.name !== 'Assertion') throw e
45
+ results.push({
46
+ label: stripAnsi(String(e.message ?? given ?? name)),
47
+ operator: name,
48
+ pass: false,
49
+ actual: e.actual,
50
+ expected: e.expects,
51
+ diff: e.details ? stripAnsi(String(e.details)) : undefined,
52
+ location: locate(new Error().stack),
53
+ })
54
+ if (mode === 'hard') throw e
55
+ }
56
+ }
57
+ }
58
+
59
+ const u = uvu as unknown as Record<string, any>
60
+ const out: Record<string, any> = {}
61
+ for (const key of [
62
+ 'ok',
63
+ 'is',
64
+ 'equal',
65
+ 'type',
66
+ 'instance',
67
+ 'match',
68
+ 'throws',
69
+ 'unreachable',
70
+ 'snapshot',
71
+ 'fixture',
72
+ ]) {
73
+ if (typeof u[key] === 'function') out[key] = wrap(key, u[key])
74
+ }
75
+ const not = wrap('not', u.not)
76
+ for (const key of Object.keys(u.not ?? {})) {
77
+ if (typeof u.not[key] === 'function') (not as any)[key] = wrap(`not.${key}`, u.not[key])
78
+ }
79
+ out.not = not
80
+ return { assert: out as Assert, results }
81
+ }
package/src/config.ts ADDED
@@ -0,0 +1,69 @@
1
+ import type { FakerConfig } from './faker'
2
+ import type { AuthProfile } from './runner/run'
3
+ import type { SecretProvider, SecretRef } from './secrets'
4
+
5
+ /** What the UI shows for an environment beyond its variables. */
6
+ export interface EnvironmentConfig {
7
+ vars?: Record<string, unknown>
8
+ /**
9
+ * How careful the UI should look. `production` shows the environment in red wherever it is named;
10
+ * `sandbox` keeps it amber even when the name contains "prod". Unset: red if the name contains
11
+ * "prod", amber otherwise (`mock` is always neutral).
12
+ */
13
+ risk?: 'production' | 'sandbox'
14
+ }
15
+
16
+ export interface ServiceConfig<E extends string = string> {
17
+ /** Path to an OpenAPI/Swagger file (relative to the config's project root). */
18
+ spec: string
19
+ baseUrl: Partial<Record<E | 'live', string>>
20
+ /** Default auth profile for this service. */
21
+ auth?: string
22
+ }
23
+
24
+ /** How the docs site presents itself. Everything is optional; unset fields fall back to the API's own title. */
25
+ export interface BrandingConfig {
26
+ /** Header, overview and browser-tab title. */
27
+ name?: string
28
+ /** The overview's headline, under the logo: what this site is for. The name stays in the header and tab. */
29
+ title?: string
30
+ tagline?: string
31
+ /** Accent colour for buttons, selection and links (any hex colour). */
32
+ accent?: string
33
+ /** Accent in dark mode, when the light one needs to differ. */
34
+ accentDark?: string
35
+ /**
36
+ * Logo and favicon as URLs. Without these, drop `logo.svg` / `favicon.svg` (or png, jpg, webp)
37
+ * into `.specbook/branding/` and they are picked up.
38
+ */
39
+ logo?: string
40
+ favicon?: string
41
+ /** Links shown under the overview text. */
42
+ links?: { label: string; href: string }[]
43
+ /**
44
+ * Show "Powered by Specbook" under the overview of a published site (default). Set `false` to hide it.
45
+ * It is never shown by the dev server, or on a `*.specbook.dev` page.
46
+ */
47
+ badge?: boolean
48
+ }
49
+
50
+ export interface SpecbookConfig<E extends string = string> {
51
+ project: string
52
+ /** Non-sensitive variables, safe to commit. */
53
+ vars?: Record<string, unknown>
54
+ environments?: Partial<Record<E, EnvironmentConfig>>
55
+ /** Secret references; OSS resolves them from env vars / dotenv files. */
56
+ secrets?: Record<string, SecretRef>
57
+ services: Record<string, ServiceConfig<E>>
58
+ auth?: Record<string, AuthProfile>
59
+ secretProviders?: SecretProvider[]
60
+ faker?: FakerConfig
61
+ codegen?: { outDir?: string; importPrefix?: string }
62
+ branding?: BrandingConfig
63
+ }
64
+
65
+ export function defineConfig<const E extends string = string>(
66
+ c: SpecbookConfig<E>,
67
+ ): SpecbookConfig<E> {
68
+ return c
69
+ }
@@ -0,0 +1,167 @@
1
+ // Tiny Storybook-style controls DSL: runtime descriptors + inferred value types.
2
+
3
+ export interface Control<T = unknown> {
4
+ readonly kind:
5
+ | 'text'
6
+ | 'number'
7
+ | 'boolean'
8
+ | 'date'
9
+ | 'select'
10
+ | 'multiselect'
11
+ | 'object'
12
+ | 'array'
13
+ readonly opts: Record<string, any>
14
+ /** Phantom type carrier. */
15
+ readonly _type?: T
16
+ }
17
+
18
+ export type Controls = Record<string, Control<any>>
19
+ export type InferControls<P> = { [K in keyof P]: P[K] extends Control<infer T> ? T : never }
20
+
21
+ interface SelectOpts<O, V> {
22
+ /** shown beside the control in the UI */
23
+ description?: string
24
+ label?: (o: O) => string
25
+ value?: (o: O) => V
26
+ default?: number | V | ((options: readonly O[]) => V)
27
+ }
28
+
29
+ export const c = {
30
+ text: (o: { default?: string; label?: string; description?: string } = {}): Control<string> => ({
31
+ kind: 'text',
32
+ opts: o,
33
+ }),
34
+ number: (
35
+ o: { default?: number; min?: number; max?: number; label?: string; description?: string } = {},
36
+ ): Control<number> => ({
37
+ kind: 'number',
38
+ opts: o,
39
+ }),
40
+ boolean: (
41
+ o: { default?: boolean; label?: string; description?: string } = {},
42
+ ): Control<boolean> => ({
43
+ kind: 'boolean',
44
+ opts: o,
45
+ }),
46
+ date: (o: { default?: string; label?: string; description?: string } = {}): Control<string> => ({
47
+ kind: 'date',
48
+ opts: o,
49
+ }),
50
+ select: <const O, V = O>(
51
+ options: readonly O[],
52
+ o: SelectOpts<O, V> = {},
53
+ ): Control<NoInfer<V>> => ({
54
+ kind: 'select',
55
+ opts: { ...o, options },
56
+ }),
57
+ multiselect: <const O, V = O>(
58
+ options: readonly O[],
59
+ o: SelectOpts<O, V> = {},
60
+ ): Control<NoInfer<V>[]> => ({
61
+ kind: 'multiselect',
62
+ opts: { ...o, options },
63
+ }),
64
+ object: <S extends Controls>(shape: S): Control<InferControls<S>> => ({
65
+ kind: 'object',
66
+ opts: { shape },
67
+ }),
68
+ array: <T>(
69
+ item: Control<T>,
70
+ o: { default?: T[]; min?: number; max?: number } = {},
71
+ ): Control<T[]> => ({ kind: 'array', opts: { ...o, item } }),
72
+ }
73
+
74
+ export class ControlError extends Error {}
75
+
76
+ function optionValue(ctl: Control, option: unknown): unknown {
77
+ const fn = ctl.opts.value as ((o: unknown) => unknown) | undefined
78
+ return fn ? fn(option) : option
79
+ }
80
+
81
+ function same(a: unknown, b: unknown): boolean {
82
+ return a === b || JSON.stringify(a) === JSON.stringify(b)
83
+ }
84
+
85
+ /** Resolve one control to a value: given > default > first option / empty. */
86
+ export function resolveControl(name: string, ctl: Control, given: unknown): unknown {
87
+ switch (ctl.kind) {
88
+ case 'select': {
89
+ const options = ctl.opts.options as unknown[]
90
+ if (options.length === 0) throw new ControlError(`No options available for "${name}"`)
91
+ const values = options.map((o) => optionValue(ctl, o))
92
+ if (given !== undefined) {
93
+ if (!values.some((v) => same(v, given))) {
94
+ throw new ControlError(`Value for "${name}" is not one of the available options`)
95
+ }
96
+ return given
97
+ }
98
+ const d = ctl.opts.default
99
+ if (
100
+ typeof d === 'number' &&
101
+ Number.isInteger(d) &&
102
+ d >= 0 &&
103
+ d < values.length &&
104
+ !(typeof values[0] === 'number')
105
+ )
106
+ return values[d]
107
+ if (typeof d === 'function') return d(options)
108
+ if (d !== undefined) return d
109
+ return values[0]
110
+ }
111
+ case 'multiselect': {
112
+ const options = ctl.opts.options as unknown[]
113
+ if (given !== undefined) return given
114
+ const d = ctl.opts.default
115
+ if (typeof d === 'function') return d(options)
116
+ return d ?? []
117
+ }
118
+ case 'object': {
119
+ const shape = ctl.opts.shape as Controls
120
+ const g = (given ?? {}) as Record<string, unknown>
121
+ return Object.fromEntries(
122
+ Object.entries(shape).map(([k, v]) => [k, resolveControl(k, v, g[k])]),
123
+ )
124
+ }
125
+ case 'array': {
126
+ const v = (given ?? ctl.opts.default ?? []) as unknown[]
127
+ const { min, max } = ctl.opts
128
+ if (min !== undefined && v.length < min)
129
+ throw new ControlError(`"${name}" needs at least ${min} items`)
130
+ if (max !== undefined && v.length > max)
131
+ throw new ControlError(`"${name}" allows at most ${max} items`)
132
+ return v.map((item) => resolveControl(name, ctl.opts.item, item))
133
+ }
134
+ case 'boolean':
135
+ return given ?? ctl.opts.default ?? false
136
+ case 'number':
137
+ return given ?? ctl.opts.default ?? 0
138
+ default:
139
+ return given ?? ctl.opts.default ?? ''
140
+ }
141
+ }
142
+
143
+ export function resolveControls(
144
+ controls: Controls,
145
+ given: Record<string, unknown> = {},
146
+ ): Record<string, unknown> {
147
+ return Object.fromEntries(
148
+ Object.entries(controls).map(([k, ctl]) => [k, resolveControl(k, ctl, given[k])]),
149
+ )
150
+ }
151
+
152
+ /** Serializable description for a UI to render an input step. */
153
+ export function describeControls(controls: Controls) {
154
+ return Object.fromEntries(
155
+ Object.entries(controls).map(([k, ctl]) => {
156
+ if (ctl.kind === 'select' || ctl.kind === 'multiselect') {
157
+ const labelFn = ctl.opts.label as ((o: unknown) => string) | undefined
158
+ const options = (ctl.opts.options as unknown[]).map((o) => ({
159
+ label: labelFn ? labelFn(o) : String(optionValue(ctl, o)),
160
+ value: optionValue(ctl, o),
161
+ }))
162
+ return [k, { kind: ctl.kind, options }]
163
+ }
164
+ return [k, { kind: ctl.kind, default: ctl.opts.default }]
165
+ }),
166
+ )
167
+ }
package/src/example.ts ADDED
@@ -0,0 +1,80 @@
1
+ import { flattenForm, isBlob } from './runner/encode'
2
+ import type { OpMeta } from './types'
3
+
4
+ /** A complete request an API author (or the spec) defines for an operation. */
5
+ export interface Example {
6
+ name: string
7
+ title?: string
8
+ summary?: string
9
+ source: 'spec' | 'generated' | 'project'
10
+ path?: Record<string, unknown>
11
+ /** Arrays send the same key repeatedly. */
12
+ query?: Record<string, unknown>
13
+ headers?: Record<string, string>
14
+ body?: unknown
15
+ /** Directory file-field paths are relative to; set by whoever loads the example. */
16
+ dir?: string
17
+ }
18
+
19
+ export type ExampleLoader = (op: OpMeta, name: string) => Promise<Example | undefined>
20
+ export type FileReader = (path: string, dir?: string) => Promise<File>
21
+
22
+ /** One line of an inline grid editor. Disabled rows stay visible but are not sent. */
23
+ export interface Row {
24
+ key: string
25
+ value: string
26
+ enabled: boolean
27
+ }
28
+
29
+ const text = (v: unknown) =>
30
+ typeof v === 'string' ? v : typeof v === 'object' && v !== null ? JSON.stringify(v) : String(v)
31
+
32
+ export function toRows(o: Record<string, unknown> = {}): Row[] {
33
+ return Object.entries(o).flatMap(([key, v]) =>
34
+ (Array.isArray(v) ? v : [v])
35
+ .filter((x) => x !== undefined && x !== null)
36
+ .map((x) => ({ key, value: text(x), enabled: true })),
37
+ )
38
+ }
39
+
40
+ export function fromRows(rows: Row[]): Record<string, unknown> {
41
+ const out: Record<string, unknown> = {}
42
+ for (const { key, value, enabled } of rows) {
43
+ if (!enabled || key === '') continue
44
+ const had = out[key]
45
+ out[key] = had === undefined ? value : [...(Array.isArray(had) ? had : [had]), value]
46
+ }
47
+ return out
48
+ }
49
+
50
+ /** Rows for a form/multipart body: nested values become bracket keys (`metadata[order]`). Files stay as values. */
51
+ export function toFormRows(body: unknown): Row[] {
52
+ if (body === null || typeof body !== 'object') return []
53
+ return flattenForm(body).map(([key, value]) => ({ key, value, enabled: true }))
54
+ }
55
+
56
+ const SEGMENT = /[^[\]]+|\[\]/g
57
+
58
+ function assign(node: any, segs: string[], value: unknown): any {
59
+ const [head, ...rest] = segs as [string, ...string[]]
60
+ if (head === '[]') {
61
+ const list: unknown[] = node ?? []
62
+ list.push(rest.length ? assign(undefined, rest, value) : value)
63
+ return list
64
+ }
65
+ const target = node ?? (/^\d+$/.test(head) ? [] : {})
66
+ const had = target[head]
67
+ if (rest.length) target[head] = assign(had, rest, value)
68
+ else target[head] = had === undefined ? value : [...([] as unknown[]).concat(had), value]
69
+ return target
70
+ }
71
+
72
+ /** Inverse of `toFormRows`; disabled rows are dropped and a repeated plain key becomes an array. */
73
+ export function fromFormRows(rows: (Row & { file?: File })[]): Record<string, unknown> {
74
+ let out: Record<string, unknown> = {}
75
+ for (const { key, value, enabled, file } of rows) {
76
+ if (!enabled || key === '') continue
77
+ out = assign(out, key.match(SEGMENT) ?? [key], file && isBlob(file) ? file : value)
78
+ }
79
+ return out
80
+ }
package/src/faker.ts ADDED
@@ -0,0 +1,80 @@
1
+ // Locale-aware faker proxy. `@faker-js/faker` is a peer dependency of the user's project;
2
+ // the runtime (CLI / UI) installs the concrete instance for the selected locale before a run.
3
+
4
+ type FakerLike = Record<string, any>
5
+
6
+ // Held on globalThis so the CLI's bundled copy of core and the project's own import share one instance.
7
+ const KEY = '__specbookFaker'
8
+ const store = globalThis as unknown as Record<string, FakerLike | undefined>
9
+
10
+ export const faker: FakerLike = new Proxy({} as FakerLike, {
11
+ get(_t, key) {
12
+ const current = store[KEY]
13
+ if (!current) {
14
+ throw new Error(
15
+ 'faker is not initialised. Install "@faker-js/faker" in your project; specbook sets it up before each run.',
16
+ )
17
+ }
18
+ return current[key as string]
19
+ },
20
+ })
21
+
22
+ export function setFaker(instance: FakerLike | undefined): void {
23
+ store[KEY] = instance
24
+ }
25
+
26
+ export interface FakerConfig {
27
+ /** locale name -> fallback chain of faker locale export names, e.g. { de: ['de', 'en', 'base'] } */
28
+ locales?: Record<string, string[]>
29
+ default?: string
30
+ }
31
+
32
+ interface FakerModule {
33
+ Faker: new (opts: { locale: unknown[] }) => FakerLike
34
+ [locale: string]: unknown
35
+ }
36
+
37
+ /** The locale exports a run needs, in fallback order: the chain for `locale`, then `base`. */
38
+ export function fakerLocaleNames(config: FakerConfig = {}, locale?: string): string[] {
39
+ const name = locale ?? config.default ?? 'en'
40
+ return [...new Set([...(config.locales?.[name] ?? [name, 'en']), 'base'])]
41
+ }
42
+
43
+ /**
44
+ * The module `installFaker` needs. A bundled build offers each locale as `locales[name]()` so only
45
+ * the ones a run falls back through are fetched; a plain faker module already has them all.
46
+ */
47
+ export async function prepareFaker(
48
+ mod: Record<string, any>,
49
+ config: FakerConfig = {},
50
+ locale?: string,
51
+ ): Promise<FakerModule> {
52
+ const locales = mod.locales as Record<string, () => Promise<unknown>> | undefined
53
+ if (!locales) return mod as FakerModule
54
+ const ready: Record<string, unknown> = { Faker: mod.Faker }
55
+ await Promise.all(
56
+ fakerLocaleNames(config, locale).map(async (n) => {
57
+ const load = locales[n]
58
+ if (load) ready[n] = await load()
59
+ }),
60
+ )
61
+ return ready as FakerModule
62
+ }
63
+
64
+ /** Build a Faker for `locale` from the (dynamically imported) module, install it, and optionally seed it. */
65
+ export function installFaker(
66
+ mod: FakerModule,
67
+ config: FakerConfig = {},
68
+ opts: { locale?: string; seed?: number } = {},
69
+ ): FakerLike {
70
+ const name = opts.locale ?? config.default ?? 'en'
71
+ const locales = fakerLocaleNames(config, name).map((n) => {
72
+ const l = mod[n]
73
+ if (!l) throw new Error(`Unknown faker locale "${n}" (from "${name}")`)
74
+ return l
75
+ })
76
+ const instance = new mod.Faker({ locale: locales })
77
+ if (opts.seed !== undefined) instance.seed(opts.seed)
78
+ setFaker(instance)
79
+ return instance
80
+ }
package/src/index.ts ADDED
@@ -0,0 +1,27 @@
1
+ export { assert, createAsserter, stripAnsi } from './assert'
2
+ export * from './config'
3
+ export * from './controls'
4
+ export type { Example, ExampleLoader, FileReader, Row } from './example'
5
+ export { fromFormRows, fromRows, toFormRows, toRows } from './example'
6
+ export type { FakerConfig } from './faker'
7
+ export { faker, fakerLocaleNames, installFaker, prepareFaker, setFaker } from './faker'
8
+ export * from './mock'
9
+ export * from './proxy/index'
10
+ export * from './reporters/index'
11
+ export { defineOperation, request } from './request'
12
+ export * from './runner/index'
13
+ export * from './secrets'
14
+ export type { Sequence, SequenceMeta, StepCtx, StepNode } from './sequence'
15
+ export { sequence } from './sequence'
16
+ export type { SnippetInput, SnippetLang } from './snippets'
17
+ export { snippetLangs, toSnippet } from './snippets'
18
+ export { authFromSpec, type JsonAuthProfile, liveBaseUrl, type SpecAuth } from './spec-config'
19
+ export {
20
+ collectPlaceholders,
21
+ deepMerge,
22
+ render,
23
+ renderString,
24
+ TemplateError,
25
+ usesFaker,
26
+ } from './template'
27
+ export * from './types'