mirror of
https://github.com/langgenius/dify.git
synced 2026-11-13 06:41:07 +08:00
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
674 lines
33 KiB
Markdown
674 lines
33 KiB
Markdown
# @dify/openapi-content-mapper
|
|
|
|
Import OpenAPI documents as typed, tree-shakable API calls and TanStack Query helpers using **TypeScript 7.1 Content Mappers**. No generated SDK source or declaration files need to be committed.
|
|
|
|
**Experimental beta:** the compiler integration targets TypeScript `7.1.0-beta`. The API and compiler protocol may change between prereleases. This package is ESM-only.
|
|
|
|
## Compatibility
|
|
|
|
| Environment | Supported setup |
|
|
| --------------------- | ---------------------------------------------------------------------- |
|
|
| Compiler | TypeScript `7.1.0-beta`, with `--runExternalCode` |
|
|
| Editor | TypeScript 7 extension, workspace compiler and content mappers enabled |
|
|
| Build/runtime tooling | Node.js 24+ |
|
|
| Vite | Official Vite 8.3.5; optional peer dependency |
|
|
| Next.js | Next.js 16.4.2, Turbopack and Webpack with the loader below |
|
|
| Browser runtime | Modern Fetch, Headers, FormData, Blob and ReadableStream APIs |
|
|
| Specification | OpenAPI 3.0/3.1, local JSON Pointer references |
|
|
|
|
## Install and try
|
|
|
|
After the beta is published:
|
|
|
|
```sh
|
|
npm install @dify/openapi-content-mapper@beta
|
|
npm install --save-dev typescript@7.1.0-beta vite@8.3.5
|
|
```
|
|
|
|
Before publication, install a locally packed release candidate with `npm install /path/to/dify-openapi-content-mapper-0.1.0-beta.0.tgz`. See [Development and release] for packaging and verification.
|
|
|
|
Create `pets.openapi.yaml`:
|
|
|
|
```yaml
|
|
openapi: 3.1.0
|
|
info:
|
|
title: Pets
|
|
version: '1'
|
|
servers:
|
|
- url: https://api.example.com
|
|
paths:
|
|
/pets/{petId}:
|
|
get:
|
|
parameters:
|
|
- name: petId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
responses:
|
|
'200':
|
|
description: A pet
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [id, name]
|
|
properties:
|
|
id:
|
|
type: integer
|
|
name:
|
|
type: string
|
|
```
|
|
|
|
Configure `tsconfig.json`:
|
|
|
|
```json
|
|
{
|
|
"compilerOptions": {
|
|
"strict": true,
|
|
"target": "ES2022",
|
|
"module": "ESNext",
|
|
"moduleResolution": "Bundler",
|
|
"noEmit": true
|
|
},
|
|
"contentMappers": [
|
|
{
|
|
"package": "@dify/openapi-content-mapper",
|
|
"extensions": [".yaml", ".yml", ".openapi"]
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
Configure `vite.config.ts`:
|
|
|
|
```ts
|
|
import { openapi } from '@dify/openapi-content-mapper/vite'
|
|
import { defineConfig } from 'vite'
|
|
|
|
export default defineConfig({ plugins: [openapi()] })
|
|
```
|
|
|
|
Then call the API:
|
|
|
|
```ts
|
|
import { api, query } from './pets.openapi.yaml'
|
|
|
|
const pet = await api.pets.byPetId.get({ params: { petId: 1 } })
|
|
pet.name // string
|
|
|
|
const options = query.pets.byPetId.get.queryOptions({
|
|
input: { params: { petId: 1 } },
|
|
})
|
|
// Pass options to useQuery(), queryClient.fetchQuery(), etc.
|
|
```
|
|
|
|
Run `npx tsc --runExternalCode --noEmit`. The compiler supplies types; the Vite plugin supplies runtime modules. Both are required for this setup.
|
|
|
|
For VS Code, install Microsoft's TypeScript 7 extension (`TypeScriptTeam.native-preview`), select the workspace TypeScript 7.1 compiler, trust the workspace and enable content mappers. Open a consuming `.ts` file so the language server discovers its config.
|
|
|
|
### Compiler cache
|
|
|
|
Successful mapper results are cached in the current user's temporary directory under `dify-openapi-content-mapper-<user>`, retaining up to 64 documents. Content, implementation and configuration-sidecar presence changes invalidate the cache; sidecar contents are checked by TypeScript. The cache can be deleted safely. Corrupt entries or unavailable storage fall back to transformation, and no cache files are shipped to the browser.
|
|
|
|
### Existing JSON files
|
|
|
|
The TS 7.1 beta cannot register mappers for built-in `.json` imports, including `.openapi.json`. Store JSON in a file ending in `.openapi`, or symlink an existing JSON file:
|
|
|
|
```sh
|
|
ln -s pets.json pets.openapi
|
|
```
|
|
|
|
Import `./pets.openapi`. On platforms where symlinks are unavailable, use a regular `.openapi` file containing JSON. Vite and Node preserve the logical extension of symlinks.
|
|
|
|
### Local backend sources without generated specification files
|
|
|
|
Use an authored `pets.openapi-source` descriptor when the backend can export its
|
|
OpenAPI document to stdout. The descriptor contains the command, not the schema:
|
|
|
|
```json
|
|
{
|
|
"command": ["python", "export_openapi.py"],
|
|
"cwd": "../backend",
|
|
"inputs": ["**/*.py", "pyproject.toml"],
|
|
"exclude": ["tests/**"],
|
|
"optionalInputs": [".env"],
|
|
"env": ["API_EDITION"],
|
|
"timeoutMs": 60000
|
|
}
|
|
```
|
|
|
|
`cwd` is relative to the descriptor; input glob patterns are relative to `cwd`.
|
|
Declare every source/configuration input and any environment variables that affect
|
|
exported schemas. Input globs must match existing paths; `exclude` removes glob
|
|
matches that cannot affect the contract. `optionalInputs` lists exact paths that
|
|
may not exist yet, such as `.env`; their creation, changes and removal invalidate
|
|
the source. Dependency scanning excludes
|
|
`node_modules`, `.venv`, `.git` and `__pycache__`. Commands run without a shell;
|
|
`node` uses the current Node executable. Write exactly one OpenAPI JSON document to
|
|
stdout and send diagnostics to stderr. No running backend is needed if the exporter
|
|
can construct its schemas without starting services.
|
|
|
|
Register both the extension and the source in the consuming tsconfig:
|
|
|
|
```json
|
|
{
|
|
"contentMappers": [
|
|
{
|
|
"package": "@dify/openapi-content-mapper",
|
|
"extensions": [".openapi-source", ".openapi", ".yaml", ".yml"],
|
|
"options": { "sources": ["./pets.openapi-source"] }
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
Source entries are relative to this tsconfig's directory; inherited configurations
|
|
should declare them in the consuming project. Registration lets TypeScript watch
|
|
backend inputs and invalidate incremental builds even when the descriptor is
|
|
unchanged. Keep the descriptor in a package with an SDK dependency so its inferred
|
|
types can resolve the SDK. Then import it normally:
|
|
|
|
```ts
|
|
import { api, query } from './pets.openapi-source'
|
|
```
|
|
|
|
Vite and Node recognize `.openapi-source` automatically. Add a matching loader rule
|
|
for Next.js, as for an ordinary `.openapi` file. Existing document imports continue
|
|
to work alongside command sources. Configuration, queries and errors use the same
|
|
adjacent sidecars, such as `pets.openapi-source.config.ts`.
|
|
|
|
The editor, type checker and runtime adapters execute the same command-source
|
|
resolver. Exported OpenAPI content is cached in process memory until its declared
|
|
inputs change; the mapper's temporary cache stores derived types, not a committed
|
|
specification. Each new process needs the backend export environment. The exporter
|
|
and schema do not ship to the browser. Definitions from a command source have no
|
|
schema-file location; file imports retain their existing source navigation.
|
|
|
|
The repository Console exporter additionally shares its in-memory result between
|
|
compiler, editor and build processes through a local POSIX helper. It starts on
|
|
demand and exits after two idle minutes. A private Unix socket and empty lifecycle
|
|
lock are the only IPC files; OpenAPI content is never written to disk. The helper
|
|
is specific to the Console source, not enabled for arbitrary exporter commands.
|
|
|
|
On changes, a fresh child imports the current controllers from source, with safe
|
|
model dependencies preloaded. Changing a preloaded dependency, adding/removing a
|
|
Python file, changing `.env`, or changing the environment invalidates that state.
|
|
Python bytecode caches are bypassed to handle rapid same-size edits. Export errors
|
|
are surfaced rather than serving the old contract. Refreshes interrupted by more
|
|
source edits are retried. Console and Community type descriptors track Python
|
|
sources in `api/` and the editable `dify-agent/src/` dependency, together with their
|
|
package configuration. Backend tests are excluded from Console source inputs.
|
|
Native threads are checked before forking; unsafe snapshots and platforms without
|
|
fork use a fresh-process export instead. Those refreshes still incur cold startup.
|
|
|
|
To stop the local helper early, run from the same directory and environment used
|
|
by the source descriptor:
|
|
|
|
```sh
|
|
uv run --project api api/dev/cached_console_openapi.py --stop
|
|
```
|
|
|
|
The uncached reference exporter remains `api/dev/export_console_openapi.py`.
|
|
Command-source JSON is transformed directly, avoiding the YAML syntax tree and
|
|
source mappings needed only for file-backed documents.
|
|
|
|
### Navigate to backend implementations
|
|
|
|
An operation can carry optional development metadata:
|
|
|
|
```json
|
|
"x-source-location": { "file": "/checkout/api/controllers/pets.py", "line": 42 }
|
|
```
|
|
|
|
`line` is one-based. Relative paths resolve against the imported OpenAPI file or
|
|
`.openapi-source` descriptor. The mapper adds an **Open backend implementation**
|
|
link to the method's hover documentation, for both `api` and `query`. This works
|
|
with command sources and imported OpenAPI files. Metadata is excluded from runtime
|
|
operation descriptors and browser bundles.
|
|
|
|
For F12 / Cmd+click and Go to Implementation, install the optional local
|
|
[VS Code extension]. It uses the same hover link to find the
|
|
backend target. Without source metadata, normal schema navigation is preserved;
|
|
the extension skips backend files that are not present locally.
|
|
|
|
The Dify Console stdout exporter adds these locations automatically, including
|
|
unwrapping decorated handlers. This affects only the local in-memory export;
|
|
live API schemas and retained Enterprise snapshots are unchanged.
|
|
|
|
## API and transport
|
|
|
|
Documents export `api` and `query` at runtime, plus the type-only `Schemas`, `Inputs` and `Outputs` maps; operation IDs are not exports. `/pets/{petId}` becomes `pets.byPetId`, hyphenated segments become camelCase and HTTP methods are leaf functions. Colon actions such as `/chat:run` become `chat.run`. `key` is reserved as a route segment for query helpers. Unresolvable member collisions fail during compilation.
|
|
|
|
An operation may set `x-client-path` to an explicit array such as `["enterprise", "access", "getEnvironmentApi"]` to preserve an existing public API. This replaces its entire client member path, including the method leaf, and also determines its query key. HTTP methods, URLs and per-operation sidecar keys still use the OpenAPI operation. Colliding or reserved member names fail during compilation.
|
|
|
|
The first argument contains `params`, `query`, `headers`, `cookie`, `body` and `contentType` as declared by the specification. The optional second argument accepts `client`, `signal`, additional transport `headers`, `credentials` and document `context`.
|
|
|
|
```ts
|
|
import { createClient } from '@dify/openapi-content-mapper'
|
|
import { api } from './pets.openapi.yaml'
|
|
|
|
const transport = createClient({
|
|
baseUrl: 'https://api.example.com/v1',
|
|
headers: { Authorization: 'Bearer …' },
|
|
fetch: globalThis.fetch,
|
|
})
|
|
|
|
await api.pets.byPetId.get(
|
|
{ params: { petId: 1 } },
|
|
{ client: transport, signal: AbortSignal.timeout(5000) },
|
|
)
|
|
```
|
|
|
|
A client override takes precedence over document configuration. Otherwise the document's `servers` URL is used; relative server URLs require a browser origin or an explicit absolute `baseUrl` in Node. The exported shared `client` also supports `setConfig()`, but request-specific credentials should use independent clients.
|
|
|
|
Successful calls return decoded response data. Non-2xx responses throw `ApiError` with `response` and decoded `data`; network and cancellation failures propagate. Responses are not runtime-validated and schema defaults are not filled in. JSON binary-formatted fields remain strings; binary response media return Blob objects (File with the response filename when Content-Disposition is present); SSE returns a raw `ReadableStream<Uint8Array>`.
|
|
|
|
Wildcard response media follow the most specific matching declaration for the response status: `*/*` and `application/*` return Blob, while `text/*` returns text. An explicit `application/json` or `text/event-stream` declaration takes precedence over a wildcard. HTTP errors are decoded from their actual Content-Type.
|
|
|
|
With one request media type, `contentType` can be omitted. With multiple media types, only `application/json` is implicit; other alternatives require an explicit matching `contentType`. Multipart accepts flat objects with scalar, Blob/File or repeated array fields, plus explicit JSON part encodings. Fetch supplies its boundary. Optional request bodies may be omitted entirely.
|
|
|
|
### Reuse model, input and output types
|
|
|
|
See [Petstore type examples] for reusable aliases and
|
|
a legacy request envelope, and [the runnable demo]
|
|
for typed HTTP calls. Run `pnpm --filter @dify/openapi-content-mapper type-check`
|
|
to check the examples, or `pnpm --filter @dify/openapi-content-mapper demo`
|
|
to execute them against a local server started by the demo.
|
|
|
|
Import types from the same document as the API. This works for YAML, JSON `.openapi`
|
|
files and `.openapi-source` command sources, without generating files:
|
|
|
|
```ts
|
|
import type { Schemas, Inputs, Outputs } from './pets.openapi.yaml'
|
|
|
|
type Pet = Schemas['Pet']
|
|
type CreatePetBody = Inputs['pets']['post']['body']
|
|
type GetPetInput = Inputs['pets']['byPetId']['get']
|
|
type PetList = Outputs['pets']['get']
|
|
type ListQuery = NonNullable<Inputs['pets']['get']>['query']
|
|
```
|
|
|
|
`Schemas` contains every `components.schemas` entry under its original name,
|
|
including unused and recursive models. Names containing `/` or `~` are accessed
|
|
with their original spelling. It describes JSON values: binary-formatted strings
|
|
remain `string`. For a multipart body, use `Inputs` to get the appropriate
|
|
`Blob`/`File`-compatible fields. Documents without component schemas export an
|
|
empty `Schemas` map.
|
|
|
|
`Inputs` and `Outputs` follow the `api` member paths, including `x-client-path`
|
|
overrides. Each input is the operation's first argument, equivalent to
|
|
`Parameters<typeof api.pets.get>[0]`; optional inputs include `undefined`. It does
|
|
not include the second argument's transport options or context. Each output is
|
|
the awaited successful result, equivalent to `Awaited<ReturnType<...>>`, including
|
|
`void` for empty responses, `Blob` for binary responses and streams for SSE.
|
|
Inline schemas and media-type discrimination are preserved. All three exports
|
|
are erased from JavaScript; use `import type` or `export type` to consume them.
|
|
|
|
### Dify contract type subpaths
|
|
|
|
Inside the Dify repository, import compatible contract types directly from this
|
|
package's subpaths. Existing type names and structures are preserved; only the
|
|
module specifier changes:
|
|
|
|
```ts
|
|
import type {
|
|
DatasetCreatePayload,
|
|
PostDatasetsData,
|
|
PostDatasetsResponses,
|
|
PostDatasetsResponse,
|
|
PostDatasetsErrors,
|
|
PostDatasetsError,
|
|
} from '@dify/openapi-content-mapper/console/datasets'
|
|
|
|
const body: DatasetCreatePayload = { name: 'Documentation' }
|
|
const request: PostDatasetsData = { url: '/datasets', body }
|
|
type Created = PostDatasetsResponses[201]
|
|
type Conflict = PostDatasetsErrors[409]
|
|
```
|
|
|
|
Available subpaths are `/console/*`, `/service`, `/web`, `/openapi`, `/enterprise`
|
|
and `/enterprise-app-deploy`. They provide models, `ClientOptions`, legacy `*Data`
|
|
request envelopes, status-indexed `*Responses` / `*Errors`, and the singular
|
|
response/error unions. Same-named types stay isolated in their modules. The
|
|
original `@dify/contracts` exports remain unchanged; no `customConditions` or
|
|
conditional overrides of those paths are needed.
|
|
|
|
These subpaths are **type-only**. For runtime enum objects, continue to import the
|
|
original contracts module, while the mapper subpath can supply the type:
|
|
|
|
```ts
|
|
import type { AccessMode } from '@dify/openapi-content-mapper/enterprise'
|
|
import { AccessMode as AccessModes } from '@dify/contracts/enterprise/types.gen'
|
|
|
|
const mode: AccessMode = AccessModes.ACCESS_MODE_PRIVATE
|
|
```
|
|
|
|
Enable `.openapi-types` in the consuming project's mapper settings. The mapper
|
|
automatically registers the contract sources shipped in its workspace package:
|
|
|
|
```json
|
|
{
|
|
"contentMappers": [
|
|
{
|
|
"package": "@dify/openapi-content-mapper",
|
|
"extensions": [".openapi", ".openapi-source", ".openapi-types"]
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
No `options.sources` or `paths` entries are needed for these package subpaths.
|
|
Registration watches descriptors and backend inputs without running exporters;
|
|
exporters run when their modules are imported. Modules sharing inputs reuse one
|
|
filesystem scan per registration. Custom `.openapi-source` and `.openapi-types`
|
|
files outside the package's `contracts/` directory still require `options.sources`,
|
|
with paths relative to the consuming tsconfig. Explicit sources are combined with
|
|
the automatic sources. Run TypeScript 7.1 with `--runExternalCode`, and enable
|
|
content mappers in the editor. The [repository compatibility example]
|
|
and its tsconfig already use these subpaths:
|
|
|
|
```sh
|
|
pnpm --filter @dify/openapi-content-mapper type-check:contracts
|
|
```
|
|
|
|
The compatibility exporter reuses the same Community document normalization,
|
|
Console module splitting and Hey API TypeScript plugin as regular generation.
|
|
It exports current Python schemas into memory and runs Hey API with disk output
|
|
and cleanup disabled. Request `path`/`url`, status maps, writable variants,
|
|
recursive models, binary types and enum naming therefore follow the existing
|
|
contracts rules, rather than the mapper RPC `Inputs`/`Outputs` rules. Backend
|
|
contract changes still change the inferred types.
|
|
|
|
Type hovers include **Open backend implementation** links for registered Community
|
|
Python models and API request/response/error envelopes. With the local
|
|
[VS Code extension], F12 / Cmd+click and Go to
|
|
Implementation follow these links, including through import aliases and re-exports.
|
|
In the example, `DatasetCreatePayload` opens its Python class and `PostDatasetsData`
|
|
opens `DatasetListApi.post`. Synthetic models and snapshots without source metadata
|
|
have no backend target; the exporter does not guess by type name.
|
|
|
|
Enterprise and App Deploy use the retained OpenAPI snapshots. These snapshots omit
|
|
some non-Console models; missing exports are re-exported from the existing complete
|
|
contracts modules. Those retained types update with regular Enterprise generation,
|
|
rather than with unavailable private backend sources.
|
|
|
|
The three contracts generation commands also regenerate the mapper's thin subpath
|
|
selectors and `.d.ts` entry points. To refresh them alone after adding modules, run
|
|
`pnpm --filter @dify/contracts gen-compatibility-sources`. Each selector contains
|
|
only an `extends` path and module argument; exporter commands and watched inputs
|
|
live in two shared source configurations. Each declaration entry contains only an
|
|
`export type *` pointing to its selector; model declarations remain virtual and new
|
|
names are discovered automatically. No additional OpenAPI snapshots are written.
|
|
|
|
Package exports resolve through these standard `.d.ts` entries so TypeScript 7's
|
|
language service does not report a missing-declaration hint for the custom source
|
|
extension. Consumers need no `compilerOptions.paths` overrides or ambient module
|
|
declarations; they only enable the mapper and extension as shown above.
|
|
|
|
Dify-specific subpaths require the repository's Python and JavaScript development
|
|
dependencies, but no running API or database. They are workspace integrations and
|
|
are excluded from the standalone npm SDK's published exports and files. The
|
|
standalone package continues to support generic OpenAPI imports. Production
|
|
consumers of `@dify/contracts` are unaffected.
|
|
|
|
#### Type-only command sources
|
|
|
|
`.openapi-types` uses the same descriptor shape, watched-input validation and
|
|
cache invalidation as `.openapi-source`. Its exporter writes JSON of the form
|
|
`{"typescript":"export type Example = { id: string };"}` to stdout. The mapper
|
|
uses this TypeScript module as virtual compiler input. Exporter logs belong on
|
|
stderr, and empty or malformed results are diagnostics, not cached successes.
|
|
These files are for type resolution, not runtime imports. Dify subpaths expose
|
|
only a `types` target; runtime imports continue to use the original contracts. Ordinary OpenAPI imports still export `Schemas`,
|
|
`Inputs`, `Outputs`, `api` and `query` as described above.
|
|
|
|
Descriptors can share a configuration with `extends`. `extends` and `cwd` paths
|
|
are resolved from the descriptor that defines them; input patterns are relative
|
|
to the effective `cwd`. Other fields replace inherited values, and `args` is
|
|
appended to the effective command. Every inherited descriptor
|
|
is watched and included in cache invalidation. For example:
|
|
|
|
```json
|
|
{
|
|
"extends": "../community.source.json",
|
|
"args": ["api/console/datasets"]
|
|
}
|
|
```
|
|
|
|
### Document configuration
|
|
|
|
Place `pets.openapi.yaml.config.ts` next to `pets.openapi.yaml`:
|
|
|
|
```ts
|
|
import type { RuntimeConfig } from '@dify/openapi-content-mapper'
|
|
import { createClient } from '@dify/openapi-content-mapper'
|
|
|
|
export type Context = { token?: string }
|
|
|
|
const runtime: RuntimeConfig<Context> = {
|
|
createClient: ({ context }) =>
|
|
createClient({
|
|
baseUrl: 'https://api.example.com',
|
|
headers: context?.token ? { Authorization: `Bearer ${context.token}` } : {},
|
|
}),
|
|
queryKeyPrefix: ['pets'],
|
|
}
|
|
export default runtime
|
|
```
|
|
|
|
Both `api` and `query` infer `Context`. The factory runs per call. An optional `onError(error)` hook observes failures; avoid throwing from it if you want the original error to propagate. Restart the development server after adding or removing a sidecar; edits to existing files follow normal HMR.
|
|
|
|
### Per-operation query defaults and typed errors
|
|
|
|
Export defaults from `<document>.queries.ts` under the exact `METHOD /path` name:
|
|
|
|
```ts
|
|
// pets.openapi.queries.ts
|
|
import type { experimental_RouterUtilsDefaults } from '@orpc/tanstack-query'
|
|
import type { api } from './pets.openapi'
|
|
|
|
const list: experimental_RouterUtilsDefaults<typeof api.pets.get> = {
|
|
queryOptions: { staleTime: 30_000, retry: false },
|
|
}
|
|
export { list as 'GET /pets' }
|
|
```
|
|
|
|
Defaults use oRPC's query helpers, including mutation callbacks and infinite queries.
|
|
Caller options override defaults, including callbacks; they are not composed. Return
|
|
cache invalidation promises from mutation callbacks when the mutation must remain
|
|
pending until refresh completes. Keep policy dependencies independent of a complete
|
|
runtime router: importing or enumerating a whole client can retain unrelated operations.
|
|
|
|
An optional `<document>.errors.ts` exports oRPC error maps with the same names:
|
|
|
|
```ts
|
|
// pets.openapi.errors.ts
|
|
import type { ErrorMap } from '@orpc/contract'
|
|
import { conflictSchema } from './error-schemas'
|
|
|
|
const conflicts = {
|
|
CONFLICT: { status: 409, data: conflictSchema },
|
|
} satisfies ErrorMap
|
|
export { conflicts as 'POST /pets' }
|
|
```
|
|
|
|
The `data` validator can use any Standard Schema implementation. API calls and query
|
|
helpers infer the map's error union, so `safe` and `isDefinedError` narrow the code and
|
|
validated payload. HTTP status and schema validation must match before an error is
|
|
marked defined. Network/cancellation failures keep their identity. Bare HTTP errors
|
|
with a `code` envelope are converted to `ORPCError` when an error map is configured;
|
|
existing transport `ORPCError`s are preserved and validated.
|
|
|
|
Each operation imports its own defaults and lazy error module. Error validators load
|
|
on HTTP failure, and unused maps/defaults can be removed by the bundler. Vite
|
|
application builds group retained error modules by document to avoid a tiny chunk per
|
|
operation, while keeping validators lazy and respecting custom chunk groups. Strict library exports and legacy manual chunk
|
|
configurations keep the bundler's existing chunking behavior. A validator
|
|
chunk that fails to load leaves the original HTTP error undefined. Serve the emitted
|
|
chunks together with the entry bundle. Keep validators free of module-level side
|
|
effects; schema construction must be removable when its map is unused. Named
|
|
re-exports and `export *` are supported; sidecars are inspected without executing them.
|
|
Vite and Next track sidecar changes; Node imports follow normal module caching.
|
|
Sidecar star re-exports use the consuming bundler's resolver. Vite supports aliases and TypeScript extension resolution (such as `./defaults.js` resolving to `defaults.ts`); Next.js Webpack requires the same `experimental.extensionAlias` configuration as ordinary imports for that convention. For Turbopack, use explicit relative source paths such as `./defaults.ts`: its loader resolver does not support those aliases. Native Node execution retains Node's ESM resolution rules and requires executable import paths.
|
|
|
|
### Query helpers and bundles
|
|
|
|
Import `query` directly for `queryOptions`, `mutationOptions`, `queryKey` and namespace `key()` helpers. Give each document a distinct `queryKeyPrefix` when routes overlap. For an application-level entry, preserve direct ESM re-exports:
|
|
|
|
```ts
|
|
export { api as apiClient, query as apiQuery } from './pets.openapi.yaml'
|
|
```
|
|
|
|
Static calls retain only used operations. Dynamic indexing, enumeration or passing whole namespaces to opaque functions can retain other operations. The runtime does not bundle response schemas or the YAML parser.
|
|
|
|
### Comparison with oRPC
|
|
|
|
This package consumes OpenAPI directly and prunes statically used operations while keeping oRPC-shaped calls and reusing its TanStack Query helpers. It requires the TS 7.1 beta toolchain. Per-operation defaults and error maps are opt-in; arbitrary oRPC Links and interceptors still require an application transport adapter.
|
|
|
|
## Other runtime adapters
|
|
|
|
### Node.js
|
|
|
|
```sh
|
|
node --import @dify/openapi-content-mapper/register app.ts
|
|
```
|
|
|
|
The hook loads YAML, `.openapi` and `.openapi-source` files; Node's native TypeScript support loads configuration sidecars. Native execution does not replace the separate type check.
|
|
|
|
### Next.js
|
|
|
|
For `pets.openapi` linked to `pets.json`, configure `next.config.mjs`:
|
|
|
|
```js
|
|
const loader = {
|
|
loader: '@dify/openapi-content-mapper/loader',
|
|
// Optional; relative to the resolved specification, not this config file.
|
|
options: {
|
|
runtime: './pets.openapi.config.ts',
|
|
sidecars: './pets.openapi',
|
|
},
|
|
}
|
|
|
|
export default {
|
|
turbopack: {
|
|
rules: {
|
|
'*pets.json': { loaders: [loader], as: '*.js' },
|
|
'*openapi-namespace.js': { loaders: ['@dify/openapi-content-mapper/loader'], as: '*.js' },
|
|
},
|
|
},
|
|
webpack(config) {
|
|
config.module.rules.push({
|
|
test: /pets\.json$/,
|
|
type: 'javascript/auto',
|
|
use: [loader],
|
|
})
|
|
config.module.rules.push({
|
|
test: /openapi-namespace\.js$/,
|
|
type: 'javascript/auto',
|
|
use: ['@dify/openapi-content-mapper/loader'],
|
|
})
|
|
return config
|
|
},
|
|
}
|
|
```
|
|
|
|
Merge these rules into existing configuration. Match the resolved JSON filename for symlinks; match the actual `.openapi` or YAML filename for regular files. Apply the loader to `openapi-namespace.js` too, without document-specific options. Each document carries its own runtime and sidecar bindings into its child namespaces, so multiple documents can use different transports in one build. Omit `options.runtime` to discover `<resolved specification filename>.config.ts`. Set `options.sidecars` to the logical document path (relative to the resolved specification) when its `.queries.ts` and `.errors.ts` files live beside a symlink. Without this option they are discovered beside the resolved specification. Next.js runtime options do not change compiler context discovery, which uses the imported document's adjacent sidecar.
|
|
|
|
The loader uses a tiny shared resource for child namespaces so the full specification is not copied into every bundler module. Adapters use internal subpaths (`runtime`, `query-runtime`, `namespace`, `mapper`); application code should use the root, `vite`, `loader` and `register` entry points.
|
|
|
|
## Limitations
|
|
|
|
External references, Swagger 2, URL-encoded forms, advanced parameter serialization, nested multipart file dictionaries and YAML aliases are unsupported. Inline schema inference has a 24-step fallback; local referenced schemas use recursive aliases. Unsupported request serialization fails explicitly. Missing or inaccurate OpenAPI declarations cannot be repaired by inference.
|
|
|
|
Existing oRPC Links and interceptors require explicit transport adapters; query policies and typed errors require the per-operation sidecars above. Browser Fetch governs cookie headers. Supported schema features include required/optional properties, enum/const, arrays and dictionaries, `oneOf`, `anyOf`, `allOf`, and nullable types.
|
|
|
|
## Repository setup
|
|
|
|
The Dify workspace configures TypeScript 7.1, VS Code, Vite and Next.js to support
|
|
future OpenAPI imports. Production Console and Marketplace calls continue to use
|
|
their existing oRPC clients. Application transports, query-policy migration and
|
|
business call-site changes are deferred.
|
|
|
|
The repository Console source descriptor lives in
|
|
`examples/repository/console.openapi-source`. Its command exports community Console
|
|
and FastOpenAPI schemas from local Python code into memory, then composes them with
|
|
the retained Enterprise and App Deploy snapshots. Community specifications and the
|
|
composed document are not committed or written during workspace installation.
|
|
Ordinary installation and production application builds do not execute the exporter;
|
|
consuming the experimental Console source requires Python dependencies:
|
|
|
|
```sh
|
|
uv sync --project api --frozen
|
|
pnpm --filter @dify/openapi-content-mapper build
|
|
pnpm --filter @dify/openapi-content-mapper check:console
|
|
```
|
|
|
|
No running API, database or private source checkout is needed. CI installs the
|
|
Python environment before the standalone Console checks. `compose:console` now
|
|
prints the composed OpenAPI JSON to stdout instead of creating a file.
|
|
|
|
Open [the real Dify type examples] to inspect dataset
|
|
models, list/create inputs and outputs, multipart file uploads, and compatibility
|
|
aliases retaining existing contracts names and request envelopes. The example
|
|
imports `Schemas`, `Inputs` and `Outputs` from the live Console source descriptor;
|
|
its sample values do not send requests. Check all repository examples with:
|
|
|
|
```sh
|
|
pnpm --filter @dify/openapi-content-mapper exec tsc --runExternalCode --noEmit -p examples/repository/tsconfig.json
|
|
```
|
|
|
|
The package uses `vp pack` and builds through `prepare` during workspace installation,
|
|
like `@langgenius/dev-proxy`. Web's `prebuild` and `prebuild:vinext` hooks also build
|
|
the SDK so production builds work after an installation with `--ignore-scripts`,
|
|
including the unchanged Docker build flow.
|
|
|
|
The Console audit checks every generated procedure against a reviewed per-operation
|
|
type-difference baseline. Standalone fixtures verify bundle pruning, error maps and
|
|
workspace editor support without importing a production mapper client. The broader
|
|
`test:repository` suite also needs the ignored API specifications generated with
|
|
`pnpm --dir packages/contracts gen-api-contract`.
|
|
|
|
Regenerate existing oRPC contracts when backend schemas change. Enterprise snapshot
|
|
regeneration still uses the matching source checkout through
|
|
`DIFY_ENTERPRISE_SERVER`; those snapshots remain tracked because community checkouts
|
|
cannot regenerate them. Inspect the comparison report before accepting intentional
|
|
baseline changes with `compare:orpc -- --console --update-baseline`.
|
|
|
|
## Development and release
|
|
|
|
From the repository root, build and verify the package:
|
|
|
|
```sh
|
|
pnpm --filter @dify/openapi-content-mapper build
|
|
pnpm --filter @dify/openapi-content-mapper type-check
|
|
pnpm --filter @dify/openapi-content-mapper test
|
|
OPENAPI_PACKAGE_TARBALL=/tmp/dify-openapi-content-mapper.tgz \
|
|
pnpm --filter @dify/openapi-content-mapper test:package
|
|
```
|
|
|
|
The independent package check packs and installs the tarball outside the monorepo, then exercises its declarations, compiler mapper, Node runtime and official Vite adapter. It requires network access and performs no publication. Full application and Next.js builds run in CI.
|
|
|
|
Update the package version and [CHANGELOG] together before release. Use pnpm to pack: it resolves workspace `catalog:` dependencies, while `prepack` cleans and rebuilds `dist`. The package contains compiled ESM/declarations, README, changelog and license; consumers do not build it during installation.
|
|
|
|
A maintainer with npm access can publish the same verified artifact after confirming its name, version, license and registry:
|
|
|
|
```sh
|
|
npm publish /tmp/dify-openapi-content-mapper.tgz --access public --tag beta
|
|
```
|
|
|
|
The default tag is `beta`. Publication is manual; merging changes or creating a Dify release does not publish the package. Verify new compiler versions before changing the supported TS 7.1 beta protocol.
|
|
|
|
## License
|
|
|
|
This package retains the repository's modified Apache 2.0 terms, including its additional conditions. See [LICENSE]; it is not licensed under unmodified Apache-2.0 or MIT.
|
|
|
|
[CHANGELOG]: CHANGELOG.md
|
|
[Development and release]: #development-and-release
|
|
[LICENSE]: LICENSE
|
|
[Petstore type examples]: examples/petstore/types.ts
|
|
[VS Code extension]: ./vscode/README.md
|
|
[repository compatibility example]: examples/repository/compatibility.ts
|
|
[the real Dify type examples]: examples/repository/types.ts
|
|
[the runnable demo]: examples/petstore/demo.ts
|