import * as axios from 'axios'; import { AxiosInstance, AxiosResponse, AxiosRequestConfig, InternalAxiosRequestConfig } from 'axios'; interface SubQueryOptions { path?: string; compact?: boolean; } interface ListArgs { skip?: number; limit?: number; page?: number; pageSize?: number; } interface ListOptions { skim?: boolean; includePermissions?: boolean; includeCount?: boolean; includeExtraHeaders?: boolean; ignoreCache?: boolean; sq?: SubQueryOptions; } interface ListAdvancedArgs { select?: TSelect; populate?: Populate[] | Populate | string; /** * ACI-04: legacy `Include` joins or `$include()`-converted correlated * payloads (`CorrelatedIncludeInput`). Converted payloads travel as data; * unconverted descriptors / live requests are rejected with a controlled * error at call time. */ include?: CorrelatedIncludeInput | CorrelatedIncludeInput[]; sort?: Sort; skip?: string | number; limit?: string | number; page?: string | number; pageSize?: string | number; tasks?: Task | Task[]; } interface ListAdvancedOptions { skim?: boolean; includePermissions?: boolean; includeCount?: boolean; includeExtraHeaders?: boolean; ignoreCache?: boolean; populateAccess?: PopulateAccess; sq?: SubQueryOptions; } interface ReadOptions { includePermissions?: boolean; tryList?: boolean; ignoreCache?: boolean; sq?: SubQueryOptions; } interface ReadAdvancedArgs { select?: TSelect; sort?: Sort; populate?: Populate[] | Populate | string; /** * ACI-04: legacy `Include` joins or `$include()`-converted correlated * payloads (`CorrelatedIncludeInput`). See `ListAdvancedArgs.include`. */ include?: CorrelatedIncludeInput | CorrelatedIncludeInput[]; tasks?: Task | Task[]; } interface ReadAdvancedOptions { skim?: boolean; includePermissions?: boolean; tryList?: boolean; populateAccess?: PopulateAccess; ignoreCache?: boolean; sq?: SubQueryOptions; } interface CreateOptions { includePermissions?: boolean; } interface CreateAdvancedArgs { select?: TSelect; populate?: Populate[] | Populate | string; tasks?: Task | Task[]; } interface CreateAdvancedOptions { includePermissions?: boolean; populateAccess?: PopulateAccess; } interface UpdateOptions { returningAll?: boolean; includePermissions?: boolean; } interface UpdateAdvancedArgs { select?: TSelect; populate?: Populate[] | Populate | string; tasks?: Task | Task[]; } interface UpdateAdvancedOptions { returningAll?: boolean; includePermissions?: boolean; populateAccess?: PopulateAccess; } type UpsertOptions = UpdateOptions; type UpsertAdvancedArgs = UpdateAdvancedArgs; type UpsertAdvancedOptions = UpdateAdvancedOptions; interface Defaults { listArgs?: ListArgs; listOptions?: ListOptions; listAdvancedArgs?: ListAdvancedArgs; listAdvancedOptions?: ListAdvancedOptions; readOptions?: ReadOptions; readAdvancedArgs?: ReadAdvancedArgs; readAdvancedOptions?: ReadAdvancedOptions; createOptions?: CreateOptions; createAdvancedArgs?: CreateAdvancedArgs; createAdvancedOptions?: CreateAdvancedOptions; updateOptions?: UpdateOptions; updateAdvancedArgs?: UpdateAdvancedArgs; updateAdvancedOptions?: UpdateAdvancedOptions; upsertOptions?: UpsertOptions; upsertAdvancedArgs?: UpsertAdvancedArgs; upsertAdvancedOptions?: UpsertAdvancedOptions; } interface DataListArgs { skip?: number; limit?: number; page?: number; pageSize?: number; } interface DataListOptions { includeCount?: boolean; includeExtraHeaders?: boolean; ignoreCache?: boolean; } interface DataListAdvancedArgs { select?: TSelect; sort?: string; skip?: string | number; limit?: string | number; page?: string | number; pageSize?: string | number; } interface DataListAdvancedOptions { includeCount?: boolean; includeExtraHeaders?: boolean; ignoreCache?: boolean; } interface DataReadOptions { ignoreCache?: boolean; } interface DataReadAdvancedArgs { select?: TSelect; } /** * Options for `DataService.readAdvanced` and `DataService.readAdvancedFilter`. * * `ignoreCache` is the documented cache-bypass knob for advanced reads. It * lives here — not on `DataReadAdvancedArgs` — so callers use `{ ignoreCache: * true }` in the options position to skip an existing cache entry, matching * the placement used by the basic `list`, `listAdvanced`, and `read` service * methods. * * `includePermissions` is intentionally absent. The access-router data * router body schema for advanced reads (`dataReadByIdBodySchema` and * `dataReadFilterBodySchema`) explicitly rejects the `options` key, and the * root router drops `item.options` when dispatching data operations * server-side (root-router.ts passes `{}` as the options argument to * `findById`/`findOne`). Advertising `includePermissions` here was a * type-level promise the server cannot honor and the grouped path silently * passed through `__query.options.includePermissions` only for the root * router to discard it — a direct/grouped asymmetry. The fix removes the * dead-letter field from the type and from `__query.options` so direct and * grouped advanced reads compose identical payloads. */ interface DataReadAdvancedOptions { ignoreCache?: boolean; } interface DataDefaults { listArgs?: DataListArgs; listOptions?: DataListOptions; listAdvancedArgs?: DataListAdvancedArgs; listAdvancedOptions?: DataListAdvancedOptions; readOptions?: DataReadOptions; readAdvancedArgs?: DataReadAdvancedArgs; readAdvancedOptions?: DataReadAdvancedOptions; } interface AdditionalReqConfig { throwOnError?: boolean; } /** * Normalized failure payload. Mirrors {@link FailureResult} but kept * structurally loose (the {@link Response} discriminated union narrows * these fields automatically when consumers branch on `result.success`). */ interface ResultError { success: false; raw: unknown; data: null; message: string; status: number; headers: Record; totalCount?: number; } /** * Low-level base class shared by {@link ModelService} and {@link DataService}. * Subclassing is supported as an advanced opt-in for callers that need a * bespoke service shape: subclasses extend `Service`, build on the shared * Axios instance, and reuse the `wrapGet`/`wrapPost`/... paths registered * against the adapter's `basePath`. Most callers should use * `adapter.createModelService(...)` / `adapter.createDataService(...)` * rather than subclassing `Service` directly. * * The `handleSuccess`/`handleError` helpers normalize Axios responses into * the package's {@link Response} discriminated union so direct subclasses * produce the same success/failure contract as the built-in services. */ declare class Service { protected _axios: AxiosInstance; protected _basePath: string; private _wrap; private _throwOnError; constructor(axios: AxiosInstance, basePath: string, throwOnError?: boolean); protected handleSuccess = Response>(res: AxiosResponse, extra?: {}): T; protected handleError>(error: unknown): Extract; /** Resolves per-call policy against the already-resolved service/adapter default. */ resolveThrowOnError(override?: boolean): boolean; wrapGet(url: string, defaultAxiosRequestConfig?: AxiosRequestConfig): (options?: WrapOptions, requestConfig?: AxiosRequestConfig) => Promise>; wrapPost(url: string, defaultAxiosRequestConfig?: AxiosRequestConfig): (data?: unknown, options?: WrapOptions, requestConfig?: AxiosRequestConfig) => Promise>; wrapPut(url: string, defaultAxiosRequestConfig?: AxiosRequestConfig): (data?: unknown, options?: WrapOptions, requestConfig?: AxiosRequestConfig) => Promise>; wrapPatch(url: string, defaultAxiosRequestConfig?: AxiosRequestConfig): (data?: unknown, options?: WrapOptions, requestConfig?: AxiosRequestConfig) => Promise>; wrapDelete(url: string, defaultAxiosRequestConfig?: AxiosRequestConfig): (options?: WrapOptions, requestConfig?: AxiosRequestConfig) => Promise>; /** * Public bridge to the per-service success/failure callback pipeline and * `throwOnError` policy. Adapter-internal grouping machinery calls this so * that grouped entries go through the same finalization the direct path * uses (`createResponseHandler`). Returns `res` unchanged on success and * throws `ServiceError` when both `res.success === false` and the * `throwOnError` override (or the service-level default) are enabled. */ applyResponseCallbacks(res: T, throwOnErrorOverride?: boolean): T; /** * Returns a fresh headers object that includes the package-owned * `CACHE_HEADER` set to `"true"` (cache eligible) or `"false"` (bypass) * according to the `ignoreCache` option. The caller's `CACHE_HEADER` * value, if any, wins over the `ignoreCache` default. * * The input `headers` object is **never mutated**: an `AxiosHeaders` * instance is cloned via `.toJSON()` before any value is set, and a * plain-object headers input is shallow-copied. Reusing the same * caller-owned headers across multiple requests therefore has no * hidden side effects, and the order of invocations is irrelevant. */ updateHeaders(headers: AxiosRequestConfig['headers'], { ignoreCache }: { ignoreCache?: boolean; }): AxiosRequestConfig['headers']; } declare class ServiceError extends Error { success: false; readonly raw: unknown; readonly data: null; readonly status: number; readonly headers: Record; constructor(result: ResultError); } type RequestConfig$1 = AxiosRequestConfig & AdditionalReqConfig; interface Props$1 { axios: AxiosInstance; modelName: string; basePath: string; queryPath: string; mutationPath: string; onSuccess: ResponseCallback; onFailure: ResponseCallback; throwOnError: boolean; } /** * Typed client for `access-router` model CRUD routes. Created by * `adapter.createModelService(...)`. * * @example * const userService = adapter.createModelService({ modelName: 'User', basePath: 'users' }); * const user = await userService.read('user-id-1'); */ type InferredSubDocument = [S] extends [never] ? NonNullable extends readonly (infer TItem)[] ? TItem : never : S; declare class ModelService, TUpdateInput extends object = ModelMutationInput, TUpsertInput extends object = ModelMutationInput> extends Service { private _modelName; private _queryPath; private _mutationPath; private _handleCallbacks; private _defaults; constructor({ axios, modelName, basePath, queryPath, mutationPath, onSuccess, onFailure, throwOnError }: Props$1, defaults?: Defaults); list = T>(args?: ListArgs, options?: ListOptions, axiosRequestConfig?: RequestConfig$1): ModelRequest> & IncludableBasicList; listAdvanced | never = never, TSelect extends Projection = Projection, TInc extends CorrelatedIncludeInput | readonly CorrelatedIncludeInput[] | undefined = CorrelatedIncludeInput | readonly CorrelatedIncludeInput[] | undefined>(filter: FilterQuery, args?: ListAdvancedArgs & { include?: TInc; }, options?: ListAdvancedOptions, axiosRequestConfig?: RequestConfig$1): ModelRequest, TInc>>> & IncludableList; listAdvanced | never = never, TSelect extends Projection = Projection>(filter: CorrelatedFilterQuery, args?: ListAdvancedArgs, options?: ListAdvancedOptions, axiosRequestConfig?: RequestConfig$1): CorrelatedListDescriptor; create = T>(data: TCreateInput[], options?: CreateOptions, axiosRequestConfig?: RequestConfig$1): ModelRequest>; create = T>(data: TCreateInput, options?: CreateOptions, axiosRequestConfig?: RequestConfig$1): ModelRequest>; createAdvanced | never = never, TSelect extends Projection = Projection>(data: TCreateInput[], args?: CreateAdvancedArgs, options?: CreateAdvancedOptions, axiosRequestConfig?: RequestConfig$1): ModelRequest>>; createAdvanced | never = never, TSelect extends Projection = Projection>(data: TCreateInput, args?: CreateAdvancedArgs, options?: CreateAdvancedOptions, axiosRequestConfig?: RequestConfig$1): ModelRequest>>; upsert = T>(data: TUpsertInput, options?: UpsertOptions, axiosRequestConfig?: RequestConfig$1): ModelPromiseMeta & LazyRequest>; upsertAdvanced | never = never, TSelect extends Projection = Projection>(data: TUpsertInput, args?: UpsertAdvancedArgs, options?: UpsertAdvancedOptions, axiosRequestConfig?: RequestConfig$1): ModelRequest>>; delete(identifier: string, axiosRequestConfig?: RequestConfig$1): ModelPromiseMeta & LazyRequest>; new = T>(axiosRequestConfig?: RequestConfig$1): ModelPromiseMeta & LazyRequest>; /** * BND-11: distinct values are `unknown[]`, not `string[]`. The sibling * server returns raw distinct values without string conversion, so numeric * and boolean values arrive as-is. Source-compat: callers that assumed * `string[]` must narrow first (e.g. `typeof v === 'string'` or a type * guard) before calling string methods; see the BND-11 task record for * migration. No server values are stringified to satisfy the old type. */ distinct(field: string, axiosRequestConfig?: RequestConfig$1): ModelPromiseMeta & LazyRequest>; /** * BND-11: filtered distinct variant. Same `unknown[]` contract as * {@link distinct}: narrow elements before assuming strings. No * stringification is applied; dynamic field names are accepted as `string`. */ distinctAdvanced(field: string, conditions: FilterQuery, axiosRequestConfig?: RequestConfig$1): ModelPromiseMeta & LazyRequest>; count(axiosRequestConfig?: RequestConfig$1): ModelRequest> & IncludableBasicCount; countAdvanced(filter: FilterQuery, axiosRequestConfig?: RequestConfig$1): ModelRequest> & IncludableCount; countAdvanced(filter: CorrelatedFilterQuery, axiosRequestConfig?: RequestConfig$1): CorrelatedCountDescriptor; read = T>(identifier: string, options?: ReadOptions, axiosRequestConfig?: RequestConfig$1): ModelRequest> & IncludableRead; read(identifier: ParentRef, options?: ReadOptions, axiosRequestConfig?: RequestConfig$1): CorrelatedReadDescriptor; readAdvanced | never = never, TSelect extends Projection = Projection, TInc extends CorrelatedIncludeInput | readonly CorrelatedIncludeInput[] | undefined = CorrelatedIncludeInput | readonly CorrelatedIncludeInput[] | undefined>(identifier: string, args?: ReadAdvancedArgs & { include?: TInc; }, options?: ReadAdvancedOptions, axiosRequestConfig?: RequestConfig$1): ModelRequest, TInc>>> & IncludableRead; readAdvanced | never = never, TSelect extends Projection = Projection>(identifier: ParentRef, args?: ReadAdvancedArgs, options?: ReadAdvancedOptions, axiosRequestConfig?: RequestConfig$1): CorrelatedReadDescriptor; readAdvancedFilter | never = never, TSelect extends Projection = Projection, TInc extends CorrelatedIncludeInput | readonly CorrelatedIncludeInput[] | undefined = CorrelatedIncludeInput | readonly CorrelatedIncludeInput[] | undefined>(filter: FilterQuery, args?: ReadAdvancedArgs & { include?: TInc; }, options?: ReadAdvancedOptions, axiosRequestConfig?: RequestConfig$1): ModelRequest, TInc>>> & IncludableRead; readAdvancedFilter | never = never, TSelect extends Projection = Projection>(filter: CorrelatedFilterQuery, args?: ReadAdvancedArgs, options?: ReadAdvancedOptions, axiosRequestConfig?: RequestConfig$1): CorrelatedReadDescriptor; update = T>(identifier: string, data: TUpdateInput, options?: UpdateOptions, axiosRequestConfig?: RequestConfig$1): ModelPromiseMeta & LazyRequest>; updateAdvanced | never = never, TSelect extends Projection = Projection>(identifier: string, data: TUpdateInput, args?: UpdateAdvancedArgs, options?: UpdateAdvancedOptions, axiosRequestConfig?: RequestConfig$1): ModelRequest>>; id(id: string): { subs: >, TSubUpdateInput = SubDocumentMutationInput>>(field: K) => { list: (axiosRequestConfig?: AxiosRequestConfig & { throwOnError?: boolean; }) => ModelPromiseMeta & LazyRequest>>; listAdvanced: > = never, TSelect extends readonly string[] = readonly string[]>(filter?: FilterQuery>, args?: { select?: TSelect; }, axiosRequestConfig?: AxiosRequestConfig & { throwOnError?: boolean; }) => ModelPromiseMeta & LazyRequest, ResolvedSelectedShape, TSelect, TData>>>; read: (subId: string, axiosRequestConfig?: AxiosRequestConfig & { throwOnError?: boolean; }) => ModelPromiseMeta & LazyRequest>>; readAdvanced: > = never, TSelect_1 extends readonly string[] = readonly string[]>(subId: string, args?: { select?: TSelect_1; populate?: unknown; }, axiosRequestConfig?: AxiosRequestConfig & { throwOnError?: boolean; }) => ModelPromiseMeta & LazyRequest, ResolvedSelectedShape, TSelect_1, TData>>>; update: (subId: string, data: TSubUpdateInput, axiosRequestConfig?: AxiosRequestConfig & { throwOnError?: boolean; }) => ModelPromiseMeta & LazyRequest>>; bulkUpdate: (data: TSubUpdateInput[], axiosRequestConfig?: AxiosRequestConfig & { throwOnError?: boolean; }) => ModelPromiseMeta & LazyRequest>>; create: (data: TSubCreateInput | TSubCreateInput[], axiosRequestConfig?: AxiosRequestConfig & { throwOnError?: boolean; }) => ModelPromiseMeta & LazyRequest>>; delete: (subId: string, axiosRequestConfig?: AxiosRequestConfig & { throwOnError?: boolean; }) => ModelPromiseMeta & LazyRequest>; }; fetch: (args?: ReadAdvancedArgs, options?: ReadAdvancedOptions, axiosRequestConfig?: RequestConfig$1) => ModelPromiseMeta & LazyRequest>> & IncludableRead; }; } type RequestConfig = AxiosRequestConfig & AdditionalReqConfig; interface Props { axios: AxiosInstance; dataName: string; basePath: string; queryPath: string; onSuccess: ResponseCallback; onFailure: ResponseCallback; throwOnError: boolean; } /** * Typed client for `access-router` in-memory data routes. Created by * `adapter.createDataService(...)`. * * @example * const fruitService = adapter.createDataService({ dataName: 'fruit', basePath: 'fruit' }); */ declare class DataService extends Service { private _dataName; private _queryPath; private _handleCallbacks; private _defaults; constructor({ axios, dataName, basePath, queryPath, onSuccess, onFailure, throwOnError }: Props, defaults?: DataDefaults); list = T>(args?: DataListArgs, options?: DataListOptions, axiosRequestConfig?: RequestConfig): DataPromiseMeta & LazyRequest>; listAdvanced | never = never, TSelect extends Projection = Projection>(filter: FilterQuery, args?: DataListAdvancedArgs, options?: DataListAdvancedOptions, axiosRequestConfig?: RequestConfig): DataRequest>>; read = T>(identifier: string, options?: DataReadOptions, axiosRequestConfig?: RequestConfig): DataPromiseMeta & LazyRequest>; readAdvanced | never = never, TSelect extends Projection = Projection>(identifier: string, args?: DataReadAdvancedArgs, options?: DataReadAdvancedOptions, axiosRequestConfig?: RequestConfig): DataRequest>>; readAdvancedFilter | never = never, TSelect extends Projection = Projection>(filter: FilterQuery, args?: DataReadAdvancedArgs, options?: DataReadAdvancedOptions, axiosRequestConfig?: RequestConfig): DataRequest>>; } /** * Thrown by {@link Model.save} when the wrapper cannot determine whether * to create or update. This is the "no silent create from a projected * read" guarantee (ARC-21): when a read projection omits `_id` AND no * persistence identity was captured at read time (the case for * `readAdvancedFilter` and other list/filter reads that do not know a * single document id), `save()` refuses to POST a new document the * caller may not have meant to create. Callers can recover by reading the * document with `read(id)` / `readAdvanced(id, ...)` (both capture a * persistence identity), or by including `_id` in the projection. */ declare class MissingPersistenceIdentityError extends Error { constructor(message: string); } /** * A dirty-tracking wrapper around a model document. Constructed via * {@link ModelService.create}, {@link ModelService.read}, * {@link ModelService.readAdvanced}, or the list methods that return * `Model[]`. Property access through the wrapper directly reads/writes * the underlying data; `save()` persists only the paths flagged dirty * since the last save and merges the server response per the documented * concurrency contract. * * `Model.create(data, service)` is typed as `Model & * ModelData` so callers can read/write ordinary fields directly on * the wrapper (`user.role = 'owner'`) while still calling * `save()`/`reset()`/`isDirty(...)`. Fields whose names collide with public * model methods or properties are reserved for the wrapper API and remain * reachable through `get(...)`, `set(...)`, `assign(...)`, and `toObject()`. * The returned wrapper is a fresh snapshot of the post-operation local state; * mutating it does not affect sibling wrappers created from the same * underlying document. * * Persistence identity (ARC-21): identity is stored SEPARATELY from the * projected document data. When a service reads a single document by id * (`read`, `readAdvanced`), it threads an explicit `persistenceId` into * `Model.create(...)` so that `save()` resolves the create-vs-update * branch from that captured identity, not from `_data._id`. This means a * read projection that deliberately omits `_id` (e.g. `select: { name: 1, * _id: 0 }`) cannot silently cause a subsequent `save()` to create a * duplicate — `save()` updates the same document the wrapper was read * from. When neither a `persistenceId` nor an `_id` is present (e.g. an * `readAdvancedFilter` projection that strips `_id`), `save()` throws an * explicit `MissingPersistenceIdentityError` rather than POSTing a new * document the user did not mean to create. */ declare class Model = T> { private _data; private _snapshot; private readonly _service; private modifiedPaths; private _saveQueue; private _persistenceId; private readonly _fromExisting; constructor(data: TData, adapter: ModelService, persistenceId?: string, fromExisting?: boolean); static create = T>(data: TData, adapter: ModelService, persistenceId?: string, fromExisting?: boolean): Model & ModelData; /** * Persists the currently dirty paths to the server, then merges the * server's response back into local state. * * Concurrency contract: * * 1. Multiple `save()` calls on the same wrapper are serialized in call * order. A later save snapshots its dirty paths only after the previous * save has finished reconciling, so overlapping callers cannot submit * the same stale dirty set concurrently. * 2. Submitted paths and their values are snapshotted before the request * starts, so an in-flight response cannot wipe edits that were made * while the request was pending. * 3. On success, a submitted path is cleared from `modifiedPaths` only if * its current local value still equals the submitted value — i.e. the * user has not concurrently re-edited it to a different value. * 4. Server-returned values overwrite local values for paths the user did * NOT concurrently re-modify during the in-flight save; for paths the * user did concurrently re-modify, the local value is preserved and * the dirty flag is retained so the concurrent edit is resubmitted on * the next `save()`. (Deterministic conflict rule: the newer local * edit wins for the same path; the server value becomes its reset * baseline without replacing the newer local value.) * 5. On failure, no dirty state is cleared and no local value is * overwritten; the caller can retry `save()` with the same set. * 6. The return value echoes `{ ...result, data }` where `data` is a * refreshed `Model` snapshot of the post-save local state (or `null` * on failure), matching `ModelResponse`. * * Persistence identity (ARC-21): create-vs-update is resolved from a * captured persistence identity rather than from the projected `_data` * payload alone, so a read that strips `_id` (e.g. `select: { name: 1, * _id: 0 }`) cannot turn a subsequent `save()` into a silent create of * a duplicate. When `_data._id` is present it takes precedence so callers * can still deliberately aim `_id` at a bogus id to observe a failing * save. When neither `_data._id` nor a captured persistence identity is * available (e.g. `readAdvancedFilter` with an `_id`-excluding * projection), `save()` throws `MissingPersistenceIdentityError` instead * of POSTing a new document. */ save(reqConfig?: AxiosRequestConfig): Promise>; private saveNow; isDirty(path?: keyof TData | string): boolean; /** * Marks a path dirty and skips snapshot reconciliation. This is the * explicit "include this path on the next save()" escape hatch: even when * the effective value still equals the snapshot, the path stays dirty so * callers can force a field to be re-sent to the server (e.g., to retrigger * server-side defaults or to re-submit a value that another client may * have reverted). * * For implicit writes that reconcile against the snapshot automatically * (reverting a field to its baseline clears the dirty flag), use `set()`, * `assign(...)`, or direct property assignment — those entry points all * run `reconcilePath` after the write. */ markModified(path: keyof TData | string): this; get(path: TKey): TData[TKey]; get(path: string): unknown; set(path: TKey, value: TData[TKey]): this; set(path: string, value: unknown): this; assign(partial: Partial): this; reset(): this; toObject(): TData; toJSON(): TData; private replaceData; private initializeDirtyState; private prepareData; private defineHiddenDataProp; private defineHiddenAdapterProp; private definePublicDataProps; private trackModified; private normalizePath; /** * Removes `path` from the dirty set when its current value deeply equals * the snapshot baseline. Invariant: unsaved drafts never reconcile clean * (snapshot is unpersisted); `_id` is never reconciled here. */ private isUnsavedDraft; private reconcilePath; } /** * Directly exposed data fields for a `Model` wrapper. * * Runtime property forwarding reserves public `Model` member names such as * `save`, `reset`, `set`, `get`, `assign`, `toObject`, and `toJSON` for the * wrapper API. Documents may still contain those field names, but callers must * access them via `get(...)`, `set(...)`, `assign(...)`, or `toObject()` rather * than ordinary direct property access. */ type ModelData = T> = Omit>; type AnyArray = T[] | ReadonlyArray; type Unpacked = T extends (infer U)[] ? U : T extends ReadonlyArray ? U : T; /** * Values a known field condition accepts without an operator wrapper. * * - The scalar value itself (`name: 'Max'`). * - An array of scalars — the sibling server expands this to an `$in` query. * - For array-typed document fields, the element type is also accepted as a * bare condition (e.g. `tags: 'vip'` matches any array containing `'vip'`). * - RegExp is only accepted where `T` is (or unwraps to) `string`. * * The naked `unknown` that previously terminated this union is gone. Use * `ServerSideCast` / `DottedPathFilter` for the cases that needed it * (dynamic dotted paths and explicit server-side casting). */ type ApplyBasicQueryCasting = T | T[] | (T extends AnyArray ? Unpacked : never) | (T extends string ? RegExp : never); type QueryOperatorOperand = T extends AnyArray ? Unpacked : T; type Condition = ApplyBasicQueryCasting | QuerySelector | LazyRequest | EscapeLiteral; type _FilterQuery = { [P in keyof T]?: Condition; } & RootQuerySelector; type RootQuerySelector = { /** @see https://www.mongodb.com/docs/manual/reference/operator/query/and/#op._S_and */ $and?: Array<_FilterQuery>; /** @see https://www.mongodb.com/docs/manual/reference/operator/query/nor/#op._S_nor */ $nor?: Array<_FilterQuery>; /** @see https://www.mongodb.com/docs/manual/reference/operator/query/or/#op._S_or */ $or?: Array<_FilterQuery>; /** @see https://www.mongodb.com/docs/manual/reference/operator/query/text */ $text?: { $search: string; $language?: string; $caseSensitive?: boolean; $diacriticSensitive?: boolean; }; /** @see https://www.mongodb.com/docs/manual/reference/operator/query/where/#op._S_where */ $where?: string | ((...args: never[]) => unknown); /** @see https://www.mongodb.com/docs/manual/reference/operator/query/comment/#op._S_comment */ $comment?: string; /** * ACI-04: structural guard so a `ParentRef` marker (`{ $parent: path }`) * cannot silently satisfy the all-optional `QuerySelector`/root shape. * Without this, a reference-bearing filter would match the strict * `FilterQuery` overload and mistype a descriptor as executable. * `$parent` as an object *key* is never a marker (ACI-01 D2.3); this only * blocks marker *values* from leaking into the strict surface. */ $parent?: never; }; type QuerySelector = { $eq?: ApplyBasicQueryCasting; $gt?: QueryOperatorOperand; $gte?: QueryOperatorOperand; $in?: QueryOperatorOperand[]; $lt?: QueryOperatorOperand; $lte?: QueryOperatorOperand; $ne?: ApplyBasicQueryCasting; $nin?: QueryOperatorOperand[]; $not?: QueryOperatorOperand extends string ? QuerySelector | RegExp : QuerySelector; /** * When `true`, `$exists` matches the documents that contain the field, * including documents where the field value is null. */ $exists?: boolean; $type?: string | number; $expr?: unknown; $jsonSchema?: unknown; $mod?: QueryOperatorOperand extends number ? [number, number] : never; $regex?: QueryOperatorOperand extends string ? RegExp | string : never; $options?: QueryOperatorOperand extends string ? string : never; /** * ACI-04: same structural guard as the root selector (see above). Blocks * marker values from satisfying strict field-operator bags so overloads * discriminate reference-bearing filters at compile time. */ $parent?: never; }; /** * Escape hatch for dynamic dotted paths and explicit server-side casting. * * `DottedPathFilter` restores schema-less field matching: every * `Record` value is forwarded to the sibling server * untouched, so dotted paths such as `'user.friends.name'` and values cast * on the server side still typecheck. * * Crucially, `DottedPathFilter` restores this looseness only when the * caller explicitly asks for it; it does NOT weaken the typed * `FilterQuery` surface, so a stray invalid value on a known field still * fails to compile. */ type DottedPathFilter = _FilterQuery & { [key: string]: unknown; }; /** * Escape hatch for explicit server-side casting. Use this at the call site * of any typed `FilterQuery` parameter when you need to forward a value * the client type cannot express (server-side casting, aggregation-shaped * values for `$expr`, or a dotted-path condition that the typed surface does * not model). The sibling server accepts arbitrary objects/arrays for * filters (`objectOrArraySchema`), so this never causes a runtime failure; * it is purely a deliberate compile-time opt-out. */ type ServerSideCast = DottedPathFilter; /** * Literal-object escape for the correlated-include marker shape (ACI-01 * D2.2): `{ $escape: { $parent: '' } }` matches the literal object * `{ $parent: '' }` against target data and is never interpreted as a * parent reference. Admitted in both the strict and the correlated filter * surface because an escape is data, not a reference: admitting it in the * strict surface keeps escape-only filters on the executable overload so the * static type agrees with the runtime (escapes never create descriptors). */ interface EscapeLiteral { readonly $escape: ParentRef; } type CorrelatedCondition = ApplyBasicQueryCasting | ParentRef | CorrelatedQuerySelector | LazyRequest | EscapeLiteral; /** * Field-operator bag mirroring `QuerySelector` with `ParentRef` admitted * exactly in the ACI-01 D3.1 value positions: comparison operators, `$in` / * `$nin` elements, `$regex` / `$options` (string fields only — the reference * stays inside the string-conditional branch so `$regex` on a numeric field * still fails to compile), and nested `$not` selectors. Flags that take no * value reference (`$exists`, `$type`, `$mod`) stay strict, as do bare array * elements (only `$in` / `$nin` arrays admit reference elements per D3.1). */ type CorrelatedQuerySelector = { $eq?: ApplyBasicQueryCasting | ParentRef; $gt?: QueryOperatorOperand | ParentRef; $gte?: QueryOperatorOperand | ParentRef; $in?: Array | ParentRef> | ParentRef; $lt?: QueryOperatorOperand | ParentRef; $lte?: QueryOperatorOperand | ParentRef; $ne?: ApplyBasicQueryCasting | ParentRef; $nin?: Array | ParentRef> | ParentRef; $not?: QueryOperatorOperand extends string ? CorrelatedQuerySelector | RegExp : CorrelatedQuerySelector; $exists?: boolean; $type?: string | number; $expr?: unknown; $jsonSchema?: unknown; $mod?: QueryOperatorOperand extends number ? [number, number] : never; $regex?: QueryOperatorOperand extends string ? RegExp | string | ParentRef : never; $options?: QueryOperatorOperand extends string ? string | ParentRef : never; $parent?: never; }; type CorrelatedRootQuerySelector = { $and?: Array>; $nor?: Array>; $or?: Array>; $text?: { $search: string; $language?: string; $caseSensitive?: boolean; $diacriticSensitive?: boolean; }; $where?: string | ((...args: never[]) => unknown); $comment?: string; /** * Structural guard: a bare `ParentRef` must not satisfy a filter clause, * so `{ $and: [parentField('x')] }` (a server-side `BadRequest` per * ACI-01 D3.1/ACI-02) fails to compile instead of mistyping. */ $parent?: never; }; /** * Filter surface for the seven correlated-include-capable methods * (ACI-04). A strict `FilterQuery` value is always assignable here, and * additionally `ParentRef` markers are admitted in bare field positions and * the supported operator positions above. Passing a value containing a live * marker selects the descriptor overload at compile time; the runtime scan * enforces the same boundary for unchecked JavaScript callers. * * Existing `$$sq` (embedded `LazyRequest`) and typed-filter (`DottedPathFilter` * / `ServerSideCast`) escape hatches keep working: subquery values are still * admitted and are rewritten to `$$sq` payloads at `$include()` conversion * time while markers pass through untouched. */ type CorrelatedFilterQuery = { [P in keyof T]?: CorrelatedCondition; } & CorrelatedRootQuerySelector; /** * Wraps a lazy promise function with optional metadata. * * The promise is only created when `.then()`, `.catch()`, `.finally()`, or * `.exec()` is called, and a single underlying promise is shared across all * of those entry points so repeated chaining attaches to the same execution * rather than re-invoking the executor. * * Behavior notes: * * - **Sync executor failures become rejections.** The executor is invoked * through `Promise.resolve().then(execute)`, so a synchronous throw from * `execute` is converted to a rejected promise and reaches `.catch()` * and `await` as a rejection rather than escaping synchronously. * - **Metadata is private.** Each meta entry is installed with * `Object.defineProperty(..., { enumerable: false, writable: false, * configurable: false })` so consumers cannot accidentally iterate, * serialize, or reassign it. Direct property reads (`prom.__query`) still * work for adapter-internal machinery (e.g. `adapter.group(...)`). * - **One execution.** The first call to `exec()`, `.then()`, * `.catch()`, or `.finally()` caches the underlying promise and stamps * the wrapper with `STARTED_KEY = true`. Subsequent calls reuse the same * promise and never re-invoke the executor. */ declare const wrapLazyPromise: (promiseFn: () => Promise, meta?: M) => M & LazyRequest; type KeyValueProjection = Partial>; type Projection = readonly string[] | string | KeyValueProjection; type SelectableKey = Extract; type IsTuple = number extends T['length'] ? false : true; type SelectedKeysFromProjectionArray = TSelect extends readonly (infer K)[] ? IsTuple extends true ? Extract> : never : never; type SelectedKeysFromProjectionString = TSelect extends string ? string extends TSelect ? never : Extract> : never; type SelectedKeysFromProjectionObject = TSelect extends KeyValueProjection ? string extends keyof TSelect ? never : { [K in keyof TSelect]-?: TSelect[K] extends 1 ? Extract> : never; }[keyof TSelect] : never; type SelectedKeys = SelectedKeysFromProjectionArray | SelectedKeysFromProjectionString | SelectedKeysFromProjectionObject; type SelectedShape = [SelectedKeys] extends [never] ? Partial : Pick> & Partial; type ResolvedSelectedShape = [TExplicit] extends [never] ? SelectedShape : TExplicit; type SortOrder = -1 | 1 | 'asc' | 'ascending' | 'desc' | 'descending'; type Sort = string | { [key: string]: SortOrder; } | [string, SortOrder][] | undefined | null; type FilterQuery = _FilterQuery; interface Include { model: string; op: 'list' | 'read' | 'count'; path: string; localField: string; foreignField: string; filter?: FilterQuery; args?: Record; options?: Record; } /** * Explicit parent-field reference created by `parentField(path)` (ACI-01 * D2.1). Recognition is structural and value-position-only: a plain object * whose own enumerable keys are exactly `['$parent']` with a non-empty * string value. Magic `$field` strings are never markers — `'$special'` or * `'$parent'` in a field-value position keeps its existing literal query * meaning. * * The object returned by `parentField()` is frozen. References resolve * against the immediate parent document on the outer server (ACI-01 D3.4). */ interface ParentRef { readonly $parent: string; } /** Wire operation carried by a correlated include (fixed by the originating method). */ type CorrelatedIncludeOp = 'list' | 'read' | 'count'; /** * Allowlisted inner-query args for a correlated include (ACI-01 D9.1): * identifier/filter reads forward `{ select, sort, include }`, lists * additionally forward per-parent pagination `{ skip, limit, page, pageSize }`, * basic `read`/`count` carry no args. Everything else (notably `populate` * and `tasks`) is dropped or rejected at `$include()` conversion time. */ interface CorrelatedIncludeArgs { readonly select?: unknown; readonly sort?: unknown; readonly include?: CorrelatedIncludeInput | CorrelatedIncludeInput[]; readonly skip?: unknown; readonly limit?: unknown; readonly page?: unknown; readonly pageSize?: unknown; } /** * Serialized correlated include (ACI-01 D1): the discriminated * `mode: 'correlated'` variant of the wire `Include` union. Produced purely * by `$include()` — synchronously, with zero HTTP calls — from a frozen * per-call snapshot, so repeated conversion yields independently owned * payloads and never shares mutable state. * * `Path` is the explicit output path; `Out` is the caller-supplied result * generic (default `unknown`, carried type-only via `__correlatedOutput` and * never serialized). Inner values are plain: read outputs are `Out | null`, * list outputs are `Out[]`, count outputs are `number` (see * `WithCorrelatedOutputs`). Nothing is inferred from partial projections, no * guaranteed read match is promised, and values are never `Model`-wrapped. */ interface CorrelatedInclude { readonly mode: 'correlated'; readonly model: string; readonly op: Op; readonly path: Path; readonly id?: string | ParentRef; readonly filter?: unknown; readonly args?: CorrelatedIncludeArgs; /** Type-only carrier for the `$include(path)` result generic; never set at runtime. */ readonly __correlatedOutput?: Out; } /** Anything an outer `include` array may carry: legacy joins or correlated payloads. */ type CorrelatedIncludeInput = Include | CorrelatedInclude; /** Supplemental filter option for basic `list()` / `count()` `$include()` (ACI-01 D9.3). */ interface SupplementalIncludeOptions { filter: CorrelatedFilterQuery; } type CorrelatedOutputOf = Entry extends CorrelatedInclude ? string extends Path ? Record : Op extends 'read' ? { [K in Path]: Out | null; } : Op extends 'list' ? { [K in Path]: Out[]; } : Op extends 'count' ? { [K in Path]: number; } : Record : Record; type UnionToIntersection = (U extends unknown ? (arg: U) => void : never) extends (arg: infer I) => void ? I : never; type FlattenIncludeInput = TInc extends readonly (infer Entry)[] ? Entry : TInc; type CorrelatedOutputs = UnionToIntersection>>; /** * Merges `$include(path)` output paths into an outer response shape * (ACI-01 D10). Legacy `Include` entries and wide (non-literal) paths * contribute nothing, so outer types without correlated payloads are exactly * their previous shape. Correlated entries contribute their explicit output * path with the caller-supplied generic: reads admit `null` (no guaranteed * match), lists are arrays, counts are numbers; values stay plain. * * Generic order is path-first (`$include<'org', Org>('org')`): TypeScript * has no partial type-argument inference, so a result-generic-first order * would widen the output path to `string` whenever the generic is supplied * explicitly. Path-first keeps the literal in both the untyped * (`$include('org')`) and explicitly typed forms; a lone result generic in * first position (e.g. `$include`) is rejected by the `string` * constraint instead of silently dropping the merge. */ type WithCorrelatedOutputs = [keyof CorrelatedOutputs] extends [never] ? TBase : TBase & CorrelatedOutputs; /** `$include(path)` mixin for executable or descriptor read-shaped queries. */ interface IncludableRead { $include(path: TPath): CorrelatedInclude; } /** `$include(path)` mixin for executable or descriptor list-shaped queries. */ interface IncludableList { $include(path: TPath): CorrelatedInclude; } /** `$include(path)` mixin for executable or descriptor count-shaped queries. */ interface IncludableCount { $include(path: TPath): CorrelatedInclude; } /** * `$include(path, { filter })` mixin for basic `list()` executables. The * supplemental filter is the inner filter (basic methods have no filter of * their own, so the sources cannot conflict by construction — ACI-01 D9.3). */ interface IncludableBasicList { $include(path: TPath, options: SupplementalIncludeOptions): CorrelatedInclude; } /** `$include(path, { filter })` mixin for basic `count()` executables. */ interface IncludableBasicCount { $include(path: TPath, options: SupplementalIncludeOptions): CorrelatedInclude; } /** * Frozen, non-thenable reference-bearing descriptor returned by the seven * correlated-capable methods when their reference positions (`id` / `filter`) * contain live `ParentRef` markers (ACI-01 D8.1). It carries no executor, so * zero HTTP is possible from it; `await` yields the descriptor unchanged * (a programming error, documented rather than guarded — JavaScript * `await` on non-thenables cannot be intercepted). * * Convert with `$include(path)` into a detached wire payload and embed it in * an outer request's `include` array. The descriptor holds a frozen snapshot * captured at call time: conversion is synchronous, pure, and repeatable. */ interface CorrelatedReadDescriptor { $include(path: TPath): CorrelatedInclude; } /** List-shaped variant of {@link CorrelatedReadDescriptor}. */ interface CorrelatedListDescriptor { $include(path: TPath): CorrelatedInclude; } /** Count-shaped variant of {@link CorrelatedReadDescriptor}. */ interface CorrelatedCountDescriptor { $include(path: TPath): CorrelatedInclude; } type PopulateAccess = 'list' | 'read'; interface Populate { path: string; select?: Projection; match?: Record | null; access?: PopulateAccess; } interface Document { _id?: string; } /** * Default request payload type for model mutations. * * The sibling access-router runtime accepts generic records and does not know * a consumer application's required create/update schema. The client therefore * defaults mutation inputs to `Partial` so known fields are checked without * claiming compile-time requiredness. Consumers with distinct request schemas * can pass explicit `ModelService` * or `createModelService(...)` generics. */ type ModelMutationInput = Partial; /** Default request payload type for subdocument create/update helpers. */ type SubDocumentMutationInput = T extends object ? Partial : T; /** * Successful response. `raw` and `data` are non-null and `success` is * narrowed to `true` so `if (result.success)` exposes the documented * payload shape. `message` is initialized for symmetry with failures but * may be the empty string when the server omits a message on success. */ interface SuccessResult { success: true; raw: T1; data: T2; message: string; status: number; headers: Record; } /** * Failure response. `raw` carries the server error payload (or `null` * when no response body was received, e.g. a network error). `data` is * always `null` on failure. `message` is populated from the structured * problem payload when possible; `status` is the failing HTTP status * (or `0` when no response was received). */ interface FailureResult { success: false; raw: TError | null; data: null; message: string; status: number; headers: Record; } /** * Discriminated response union. Branch on `result.success` to narrow * `raw`/`data` to their successful shapes or to the documented error * payload. * * `T1` is the successful `raw` payload type; `T2` is the successful `data` * payload type (after client wrapping, e.g. `Model`). `TError` is the * optional server error payload type and defaults to `unknown`. On failure, * `data` is `null` and `raw` is `TError | null`, never the success payload * type unless a caller explicitly chooses that error type. */ type Response = SuccessResult | FailureResult; type ModelResponse = T> = Response & ModelData>; type ArrayModelResponse = T> = Response & ModelData)[]>; /** * `ListModelResponse` always carries `totalCount` on both branches. The field * defaults to `0` at runtime on failure or when the server did not emit count * metadata (`includeCount: false`), so callers that read it without narrowing * see a deterministic number rather than `undefined`. */ type ListModelResponse = T> = ArrayModelResponse & { totalCount: number; }; /** * Subdocument responses deliberately do NOT wrap `data` in `Model`. * Returning a save-capable `Model` here was unsafe because `Model.save()` * would target the parent route with the subdocument `_id` instead of * `/:parentId/:sub/:subId`. Subdocument callers that need persistence must * call `subService.update(subId, data)` (or `create`/`bulkUpdate`) explicitly * with the parent-scoped helper returned by `id(parentId).subs(field)`. * * `SubDocumentResponse` is the single-document shape; `data` is the plain * subdocument payload or `null` on failure. */ type SubDocumentResponse = S> = Response; /** * Subdocument list/array responses. `data` is the plain array of subdocument * payloads (no `Model` wrapping) and `raw` is the server's original array * payload. `count` mirrors the server's `count` field (the length of the * returned array); the sibling server never emits a `totalCount` here. */ type SubDocumentListResponse = S> = Response & { count: number; }; interface Task { type: string; args: unknown; options: Record; } type RootModelOperation = 'new' | 'list' | 'read' | 'create' | 'update' | 'upsert' | 'delete' | 'distinct' | 'count' | 'subList' | 'subRead' | 'subCreate' | 'subUpdate' | 'subBulkUpdate' | 'subDelete'; type RootDataOperation = 'list' | 'read'; interface RootModelQueryMeta { target: 'model'; name: string; /** * Carries the model name when this entry is consumed as a sub-query * source: the sibling server reads `model` from a `$$sq` payload to * resolve the target model service. Top-level root entries omit `model` * (the sibling `RootQueryEntry` schema uses `name`); the server schema * permits extra fields via `.passthrough()`, so a stray `model` is * harmless there. */ model?: string; op: RootModelOperation; id?: string; sub?: string; subId?: string; field?: string; filter?: unknown; data?: unknown; args?: Record; options?: Record; order?: number; sqOptions?: SubQueryOptions; } interface RootDataQueryMeta { target: 'data'; name: string; op: RootDataOperation; id?: string; filter?: unknown; data?: unknown; args?: Record; options?: Record; order?: number; } type RootQueryMeta = RootModelQueryMeta | RootDataQueryMeta; interface ModelPromiseMeta { __op: string; __throwOnError?: boolean; __query: RootModelQueryMeta; __requestConfig?: AxiosRequestConfig; __service?: ModelService; } interface LazyRequest extends Promise { exec(): Promise; } type ModelRequest = ModelPromiseMeta & LazyRequest; type DataResponse = Response; type ArrayDataResponse = Response; type ListDataResponse = ArrayDataResponse & { totalCount: number; }; interface DataPromiseMeta { __op: string; __throwOnError?: boolean; __query: RootDataQueryMeta; __requestConfig?: AxiosRequestConfig; __service?: DataService; } type DataRequest = DataPromiseMeta & LazyRequest; type ResponseCallback = (res: unknown) => void; interface WrapOptions { queryParams?: Record; pathParams?: Record; } /** * Adapter-scoped cache control surface returned by `useCacheInterceptors`. * The adapter delegates `clearCache()` to {@link clear} on credential * transitions (login/logout/token refresh/tenant change) and * `disposeCache()` to {@link dispose} when the adapter is torn down to * release cache timers so they do not keep a Node process alive. */ interface CacheController { clear(): void; dispose(): void; } /** * Resolves a stable, non-secret identity partition token for a credentialed * request. Requests that share a token share cache entries; requests with * different tokens never do. Returning `undefined` bypasses the cache for that * credentialed request, so credentials cannot be reused across identities. * * The token is mixed into the cache key alongside the URL and request body. Do * not return raw cookies, authorization values, or other secrets; sensitive * auth headers are excluded from cache keys regardless of the returned token. */ type CachePartitioner = (config: InternalAxiosRequestConfig) => string | undefined; /** * Options for {@link createAdapter}. `rootRouterPath` is the single-segment * path used by `adapter.group(...)` for batched root requests * (defaults to `'root'`). Per-adapter `onSuccess`/`onFailure`/`throwOnError` * apply to every service created by this adapter and are overridden by * per-service options on {@link ModelServiceOptions} and * {@link DataServiceOptions}. * * Cache controls (only in effect when `cacheTTL > 0`): * * - `cacheTTL` — milliseconds a cached GET response is reused before revalidation. * - `cachePartition` — required to cache credentialed requests safely (see * {@link CachePartitioner}); requests using browser cookies, * `withCredentials`, or explicit auth headers without a stable, non-secret * partition token bypass the cache so one identity cannot receive a * response created under another. * - `cacheCapacity` — bounds the number of cached entries; defaults to 100 and * evicts the LRU entry when the limit is exceeded. * * Use the returned adapter's `clearCache()` on credential transitions * (login/logout/token refresh/tenant change) and `disposeCache()` when the * adapter is no longer needed to release cache timers. */ interface AdapterOptions { rootRouterPath?: string; onSuccess?: ResponseCallback; onFailure?: ResponseCallback; throwOnError?: boolean; cacheTTL?: number; /** * Partition strategy for credentialed cache entries. When a request uses * browser cookies, `withCredentials`, or explicit auth headers, caching is * only enabled when `cachePartition` returns a stable, non-secret identity * token. Requests without a partition key bypass the cache so that one * identity can never receive a response created under another identity. * * The returned value must be a stable, non-secret token (for example a user * id or tenant id). Never return raw cookies, authorization values, or other * secrets; those headers are excluded from cache keys regardless. */ cachePartition?: CachePartitioner; /** * Maximum number of cached entries retained per adapter. Defaults to 100. */ cacheCapacity?: number; modelDefaults?: Defaults; dataDefaults?: DataDefaults; } /** * Options for {@link createAdapter}.createModelService. Mirrors the * server-side `access-router` model route configuration: `modelName` is the * server-registered model name, `basePath` is the URL segment relative to * the adapter `baseURL` (e.g. `'users'` resolves to `${baseURL}/users`), * `queryPath` defaults to `'__query'` and `mutationPath` defaults to * `'__mutation'` — match these to the server's `queryRouteSegment` and * mutation route configuration. Per-service `onSuccess`/`onFailure`/ * `throwOnError` override the adapter-level defaults. */ interface ModelServiceOptions { modelName: string; basePath: string; queryPath?: string; mutationPath?: string; onSuccess?: ResponseCallback; onFailure?: ResponseCallback; throwOnError?: boolean; } /** * Options for {@link createAdapter}.createDataService. Mirrors the * server-side `access-router` data route configuration: `dataName` is the * server-registered data name, `basePath` is the URL segment relative to * the adapter `baseURL`, `queryPath` defaults to `'__query'`. Per-service * `onSuccess`/`onFailure`/`throwOnError` override the adapter-level * defaults. */ interface DataServiceOptions { dataName: string; basePath: string; queryPath?: string; onSuccess?: ResponseCallback; onFailure?: ResponseCallback; throwOnError?: boolean; } /** * Creates a typed API adapter for `@web-ts-toolkit/access-router` model and data routes. * * The adapter owns its own Axios instance, optional request cache, and * per-adapter identity token (used by {@link group} to reject requests * owned by a different adapter before any network activity). The returned * adapter is frozen and exposes: * * - `axios` — the underlying Axios instance for advanced configuration or * attaching interceptors (the package's cache interceptors are installed * when `cacheTTL > 0`). * - `createModelService(...)` / `createDataService(...)` — typed * factories for the model and data route clients. * - `clearCache()` / `disposeCache()` — adapter-scoped cache controls * installed by {@link AdapterOptions.cacheTTL}. `clearCache()` drops all * cached entries (call on login/logout/token refresh/tenant switch); * `disposeCache()` also releases the cache's timers so a long-lived * adapter can be torn down cleanly. * - `wrapGet` / `wrapPost` / `wrapPut` / `wrapPatch` / `wrapDelete` — * low-level helpers that wrap a raw Axios call to a single path segment * with `pathParams`/`queryParams` templating and the package's * normalized success/failure handling. * - `group(...)` — batches multiple lazy requests created by this * adapter's services into one root round trip. Rejected before network * activity if any input has already started execution or was created by * a different adapter. * * @example * const adapter = createAdapter({ baseURL: 'http://localhost:3000/api' }); * const userService = adapter.createModelService({ modelName: 'User', basePath: 'users' }); */ declare function createAdapter(axiosConfig?: AxiosRequestConfig, adapterOptions?: AdapterOptions): Readonly<{ axios: axios.AxiosInstance; clearCache: () => void; disposeCache: () => void; createModelService: , TUpdateInput extends object = Partial, TUpsertInput extends object = Partial>({ modelName, basePath, queryPath, mutationPath, onSuccess, onFailure, throwOnError, }: ModelServiceOptions, defaults?: Defaults) => ModelService; createDataService: ({ dataName, basePath, queryPath, onSuccess, onFailure, throwOnError }: DataServiceOptions, defaults?: DataDefaults) => DataService; wrapGet: (url: string, defaultConfig?: AxiosRequestConfig) => (options?: WrapOptions, requestConfig?: AxiosRequestConfig) => Promise>; wrapPost: (url: string, defaultConfig?: AxiosRequestConfig) => (data?: unknown, options?: WrapOptions, requestConfig?: AxiosRequestConfig) => Promise>; wrapPut: (url: string, defaultConfig?: AxiosRequestConfig) => (data?: unknown, options?: WrapOptions, requestConfig?: AxiosRequestConfig) => Promise>; wrapPatch: (url: string, defaultConfig?: AxiosRequestConfig) => (data?: unknown, options?: WrapOptions, requestConfig?: AxiosRequestConfig) => Promise>; wrapDelete: (url: string, defaultConfig?: AxiosRequestConfig) => (options?: WrapOptions, requestConfig?: AxiosRequestConfig) => Promise>; group: | DataRequest)[]>(...proms: T) => Promise<{ [K in keyof T]: Awaited; }>; }>; declare enum CustomHeaders { TotalCount = "wtt-total-count", ReturnedCount = "wtt-returned-count", Page = "wtt-page", PageSize = "wtt-page-size", TotalPages = "wtt-total-pages", HasNextPage = "wtt-has-next-page", HasPreviousPage = "wtt-has-previous-page" } declare function replaceItemById(items: T[], targetItem: T, options?: { merge: boolean; }): T[]; declare function removeItemById(items: T[], targetItem: T): T[]; /** * ACI-04: client parent references (`parentField()`) and include composition * (`$include()`). * * Implements the ACI-01 contract decision (D1–D10) on the client: * * - `parentField(path)` builds the explicit structural marker * `{ $parent: path }` (frozen). Magic `$field` strings are never markers. * - Each of the seven correlated-capable service methods scans its reference * positions (`id` / `filter`) at call time. Markers present returns a * frozen, non-thenable descriptor (no executor, zero HTTP possible); * markers absent returns the ordinary executable lazy request with an * attached pure `$include()` converter. * - `$include()` converts a frozen per-call snapshot into a detached, * serializable wire payload (`mode: 'correlated'`, ACI-01 D1/D6) without * performing HTTP or claiming execution ownership. * - Explicit unsupported per-call args/options fail loudly at `$include()`; * inherited service/adapter defaults for those same keys are silently * dropped (ACI-01 D9.1/D9.2). * * Browser note: plain objects/arrays/`Symbol` only — no Node built-ins and * no server runtime dependencies. */ /** * Creates an explicit parent-field reference (ACI-01 D2.1). Validates only * `typeof path === 'string' && path.length > 0` here; dotted-segment and * dangerous-segment rules (ACI-01 D2.4) are enforced fail-fast when the * marker is consumed by a service method, matching the server verdict. * * The returned marker is frozen and resolves against the immediate parent * document on the outer server. */ declare function parentField(path: string): ParentRef; /** * Controlled error for every client-side correlated-include misuse: * malformed `parentField()` input, malformed marker shapes from unchecked * JavaScript callers, markers in forbidden positions, descriptors embedded in * filters or include arrays, descriptors passed to `adapter.group()`, and * unsupported per-call args/options supplied to `$include()`. */ declare class CorrelatedIncludeError extends Error { constructor(message: string); } export { type AdapterOptions, type AdditionalReqConfig, type ArrayDataResponse, type ArrayModelResponse, type CacheController, type CachePartitioner, type CorrelatedCountDescriptor, type CorrelatedFilterQuery, type CorrelatedInclude, type CorrelatedIncludeArgs, CorrelatedIncludeError, type CorrelatedIncludeInput, type CorrelatedIncludeOp, type CorrelatedListDescriptor, type CorrelatedQuerySelector, type CorrelatedReadDescriptor, type CreateAdvancedArgs, type CreateAdvancedOptions, type CreateOptions, CustomHeaders, type DataDefaults, type DataListAdvancedArgs, type DataListAdvancedOptions, type DataListArgs, type DataListOptions, type DataPromiseMeta, type DataReadAdvancedArgs, type DataReadAdvancedOptions, type DataReadOptions, type DataRequest, type DataResponse, DataService, type DataServiceOptions, type Defaults, type Document, type DottedPathFilter, type EscapeLiteral, type FailureResult, type FilterQuery, type IncludableBasicCount, type IncludableBasicList, type IncludableCount, type IncludableList, type IncludableRead, type Include, type KeyValueProjection, type LazyRequest, type ListAdvancedArgs, type ListAdvancedOptions, type ListArgs, type ListDataResponse, type ListModelResponse, type ListOptions, MissingPersistenceIdentityError, Model, type ModelData, type ModelMutationInput, type ModelPromiseMeta, type ModelRequest, type ModelResponse, ModelService, type ModelServiceOptions, type ParentRef, type Populate, type PopulateAccess, type Projection, type ReadAdvancedArgs, type ReadAdvancedOptions, type ReadOptions, type ResolvedSelectedShape, type Response, type ResponseCallback, type ResultError, type RootDataQueryMeta, type RootModelQueryMeta, type RootQueryMeta, type SelectedKeys, type SelectedShape, type ServerSideCast, Service, ServiceError, type Sort, type SortOrder, type SubDocumentListResponse, type SubDocumentMutationInput, type SubDocumentResponse, type SubQueryOptions, type SuccessResult, type SupplementalIncludeOptions, type Task, type UpdateAdvancedArgs, type UpdateAdvancedOptions, type UpdateOptions, type UpsertAdvancedArgs, type UpsertAdvancedOptions, type UpsertOptions, type WithCorrelatedOutputs, type WrapOptions, createAdapter, parentField, removeItemById, replaceItemById, wrapLazyPromise };