@dynamicforms/fastapi-viewsets 0.5.7 → 0.6.1
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/dist/fastapi-viewsets.js +142 -120
- package/dist/fastapi-viewsets.js.map +1 -1
- package/dist/fastapi-viewsets.umd.cjs +2 -2
- package/dist/fastapi-viewsets.umd.cjs.map +1 -1
- package/dist/index.d.ts +695 -11
- package/package.json +6 -6
- package/dist/index.d.ts.map +0 -1
- package/dist/mixins.d.ts +0 -199
- package/dist/mixins.d.ts.map +0 -1
- package/dist/muxws-proxy.d.ts +0 -69
- package/dist/muxws-proxy.d.ts.map +0 -1
- package/dist/proxy-base.d.ts +0 -159
- package/dist/proxy-base.d.ts.map +0 -1
- package/dist/rest-proxy.d.ts +0 -84
- package/dist/rest-proxy.d.ts.map +0 -1
- package/dist/viewset.d.ts +0 -69
- package/dist/viewset.d.ts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fastapi-viewsets.umd.cjs","names":[],"sources":["../vue/mixins.ts","../vue/proxy-base.ts","../vue/muxws-proxy.ts","../vue/rest-proxy.ts","../vue/viewset.ts"],"sourcesContent":["/**\n * FE counterpart of BE mixins.py — the mixins a ViewSet declaration is composed of.\n *\n * Each mixin is an interface merged into a class of the same name. The interface names the actions\n * and their signatures; the class carries the `actions` list that the schema check reads at\n * runtime. Both halves are needed because neither alone can do the job: a TypeScript `implements`\n * clause is erased before anything runs, and a runtime list of strings says nothing about types.\n *\n * class ItemViewSet extends restViewSet<Item>()('id', [ReadOnlyViewSetMixin, LookupMixin]) {}\n *\n * The list is written once, as values. `restViewSet` reads the action names off the mixins'\n * instance types to build the ViewSet's public surface, and hands the same list to the proxy so\n * that the schema check can compare it against the BE.\n *\n * The members are methods rather than properties, and are reached through an intersection rather\n * than a `Pick<>` of a lookup table: a mapped type re-emits a method as a function-valued property,\n * and a subclass may then not override an action with a method (TS2425). Overriding one to add\n * caching or reshape parameters works on a hand-written proxy subclass today, and must keep\n * working here.\n *\n * A composite mixin restates nothing: its interface extends the leaves its `actions` spread names,\n * so each action's signature exists in exactly one place.\n *\n * The methods have no implementation anywhere in this file. The implementation is one HTTP call in\n * ViewSetProxyBase, and a mixin carrying its own would be a second copy of it.\n */\n\n/*\n * The two rules below object to precisely what this file is for.\n *\n * no-unsafe-declaration-merging guards against a class and an interface merging by accident, where\n * the interface promises members the constructor never assigns. Here it is the design: the members\n * are implemented by ViewSetProxyBase, and a mixin that assigned them would be a second copy of the\n * implementation.\n *\n * no-unused-vars fires on the class half's type parameters, which only the interface half uses. They\n * cannot be dropped: declaration merging requires both halves to declare identical type parameters.\n */\n/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging, @typescript-eslint/no-unused-vars */\n\nexport interface LookupItem {\n group: unknown;\n pk: unknown;\n title: string;\n icon: string | null;\n}\n\nexport type KeyType = string | number;\nexport type DestroyReturnData = Record<KeyType, any>;\n// ---------------------------------------------------------------------------\n// Individual operation mixins\n// ---------------------------------------------------------------------------\n\nexport interface CreateMixin<T, PK extends keyof T> {\n create(data: Omit<T, PK>): Promise<T>;\n}\nexport class CreateMixin<T, PK extends keyof T> {\n static readonly actions: readonly string[] = ['create'];\n}\n\nexport interface BulkOnlyCreateMixin<T, PK extends keyof T> {\n bulkCreate(data: Omit<T, PK>[]): Promise<T[]>;\n}\nexport class BulkOnlyCreateMixin<T, PK extends keyof T> {\n static readonly actions: readonly string[] = ['bulkCreate'];\n}\n\nexport interface BulkCreateMixin<T, PK extends keyof T> extends CreateMixin<T, PK>, BulkOnlyCreateMixin<T, PK> {}\nexport class BulkCreateMixin<T, PK extends keyof T> extends CreateMixin<T, PK> {\n static readonly actions: readonly string[] = [...CreateMixin.actions, ...BulkOnlyCreateMixin.actions];\n}\n\nexport interface ListMixin<T> {\n list(params?: ListParams): Promise<T[]>;\n}\nexport class ListMixin<T> {\n static readonly actions: readonly string[] = ['list'];\n}\n\n/** Query parameters a list call accepts. `sort` is 'column:asc,other:desc'; the rest are filters. */\nexport interface ListParams {\n sort?: string;\n [key: string]: string | number | boolean | null | undefined | Array<string | number>;\n}\n\n/**\n * One page, mirroring the BE PaginatedList.\n *\n * `count` is null when the backend could not know it without draining a lazy source. `hasMore` and\n * `hasPrevious` are stated rather than inferred — a client that guesses from a null gets the guess\n * wrong exactly at the boundary where it matters.\n */\nexport interface PaginatedList<T> {\n results: T[];\n offset: number;\n limit: number | null;\n count: number | null;\n hasMore: boolean;\n hasPrevious: boolean;\n}\n\nexport interface PageParams extends ListParams {\n offset?: number;\n limit?: number;\n}\n\n/**\n * One cursor page.\n *\n * `next`/`previous` are exclusive, so following them never repeats a row. `first`/`last` are the\n * same two edges read inclusively: they return their own row again — one duplicate to drop — and\n * in exchange they survive rows being inserted at that edge, which is what polling a live list\n * needs. They are present whenever the page is non-empty, even when `next` is null.\n *\n * There is no total count: producing one costs a second full pass per request and is stale by the\n * time it is read.\n */\nexport interface CursorPage<T> {\n results: T[];\n limit: number;\n hasMore: boolean;\n hasPrevious: boolean;\n next: string | null;\n previous: string | null;\n first: string | null;\n last: string | null;\n}\n\nexport interface CursorParams extends ListParams {\n cursor?: string;\n limit?: number;\n}\n\n/** FE counterpart of the BE CursorListMixin. See PaginatedListMixin on declaring several shapes. */\nexport interface CursorListMixin<T> {\n listCursor(params?: CursorParams): Promise<CursorPage<T>>;\n}\nexport class CursorListMixin<T> {\n static readonly actions: readonly string[] = ['listCursor'];\n}\n\n/**\n * FE counterpart of the BE PaginatedListMixin. `GET {basePath}` is one endpoint answering in the\n * shape the BE viewset declared as its default; this client sends no X-List-Shape header, so a\n * ViewSet declares the mixin matching that default rather than one per shape it might want.\n */\nexport interface PaginatedListMixin<T> {\n listPage(params?: PageParams): Promise<PaginatedList<T>>;\n}\nexport class PaginatedListMixin<T> {\n static readonly actions: readonly string[] = ['listPage'];\n}\n\nexport interface RetrieveMixin<K extends KeyType, T> {\n retrieve(pk: K): Promise<T>;\n}\nexport class RetrieveMixin<K extends KeyType, T> {\n static readonly actions: readonly string[] = ['retrieve'];\n}\n\nexport interface UpdateMixin<K extends KeyType, T> {\n update(pk: K, data: T): Promise<T>;\n partialUpdate(pk: K, data: Partial<T>): Promise<T>;\n}\nexport class UpdateMixin<K extends KeyType, T> {\n static readonly actions: readonly string[] = ['update', 'partialUpdate'];\n}\n\nexport interface BulkOnlyUpdateMixin<K extends KeyType, T> {\n bulkUpdate(records: Record<K, T>): Promise<T[]>;\n bulkPartialUpdate(records: Record<K, Partial<T>>): Promise<T[]>;\n}\nexport class BulkOnlyUpdateMixin<K extends KeyType, T> {\n static readonly actions: readonly string[] = ['bulkUpdate', 'bulkPartialUpdate'];\n}\n\nexport interface BulkUpdateMixin<K extends KeyType, T> extends UpdateMixin<K, T>, BulkOnlyUpdateMixin<K, T> {}\nexport class BulkUpdateMixin<K extends KeyType, T> extends UpdateMixin<K, T> {\n static readonly actions: readonly string[] = [...UpdateMixin.actions, ...BulkOnlyUpdateMixin.actions];\n}\n\nexport interface DestroyMixin<K extends KeyType> {\n destroy(pk: K): Promise<DestroyReturnData>;\n}\nexport class DestroyMixin<K extends KeyType> {\n static readonly actions: readonly string[] = ['destroy'];\n}\n\nexport interface BulkOnlyDestroyMixin<K extends KeyType> {\n bulkDestroy(pks: K[]): Promise<DestroyReturnData[]>;\n}\nexport class BulkOnlyDestroyMixin<K extends KeyType> {\n static readonly actions: readonly string[] = ['bulkDestroy'];\n}\n\nexport interface BulkDestroyMixin<K extends KeyType> extends DestroyMixin<K>, BulkOnlyDestroyMixin<K> {}\nexport class BulkDestroyMixin<K extends KeyType> extends DestroyMixin<K> {\n static readonly actions: readonly string[] = [...DestroyMixin.actions, ...BulkOnlyDestroyMixin.actions];\n}\n\nexport interface LookupMixin {\n lookup(): Promise<LookupItem[]>;\n}\nexport class LookupMixin {\n static readonly actions: readonly string[] = ['lookup'];\n}\n\nexport interface ReadOnlyViewSetMixin<K extends KeyType, T> extends ListMixin<T>, RetrieveMixin<K, T> {}\nexport class ReadOnlyViewSetMixin<K extends KeyType, T> extends ListMixin<T> {\n static readonly actions: readonly string[] = [...ListMixin.actions, ...RetrieveMixin.actions];\n}\n\nexport interface ViewSetMixin<K extends KeyType, T, PK extends keyof T>\n extends ReadOnlyViewSetMixin<K, T>, CreateMixin<T, PK>, UpdateMixin<K, T>, DestroyMixin<K> {}\nexport class ViewSetMixin<K extends KeyType, T, PK extends keyof T> extends ReadOnlyViewSetMixin<K, T> {\n static readonly actions: readonly string[] = [\n ...ReadOnlyViewSetMixin.actions,\n ...CreateMixin.actions,\n ...UpdateMixin.actions,\n ...DestroyMixin.actions,\n ];\n}\n\nexport interface BulkViewSetMixin<K extends KeyType, T, PK extends keyof T>\n extends ViewSetMixin<K, T, PK>, BulkOnlyCreateMixin<T, PK>, BulkOnlyUpdateMixin<K, T>, BulkOnlyDestroyMixin<K> {}\nexport class BulkViewSetMixin<K extends KeyType, T, PK extends keyof T> extends ViewSetMixin<K, T, PK> {\n static readonly actions: readonly string[] = [\n ...ViewSetMixin.actions,\n ...BulkOnlyCreateMixin.actions,\n ...BulkOnlyUpdateMixin.actions,\n ...BulkOnlyDestroyMixin.actions,\n ];\n}\n\n// ---------------------------------------------------------------------------\n// The action vocabulary\n// ---------------------------------------------------------------------------\n\n/**\n * The actions named by `A`, each with the signature the mixin that contributes it declares. One\n * conditional per single-action mixin; the composites are absent on purpose, so that any action's\n * signature is written in exactly one place.\n *\n * An intersection rather than `Pick<>` of a table: a mapped type turns a method into a property,\n * and a subclass cannot then override an action with a method (TS2425).\n */\nexport type ActionSurface<K extends KeyType, T, PK extends keyof T, A> = ('create' extends A\n ? CreateMixin<T, PK>\n : unknown) &\n ('bulkCreate' extends A ? BulkOnlyCreateMixin<T, PK> : unknown) &\n ('list' extends A ? ListMixin<T> : unknown) &\n ('listPage' extends A ? PaginatedListMixin<T> : unknown) &\n ('listCursor' extends A ? CursorListMixin<T> : unknown) &\n ('retrieve' extends A ? RetrieveMixin<K, T> : unknown) &\n ('update' extends A ? UpdateMixin<K, T> : unknown) &\n ('bulkUpdate' extends A ? BulkOnlyUpdateMixin<K, T> : unknown) &\n ('destroy' extends A ? DestroyMixin<K> : unknown) &\n ('bulkDestroy' extends A ? BulkOnlyDestroyMixin<K> : unknown) &\n ('lookup' extends A ? LookupMixin : unknown);\n\n/** Every action name there is: `A = string` selects all of them. */\nexport type ActionName = keyof ActionSurface<KeyType, unknown, never, string>;\n","/**\n * Transport-independent half of the ViewSet proxies.\n *\n * Every ViewSet method is the same regardless of how the request travels — `list()` is always a\n * GET on the base path, `destroy(pk)` is always a DELETE on `/{pk}`. Only the sending differs, so\n * that is the only thing subclasses supply: one `request()` method. `RestProxyImpl` sends over\n * HTTP with axios, `MuxwsProxyImpl` sends over a muxws stream.\n *\n * Custom endpoints should be written against `request()` rather than against a transport, so that\n * the same ViewSet class works on either:\n *\n * class MusicTrackViewSet extends RestProxyImpl<number, MusicTrack, 'id'> {\n * async count(): Promise<number> {\n * return this.request<number>('GET', '/count');\n * }\n * }\n */\n\nimport type {\n ActionName,\n BulkViewSetMixin,\n CursorListMixin,\n CursorPage,\n CursorParams,\n DestroyReturnData,\n KeyType,\n ListParams,\n LookupItem,\n LookupMixin,\n PageParams,\n PaginatedList,\n PaginatedListMixin,\n} from './mixins';\n\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n\n/** Query values; an array becomes a repeated key, which is how FastAPI binds `list[str]`. */\nexport type QueryParams = Record<string, string | number | boolean | null | undefined | Array<string | number>>;\n\nexport interface RequestOptions {\n query?: QueryParams;\n body?: unknown;\n}\n\n/**\n * What a failed call throws over muxws, where there is no axios to raise anything.\n *\n * The shape mirrors `AxiosError` — `error.response.status`, `error.response.data` and\n * `error.response.headers` — so a caller reads the same fields whichever transport the ViewSet\n * speaks. Over HTTP axios raises its own error, already in that shape, and it is passed through\n * untouched.\n *\n * `response` is always set here, unlike `AxiosError.response`, which is absent when the request\n * never reached a reply.\n */\nexport class ViewSetRequestError extends Error {\n readonly response: { status: number; data: unknown; headers: Record<string, string> };\n\n constructor(status: number, data: unknown, headers: Record<string, string> = {}) {\n super(`Request failed with status code ${status}`);\n this.name = 'ViewSetRequestError';\n this.response = { status, data, headers };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Schema validation constants\n// ---------------------------------------------------------------------------\n\nconst HTTP_METHODS = new Set(['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']);\n\n/**\n * Maps (path type, HTTP method) → the actions that endpoint can satisfy.\n * Path types: 'base' = root, 'pk' = /{pk}, 'bulk' = /bulk, 'lookup' = /lookup.\n *\n * A list is a list of alternatives rather than one name because `GET {base}` is a single BE\n * endpoint that answers in whichever shape the viewset declared - see list_shapes.py. Declaring\n * `listCursor` and being served `GET {base}` is agreement, not a mismatch.\n */\nconst ENDPOINT_TO_FE_METHOD: Readonly<Record<string, Readonly<Record<string, readonly ActionName[]>>>> = {\n base: { GET: ['list', 'listPage', 'listCursor'], POST: ['create'] },\n pk: {\n GET: ['retrieve'],\n PUT: ['update'],\n PATCH: ['partialUpdate'],\n DELETE: ['destroy'],\n },\n bulk: {\n POST: ['bulkCreate'],\n PUT: ['bulkUpdate'],\n PATCH: ['bulkPartialUpdate'],\n DELETE: ['bulkDestroy'],\n },\n lookup: { GET: ['lookup'] },\n};\n\n/**\n * Every `ActionName` mapped to `true`, in a stable order for warning output. A `Record<ActionName,\n * true>` rather than a plain array: an action added to (or dropped from) the `ActionName` union\n * without a matching change here fails to compile - a missing key or an excess one - instead of\n * silently narrowing what the mismatch check below is able to report.\n */\nconst ACTION_NAME_COVERAGE: Record<ActionName, true> = {\n list: true,\n listPage: true,\n listCursor: true,\n create: true,\n retrieve: true,\n update: true,\n partialUpdate: true,\n destroy: true,\n bulkCreate: true,\n bulkUpdate: true,\n bulkPartialUpdate: true,\n bulkDestroy: true,\n lookup: true,\n};\n\n/** All standard FE method names, in a stable order for warning output. */\nconst STANDARD_FE_METHODS: readonly ActionName[] = Object.keys(ACTION_NAME_COVERAGE) as ActionName[];\n\n/** One entry of a ViewSet's `static declares` list: a mixin naming the actions it contributes. */\nexport interface ViewSetMixinDeclaration {\n readonly actions: readonly string[];\n}\n\n/**\n * Marks a class's `declares` as coming from the ViewSet factory (viewset.ts's `FactoryDeclares<D>`)\n * rather than being hand-written. Declared here, the module both viewset.ts and the two proxy\n * implementations already import from, so all three sides see the same `unique symbol` and a\n * structural check against it type-checks identically everywhere - `route_rest`/`route_muxws` use it\n * to reject a factory-built class at the call site (see rest-proxy.ts, muxws-proxy.ts), which would\n * otherwise type-check and hand back a bare proxy missing the class's own custom methods.\n */\nexport declare const FACTORY_BUILT: unique symbol;\n\n/**\n * Which actions this ViewSet claims to have, from the `static declares` list on its class.\n *\n * Ordinary static lookup, so a subclass that declares its own list replaces its parent's rather\n * than adding to it - which is what a subclass pointed at a smaller BE viewset means. The new list\n * still has to be assignable to the parent's, since TypeScript checks a subclass's static side\n * against its base; a list the parent's type does not cover needs its own ViewSet.\n *\n * A ViewSet that declares nothing yields null rather than an empty set: \"said nothing\" and \"said\n * it has no actions\" are different claims, and only the second is worth checking against.\n */\nfunction declaredActions(instance: object, fromOptions?: readonly ViewSetMixinDeclaration[]): Set<string> | null {\n const declares = fromOptions ?? (instance.constructor as { declares?: readonly ViewSetMixinDeclaration[] }).declares;\n if (!Array.isArray(declares)) return null;\n\n const actions = new Set<string>();\n for (const mixin of declares) for (const action of mixin?.actions ?? []) actions.add(action);\n return actions;\n}\n\nexport interface ProxyBaseOptions {\n /** Base path to the resource, e.g. '/items'. */\n basePath: string;\n /** Name of the PK field on the model, e.g. 'id'. */\n pkFieldName: string;\n /** Set false to skip the startup schema check (it is advisory and costs one request). */\n validateSchema?: boolean;\n /**\n * The mixins the ViewSet declares, for the `route_rest` / `route_muxws` path: those build a bare\n * proxy and use the ViewSet class only for typing, so a `static declares` on it would otherwise\n * never reach the object being checked.\n */\n declares?: readonly ViewSetMixinDeclaration[];\n}\n\n/**\n * What a ViewSet's own methods may reach - everything a custom endpoint needs, and nothing a caller\n * does.\n *\n * A real base class rather than a type, because `protected` is checked nominally: a subclass body\n * reaches `request()` only if it genuinely descends from the class that declared it. The ViewSet\n * factory hands back a class typed as this plus the declared actions, which is how a factory-built\n * ViewSet ends up with a narrow public surface and a usable private one.\n */\nexport abstract class ViewSetInternals {\n protected readonly basePath: string;\n\n protected constructor(basePath: string) {\n this.basePath = basePath.replace(/\\/$/, '');\n }\n\n /**\n * Sends one request and returns the decoded response body.\n *\n * `path` is relative to `basePath` — '' for the collection, '/1' for a record, '/bulk', and so\n * on. Implementations must throw on a status of 400 or above, with `response.status` readable on\n * the thrown value. Below 400 the two differ, on a band a caller rarely sees: the muxws proxy\n * returns the body for any 3xx, while the REST proxy follows a redirect it can follow and rejects\n * whatever axios' default `validateStatus` then leaves outside 200-299.\n *\n * Concrete here, and never reached: ViewSetProxyBase re-declares it abstract, which is where a\n * transport is actually held to implementing it. It cannot be abstract at this level because\n * TypeScript propagates an abstract member through a constructor type, and the factory hands one\n * back - every ViewSet a consumer wrote would then fail TS2515 for a method the transport\n * underneath it has always implemented.\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- the signature is the point; the body cannot run\n protected request<R>(method: HttpMethod, path: string, options?: RequestOptions): Promise<R> {\n throw new Error('ViewSetInternals.request: no transport');\n }\n}\n\nexport abstract class ViewSetProxyBase<K extends KeyType, T, PK extends keyof T>\n extends ViewSetInternals\n implements BulkViewSetMixin<K, T, PK>, CursorListMixin<T>, PaginatedListMixin<T>, LookupMixin\n{\n /**\n * The mixins this ViewSet is composed of — the FE counterpart of a BE viewset's base classes.\n *\n * class ItemViewSet extends RestProxyImpl<number, Item, 'id'> {\n * static declares = [ReadOnlyViewSetMixin, LookupMixin];\n * }\n *\n * Left undefined, the ViewSet is not checked against the BE schema at all. That is deliberate:\n * a ViewSet that never said what it has cannot be caught contradicting itself, and guessing on\n * its behalf is what made the check report actions nobody ever claimed.\n */\n static declares?: readonly ViewSetMixinDeclaration[];\n\n /**\n * Which field of `T` is the primary key, e.g. `'id'`. `public`, not `protected`: nothing in this\n * library reads it back, but a generic caller holding a ViewSet instance - a grid or table\n * component needing to know which column identifies a row - has no other way to ask.\n */\n readonly pkFieldName: string;\n\n private readonly schemaValidationEnabled: boolean;\n\n private readonly declaredMixins?: readonly ViewSetMixinDeclaration[];\n\n protected constructor(options: ProxyBaseOptions) {\n super(options.basePath);\n this.pkFieldName = options.pkFieldName;\n this.schemaValidationEnabled = options.validateSchema !== false;\n this.declaredMixins = options.declares;\n }\n\n /**\n * Starts the advisory schema check. Subclasses must call this as the *last* statement of their\n * constructor, never the base constructor itself: `request()` reads fields the subclass has not\n * assigned yet while `super()` is still running, and since the check swallows its own errors,\n * doing it here would leave it permanently and silently dead.\n */\n protected initSchemaValidation(): void {\n if (this.schemaValidationEnabled) void this.validateAgainstSchema();\n }\n\n /** Abstract here, where a transport is actually held to it. See ViewSetInternals.request. */\n protected abstract override request<R>(method: HttpMethod, path: string, options?: RequestOptions): Promise<R>;\n\n /**\n * Fetches the BE schema and compares it against what this ViewSet declared.\n * Logs a console warning for any mismatch found.\n *\n * The comparison is against `static declares`, not against which methods exist on the object:\n * every action is implemented unconditionally on this class, so `typeof this[action]` is true for\n * every ViewSet and answers a question nobody asked. `declares` is the only place the FE says\n * anything a BE viewset could disagree with.\n *\n * Non-critical: errors during fetch or parsing are silently ignored. Note that the schema is\n * fetched over this proxy's own transport, so a muxws proxy validates against the muxws\n * endpoint set and a REST proxy against the REST one — which is the point, since the two are\n * allowed to differ.\n */\n private async validateAgainstSchema(): Promise<void> {\n // A ViewSet that declares nothing is not checked, and does not pay for the schema request\n // either. The alternative - assuming it meant \"all of them\" - is what the previous\n // implementation effectively did, and it is why a viewset built from one mixin reported every\n // action it had never claimed to have.\n const declared = declaredActions(this, this.declaredMixins);\n if (declared === null) return;\n\n try {\n const schema = await this.request<{ paths?: Record<string, Record<string, unknown>> }>('GET', '/schema');\n const paths = schema?.paths ?? {};\n\n const beActions = new Set<string>();\n const beAlternatives: { endpoint: string; actions: readonly string[] }[] = [];\n const customEndpoints: { endpoint: string; method: string }[] = [];\n\n for (const [path, pathItem] of Object.entries(paths)) {\n const suffix = path.slice(this.basePath.length).replace(/^\\//, '');\n const verbs = Object.keys(pathItem).filter((v) => HTTP_METHODS.has(v.toLowerCase()));\n\n let pathType: string;\n if (suffix === '') {\n pathType = 'base';\n } else if (suffix === 'bulk') {\n pathType = 'bulk';\n } else if (suffix === 'lookup') {\n pathType = 'lookup';\n } else if (suffix === 'schema') {\n continue;\n } else if (suffix.startsWith('{') && !suffix.includes('/')) {\n pathType = 'pk';\n } else {\n // A custom endpoint. Here - and only here - asking whether the proxy has a method of that\n // name is honest: the base implements no custom actions, so whatever answers is something\n // this ViewSet's own author wrote.\n for (const verb of verbs) {\n customEndpoints.push({ endpoint: `${verb.toUpperCase()} ${path}`, method: suffix.split('/')[0] });\n }\n continue;\n }\n\n const methodMap = ENDPOINT_TO_FE_METHOD[pathType] ?? {};\n for (const verb of verbs) {\n const actions = methodMap[verb.toUpperCase()];\n if (!actions) continue;\n beAlternatives.push({ endpoint: `${verb.toUpperCase()} ${path}`, actions });\n for (const action of actions) beActions.add(action);\n }\n }\n\n const warnings: string[] = [];\n\n for (const action of STANDARD_FE_METHODS) {\n if (declared.has(action) && !beActions.has(action)) {\n warnings.push(`declares '${action}' but the BE viewset serves no such endpoint`);\n }\n }\n\n for (const { endpoint, actions } of beAlternatives) {\n if (!actions.some((action) => declared.has(action))) {\n warnings.push(`BE serves '${endpoint}' but the ViewSet declares no ${actions.join(' / ')}`);\n }\n }\n\n for (const { endpoint, method } of customEndpoints) {\n if (typeof (this as unknown as Record<string, unknown>)[method] !== 'function') {\n warnings.push(`BE serves '${endpoint}' but the ViewSet has no '${method}()' method`);\n }\n }\n\n if (warnings.length > 0) {\n console.warn(\n `[ViewSet ${this.basePath}] FE/BE definition mismatch:\\n` + warnings.map((w) => ` • ${w}`).join('\\n'),\n );\n }\n } catch {\n // Schema validation is non-critical; ignore fetch/parse errors silently\n }\n }\n\n async create(data: Omit<T, PK>): Promise<T> {\n return this.request<T>('POST', '', { body: data });\n }\n\n async bulkCreate(data: Omit<T, PK>[]): Promise<T[]> {\n return this.request<T[]>('POST', '/bulk', { body: data });\n }\n\n async list(params?: ListParams): Promise<T[]> {\n return this.request<T[]>('GET', '', { query: params });\n }\n\n /**\n * Fetches one page. Only meaningful against a viewset built on the BE PaginatedListMixin — a\n * plain ListMixin ignores offset/limit and answers with the whole collection, which would not\n * match this return type.\n *\n * The BE speaks snake_case (`has_more`); the rest of this client speaks whatever the model\n * declares, so only the envelope's own fields are renamed here. The records inside are passed\n * through untouched.\n */\n /**\n * Fetches one cursor page. Only meaningful against a viewset built on the BE CursorListMixin.\n *\n * Follow `next` to walk forward. Unlike offset paging, a row inserted or removed behind you\n * cannot make the next page repeat or skip anything.\n */\n async listCursor(params?: CursorParams): Promise<CursorPage<T>> {\n const page = await this.request<{\n results: T[];\n limit: number;\n has_more: boolean;\n has_previous: boolean;\n next: string | null;\n previous: string | null;\n first: string | null;\n last: string | null;\n }>('GET', '', { query: params });\n return {\n results: page.results,\n limit: page.limit,\n hasMore: page.has_more,\n hasPrevious: page.has_previous,\n next: page.next,\n previous: page.previous,\n first: page.first,\n last: page.last,\n };\n }\n\n async listPage(params?: PageParams): Promise<PaginatedList<T>> {\n const page = await this.request<{\n results: T[];\n offset: number;\n limit: number | null;\n count: number | null;\n has_more: boolean;\n has_previous: boolean;\n }>('GET', '', { query: params });\n return {\n results: page.results,\n offset: page.offset,\n limit: page.limit,\n count: page.count,\n hasMore: page.has_more,\n hasPrevious: page.has_previous,\n };\n }\n\n async retrieve(pk: K): Promise<T> {\n return this.request<T>('GET', `/${pk}`);\n }\n\n async update(pk: K, data: T): Promise<T> {\n return this.request<T>('PUT', `/${pk}`, { body: data });\n }\n\n async partialUpdate(pk: K, data: Partial<T>): Promise<T> {\n return this.request<T>('PATCH', `/${pk}`, { body: data });\n }\n\n async bulkUpdate(records: Record<K, T>): Promise<T[]> {\n return this.request<T[]>('PUT', '/bulk', { body: records });\n }\n\n async bulkPartialUpdate(records: Record<K, Partial<T>>): Promise<T[]> {\n return this.request<T[]>('PATCH', '/bulk', { body: records });\n }\n\n async destroy(pk: K): Promise<DestroyReturnData> {\n return this.request<DestroyReturnData>('DELETE', `/${pk}`);\n }\n\n async bulkDestroy(pks: K[]): Promise<DestroyReturnData[]> {\n return this.request<DestroyReturnData[]>('DELETE', '/bulk', { body: pks });\n }\n\n async lookup(): Promise<LookupItem[]> {\n return this.request<LookupItem[]>('GET', '/lookup');\n }\n}\n","/**\n * muxws proxy for ViewSets — the same ViewSet surface as route_rest, sent over one WebSocket.\n *\n * import { connect } from 'muxws';\n *\n * const peer = await connect('ws://localhost:8000/ws');\n * const items = route_muxws<BulkViewSetMixin<number, Item> & LookupMixin>(\n * ItemViewSet, { basePath: '/items', pkFieldName: 'id', peer },\n * );\n * await items.list();\n *\n * A proxy speaks one transport. Creating a REST proxy and a muxws proxy for the same ViewSet is\n * the supported way to have both — they share nothing but the ViewSet's own type.\n */\n\nimport type { KeyType } from './mixins';\nimport {\n FACTORY_BUILT,\n type HttpMethod,\n type ProxyBaseOptions,\n type QueryParams,\n type RequestOptions,\n ViewSetProxyBase,\n type ViewSetMixinDeclaration,\n ViewSetRequestError,\n} from './proxy-base';\n\n/**\n * The bits of a muxws Peer this proxy uses. Typed structurally rather than by importing the muxws\n * types, so that `muxws` stays an optional dependency: an application that only uses route_rest\n * should not have to install it.\n */\nexport interface MuxwsStreamLike {\n result(options?: { timeoutMs?: number }): Promise<unknown>;\n readonly replyHeadersArrived: Promise<void>;\n readonly replyHeaders: Record<string, unknown> | null | undefined;\n}\n\nexport interface MuxwsPeerLike {\n open(payload?: unknown, options?: { headers?: Record<string, unknown>; end?: boolean }): MuxwsStreamLike;\n}\n\n/**\n * Where the peer comes from. A bare peer is the simple case; a function is for the common shape\n * where the proxy is constructed at module scope but `connect()` has not resolved yet. The\n * function is called once and its result cached — a muxws Peer survives its own reconnects, so\n * there is nothing to re-resolve afterwards.\n */\nexport type MuxwsPeerSource = MuxwsPeerLike | (() => MuxwsPeerLike | Promise<MuxwsPeerLike>);\n\nexport type MuxwsProxy<M> = M;\n\ntype ViewSetClass = abstract new (...args: any[]) => any;\n\n/**\n * The shape a factory-built class's constructor has, purely to give `route_muxws` an overload that\n * rejects it - see rest-proxy.ts's `FactoryBuiltClass` for why this has to be a plain overload\n * parameter rather than a type parameter conditioned on the argument.\n */\ntype FactoryBuiltClass = (abstract new (...args: any[]) => any) & {\n declares: { readonly [FACTORY_BUILT]: any };\n};\n\n/** Never assigned; its only use is `typeof FACTORY_BUILT_REJECTION` as a self-describing return type. */\n// eslint-disable-next-line @typescript-eslint/no-unused-vars -- type-only, read via `typeof` below\ndeclare const FACTORY_BUILT_REJECTION: 'route_muxws cannot take a factory-built class - extend it directly instead';\n\nexport interface MuxwsProxyOptions extends ProxyBaseOptions {\n peer: MuxwsPeerSource;\n /** Per-request timeout in milliseconds. muxws resets the stream with TIMEOUT when it expires. */\n timeoutMs?: number;\n /**\n * Headers added to every request this proxy makes. The WebSocket handshake already carries\n * whatever identified the session and the server treats that as the baseline; these override it\n * for this proxy's calls. There is no per-call header: `RequestOptions` is `{ query, body }`.\n */\n headers?: Record<string, string>;\n}\n\nexport class MuxwsProxyImpl<K extends KeyType, T, PK extends keyof T> extends ViewSetProxyBase<K, T, PK> {\n private readonly peerSource: MuxwsPeerSource;\n\n private readonly timeoutMs?: number;\n\n private readonly headers: Record<string, string>;\n\n private peerPromise?: Promise<MuxwsPeerLike>;\n\n constructor(options: MuxwsProxyOptions) {\n super(options);\n this.peerSource = options.peer;\n this.timeoutMs = options.timeoutMs;\n this.headers = options.headers ?? {};\n this.initSchemaValidation();\n }\n\n private peer(): Promise<MuxwsPeerLike> {\n if (!this.peerPromise) {\n const source = this.peerSource;\n this.peerPromise = Promise.resolve(typeof source === 'function' ? source() : source);\n }\n return this.peerPromise;\n }\n\n protected async request<R>(method: HttpMethod, path: string, options: RequestOptions = {}): Promise<R> {\n const peer = await this.peer();\n\n // Addressing mirrors HTTP/2: :-prefixed pseudo-headers for the request line, plain keys for\n // real HTTP headers. muxws itself never reads any of them (SPEC WSM-AUT-002).\n const headers: Record<string, unknown> = {\n ...this.headers,\n ':method': method,\n ':path': `${this.basePath}${path}`,\n };\n if (options.query && Object.keys(options.query).length > 0) headers[':query'] = cleanQuery(options.query);\n\n // end: true — this is a unary call, so the request is complete with its opening frame.\n const stream = peer.open(options.body, { headers, end: true });\n\n // The status is announced before the body, in the answering side's leading headers (muxws\n // 0.3.1+). Awaiting the gate first means an error status is known without reading the response\n // at all — which is what makes this work for a streaming reply and not only a unary one.\n await stream.replyHeadersArrived;\n const status = readStatus(stream.replyHeaders);\n\n const body = await stream.result(this.timeoutMs === undefined ? undefined : { timeoutMs: this.timeoutMs });\n if (status >= 400) throw new ViewSetRequestError(status, body, readHeaders(stream.replyHeaders));\n return body as R;\n }\n}\n\nfunction cleanQuery(query: QueryParams): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined || value === null) continue;\n out[key] = value;\n }\n return out;\n}\n\n/**\n * A response with no `:status` is treated as 200. That is what a server which answered normally\n * looks like on a transport that has not been taught to say otherwise, and inventing a failure\n * for it would break every such server.\n */\nfunction readStatus(replyHeaders: Record<string, unknown> | null | undefined): number {\n const raw = replyHeaders?.[':status'];\n if (typeof raw === 'number') return raw;\n if (typeof raw === 'string') {\n const parsed = Number.parseInt(raw, 10);\n if (!Number.isNaN(parsed)) return parsed;\n }\n return 200;\n}\n\nfunction readHeaders(replyHeaders: Record<string, unknown> | null | undefined): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [key, value] of Object.entries(replyHeaders ?? {})) {\n if (!key.startsWith(':')) out[key] = String(value);\n }\n return out;\n}\n\n// Exists only to reject a factory-built class with a message at the call site; nothing implements\n// or calls it - see rest-proxy.ts's route_rest for why.\n// eslint-disable-next-line @typescript-eslint/no-unused-vars -- M keeps the call syntax identical to the real overload\nfunction route_muxws<M = never>(\n viewSetClass: FactoryBuiltClass,\n options: MuxwsProxyOptions,\n): typeof FACTORY_BUILT_REJECTION;\n/**\n * Registers a muxws proxy for the given ViewSet class. The mirror of route_rest, and it takes the\n * same generic parameter for the same reason: TypeScript cannot inspect the Python class.\n */\nfunction route_muxws<M>(viewSetClass: ViewSetClass, options: MuxwsProxyOptions): MuxwsProxy<M>;\nfunction route_muxws<M>(viewSetClass: ViewSetClass, options: MuxwsProxyOptions): MuxwsProxy<M> {\n // The class is otherwise used only for its type. Its `declares` is the one thing on it the proxy\n // needs at runtime, so it is carried across rather than lost.\n return new MuxwsProxyImpl({\n declares: (viewSetClass as { declares?: ViewSetMixinDeclaration[] }).declares,\n ...options,\n }) as unknown as MuxwsProxy<M>;\n}\n\nexport { route_muxws };\n","/**\n * REST proxy for ViewSets — FE counterpart of the BE route_viewset decorator.\n *\n * Usage:\n * const restItems = route_rest<BulkViewSetMixin<number, Item> & LookupMixin>(\n * ItemViewSet, '/items', 'id',\n * );\n * const items = await restItems.list();\n * const item = await restItems.retrieve(1);\n *\n * Everything except the sending lives in ViewSetProxyBase, which MuxwsProxyImpl shares — see\n * proxy-base.ts.\n */\n\nimport axios, { type AxiosInstance } from 'axios';\n\nimport type { KeyType } from './mixins';\nimport {\n FACTORY_BUILT,\n type HttpMethod,\n type ProxyBaseOptions,\n type RequestOptions,\n type ViewSetMixinDeclaration,\n ViewSetProxyBase,\n} from './proxy-base';\n\n// ---------------------------------------------------------------------------\n// Helper types\n// ---------------------------------------------------------------------------\n\n/** ViewSet class constructor (for type-level introspection only). */\n\ntype ViewSetClass = abstract new (...args: any[]) => any;\n\n/**\n * The shape a factory-built class's constructor has, purely to give `route_rest` an overload that\n * rejects it - a type parameter conditioned on the argument (`C extends FactoryBuiltClass ? ... :\n * C`) cannot be inferred from that position at all, so an overload with this as a plain parameter\n * type is the only form that actually sees the real argument.\n *\n * `route_rest` uses its `viewSetClass` argument only for the `declares` on it (see the function\n * body - the class itself is never `new`'d), then builds a bare `RestProxyImpl` and hands it back\n * cast to `M`. Pass a factory-built class and this still type-checks without the overload below -\n * `M` is usually inferred as `InstanceType<typeof ItemApi>` - but the object it returns is not an\n * `ItemApi`, so any custom method the factory-built class added is `undefined` at runtime despite\n * compiling. `declares` is required here, not optional: a hand-written class\n * (`class ItemViewSet extends RestProxyImpl<...> {}`) may have no `declares` at all, or one that is\n * a plain array rather than carrying `FACTORY_BUILT`, and either must fall through to the real\n * overload below rather than match this one.\n */\ntype FactoryBuiltClass = (abstract new (...args: any[]) => any) & {\n declares: { readonly [FACTORY_BUILT]: any };\n};\n\n/** Never assigned; its only use is `typeof FACTORY_BUILT_REJECTION` as a self-describing return type. */\n// eslint-disable-next-line @typescript-eslint/no-unused-vars -- type-only, read via `typeof` below\ndeclare const FACTORY_BUILT_REJECTION: 'route_rest cannot take a factory-built class - extend it directly instead';\n\n/**\n * The REST proxy type is simply the mixin interface `M` the caller declares.\n * Because TypeScript cannot inspect Python class hierarchies at runtime, the\n * caller provides the explicit type via the generic parameter `M` (see route_rest).\n */\nexport type RestProxy<M> = M;\n\nexport interface RestProxyOptions extends ProxyBaseOptions {\n /** Optional: existing axios instance. Defaults to the global axios. */\n axiosInstance?: AxiosInstance;\n}\n\n// ---------------------------------------------------------------------------\n// Proxy implementation\n// ---------------------------------------------------------------------------\n\nexport class RestProxyImpl<K extends KeyType, T, PK extends keyof T> extends ViewSetProxyBase<K, T, PK> {\n protected readonly http: AxiosInstance;\n\n constructor(options: RestProxyOptions) {\n super(options);\n this.http = options.axiosInstance ?? axios;\n this.initSchemaValidation();\n }\n\n /**\n * Dispatches to axios' per-verb methods rather than to `http.request()`, deliberately. Those\n * are the calls this proxy has always made, and they are what application interceptors and test\n * doubles are written against — routing everything through `request()` would be invisible on\n * the wire but would break every one of them.\n *\n * axios throws its own AxiosError on a non-2xx, which already carries `response.status` — the\n * shape ViewSetRequestError mirrors for the muxws side. Nothing to translate here.\n */\n protected async request<R>(method: HttpMethod, path: string, options: RequestOptions = {}): Promise<R> {\n const url = `${this.basePath}${path}`;\n const hasQuery = options.query !== undefined && Object.keys(options.query).length > 0;\n const config = hasQuery ? { params: options.query } : undefined;\n\n switch (method) {\n case 'GET': {\n const res = config ? await this.http.get<R>(url, config) : await this.http.get<R>(url);\n return res.data;\n }\n // The config argument is omitted rather than passed as undefined: axios treats the two\n // identically, but a spy does not, and the call shape is part of what this proxy promises.\n case 'POST': {\n const res = config\n ? await this.http.post<R>(url, options.body, config)\n : await this.http.post<R>(url, options.body);\n return res.data;\n }\n case 'PUT': {\n const res = config\n ? await this.http.put<R>(url, options.body, config)\n : await this.http.put<R>(url, options.body);\n return res.data;\n }\n case 'PATCH': {\n const res = config\n ? await this.http.patch<R>(url, options.body, config)\n : await this.http.patch<R>(url, options.body);\n return res.data;\n }\n case 'DELETE': {\n // A DELETE body has to go through the config object; axios has no positional slot for it.\n const deleteConfig = options.body === undefined ? config : { ...(config ?? {}), data: options.body };\n const res = deleteConfig ? await this.http.delete<R>(url, deleteConfig) : await this.http.delete<R>(url);\n return res.data;\n }\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Decorator / factory\n// ---------------------------------------------------------------------------\n\n/**\n * Registers a REST proxy for the given ViewSet class.\n *\n * The generic parameter `M` determines which mixin interfaces are available —\n * typically the ViewSet type (or a union of mixin interfaces).\n *\n * @example\n * ```ts\n * import type { BulkViewSetMixin, LookupMixin } from './mixins';\n *\n * interface Item { id: number; name: string }\n *\n * // with separate arguments (recommended)\n * const restItems = route_rest<BulkViewSetMixin<number, Item> & LookupMixin>(\n * ItemViewSet, '/items', 'id',\n * );\n *\n * // or with an options object\n * const restItems2 = route_rest<BulkViewSetMixin<number, Item> & LookupMixin>(\n * ItemViewSet, { basePath: '/items', pkFieldName: 'id' },\n * );\n *\n * const items = await restItems.list();\n * const item = await restItems.retrieve(1);\n * ```\n */\n// These two overloads exist only to reject a factory-built class with a message at the call site;\n// nothing implements or calls them - overload resolution tries them first, and a factory-built\n// class's constructor matches FactoryBuiltClass before it ever reaches the real overloads below.\n// eslint-disable-next-line @typescript-eslint/no-unused-vars -- M keeps the call syntax identical to the real overload\nfunction route_rest<M = never>(\n viewSetClass: FactoryBuiltClass,\n basePath: string,\n pkFieldName: string,\n axiosInstance?: AxiosInstance,\n): typeof FACTORY_BUILT_REJECTION;\n// eslint-disable-next-line @typescript-eslint/no-unused-vars -- M keeps the call syntax identical to the real overload\nfunction route_rest<M = never>(\n viewSetClass: FactoryBuiltClass,\n options: RestProxyOptions,\n): typeof FACTORY_BUILT_REJECTION;\nfunction route_rest<M>(\n _viewSetClass: ViewSetClass,\n basePath: string,\n pkFieldName: string,\n axiosInstance?: AxiosInstance,\n): RestProxy<M>;\nfunction route_rest<M>(_viewSetClass: ViewSetClass, options: RestProxyOptions): RestProxy<M>;\nfunction route_rest<M>(\n viewSetClass: ViewSetClass,\n basePathOrOptions: string | RestProxyOptions,\n pkFieldName?: string,\n axiosInstance?: AxiosInstance,\n): RestProxy<M> {\n const options: RestProxyOptions =\n typeof basePathOrOptions === 'string'\n ? {\n basePath: basePathOrOptions,\n pkFieldName: pkFieldName!,\n axiosInstance,\n }\n : basePathOrOptions;\n // The class is otherwise used only for its type. Its `declares` is the one thing on it the proxy\n // needs at runtime, so it is carried across rather than lost.\n return new RestProxyImpl({\n declares: (viewSetClass as { declares?: ViewSetMixinDeclaration[] }).declares,\n ...options,\n }) as unknown as RestProxy<M>;\n}\n\nexport { route_rest };\n","/**\n * The ViewSet class factory — FE counterpart of listing mixins in a BE viewset's bases.\n *\n * class ItemApi extends restViewSet<Item>()('id', [ReadOnlyViewSetMixin, LookupMixin]) {\n * async count(): Promise<number> {\n * return this.request<number>('GET', '/count');\n * }\n * }\n *\n * The mixin list is written once, as values, and does three jobs: it decides which actions the\n * class exposes to callers, it types them, and it is handed to the proxy so the startup schema\n * check can compare it against the BE. Calling an action the ViewSet did not declare is a compile\n * error rather than a 404 at runtime.\n *\n * This is what `route_rest` cannot do. That returns an instance cast to a mixin interface, so the\n * type narrows but the object is a bare proxy: a custom endpoint declared in the interface type-\n * checks and is undefined when called. Here the object is the consumer's own class, so its own\n * methods exist.\n */\n\nimport type { ActionName, ActionSurface, KeyType } from './mixins';\nimport { MuxwsProxyImpl, type MuxwsProxyOptions } from './muxws-proxy';\nimport { FACTORY_BUILT, type ProxyBaseOptions, ViewSetInternals, type ViewSetMixinDeclaration } from './proxy-base';\nimport { RestProxyImpl, type RestProxyOptions } from './rest-proxy';\n\n/** A mixin class: the runtime `actions` the schema check reads, and the type naming those actions. */\nexport type ViewSetMixinClass = ViewSetMixinDeclaration & (abstract new (...args: any[]) => object);\n\n/**\n * The actions a mixin class names, read off its instance type.\n *\n * The instance type rather than the `actions` tuple, because a tuple would have to be `as const` on\n * every mixin, and a `readonly ['create']` static cannot then be inherited by a composite whose own\n * static is a different tuple (TS2417). `D` is naked so that a union of mixins distributes.\n */\ntype ActionsOf<D> = D extends abstract new (...args: any[]) => infer I ? Extract<keyof I, ActionName> : never;\n\n/** The fields of `T` that could be a primary key. */\nexport type PkFieldName<T> = Extract<\n { [F in keyof T]-?: NonNullable<T[F]> extends KeyType ? F : never }[keyof T],\n string\n>;\n\ntype PkType<T, PK extends keyof T> = NonNullable<T[PK]> & KeyType;\n\n/**\n * Brands a factory-built class's `declares` so a subclass restating it fails on one flat \"missing\n * property\" line naming `declares` itself, rather than TypeScript recursing into which method each\n * mixin in the plain, unbranded replacement array is missing relative to the original. A record\n * type wrapping `D` behind the phantom key, not `D & {brand}`: an intersection would still expose\n * `D`'s own array shape to the comparison and recurse into it exactly as before; a plain array\n * literal has no property at all under this key, so the mismatch stops at the top. Erased at\n * runtime - `bindViewSet` casts through `unknown`, so the actual `static declares` stays a plain\n * array, and every internal reader (rest-proxy.ts, muxws-proxy.ts, proxy-base.ts's\n * `declaredActions`) already reads it through its own cast rather than this type.\n *\n * `FACTORY_BUILT` lives in proxy-base.ts, not here, so `route_rest`/`route_muxws` can check for the\n * same brand without importing from this module - see proxy-base.ts's doc comment on the symbol.\n */\ntype FactoryDeclares<D extends readonly ViewSetMixinClass[]> = { readonly [FACTORY_BUILT]: D };\n\n/**\n * What the factory hands back: a class to extend.\n *\n * A `declares` list naming no action — `[]`, or one annotated `ViewSetMixinClass[]`, which erases\n * which mixins are in it — would otherwise produce a ViewSet with no actions and no complaint.\n * TS2507 prints the type it was given, so the type is the sentence.\n *\n * `pkFieldName: PK` is added on top of `ViewSetInternals` explicitly: the constructed instance's\n * real runtime type is `ProxyBaseOptions`'s `ViewSetProxyBase`, which already carries the public\n * `pkFieldName`, but `ViewSetInternals` is deliberately narrow (see its own doc comment), so this\n * type would otherwise hide a field that is genuinely there. Typed as the literal `PK` rather than\n * `string`, since the factory already knows exactly which field it is.\n */\nexport type ViewSetClass<T, PK extends keyof T, D extends readonly ViewSetMixinClass[], O extends ProxyBaseOptions> = [\n ActionsOf<D[number]>,\n] extends [never]\n ? 'declares must name at least one action: pass the mixin classes themselves, unannotated'\n : {\n new (\n options: Omit<O, 'pkFieldName' | 'declares'>,\n ): ViewSetInternals & ActionSurface<PkType<T, PK>, T, PK, ActionsOf<D[number]>> & { readonly pkFieldName: PK };\n readonly declares: FactoryDeclares<D>;\n };\n\n/**\n * `any` on the constructor because a class expression may only extend a constructor whose members\n * are statically known, and the two transports' options differ. The real type is put back on by the\n * return type of whichever factory calls this.\n */\ntype TransportClass = new (options: any) => any;\n\n/** Named `ViewSet` so that a stack trace and the devtools prototype chain say something. */\nfunction bindViewSet(\n Impl: TransportClass,\n pkFieldName: string,\n declares: readonly ViewSetMixinDeclaration[],\n): TransportClass {\n return class ViewSet extends Impl {\n static declares = declares;\n\n constructor(options: ProxyBaseOptions) {\n // Injected into the argument expression rather than assigned afterwards: under\n // useDefineForClassFields an assignment would emit a [[Define]] that runs after super() and\n // would clobber what the base constructor had just set.\n //\n // `declares: undefined` so that the static above is what the check reads. A subclass stating\n // its own `static declares` then shadows it by ordinary static lookup, as it always has.\n super({ ...options, pkFieldName, declares: undefined });\n }\n };\n}\n\n/**\n * A REST ViewSet base class.\n *\n * The empty `()` is not decoration: TypeScript has no partial type-argument inference, so the model\n * cannot be given explicitly while the pk field and the mixin list are inferred from arguments in\n * the same call (TS2558). Currying is the only way to have both.\n */\nexport function restViewSet<T>() {\n return <PK extends PkFieldName<T> & keyof T, D extends readonly ViewSetMixinClass[]>(\n pkFieldName: PK,\n declares: D,\n ): ViewSetClass<T, PK, D, RestProxyOptions> =>\n bindViewSet(RestProxyImpl, pkFieldName, declares) as unknown as ViewSetClass<T, PK, D, RestProxyOptions>;\n}\n\n/** A muxws ViewSet base class. See `restViewSet` for why the call is curried. */\nexport function muxwsViewSet<T>() {\n return <PK extends PkFieldName<T> & keyof T, D extends readonly ViewSetMixinClass[]>(\n pkFieldName: PK,\n declares: D,\n ): ViewSetClass<T, PK, D, MuxwsProxyOptions> =>\n bindViewSet(MuxwsProxyImpl, pkFieldName, declares) as unknown as ViewSetClass<T, PK, D, MuxwsProxyOptions>;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fastapi-viewsets.umd.cjs","names":[],"sources":["../vue/mixins.ts","../vue/proxy-base.ts","../vue/muxws-proxy.ts","../vue/rest-proxy.ts","../vue/viewset.ts","../vue/errors.ts"],"sourcesContent":["/**\n * FE counterpart of BE mixins.py — the mixins a ViewSet declaration is composed of.\n *\n * Each mixin is an interface merged into a class of the same name. The interface names the actions\n * and their signatures; the class carries the `actions` list that the schema check reads at\n * runtime. Both halves are needed because neither alone can do the job: a TypeScript `implements`\n * clause is erased before anything runs, and a runtime list of strings says nothing about types.\n *\n * class ItemViewSet extends restViewSet<Item>()('id', [ReadOnlyViewSetMixin, LookupMixin]) {}\n *\n * The list is written once, as values. `restViewSet` reads the action names off the mixins'\n * instance types to build the ViewSet's public surface, and hands the same list to the proxy so\n * that the schema check can compare it against the BE.\n *\n * The members are methods rather than properties, and are reached through an intersection rather\n * than a `Pick<>` of a lookup table: a mapped type re-emits a method as a function-valued property,\n * and a subclass may then not override an action with a method (TS2425). Overriding one to add\n * caching or reshape parameters works on a hand-written proxy subclass today, and must keep\n * working here.\n *\n * A composite mixin restates nothing: its interface extends the leaves its `actions` spread names,\n * so each action's signature exists in exactly one place.\n *\n * The methods have no implementation anywhere in this file. The implementation is one HTTP call in\n * ViewSetProxyBase, and a mixin carrying its own would be a second copy of it.\n */\n\n/*\n * The two rules below object to precisely what this file is for.\n *\n * no-unsafe-declaration-merging guards against a class and an interface merging by accident, where\n * the interface promises members the constructor never assigns. Here it is the design: the members\n * are implemented by ViewSetProxyBase, and a mixin that assigned them would be a second copy of the\n * implementation.\n *\n * no-unused-vars fires on the class half's type parameters, which only the interface half uses. They\n * cannot be dropped: declaration merging requires both halves to declare identical type parameters.\n */\n/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging, @typescript-eslint/no-unused-vars */\n\nexport interface LookupItem {\n group: unknown;\n pk: unknown;\n title: string;\n icon: string | null;\n}\n\nexport type KeyType = string | number;\nexport type DestroyReturnData = Record<KeyType, any>;\n// ---------------------------------------------------------------------------\n// Individual operation mixins\n// ---------------------------------------------------------------------------\n\nexport interface CreateMixin<T, PK extends keyof T> {\n create(data: Omit<T, PK>): Promise<T>;\n}\nexport class CreateMixin<T, PK extends keyof T> {\n static readonly actions: readonly string[] = ['create'];\n}\n\nexport interface BulkOnlyCreateMixin<T, PK extends keyof T> {\n bulkCreate(data: Omit<T, PK>[]): Promise<T[]>;\n}\nexport class BulkOnlyCreateMixin<T, PK extends keyof T> {\n static readonly actions: readonly string[] = ['bulkCreate'];\n}\n\nexport interface BulkCreateMixin<T, PK extends keyof T> extends CreateMixin<T, PK>, BulkOnlyCreateMixin<T, PK> {}\nexport class BulkCreateMixin<T, PK extends keyof T> extends CreateMixin<T, PK> {\n static readonly actions: readonly string[] = [...CreateMixin.actions, ...BulkOnlyCreateMixin.actions];\n}\n\nexport interface ListMixin<T> {\n list(params?: ListParams): Promise<T[]>;\n}\nexport class ListMixin<T> {\n static readonly actions: readonly string[] = ['list'];\n}\n\n/** Query parameters a list call accepts. `sort` is 'column:asc,other:desc'; the rest are filters. */\nexport interface ListParams {\n sort?: string;\n [key: string]: string | number | boolean | null | undefined | Array<string | number>;\n}\n\n/**\n * One page, mirroring the BE PaginatedList.\n *\n * `count` is null when the backend could not know it without draining a lazy source. `hasMore` and\n * `hasPrevious` are stated rather than inferred — a client that guesses from a null gets the guess\n * wrong exactly at the boundary where it matters.\n */\nexport interface PaginatedList<T> {\n results: T[];\n offset: number;\n limit: number | null;\n count: number | null;\n hasMore: boolean;\n hasPrevious: boolean;\n}\n\nexport interface PageParams extends ListParams {\n offset?: number;\n limit?: number;\n}\n\n/**\n * One cursor page.\n *\n * `next`/`previous` are exclusive, so following them never repeats a row. `first`/`last` are the\n * same two edges read inclusively: they return their own row again — one duplicate to drop — and\n * in exchange they survive rows being inserted at that edge, which is what polling a live list\n * needs. They are present whenever the page is non-empty, even when `next` is null.\n *\n * There is no total count: producing one costs a second full pass per request and is stale by the\n * time it is read.\n */\nexport interface CursorPage<T> {\n results: T[];\n limit: number;\n hasMore: boolean;\n hasPrevious: boolean;\n next: string | null;\n previous: string | null;\n first: string | null;\n last: string | null;\n}\n\nexport interface CursorParams extends ListParams {\n cursor?: string;\n limit?: number;\n}\n\n/** FE counterpart of the BE CursorListMixin. See PaginatedListMixin on declaring several shapes. */\nexport interface CursorListMixin<T> {\n listCursor(params?: CursorParams): Promise<CursorPage<T>>;\n}\nexport class CursorListMixin<T> {\n static readonly actions: readonly string[] = ['listCursor'];\n}\n\n/**\n * FE counterpart of the BE PaginatedListMixin. `GET {basePath}` is one endpoint answering in the\n * shape the BE viewset declared as its default; this client sends no X-List-Shape header, so a\n * ViewSet declares the mixin matching that default rather than one per shape it might want.\n */\nexport interface PaginatedListMixin<T> {\n listPage(params?: PageParams): Promise<PaginatedList<T>>;\n}\nexport class PaginatedListMixin<T> {\n static readonly actions: readonly string[] = ['listPage'];\n}\n\nexport interface RetrieveMixin<K extends KeyType, T> {\n retrieve(pk: K): Promise<T>;\n}\nexport class RetrieveMixin<K extends KeyType, T> {\n static readonly actions: readonly string[] = ['retrieve'];\n}\n\nexport interface UpdateMixin<K extends KeyType, T> {\n update(pk: K, data: T): Promise<T>;\n partialUpdate(pk: K, data: Partial<T>): Promise<T>;\n}\nexport class UpdateMixin<K extends KeyType, T> {\n static readonly actions: readonly string[] = ['update', 'partialUpdate'];\n}\n\nexport interface BulkOnlyUpdateMixin<K extends KeyType, T> {\n bulkUpdate(records: Record<K, T>): Promise<T[]>;\n bulkPartialUpdate(records: Record<K, Partial<T>>): Promise<T[]>;\n}\nexport class BulkOnlyUpdateMixin<K extends KeyType, T> {\n static readonly actions: readonly string[] = ['bulkUpdate', 'bulkPartialUpdate'];\n}\n\nexport interface BulkUpdateMixin<K extends KeyType, T> extends UpdateMixin<K, T>, BulkOnlyUpdateMixin<K, T> {}\nexport class BulkUpdateMixin<K extends KeyType, T> extends UpdateMixin<K, T> {\n static readonly actions: readonly string[] = [...UpdateMixin.actions, ...BulkOnlyUpdateMixin.actions];\n}\n\nexport interface DestroyMixin<K extends KeyType> {\n destroy(pk: K): Promise<DestroyReturnData>;\n}\nexport class DestroyMixin<K extends KeyType> {\n static readonly actions: readonly string[] = ['destroy'];\n}\n\nexport interface BulkOnlyDestroyMixin<K extends KeyType> {\n bulkDestroy(pks: K[]): Promise<DestroyReturnData[]>;\n}\nexport class BulkOnlyDestroyMixin<K extends KeyType> {\n static readonly actions: readonly string[] = ['bulkDestroy'];\n}\n\nexport interface BulkDestroyMixin<K extends KeyType> extends DestroyMixin<K>, BulkOnlyDestroyMixin<K> {}\nexport class BulkDestroyMixin<K extends KeyType> extends DestroyMixin<K> {\n static readonly actions: readonly string[] = [...DestroyMixin.actions, ...BulkOnlyDestroyMixin.actions];\n}\n\nexport interface LookupMixin {\n lookup(): Promise<LookupItem[]>;\n}\nexport class LookupMixin {\n static readonly actions: readonly string[] = ['lookup'];\n}\n\nexport interface ReadOnlyViewSetMixin<K extends KeyType, T> extends ListMixin<T>, RetrieveMixin<K, T> {}\nexport class ReadOnlyViewSetMixin<K extends KeyType, T> extends ListMixin<T> {\n static readonly actions: readonly string[] = [...ListMixin.actions, ...RetrieveMixin.actions];\n}\n\nexport interface ViewSetMixin<K extends KeyType, T, PK extends keyof T>\n extends ReadOnlyViewSetMixin<K, T>, CreateMixin<T, PK>, UpdateMixin<K, T>, DestroyMixin<K> {}\nexport class ViewSetMixin<K extends KeyType, T, PK extends keyof T> extends ReadOnlyViewSetMixin<K, T> {\n static readonly actions: readonly string[] = [\n ...ReadOnlyViewSetMixin.actions,\n ...CreateMixin.actions,\n ...UpdateMixin.actions,\n ...DestroyMixin.actions,\n ];\n}\n\nexport interface BulkViewSetMixin<K extends KeyType, T, PK extends keyof T>\n extends ViewSetMixin<K, T, PK>, BulkOnlyCreateMixin<T, PK>, BulkOnlyUpdateMixin<K, T>, BulkOnlyDestroyMixin<K> {}\nexport class BulkViewSetMixin<K extends KeyType, T, PK extends keyof T> extends ViewSetMixin<K, T, PK> {\n static readonly actions: readonly string[] = [\n ...ViewSetMixin.actions,\n ...BulkOnlyCreateMixin.actions,\n ...BulkOnlyUpdateMixin.actions,\n ...BulkOnlyDestroyMixin.actions,\n ];\n}\n\n// ---------------------------------------------------------------------------\n// The action vocabulary\n// ---------------------------------------------------------------------------\n\n/**\n * The actions named by `A`, each with the signature the mixin that contributes it declares. One\n * conditional per single-action mixin; the composites are absent on purpose, so that any action's\n * signature is written in exactly one place.\n *\n * An intersection rather than `Pick<>` of a table: a mapped type turns a method into a property,\n * and a subclass cannot then override an action with a method (TS2425).\n */\nexport type ActionSurface<K extends KeyType, T, PK extends keyof T, A> = ('create' extends A\n ? CreateMixin<T, PK>\n : unknown) &\n ('bulkCreate' extends A ? BulkOnlyCreateMixin<T, PK> : unknown) &\n ('list' extends A ? ListMixin<T> : unknown) &\n ('listPage' extends A ? PaginatedListMixin<T> : unknown) &\n ('listCursor' extends A ? CursorListMixin<T> : unknown) &\n ('retrieve' extends A ? RetrieveMixin<K, T> : unknown) &\n ('update' extends A ? UpdateMixin<K, T> : unknown) &\n ('bulkUpdate' extends A ? BulkOnlyUpdateMixin<K, T> : unknown) &\n ('destroy' extends A ? DestroyMixin<K> : unknown) &\n ('bulkDestroy' extends A ? BulkOnlyDestroyMixin<K> : unknown) &\n ('lookup' extends A ? LookupMixin : unknown);\n\n/** Every action name there is: `A = string` selects all of them. */\nexport type ActionName = keyof ActionSurface<KeyType, unknown, never, string>;\n","/**\n * Transport-independent half of the ViewSet proxies.\n *\n * Every ViewSet method is the same regardless of how the request travels — `list()` is always a\n * GET on the base path, `destroy(pk)` is always a DELETE on `/{pk}`. Only the sending differs, so\n * that is the only thing subclasses supply: one `request()` method. `RestProxyImpl` sends over\n * HTTP with axios, `MuxwsProxyImpl` sends over a muxws stream.\n *\n * Custom endpoints should be written against `request()` rather than against a transport, so that\n * the same ViewSet class works on either:\n *\n * class MusicTrackViewSet extends RestProxyImpl<number, MusicTrack, 'id'> {\n * async count(): Promise<number> {\n * return this.request<number>('GET', '/count');\n * }\n * }\n */\n\nimport type {\n ActionName,\n BulkViewSetMixin,\n CursorListMixin,\n CursorPage,\n CursorParams,\n DestroyReturnData,\n KeyType,\n ListParams,\n LookupItem,\n LookupMixin,\n PageParams,\n PaginatedList,\n PaginatedListMixin,\n} from './mixins';\n\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n\n/** Query values; an array becomes a repeated key, which is how FastAPI binds `list[str]`. */\nexport type QueryParams = Record<string, string | number | boolean | null | undefined | Array<string | number>>;\n\nexport interface RequestOptions {\n query?: QueryParams;\n body?: unknown;\n}\n\n/**\n * What a failed call throws over muxws, where there is no axios to raise anything.\n *\n * The shape mirrors `AxiosError` — `error.response.status`, `error.response.data` and\n * `error.response.headers` — so a caller reads the same fields whichever transport the ViewSet\n * speaks. Over HTTP axios raises its own error, already in that shape, and it is passed through\n * untouched.\n *\n * `response` is always set here, unlike `AxiosError.response`, which is absent when the request\n * never reached a reply.\n */\nexport class ViewSetRequestError extends Error {\n readonly response: { status: number; data: unknown; headers: Record<string, string> };\n\n constructor(status: number, data: unknown, headers: Record<string, string> = {}) {\n super(`Request failed with status code ${status}`);\n this.name = 'ViewSetRequestError';\n this.response = { status, data, headers };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Schema validation constants\n// ---------------------------------------------------------------------------\n\nconst HTTP_METHODS = new Set(['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']);\n\n/**\n * Maps (path type, HTTP method) → the actions that endpoint can satisfy.\n * Path types: 'base' = root, 'pk' = /{pk}, 'bulk' = /bulk, 'lookup' = /lookup.\n *\n * A list is a list of alternatives rather than one name because `GET {base}` is a single BE\n * endpoint that answers in whichever shape the viewset declared - see list_shapes.py. Declaring\n * `listCursor` and being served `GET {base}` is agreement, not a mismatch.\n */\nconst ENDPOINT_TO_FE_METHOD: Readonly<Record<string, Readonly<Record<string, readonly ActionName[]>>>> = {\n base: { GET: ['list', 'listPage', 'listCursor'], POST: ['create'] },\n pk: {\n GET: ['retrieve'],\n PUT: ['update'],\n PATCH: ['partialUpdate'],\n DELETE: ['destroy'],\n },\n bulk: {\n POST: ['bulkCreate'],\n PUT: ['bulkUpdate'],\n PATCH: ['bulkPartialUpdate'],\n DELETE: ['bulkDestroy'],\n },\n lookup: { GET: ['lookup'] },\n};\n\n/**\n * Every `ActionName` mapped to `true`, in a stable order for warning output. A `Record<ActionName,\n * true>` rather than a plain array: an action added to (or dropped from) the `ActionName` union\n * without a matching change here fails to compile - a missing key or an excess one - instead of\n * silently narrowing what the mismatch check below is able to report.\n */\nconst ACTION_NAME_COVERAGE: Record<ActionName, true> = {\n list: true,\n listPage: true,\n listCursor: true,\n create: true,\n retrieve: true,\n update: true,\n partialUpdate: true,\n destroy: true,\n bulkCreate: true,\n bulkUpdate: true,\n bulkPartialUpdate: true,\n bulkDestroy: true,\n lookup: true,\n};\n\n/** All standard FE method names, in a stable order for warning output. */\nconst STANDARD_FE_METHODS: readonly ActionName[] = Object.keys(ACTION_NAME_COVERAGE) as ActionName[];\n\n/** One entry of a ViewSet's `static declares` list: a mixin naming the actions it contributes. */\nexport interface ViewSetMixinDeclaration {\n readonly actions: readonly string[];\n}\n\n/**\n * Marks a class's `declares` as coming from the ViewSet factory (viewset.ts's `FactoryDeclares<D>`)\n * rather than being hand-written. Declared here, the module both viewset.ts and the two proxy\n * implementations already import from, so all three sides see the same `unique symbol` and a\n * structural check against it type-checks identically everywhere - `route_rest`/`route_muxws` use it\n * to reject a factory-built class at the call site (see rest-proxy.ts, muxws-proxy.ts), which would\n * otherwise type-check and hand back a bare proxy missing the class's own custom methods.\n */\nexport declare const FACTORY_BUILT: unique symbol;\n\n/**\n * Which actions this ViewSet claims to have, from the `static declares` list on its class.\n *\n * Ordinary static lookup, so a subclass that declares its own list replaces its parent's rather\n * than adding to it - which is what a subclass pointed at a smaller BE viewset means. The new list\n * still has to be assignable to the parent's, since TypeScript checks a subclass's static side\n * against its base; a list the parent's type does not cover needs its own ViewSet.\n *\n * A ViewSet that declares nothing yields null rather than an empty set: \"said nothing\" and \"said\n * it has no actions\" are different claims, and only the second is worth checking against.\n */\nfunction declaredActions(instance: object, fromOptions?: readonly ViewSetMixinDeclaration[]): Set<string> | null {\n const declares = fromOptions ?? (instance.constructor as { declares?: readonly ViewSetMixinDeclaration[] }).declares;\n if (!Array.isArray(declares)) return null;\n\n const actions = new Set<string>();\n for (const mixin of declares) for (const action of mixin?.actions ?? []) actions.add(action);\n return actions;\n}\n\nexport interface ProxyBaseOptions {\n /** Base path to the resource, e.g. '/items'. */\n basePath: string;\n /** Name of the PK field on the model, e.g. 'id'. */\n pkFieldName: string;\n /** Set false to skip the startup schema check (it is advisory and costs one request). */\n validateSchema?: boolean;\n /**\n * The mixins the ViewSet declares, for the `route_rest` / `route_muxws` path: those build a bare\n * proxy and use the ViewSet class only for typing, so a `static declares` on it would otherwise\n * never reach the object being checked.\n */\n declares?: readonly ViewSetMixinDeclaration[];\n}\n\n/**\n * What a ViewSet's own methods may reach - everything a custom endpoint needs, and nothing a caller\n * does.\n *\n * A real base class rather than a type, because `protected` is checked nominally: a subclass body\n * reaches `request()` only if it genuinely descends from the class that declared it. The ViewSet\n * factory hands back a class typed as this plus the declared actions, which is how a factory-built\n * ViewSet ends up with a narrow public surface and a usable private one.\n */\nexport abstract class ViewSetInternals {\n protected readonly basePath: string;\n\n protected constructor(basePath: string) {\n this.basePath = basePath.replace(/\\/$/, '');\n }\n\n /**\n * Sends one request and returns the decoded response body.\n *\n * `path` is relative to `basePath` — '' for the collection, '/1' for a record, '/bulk', and so\n * on. Implementations must throw on a status of 400 or above, with `response.status` readable on\n * the thrown value. Below 400 the two differ, on a band a caller rarely sees: the muxws proxy\n * returns the body for any 3xx, while the REST proxy follows a redirect it can follow and rejects\n * whatever axios' default `validateStatus` then leaves outside 200-299.\n *\n * Concrete here, and never reached: ViewSetProxyBase re-declares it abstract, which is where a\n * transport is actually held to implementing it. It cannot be abstract at this level because\n * TypeScript propagates an abstract member through a constructor type, and the factory hands one\n * back - every ViewSet a consumer wrote would then fail TS2515 for a method the transport\n * underneath it has always implemented.\n */\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- the signature is the point; the body cannot run\n protected request<R>(method: HttpMethod, path: string, options?: RequestOptions): Promise<R> {\n throw new Error('ViewSetInternals.request: no transport');\n }\n}\n\nexport abstract class ViewSetProxyBase<K extends KeyType, T, PK extends keyof T>\n extends ViewSetInternals\n implements BulkViewSetMixin<K, T, PK>, CursorListMixin<T>, PaginatedListMixin<T>, LookupMixin\n{\n /**\n * The mixins this ViewSet is composed of — the FE counterpart of a BE viewset's base classes.\n *\n * class ItemViewSet extends RestProxyImpl<number, Item, 'id'> {\n * static declares = [ReadOnlyViewSetMixin, LookupMixin];\n * }\n *\n * Left undefined, the ViewSet is not checked against the BE schema at all. That is deliberate:\n * a ViewSet that never said what it has cannot be caught contradicting itself, and guessing on\n * its behalf is what made the check report actions nobody ever claimed.\n */\n static declares?: readonly ViewSetMixinDeclaration[];\n\n /**\n * Which field of `T` is the primary key, e.g. `'id'`. `public`, not `protected`: nothing in this\n * library reads it back, but a generic caller holding a ViewSet instance - a grid or table\n * component needing to know which column identifies a row - has no other way to ask.\n */\n readonly pkFieldName: string;\n\n private readonly schemaValidationEnabled: boolean;\n\n private readonly declaredMixins?: readonly ViewSetMixinDeclaration[];\n\n protected constructor(options: ProxyBaseOptions) {\n super(options.basePath);\n this.pkFieldName = options.pkFieldName;\n this.schemaValidationEnabled = options.validateSchema !== false;\n this.declaredMixins = options.declares;\n }\n\n /**\n * Starts the advisory schema check. Subclasses must call this as the *last* statement of their\n * constructor, never the base constructor itself: `request()` reads fields the subclass has not\n * assigned yet while `super()` is still running, and since the check swallows its own errors,\n * doing it here would leave it permanently and silently dead.\n */\n protected initSchemaValidation(): void {\n if (this.schemaValidationEnabled) void this.validateAgainstSchema();\n }\n\n /** Abstract here, where a transport is actually held to it. See ViewSetInternals.request. */\n protected abstract override request<R>(method: HttpMethod, path: string, options?: RequestOptions): Promise<R>;\n\n /**\n * Fetches the BE schema and compares it against what this ViewSet declared.\n * Logs a console warning for any mismatch found.\n *\n * The comparison is against `static declares`, not against which methods exist on the object:\n * every action is implemented unconditionally on this class, so `typeof this[action]` is true for\n * every ViewSet and answers a question nobody asked. `declares` is the only place the FE says\n * anything a BE viewset could disagree with.\n *\n * Non-critical: errors during fetch or parsing are silently ignored. Note that the schema is\n * fetched over this proxy's own transport, so a muxws proxy validates against the muxws\n * endpoint set and a REST proxy against the REST one — which is the point, since the two are\n * allowed to differ.\n */\n private async validateAgainstSchema(): Promise<void> {\n // A ViewSet that declares nothing is not checked, and does not pay for the schema request\n // either. The alternative - assuming it meant \"all of them\" - is what the previous\n // implementation effectively did, and it is why a viewset built from one mixin reported every\n // action it had never claimed to have.\n const declared = declaredActions(this, this.declaredMixins);\n if (declared === null) return;\n\n try {\n const schema = await this.request<{ paths?: Record<string, Record<string, unknown>> }>('GET', '/schema');\n const paths = schema?.paths ?? {};\n\n const beActions = new Set<string>();\n const beAlternatives: { endpoint: string; actions: readonly string[] }[] = [];\n const customEndpoints: { endpoint: string; method: string }[] = [];\n\n for (const [path, pathItem] of Object.entries(paths)) {\n const suffix = path.slice(this.basePath.length).replace(/^\\//, '');\n const verbs = Object.keys(pathItem).filter((v) => HTTP_METHODS.has(v.toLowerCase()));\n\n let pathType: string;\n if (suffix === '') {\n pathType = 'base';\n } else if (suffix === 'bulk') {\n pathType = 'bulk';\n } else if (suffix === 'lookup') {\n pathType = 'lookup';\n } else if (suffix === 'schema') {\n continue;\n } else if (suffix.startsWith('{') && !suffix.includes('/')) {\n pathType = 'pk';\n } else {\n // A custom endpoint. Here - and only here - asking whether the proxy has a method of that\n // name is honest: the base implements no custom actions, so whatever answers is something\n // this ViewSet's own author wrote.\n for (const verb of verbs) {\n customEndpoints.push({ endpoint: `${verb.toUpperCase()} ${path}`, method: suffix.split('/')[0] });\n }\n continue;\n }\n\n const methodMap = ENDPOINT_TO_FE_METHOD[pathType] ?? {};\n for (const verb of verbs) {\n const actions = methodMap[verb.toUpperCase()];\n if (!actions) continue;\n beAlternatives.push({ endpoint: `${verb.toUpperCase()} ${path}`, actions });\n for (const action of actions) beActions.add(action);\n }\n }\n\n const warnings: string[] = [];\n\n for (const action of STANDARD_FE_METHODS) {\n if (declared.has(action) && !beActions.has(action)) {\n warnings.push(`declares '${action}' but the BE viewset serves no such endpoint`);\n }\n }\n\n for (const { endpoint, actions } of beAlternatives) {\n if (!actions.some((action) => declared.has(action))) {\n warnings.push(`BE serves '${endpoint}' but the ViewSet declares no ${actions.join(' / ')}`);\n }\n }\n\n for (const { endpoint, method } of customEndpoints) {\n if (typeof (this as unknown as Record<string, unknown>)[method] !== 'function') {\n warnings.push(`BE serves '${endpoint}' but the ViewSet has no '${method}()' method`);\n }\n }\n\n if (warnings.length > 0) {\n console.warn(\n `[ViewSet ${this.basePath}] FE/BE definition mismatch:\\n` + warnings.map((w) => ` • ${w}`).join('\\n'),\n );\n }\n } catch {\n // Schema validation is non-critical; ignore fetch/parse errors silently\n }\n }\n\n async create(data: Omit<T, PK>): Promise<T> {\n return this.request<T>('POST', '', { body: data });\n }\n\n async bulkCreate(data: Omit<T, PK>[]): Promise<T[]> {\n return this.request<T[]>('POST', '/bulk', { body: data });\n }\n\n async list(params?: ListParams): Promise<T[]> {\n return this.request<T[]>('GET', '', { query: params });\n }\n\n /**\n * Fetches one page. Only meaningful against a viewset built on the BE PaginatedListMixin — a\n * plain ListMixin ignores offset/limit and answers with the whole collection, which would not\n * match this return type.\n *\n * The BE speaks snake_case (`has_more`); the rest of this client speaks whatever the model\n * declares, so only the envelope's own fields are renamed here. The records inside are passed\n * through untouched.\n */\n /**\n * Fetches one cursor page. Only meaningful against a viewset built on the BE CursorListMixin.\n *\n * Follow `next` to walk forward. Unlike offset paging, a row inserted or removed behind you\n * cannot make the next page repeat or skip anything.\n */\n async listCursor(params?: CursorParams): Promise<CursorPage<T>> {\n const page = await this.request<{\n results: T[];\n limit: number;\n has_more: boolean;\n has_previous: boolean;\n next: string | null;\n previous: string | null;\n first: string | null;\n last: string | null;\n }>('GET', '', { query: params });\n return {\n results: page.results,\n limit: page.limit,\n hasMore: page.has_more,\n hasPrevious: page.has_previous,\n next: page.next,\n previous: page.previous,\n first: page.first,\n last: page.last,\n };\n }\n\n async listPage(params?: PageParams): Promise<PaginatedList<T>> {\n const page = await this.request<{\n results: T[];\n offset: number;\n limit: number | null;\n count: number | null;\n has_more: boolean;\n has_previous: boolean;\n }>('GET', '', { query: params });\n return {\n results: page.results,\n offset: page.offset,\n limit: page.limit,\n count: page.count,\n hasMore: page.has_more,\n hasPrevious: page.has_previous,\n };\n }\n\n async retrieve(pk: K): Promise<T> {\n return this.request<T>('GET', `/${pk}`);\n }\n\n async update(pk: K, data: T): Promise<T> {\n return this.request<T>('PUT', `/${pk}`, { body: data });\n }\n\n async partialUpdate(pk: K, data: Partial<T>): Promise<T> {\n return this.request<T>('PATCH', `/${pk}`, { body: data });\n }\n\n async bulkUpdate(records: Record<K, T>): Promise<T[]> {\n return this.request<T[]>('PUT', '/bulk', { body: records });\n }\n\n async bulkPartialUpdate(records: Record<K, Partial<T>>): Promise<T[]> {\n return this.request<T[]>('PATCH', '/bulk', { body: records });\n }\n\n async destroy(pk: K): Promise<DestroyReturnData> {\n return this.request<DestroyReturnData>('DELETE', `/${pk}`);\n }\n\n async bulkDestroy(pks: K[]): Promise<DestroyReturnData[]> {\n return this.request<DestroyReturnData[]>('DELETE', '/bulk', { body: pks });\n }\n\n async lookup(): Promise<LookupItem[]> {\n return this.request<LookupItem[]>('GET', '/lookup');\n }\n}\n","/**\n * muxws proxy for ViewSets — the same ViewSet surface as route_rest, sent over one WebSocket.\n *\n * import { connect } from 'muxws';\n *\n * const peer = await connect('ws://localhost:8000/ws');\n * const items = route_muxws<BulkViewSetMixin<number, Item> & LookupMixin>(\n * ItemViewSet, { basePath: '/items', pkFieldName: 'id', peer },\n * );\n * await items.list();\n *\n * A proxy speaks one transport. Creating a REST proxy and a muxws proxy for the same ViewSet is\n * the supported way to have both — they share nothing but the ViewSet's own type.\n */\n\nimport type { KeyType } from './mixins';\nimport {\n FACTORY_BUILT,\n type HttpMethod,\n type ProxyBaseOptions,\n type QueryParams,\n type RequestOptions,\n ViewSetProxyBase,\n type ViewSetMixinDeclaration,\n ViewSetRequestError,\n} from './proxy-base';\n\n/**\n * The bits of a muxws Peer this proxy uses. Typed structurally rather than by importing the muxws\n * types, so that `muxws` stays an optional dependency: an application that only uses route_rest\n * should not have to install it.\n */\nexport interface MuxwsStreamLike {\n result(options?: { timeoutMs?: number }): Promise<unknown>;\n readonly replyHeadersArrived: Promise<void>;\n readonly replyHeaders: Record<string, unknown> | null | undefined;\n}\n\nexport interface MuxwsPeerLike {\n open(payload?: unknown, options?: { headers?: Record<string, unknown>; end?: boolean }): MuxwsStreamLike;\n}\n\n/**\n * Where the peer comes from. A bare peer is the simple case; a function is for the common shape\n * where the proxy is constructed at module scope but `connect()` has not resolved yet. The\n * function is called once and its result cached — a muxws Peer survives its own reconnects, so\n * there is nothing to re-resolve afterwards.\n */\nexport type MuxwsPeerSource = MuxwsPeerLike | (() => MuxwsPeerLike | Promise<MuxwsPeerLike>);\n\nexport type MuxwsProxy<M> = M;\n\ntype ViewSetClass = abstract new (...args: any[]) => any;\n\n/**\n * The shape a factory-built class's constructor has, purely to give `route_muxws` an overload that\n * rejects it - see rest-proxy.ts's `FactoryBuiltClass` for why this has to be a plain overload\n * parameter rather than a type parameter conditioned on the argument.\n */\ntype FactoryBuiltClass = (abstract new (...args: any[]) => any) & {\n declares: { readonly [FACTORY_BUILT]: any };\n};\n\n/** Never assigned; its only use is `typeof FACTORY_BUILT_REJECTION` as a self-describing return type. */\n// eslint-disable-next-line @typescript-eslint/no-unused-vars -- type-only, read via `typeof` below\ndeclare const FACTORY_BUILT_REJECTION: 'route_muxws cannot take a factory-built class - extend it directly instead';\n\nexport interface MuxwsProxyOptions extends ProxyBaseOptions {\n peer: MuxwsPeerSource;\n /** Per-request timeout in milliseconds. muxws resets the stream with TIMEOUT when it expires. */\n timeoutMs?: number;\n /**\n * Headers added to every request this proxy makes. The WebSocket handshake already carries\n * whatever identified the session and the server treats that as the baseline; these override it\n * for this proxy's calls. There is no per-call header: `RequestOptions` is `{ query, body }`.\n */\n headers?: Record<string, string>;\n}\n\nexport class MuxwsProxyImpl<K extends KeyType, T, PK extends keyof T> extends ViewSetProxyBase<K, T, PK> {\n private readonly peerSource: MuxwsPeerSource;\n\n private readonly timeoutMs?: number;\n\n private readonly headers: Record<string, string>;\n\n private peerPromise?: Promise<MuxwsPeerLike>;\n\n constructor(options: MuxwsProxyOptions) {\n super(options);\n this.peerSource = options.peer;\n this.timeoutMs = options.timeoutMs;\n this.headers = options.headers ?? {};\n this.initSchemaValidation();\n }\n\n private peer(): Promise<MuxwsPeerLike> {\n if (!this.peerPromise) {\n const source = this.peerSource;\n this.peerPromise = Promise.resolve(typeof source === 'function' ? source() : source);\n }\n return this.peerPromise;\n }\n\n protected async request<R>(method: HttpMethod, path: string, options: RequestOptions = {}): Promise<R> {\n const peer = await this.peer();\n\n // Addressing mirrors HTTP/2: :-prefixed pseudo-headers for the request line, plain keys for\n // real HTTP headers. muxws itself never reads any of them (SPEC WSM-AUT-002).\n const headers: Record<string, unknown> = {\n ...this.headers,\n ':method': method,\n ':path': `${this.basePath}${path}`,\n };\n if (options.query && Object.keys(options.query).length > 0) headers[':query'] = cleanQuery(options.query);\n\n // end: true — this is a unary call, so the request is complete with its opening frame.\n const stream = peer.open(options.body, { headers, end: true });\n\n // The status is announced before the body, in the answering side's leading headers (muxws\n // 0.3.1+). Awaiting the gate first means an error status is known without reading the response\n // at all — which is what makes this work for a streaming reply and not only a unary one.\n await stream.replyHeadersArrived;\n const status = readStatus(stream.replyHeaders);\n\n const body = await stream.result(this.timeoutMs === undefined ? undefined : { timeoutMs: this.timeoutMs });\n if (status >= 400) throw new ViewSetRequestError(status, body, readHeaders(stream.replyHeaders));\n return body as R;\n }\n}\n\nfunction cleanQuery(query: QueryParams): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined || value === null) continue;\n out[key] = value;\n }\n return out;\n}\n\n/**\n * A response with no `:status` is treated as 200. That is what a server which answered normally\n * looks like on a transport that has not been taught to say otherwise, and inventing a failure\n * for it would break every such server.\n */\nfunction readStatus(replyHeaders: Record<string, unknown> | null | undefined): number {\n const raw = replyHeaders?.[':status'];\n if (typeof raw === 'number') return raw;\n if (typeof raw === 'string') {\n const parsed = Number.parseInt(raw, 10);\n if (!Number.isNaN(parsed)) return parsed;\n }\n return 200;\n}\n\nfunction readHeaders(replyHeaders: Record<string, unknown> | null | undefined): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [key, value] of Object.entries(replyHeaders ?? {})) {\n if (!key.startsWith(':')) out[key] = String(value);\n }\n return out;\n}\n\n// Exists only to reject a factory-built class with a message at the call site; nothing implements\n// or calls it - see rest-proxy.ts's route_rest for why.\n// eslint-disable-next-line @typescript-eslint/no-unused-vars -- M keeps the call syntax identical to the real overload\nfunction route_muxws<M = never>(\n viewSetClass: FactoryBuiltClass,\n options: MuxwsProxyOptions,\n): typeof FACTORY_BUILT_REJECTION;\n/**\n * Registers a muxws proxy for the given ViewSet class. The mirror of route_rest, and it takes the\n * same generic parameter for the same reason: TypeScript cannot inspect the Python class.\n */\nfunction route_muxws<M>(viewSetClass: ViewSetClass, options: MuxwsProxyOptions): MuxwsProxy<M>;\nfunction route_muxws<M>(viewSetClass: ViewSetClass, options: MuxwsProxyOptions): MuxwsProxy<M> {\n // The class is otherwise used only for its type. Its `declares` is the one thing on it the proxy\n // needs at runtime, so it is carried across rather than lost.\n return new MuxwsProxyImpl({\n declares: (viewSetClass as { declares?: ViewSetMixinDeclaration[] }).declares,\n ...options,\n }) as unknown as MuxwsProxy<M>;\n}\n\nexport { route_muxws };\n","/**\n * REST proxy for ViewSets — FE counterpart of the BE route_viewset decorator.\n *\n * Usage:\n * const restItems = route_rest<BulkViewSetMixin<number, Item> & LookupMixin>(\n * ItemViewSet, '/items', 'id',\n * );\n * const items = await restItems.list();\n * const item = await restItems.retrieve(1);\n *\n * Everything except the sending lives in ViewSetProxyBase, which MuxwsProxyImpl shares — see\n * proxy-base.ts.\n */\n\nimport axios, { type AxiosInstance } from 'axios';\n\nimport type { KeyType } from './mixins';\nimport {\n FACTORY_BUILT,\n type HttpMethod,\n type ProxyBaseOptions,\n type RequestOptions,\n type ViewSetMixinDeclaration,\n ViewSetProxyBase,\n} from './proxy-base';\n\n// ---------------------------------------------------------------------------\n// Helper types\n// ---------------------------------------------------------------------------\n\n/** ViewSet class constructor (for type-level introspection only). */\n\ntype ViewSetClass = abstract new (...args: any[]) => any;\n\n/**\n * The shape a factory-built class's constructor has, purely to give `route_rest` an overload that\n * rejects it - a type parameter conditioned on the argument (`C extends FactoryBuiltClass ? ... :\n * C`) cannot be inferred from that position at all, so an overload with this as a plain parameter\n * type is the only form that actually sees the real argument.\n *\n * `route_rest` uses its `viewSetClass` argument only for the `declares` on it (see the function\n * body - the class itself is never `new`'d), then builds a bare `RestProxyImpl` and hands it back\n * cast to `M`. Pass a factory-built class and this still type-checks without the overload below -\n * `M` is usually inferred as `InstanceType<typeof ItemApi>` - but the object it returns is not an\n * `ItemApi`, so any custom method the factory-built class added is `undefined` at runtime despite\n * compiling. `declares` is required here, not optional: a hand-written class\n * (`class ItemViewSet extends RestProxyImpl<...> {}`) may have no `declares` at all, or one that is\n * a plain array rather than carrying `FACTORY_BUILT`, and either must fall through to the real\n * overload below rather than match this one.\n */\ntype FactoryBuiltClass = (abstract new (...args: any[]) => any) & {\n declares: { readonly [FACTORY_BUILT]: any };\n};\n\n/** Never assigned; its only use is `typeof FACTORY_BUILT_REJECTION` as a self-describing return type. */\n// eslint-disable-next-line @typescript-eslint/no-unused-vars -- type-only, read via `typeof` below\ndeclare const FACTORY_BUILT_REJECTION: 'route_rest cannot take a factory-built class - extend it directly instead';\n\n/**\n * The REST proxy type is simply the mixin interface `M` the caller declares.\n * Because TypeScript cannot inspect Python class hierarchies at runtime, the\n * caller provides the explicit type via the generic parameter `M` (see route_rest).\n */\nexport type RestProxy<M> = M;\n\nexport interface RestProxyOptions extends ProxyBaseOptions {\n /** Optional: existing axios instance. Defaults to the global axios. */\n axiosInstance?: AxiosInstance;\n}\n\n// ---------------------------------------------------------------------------\n// Proxy implementation\n// ---------------------------------------------------------------------------\n\nexport class RestProxyImpl<K extends KeyType, T, PK extends keyof T> extends ViewSetProxyBase<K, T, PK> {\n protected readonly http: AxiosInstance;\n\n constructor(options: RestProxyOptions) {\n super(options);\n this.http = options.axiosInstance ?? axios;\n this.initSchemaValidation();\n }\n\n /**\n * Dispatches to axios' per-verb methods rather than to `http.request()`, deliberately. Those\n * are the calls this proxy has always made, and they are what application interceptors and test\n * doubles are written against — routing everything through `request()` would be invisible on\n * the wire but would break every one of them.\n *\n * axios throws its own AxiosError on a non-2xx, which already carries `response.status` — the\n * shape ViewSetRequestError mirrors for the muxws side. Nothing to translate here.\n */\n protected async request<R>(method: HttpMethod, path: string, options: RequestOptions = {}): Promise<R> {\n const url = `${this.basePath}${path}`;\n const hasQuery = options.query !== undefined && Object.keys(options.query).length > 0;\n const config = hasQuery ? { params: options.query } : undefined;\n\n switch (method) {\n case 'GET': {\n const res = config ? await this.http.get<R>(url, config) : await this.http.get<R>(url);\n return res.data;\n }\n // The config argument is omitted rather than passed as undefined: axios treats the two\n // identically, but a spy does not, and the call shape is part of what this proxy promises.\n case 'POST': {\n const res = config\n ? await this.http.post<R>(url, options.body, config)\n : await this.http.post<R>(url, options.body);\n return res.data;\n }\n case 'PUT': {\n const res = config\n ? await this.http.put<R>(url, options.body, config)\n : await this.http.put<R>(url, options.body);\n return res.data;\n }\n case 'PATCH': {\n const res = config\n ? await this.http.patch<R>(url, options.body, config)\n : await this.http.patch<R>(url, options.body);\n return res.data;\n }\n case 'DELETE': {\n // A DELETE body has to go through the config object; axios has no positional slot for it.\n const deleteConfig = options.body === undefined ? config : { ...(config ?? {}), data: options.body };\n const res = deleteConfig ? await this.http.delete<R>(url, deleteConfig) : await this.http.delete<R>(url);\n return res.data;\n }\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Decorator / factory\n// ---------------------------------------------------------------------------\n\n/**\n * Registers a REST proxy for the given ViewSet class.\n *\n * The generic parameter `M` determines which mixin interfaces are available —\n * typically the ViewSet type (or a union of mixin interfaces).\n *\n * @example\n * ```ts\n * import type { BulkViewSetMixin, LookupMixin } from './mixins';\n *\n * interface Item { id: number; name: string }\n *\n * // with separate arguments (recommended)\n * const restItems = route_rest<BulkViewSetMixin<number, Item> & LookupMixin>(\n * ItemViewSet, '/items', 'id',\n * );\n *\n * // or with an options object\n * const restItems2 = route_rest<BulkViewSetMixin<number, Item> & LookupMixin>(\n * ItemViewSet, { basePath: '/items', pkFieldName: 'id' },\n * );\n *\n * const items = await restItems.list();\n * const item = await restItems.retrieve(1);\n * ```\n */\n// These two overloads exist only to reject a factory-built class with a message at the call site;\n// nothing implements or calls them - overload resolution tries them first, and a factory-built\n// class's constructor matches FactoryBuiltClass before it ever reaches the real overloads below.\n// eslint-disable-next-line @typescript-eslint/no-unused-vars -- M keeps the call syntax identical to the real overload\nfunction route_rest<M = never>(\n viewSetClass: FactoryBuiltClass,\n basePath: string,\n pkFieldName: string,\n axiosInstance?: AxiosInstance,\n): typeof FACTORY_BUILT_REJECTION;\n// eslint-disable-next-line @typescript-eslint/no-unused-vars -- M keeps the call syntax identical to the real overload\nfunction route_rest<M = never>(\n viewSetClass: FactoryBuiltClass,\n options: RestProxyOptions,\n): typeof FACTORY_BUILT_REJECTION;\nfunction route_rest<M>(\n _viewSetClass: ViewSetClass,\n basePath: string,\n pkFieldName: string,\n axiosInstance?: AxiosInstance,\n): RestProxy<M>;\nfunction route_rest<M>(_viewSetClass: ViewSetClass, options: RestProxyOptions): RestProxy<M>;\nfunction route_rest<M>(\n viewSetClass: ViewSetClass,\n basePathOrOptions: string | RestProxyOptions,\n pkFieldName?: string,\n axiosInstance?: AxiosInstance,\n): RestProxy<M> {\n const options: RestProxyOptions =\n typeof basePathOrOptions === 'string'\n ? {\n basePath: basePathOrOptions,\n pkFieldName: pkFieldName!,\n axiosInstance,\n }\n : basePathOrOptions;\n // The class is otherwise used only for its type. Its `declares` is the one thing on it the proxy\n // needs at runtime, so it is carried across rather than lost.\n return new RestProxyImpl({\n declares: (viewSetClass as { declares?: ViewSetMixinDeclaration[] }).declares,\n ...options,\n }) as unknown as RestProxy<M>;\n}\n\nexport { route_rest };\n","/**\n * The ViewSet class factory — FE counterpart of listing mixins in a BE viewset's bases.\n *\n * class ItemApi extends restViewSet<Item>()('id', [ReadOnlyViewSetMixin, LookupMixin]) {\n * async count(): Promise<number> {\n * return this.request<number>('GET', '/count');\n * }\n * }\n *\n * The mixin list is written once, as values, and does three jobs: it decides which actions the\n * class exposes to callers, it types them, and it is handed to the proxy so the startup schema\n * check can compare it against the BE. Calling an action the ViewSet did not declare is a compile\n * error rather than a 404 at runtime.\n *\n * This is what `route_rest` cannot do. That returns an instance cast to a mixin interface, so the\n * type narrows but the object is a bare proxy: a custom endpoint declared in the interface type-\n * checks and is undefined when called. Here the object is the consumer's own class, so its own\n * methods exist.\n */\n\nimport type { ActionName, ActionSurface, KeyType } from './mixins';\nimport { MuxwsProxyImpl, type MuxwsProxyOptions } from './muxws-proxy';\nimport { FACTORY_BUILT, type ProxyBaseOptions, ViewSetInternals, type ViewSetMixinDeclaration } from './proxy-base';\nimport { RestProxyImpl, type RestProxyOptions } from './rest-proxy';\n\n/** A mixin class: the runtime `actions` the schema check reads, and the type naming those actions. */\nexport type ViewSetMixinClass = ViewSetMixinDeclaration & (abstract new (...args: any[]) => object);\n\n/**\n * The actions a mixin class names, read off its instance type.\n *\n * The instance type rather than the `actions` tuple, because a tuple would have to be `as const` on\n * every mixin, and a `readonly ['create']` static cannot then be inherited by a composite whose own\n * static is a different tuple (TS2417). `D` is naked so that a union of mixins distributes.\n */\ntype ActionsOf<D> = D extends abstract new (...args: any[]) => infer I ? Extract<keyof I, ActionName> : never;\n\n/** The fields of `T` that could be a primary key. */\nexport type PkFieldName<T> = Extract<\n { [F in keyof T]-?: NonNullable<T[F]> extends KeyType ? F : never }[keyof T],\n string\n>;\n\ntype PkType<T, PK extends keyof T> = NonNullable<T[PK]> & KeyType;\n\n/**\n * Brands a factory-built class's `declares` so a subclass restating it fails on one flat \"missing\n * property\" line naming `declares` itself, rather than TypeScript recursing into which method each\n * mixin in the plain, unbranded replacement array is missing relative to the original. A record\n * type wrapping `D` behind the phantom key, not `D & {brand}`: an intersection would still expose\n * `D`'s own array shape to the comparison and recurse into it exactly as before; a plain array\n * literal has no property at all under this key, so the mismatch stops at the top. Erased at\n * runtime - `bindViewSet` casts through `unknown`, so the actual `static declares` stays a plain\n * array, and every internal reader (rest-proxy.ts, muxws-proxy.ts, proxy-base.ts's\n * `declaredActions`) already reads it through its own cast rather than this type.\n *\n * `FACTORY_BUILT` lives in proxy-base.ts, not here, so `route_rest`/`route_muxws` can check for the\n * same brand without importing from this module - see proxy-base.ts's doc comment on the symbol.\n */\ntype FactoryDeclares<D extends readonly ViewSetMixinClass[]> = { readonly [FACTORY_BUILT]: D };\n\n/**\n * What the factory hands back: a class to extend.\n *\n * A `declares` list naming no action — `[]`, or one annotated `ViewSetMixinClass[]`, which erases\n * which mixins are in it — would otherwise produce a ViewSet with no actions and no complaint.\n * TS2507 prints the type it was given, so the type is the sentence.\n *\n * `pkFieldName: PK` is added on top of `ViewSetInternals` explicitly: the constructed instance's\n * real runtime type is `ProxyBaseOptions`'s `ViewSetProxyBase`, which already carries the public\n * `pkFieldName`, but `ViewSetInternals` is deliberately narrow (see its own doc comment), so this\n * type would otherwise hide a field that is genuinely there. Typed as the literal `PK` rather than\n * `string`, since the factory already knows exactly which field it is.\n */\nexport type ViewSetClass<T, PK extends keyof T, D extends readonly ViewSetMixinClass[], O extends ProxyBaseOptions> = [\n ActionsOf<D[number]>,\n] extends [never]\n ? 'declares must name at least one action: pass the mixin classes themselves, unannotated'\n : {\n new (\n options: Omit<O, 'pkFieldName' | 'declares'>,\n ): ViewSetInternals & ActionSurface<PkType<T, PK>, T, PK, ActionsOf<D[number]>> & { readonly pkFieldName: PK };\n readonly declares: FactoryDeclares<D>;\n };\n\n/**\n * `any` on the constructor because a class expression may only extend a constructor whose members\n * are statically known, and the two transports' options differ. The real type is put back on by the\n * return type of whichever factory calls this.\n */\ntype TransportClass = new (options: any) => any;\n\n/** Named `ViewSet` so that a stack trace and the devtools prototype chain say something. */\nfunction bindViewSet(\n Impl: TransportClass,\n pkFieldName: string,\n declares: readonly ViewSetMixinDeclaration[],\n): TransportClass {\n return class ViewSet extends Impl {\n static declares = declares;\n\n constructor(options: ProxyBaseOptions) {\n // Injected into the argument expression rather than assigned afterwards: under\n // useDefineForClassFields an assignment would emit a [[Define]] that runs after super() and\n // would clobber what the base constructor had just set.\n //\n // `declares: undefined` so that the static above is what the check reads. A subclass stating\n // its own `static declares` then shadows it by ordinary static lookup, as it always has.\n super({ ...options, pkFieldName, declares: undefined });\n }\n };\n}\n\n/**\n * A REST ViewSet base class.\n *\n * The empty `()` is not decoration: TypeScript has no partial type-argument inference, so the model\n * cannot be given explicitly while the pk field and the mixin list are inferred from arguments in\n * the same call (TS2558). Currying is the only way to have both.\n */\nexport function restViewSet<T>() {\n return <PK extends PkFieldName<T> & keyof T, D extends readonly ViewSetMixinClass[]>(\n pkFieldName: PK,\n declares: D,\n ): ViewSetClass<T, PK, D, RestProxyOptions> =>\n bindViewSet(RestProxyImpl, pkFieldName, declares) as unknown as ViewSetClass<T, PK, D, RestProxyOptions>;\n}\n\n/** A muxws ViewSet base class. See `restViewSet` for why the call is curried. */\nexport function muxwsViewSet<T>() {\n return <PK extends PkFieldName<T> & keyof T, D extends readonly ViewSetMixinClass[]>(\n pkFieldName: PK,\n declares: D,\n ): ViewSetClass<T, PK, D, MuxwsProxyOptions> =>\n bindViewSet(MuxwsProxyImpl, pkFieldName, declares) as unknown as ViewSetClass<T, PK, D, MuxwsProxyOptions>;\n}\n","import { createTranslatable, interpolate } from '@dynamicforms/translatable';\n\n/**\n * A failed request's response body. `detail` is always a plain string - unchanged from what it has\n * always been. `detail_code` and `detail_params` are additive, and appear only when the server has\n * registered `df_viewset_exception_handler` (see the Python side's `fastapi_viewsets.exceptions`)\n * for one of this package's own built-in errors; a view's own `raise HTTPException(status_code,\n * detail=\"...\")` never carries them.\n */\nexport interface ApiErrorBody {\n detail: string;\n detail_code?: string;\n detail_params?: Record<string, unknown>;\n}\n\n/**\n * English defaults for every `detail_code` the server side raises on its own, keyed by that code\n * rather than by its English text. `{name}`-style placeholders match the keys `detail_params`\n * carries for that code.\n */\nexport const { strings: translatableStrings, translateStrings } = createTranslatable({\n not_found: 'Item with pk {pk} not found',\n session_expired: 'Session expired or invalid',\n not_authorized: 'Not authorized to perform this action',\n rate_limited: 'Rate limit exceeded',\n unsupported_list_shape: 'unsupported list shape \"{shape}\"; this endpoint offers {allowed}',\n cursor_unreadable: 'cursor is not readable: {error}',\n cursor_missing_position: 'cursor is not readable: no position in it',\n cursor_stale: 'this cursor was issued for a different ordering or filter - start from the first page',\n cursor_missing_keys: 'cursor has no value for ordering key(s): {missing}',\n cursor_value_mismatch: 'cursor value for \"{name}\" does not fit the field: {error}',\n});\n\n/**\n * A translated, interpolated message for a failed request - `body.detail` unchanged when\n * `detail_code` is absent (the server has not registered the handler, or this is a view's own\n * plain-string error) or names a code this table does not (yet) cover.\n */\nexport function translateApiError(body: ApiErrorBody): string {\n if (!body.detail_code) return body.detail;\n\n const template = (translatableStrings as Record<string, string>)[body.detail_code];\n if (template == null) return body.detail;\n\n const params = { ...body.detail_params };\n if (Array.isArray(params.allowed)) params.allowed = params.allowed.join(', ');\n if (Array.isArray(params.missing)) params.missing = params.missing.join(', ');\n\n return interpolate(template, params);\n}\n"],"mappings":"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"}
|