apixa 0.1.0 → 1.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -13
- package/dist/{chunk-QZIU7J3G.js → chunks/chunk-3ZFOEESZ.js} +1 -1
- package/dist/{chunk-QVKMZELC.js → chunks/chunk-6WSJJKM6.js} +1 -1
- package/dist/{chunk-SIFGLE22.js → chunks/chunk-7U7EGEIJ.js} +43 -14
- package/dist/chunks/chunk-BRBSHKI5.js +360 -0
- package/dist/{chunk-IAQ5YOVE.js → chunks/chunk-D2GA433L.js} +1 -1
- package/dist/{chunk-6ZX7GAWG.js → chunks/chunk-I5JYOAUR.js} +1 -1
- package/dist/{chunk-LARJYKRJ.js → chunks/chunk-JM47QQH6.js} +15 -15
- package/dist/{chunk-MLPW6EET.js → chunks/chunk-K4ELX7MW.js} +1 -1
- package/dist/{chunk-R4FXTZWU.js → chunks/chunk-OO6JF76I.js} +1 -1
- package/dist/{chunk-GWZTJ7LM.js → chunks/chunk-RYTQAJRH.js} +177 -175
- package/dist/{data-GNCSLYM4.js → chunks/data-4B7QV4F5.js} +1 -1
- package/dist/{data-B47EIZXW.js → chunks/data-7ISALG7G.js} +1 -1
- package/dist/{data-GTTIEVFI.js → chunks/data-HAGFVAZM.js} +1 -1
- package/dist/{data-DL4IKJCX.js → chunks/data-HNAJ5IYV.js} +1 -1
- package/dist/chunks/data-WOMFLQWC.js +6 -0
- package/dist/chunks/data-ZPXVMWRQ.js +6 -0
- package/dist/core.d.ts +1 -0
- package/dist/core.js +80 -0
- package/dist/index.d.ts +1 -305
- package/dist/index.js +11 -25
- package/dist/mocking/datasets/comments.d.ts +1 -6
- package/dist/mocking/datasets/comments.js +2 -2
- package/dist/mocking/datasets/companies.d.ts +1 -6
- package/dist/mocking/datasets/companies.js +2 -2
- package/dist/mocking/datasets/index.d.ts +1 -18
- package/dist/mocking/datasets/index.js +15 -15
- package/dist/mocking/datasets/orders.d.ts +1 -6
- package/dist/mocking/datasets/orders.js +2 -2
- package/dist/mocking/datasets/posts.d.ts +1 -6
- package/dist/mocking/datasets/posts.js +2 -2
- package/dist/mocking/datasets/products.d.ts +1 -6
- package/dist/mocking/datasets/products.js +2 -2
- package/dist/mocking/datasets/users.d.ts +1 -6
- package/dist/mocking/datasets/users.js +2 -2
- package/dist/mocking/index.d.ts +1 -317
- package/dist/mocking/index.js +4 -351
- package/dist/mocking/node.d.ts +1 -33
- package/dist/mocking/node.js +5 -4
- package/dist/mocking/register.d.ts +1 -2
- package/dist/mocking/register.js +2 -2
- package/dist/react/advanced.d.ts +1 -111
- package/dist/react/advanced.js +17 -6
- package/dist/react/index.d.ts +1 -283
- package/dist/react/index.js +29 -7
- package/dist/types/packages/core/src/api/client.d.ts +11 -0
- package/dist/types/packages/core/src/api/define-api.d.ts +9 -0
- package/dist/types/packages/core/src/api/define-apis.d.ts +9 -0
- package/dist/types/packages/core/src/api/index.d.ts +3 -0
- package/dist/types/packages/core/src/api/metadata.d.ts +32 -0
- package/dist/types/packages/core/src/api/reset.d.ts +7 -0
- package/dist/types/packages/core/src/api/resource-client.d.ts +20 -0
- package/dist/types/packages/core/src/api/validate.d.ts +20 -0
- package/dist/types/packages/core/src/errors/abort-error.d.ts +4 -0
- package/dist/types/packages/core/src/errors/api-error.d.ts +22 -0
- package/dist/types/packages/core/src/errors/config-error.d.ts +5 -0
- package/dist/types/packages/core/src/errors/guards.d.ts +8 -0
- package/dist/types/packages/core/src/errors/http-error.d.ts +8 -0
- package/dist/types/packages/core/src/errors/index.d.ts +13 -0
- package/dist/types/packages/core/src/errors/mock-error.d.ts +10 -0
- package/dist/types/packages/core/src/errors/network-error.d.ts +4 -0
- package/dist/types/packages/core/src/errors/parse-error.d.ts +4 -0
- package/dist/types/packages/core/src/errors/security-error.d.ts +5 -0
- package/dist/types/packages/core/src/errors/timeout-error.d.ts +4 -0
- package/dist/types/packages/core/src/errors/transform-error.d.ts +11 -0
- package/dist/types/packages/core/src/errors/validation-error.d.ts +4 -0
- package/dist/types/packages/core/src/http/body.d.ts +2 -0
- package/dist/types/packages/core/src/http/headers.d.ts +9 -0
- package/dist/types/packages/core/src/http/index.d.ts +5 -0
- package/dist/types/packages/core/src/http/path.d.ts +6 -0
- package/dist/types/packages/core/src/http/query.d.ts +3 -0
- package/dist/types/packages/core/src/http/url.d.ts +27 -0
- package/dist/types/packages/core/src/index.d.ts +16 -0
- package/dist/types/packages/core/src/mock/datasets.d.ts +31 -0
- package/dist/types/packages/core/src/mock/execute.d.ts +27 -0
- package/dist/types/packages/core/src/mock/handler.d.ts +46 -0
- package/dist/types/packages/core/src/mock/index.d.ts +5 -0
- package/dist/types/packages/core/src/mock/source.d.ts +21 -0
- package/dist/types/packages/core/src/mock/store.d.ts +73 -0
- package/dist/types/packages/core/src/operation/args.d.ts +7 -0
- package/dist/types/packages/core/src/operation/conventions.d.ts +122 -0
- package/dist/types/packages/core/src/operation/expand.d.ts +12 -0
- package/dist/types/packages/core/src/operation/index.d.ts +4 -0
- package/dist/types/packages/core/src/request/index.d.ts +2 -0
- package/dist/types/packages/core/src/request/pipeline.d.ts +95 -0
- package/dist/types/packages/core/src/request/retry.d.ts +17 -0
- package/dist/types/packages/core/src/transform/index.d.ts +1 -0
- package/dist/types/packages/core/src/transform/keys.d.ts +12 -0
- package/dist/types/packages/core/src/transport/fetch.d.ts +2 -0
- package/dist/types/packages/core/src/transport/index.d.ts +2 -0
- package/dist/types/packages/core/src/transport/read-body.d.ts +2 -0
- package/dist/types/packages/core/src/types/builtins.d.ts +48 -0
- package/dist/types/packages/core/src/types/definition.d.ts +154 -0
- package/dist/types/packages/core/src/types/errors.d.ts +20 -0
- package/dist/types/packages/core/src/types/http.d.ts +29 -0
- package/dist/types/packages/core/src/types/index.d.ts +11 -0
- package/dist/types/packages/core/src/types/inference.d.ts +600 -0
- package/dist/types/packages/core/src/types/middleware.d.ts +35 -0
- package/dist/{mock-datasets-Xa9NXK9n.d.ts → types/packages/core/src/types/mock-datasets.d.ts} +19 -20
- package/dist/types/packages/core/src/types/mock-handler.d.ts +54 -0
- package/dist/types/packages/core/src/types/retry.d.ts +47 -0
- package/dist/types/packages/core/src/types/transform.d.ts +27 -0
- package/dist/types/packages/core/src/types/transport.d.ts +79 -0
- package/dist/types/packages/core/src/utils/slice.d.ts +12 -0
- package/dist/types/packages/internal/shared/src/index.d.ts +4 -0
- package/dist/types/packages/internal/shared/src/keys.d.ts +25 -0
- package/dist/types/packages/internal/shared/src/operations.d.ts +20 -0
- package/dist/types/packages/mocking/src/catalog.d.ts +26 -0
- package/dist/types/packages/mocking/src/data/entities.d.ts +5 -0
- package/dist/types/packages/mocking/src/dataset-modules/comments.d.ts +4 -0
- package/dist/types/packages/mocking/src/dataset-modules/companies.d.ts +4 -0
- package/dist/types/packages/mocking/src/dataset-modules/orders.d.ts +4 -0
- package/dist/types/packages/mocking/src/dataset-modules/posts.d.ts +4 -0
- package/dist/types/packages/mocking/src/dataset-modules/products.d.ts +4 -0
- package/dist/types/packages/mocking/src/dataset-modules/users.d.ts +4 -0
- package/dist/types/packages/mocking/src/datasets.d.ts +17 -0
- package/dist/types/packages/mocking/src/discover.d.ts +3 -0
- package/dist/types/packages/mocking/src/errors.d.ts +17 -0
- package/dist/types/packages/mocking/src/index.d.ts +19 -0
- package/dist/types/packages/mocking/src/load.d.ts +15 -0
- package/dist/types/packages/mocking/src/loader.d.ts +16 -0
- package/dist/types/packages/mocking/src/mock-api.d.ts +25 -0
- package/dist/types/packages/mocking/src/mock-types.d.ts +279 -0
- package/dist/types/packages/mocking/src/node.d.ts +8 -0
- package/dist/types/packages/mocking/src/paths.d.ts +6 -0
- package/dist/types/packages/mocking/src/register.d.ts +1 -0
- package/dist/types/packages/mocking/src/registry.d.ts +11 -0
- package/dist/types/packages/mocking/src/resource.d.ts +13 -0
- package/dist/types/packages/mocking/src/slice.d.ts +16 -0
- package/dist/{types-mSoi1qGP.d.ts → types/packages/mocking/src/types.d.ts} +6 -8
- package/dist/types/packages/react/src/advanced.d.ts +28 -0
- package/dist/types/packages/react/src/cache.d.ts +15 -0
- package/dist/types/packages/react/src/compose.d.ts +10 -0
- package/dist/types/packages/react/src/controller.d.ts +11 -0
- package/dist/types/packages/react/src/hydration-boundary.d.ts +11 -0
- package/dist/types/packages/react/src/hydration.d.ts +5 -0
- package/dist/types/packages/react/src/index.d.ts +17 -0
- package/dist/types/packages/react/src/keys.d.ts +7 -0
- package/dist/types/packages/react/src/mutation-controller.d.ts +40 -0
- package/dist/types/packages/react/src/mutation-store.d.ts +57 -0
- package/dist/types/packages/react/src/mutation-types.d.ts +125 -0
- package/dist/types/packages/react/src/optimistic.d.ts +55 -0
- package/dist/types/packages/react/src/provider.d.ts +14 -0
- package/dist/types/packages/react/src/query-client.d.ts +31 -0
- package/dist/types/packages/react/src/query-controller.d.ts +49 -0
- package/dist/types/packages/react/src/query-helpers.d.ts +112 -0
- package/dist/types/packages/react/src/query-options.d.ts +152 -0
- package/dist/types/packages/react/src/types.d.ts +123 -0
- package/dist/types/packages/react/src/use-resource-mutation.d.ts +34 -0
- package/dist/types/packages/react/src/use-resource-query.d.ts +112 -0
- package/dist/types/packages/react/src/use-resource.d.ts +69 -0
- package/dist/types/src/core.d.ts +1 -0
- package/dist/types/src/index.d.ts +8 -0
- package/dist/types/src/mocking/datasets/comments.d.ts +1 -0
- package/dist/types/src/mocking/datasets/companies.d.ts +1 -0
- package/dist/types/src/mocking/datasets/index.d.ts +1 -0
- package/dist/types/src/mocking/datasets/orders.d.ts +1 -0
- package/dist/types/src/mocking/datasets/posts.d.ts +1 -0
- package/dist/types/src/mocking/datasets/products.d.ts +1 -0
- package/dist/types/src/mocking/datasets/users.d.ts +1 -0
- package/dist/types/src/mocking/index.d.ts +1 -0
- package/dist/types/src/mocking/node.d.ts +1 -0
- package/dist/types/src/mocking/register.d.ts +2 -0
- package/dist/types/src/react/advanced.d.ts +1 -0
- package/dist/types/src/react/index.d.ts +2 -0
- package/package.json +36 -21
- package/dist/data-JR6F6HSR.js +0 -6
- package/dist/data-WRWVXGYD.js +0 -6
- package/dist/inference-BGj3DlzE.d.ts +0 -1220
- package/dist/loader-hhMdppVZ.d.ts +0 -32
- package/dist/mutation-controller-BjCOn42y.d.ts +0 -476
- /package/dist/{chunk-WNJUPR6P.js → chunks/chunk-23CRJXIO.js} +0 -0
- /package/dist/{chunk-QY5YMHHN.js → chunks/chunk-3RDQXC6Q.js} +0 -0
- /package/dist/{chunk-KE7UM4VW.js → chunks/chunk-QPINWJOY.js} +0 -0
- /package/dist/{chunk-A3QU5MFP.js → chunks/chunk-SO3IYVYQ.js} +0 -0
- /package/dist/{chunk-ZSLMMDUX.js → chunks/chunk-VACVV4XV.js} +0 -0
- /package/dist/{chunk-DGSIZXRI.js → chunks/chunk-YZ7TR6HL.js} +0 -0
- /package/dist/{data-QF32LRM4.js → chunks/data-544O3O74.js} +0 -0
- /package/dist/{data-3HVQ54F3.js → chunks/data-5JYPNNLK.js} +0 -0
- /package/dist/{data-ADLKYCN6.js → chunks/data-BLJLBYSL.js} +0 -0
- /package/dist/{data-Y6374BIT.js → chunks/data-CJY7V7VX.js} +0 -0
- /package/dist/{data-HWX62BTJ.js → chunks/data-CPWQB3IU.js} +0 -0
- /package/dist/{data-UIVRJ77R.js → chunks/data-DD3Q7CSO.js} +0 -0
- /package/dist/{data-OKVHGTXW.js → chunks/data-MFATSP4G.js} +0 -0
- /package/dist/{data-UZVNJUNM.js → chunks/data-MG5JK7PM.js} +0 -0
- /package/dist/{data-PQPSE56A.js → chunks/data-YHFBWU6E.js} +0 -0
package/README.md
CHANGED
|
@@ -18,20 +18,63 @@ Working with APIs often means repeating the same patterns across resources and p
|
|
|
18
18
|
|
|
19
19
|
Apixa aims to reduce this duplication while preserving TypeScript's type safety and flexibility.
|
|
20
20
|
|
|
21
|
-
##
|
|
21
|
+
## Installation
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
Apixa publishes one npm package: `apixa` (`1.0.0-rc.0`, ESM, Node.js 22+). The RC is prepared
|
|
24
|
+
locally and has not been published. After release, install it with:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
npm install apixa
|
|
28
|
+
# React applications also install the optional peers:
|
|
29
|
+
npm install apixa react @tanstack/react-query
|
|
30
|
+
```
|
|
25
31
|
|
|
26
32
|
```ts
|
|
27
|
-
import { defineApi, defineApis } from "apixa";
|
|
33
|
+
import { defineApi, defineApis, defineMockApi } from "apixa";
|
|
34
|
+
import { MockStore } from "apixa/core";
|
|
35
|
+
import "apixa/mocking/register";
|
|
36
|
+
import { users } from "apixa/mocking/datasets/users";
|
|
37
|
+
import { listDatasets } from "apixa/mocking/node"; // Node-only
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import {
|
|
42
|
+
QueryClient,
|
|
43
|
+
ApixaProvider,
|
|
44
|
+
ApixaHydrationBoundary,
|
|
45
|
+
useResource,
|
|
46
|
+
useResourceQuery,
|
|
47
|
+
useResourceMutation,
|
|
48
|
+
} from "apixa/react";
|
|
49
|
+
// Server Components use the server-safe entry:
|
|
50
|
+
import { QueryClient as ServerQueryClient, dehydrate } from "apixa/react/advanced";
|
|
28
51
|
```
|
|
29
52
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
53
|
+
Framework subpaths keep integrations isolated. Root, Core and Mocking imports require neither
|
|
54
|
+
React nor TanStack Query. React and TanStack Query are optional peers, left external to the
|
|
55
|
+
library build; install them only when using `apixa/react`. Missing peers produce the loader's
|
|
56
|
+
module-not-found error when a React entry is imported. Future `apixa/vue` and `apixa/angular`
|
|
57
|
+
subpaths are planned, not implemented.
|
|
58
|
+
|
|
59
|
+
Core, Mocking, React and shared integration helpers remain private workspace modules. Their
|
|
60
|
+
canonical code is compiled into one distribution with shared runtime chunks. No scoped package
|
|
61
|
+
installation is required. See the [installation guide](./docs/guides/humans/install-apixa.md)
|
|
62
|
+
and [public API contract](./docs/public-api.md).
|
|
63
|
+
|
|
64
|
+
### Runtime support
|
|
65
|
+
|
|
66
|
+
| Runtime | Status |
|
|
67
|
+
| -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
68
|
+
| Node.js 22, 24 | Supported (CI matrix) |
|
|
69
|
+
| Modern browsers | Supported (verified in Chromium) |
|
|
70
|
+
| Cloudflare Workers | `apixa`, `apixa/core`, `apixa/mocking*` (not `/node`) and `apixa/react/advanced`, run in `workerd` without `nodejs_compat` |
|
|
71
|
+
| Vercel Edge Runtime | Same entries, run in `edge-runtime` |
|
|
72
|
+
| React 18 / 19, Next.js 15 App Router, Vite 6 | Supported (packed consumers, production and development builds) |
|
|
73
|
+
| TypeScript 5.5+ | Declarations checked with 5.5.4 and the latest release, `Bundler` and `NodeNext` resolution |
|
|
74
|
+
|
|
75
|
+
`apixa/mocking/node` reads the file system and is Node-only. Thrown errors keep `error.request`
|
|
76
|
+
(including headers and body) readable but non-enumerable, so logging an error does not print
|
|
77
|
+
credentials.
|
|
35
78
|
|
|
36
79
|
## Core Principles
|
|
37
80
|
|
|
@@ -40,7 +83,7 @@ for current consumers.
|
|
|
40
83
|
- **Convention-driven:** Generate common resource operations where appropriate.
|
|
41
84
|
- **Fully customizable:** Support custom endpoints and override generated behavior.
|
|
42
85
|
- **Framework-agnostic:** Keep the core independent of UI frameworks and data-fetching libraries.
|
|
43
|
-
- **Composable:** Allow additional capabilities through
|
|
86
|
+
- **Composable:** Allow additional capabilities through isolated subpaths.
|
|
44
87
|
|
|
45
88
|
## Usage (implemented)
|
|
46
89
|
|
|
@@ -120,7 +163,7 @@ Set `defaultOperations: false` to start with no builtins, then enable individual
|
|
|
120
163
|
groups. `writeOnly: true` filters read defaults. Group precedence and all default wire conventions
|
|
121
164
|
are documented in [docs/conventions.md](docs/conventions.md#selection-order).
|
|
122
165
|
|
|
123
|
-
Pass `transport` to inject a custom HTTP implementation (used in tests). The default is Fetch, shipped inside
|
|
166
|
+
Pass `transport` to inject a custom HTTP implementation (used in tests). The default is Fetch, shipped inside Core.
|
|
124
167
|
|
|
125
168
|
### Request reliability (implemented)
|
|
126
169
|
|
|
@@ -219,7 +262,8 @@ API methods are intended to return native promises, allowing them to work natura
|
|
|
219
262
|
packages/
|
|
220
263
|
core/ # API definitions and execution
|
|
221
264
|
mocking/ # Local datasets and standalone mock APIs
|
|
222
|
-
|
|
265
|
+
react/ # Private React integration
|
|
266
|
+
internal/shared/ # Private, never published: framework-neutral integration helpers
|
|
223
267
|
datasets/ # Version-controlled JSON fixtures
|
|
224
268
|
examples/
|
|
225
269
|
next/ # Primary frontend playground (Next.js)
|
|
@@ -245,7 +289,7 @@ Planned later packages (not created yet): `transport-fetch`, `client`, `server`,
|
|
|
245
289
|
|
|
246
290
|
## Project Status
|
|
247
291
|
|
|
248
|
-
Apixa is in early development. Last shipped: **v0.11.0**. The v1.0.0 TanStack Query workstream is in progress.
|
|
292
|
+
Apixa is in early development. Last shipped: **v0.11.0**. The v1.0.0 TanStack Query workstream is in progress. Core exposes `defineApi()` with default-first CRUD, reliability, re-callable middleware, and extension seams. Mocking augments Core for resource mocks.
|
|
249
293
|
|
|
250
294
|
See [versions/v1.0.0/acceptance-criteria.md](./versions/v1.0.0/acceptance-criteria.md) for the active milestone checklist.
|
|
251
295
|
|
|
@@ -2,9 +2,9 @@ import {
|
|
|
2
2
|
BUILTIN_CONVENTIONS,
|
|
3
3
|
createIdempotencyKey,
|
|
4
4
|
getOperationMetadata
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-RYTQAJRH.js";
|
|
6
6
|
|
|
7
|
-
// packages/
|
|
7
|
+
// packages/internal/shared/src/operations.ts
|
|
8
8
|
var BUILTIN_READ_NAMES = new Set(
|
|
9
9
|
Object.entries(BUILTIN_CONVENTIONS).filter(([, convention]) => convention.access === "read").map(([name]) => name)
|
|
10
10
|
);
|
|
@@ -45,12 +45,12 @@ function assertNoFullResponse(args, info) {
|
|
|
45
45
|
const options = args[info.optionsIndex];
|
|
46
46
|
if (isPlainObject(options) && options.full === true) {
|
|
47
47
|
throw new Error(
|
|
48
|
-
"Apixa
|
|
48
|
+
"Apixa React hooks return raw payloads; full: true needs the Core client directly."
|
|
49
49
|
);
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
// packages/
|
|
53
|
+
// packages/internal/shared/src/keys.ts
|
|
54
54
|
function createResourceKeys(input) {
|
|
55
55
|
const root = [...input.scope ?? [], ...input.queryKey];
|
|
56
56
|
return {
|
|
@@ -65,7 +65,12 @@ function createResourceKeys(input) {
|
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
// packages/
|
|
68
|
+
// packages/react/src/keys.ts
|
|
69
|
+
function createResourceKeys2(input) {
|
|
70
|
+
return createResourceKeys(input);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// packages/react/src/query-options.ts
|
|
69
74
|
import {
|
|
70
75
|
infiniteQueryOptions,
|
|
71
76
|
queryOptions
|
|
@@ -105,7 +110,7 @@ function buildResourceQueryOptions(resource, options) {
|
|
|
105
110
|
}
|
|
106
111
|
const staticArgs = Array.isArray(args) ? args : query === void 0 ? void 0 : info.optionsIndex === null ? [{ query }] : withOptions([], info, { query });
|
|
107
112
|
if (staticArgs) assertNoFullResponse(staticArgs, info);
|
|
108
|
-
const keys =
|
|
113
|
+
const keys = createResourceKeys2({ queryKey, scope });
|
|
109
114
|
const identity = keyParts ?? normalizeIdentity(staticArgs ?? [], info);
|
|
110
115
|
let generatedKey;
|
|
111
116
|
if (operation === "getAll") {
|
|
@@ -215,7 +220,7 @@ function buildResourceInfiniteQueryOptions(resource, options) {
|
|
|
215
220
|
`infinite.query needs Core's RequestOptions slot; ${operation} has a signature Core does not own \u2014 use infinite.args.`
|
|
216
221
|
);
|
|
217
222
|
}
|
|
218
|
-
const keys =
|
|
223
|
+
const keys = createResourceKeys2({ queryKey, scope });
|
|
219
224
|
const { query: pageQuery, args: pageArgs, initialPageParam, maxPages } = infinite;
|
|
220
225
|
return infiniteQueryOptions({
|
|
221
226
|
...nativeOptions,
|
|
@@ -251,7 +256,7 @@ function prefetchResourceInfiniteQuery(client, resource, options) {
|
|
|
251
256
|
});
|
|
252
257
|
}
|
|
253
258
|
|
|
254
|
-
// packages/
|
|
259
|
+
// packages/react/src/query-helpers.ts
|
|
255
260
|
var INHERITED = ["staleTime", "gcTime", "retry", "retryDelay", "networkMode", "meta"];
|
|
256
261
|
function createQueryHelpers(resource, client, keys, current) {
|
|
257
262
|
const observed = () => {
|
|
@@ -354,8 +359,8 @@ function assertInfiniteData(value) {
|
|
|
354
359
|
}
|
|
355
360
|
}
|
|
356
361
|
|
|
357
|
-
// packages/
|
|
358
|
-
function createResourceQueryController(resource, config, keys =
|
|
362
|
+
// packages/react/src/query-controller.ts
|
|
363
|
+
function createResourceQueryController(resource, config, keys = createResourceKeys2(config)) {
|
|
359
364
|
const root = { queryKey: config.queryKey, scope: config.scope };
|
|
360
365
|
const listOptions = (overrides = {}) => buildResourceQueryOptions(resource, {
|
|
361
366
|
...overrides,
|
|
@@ -407,7 +412,7 @@ function createResourceQueryController(resource, config, keys = createResourceKe
|
|
|
407
412
|
};
|
|
408
413
|
}
|
|
409
414
|
|
|
410
|
-
// packages/
|
|
415
|
+
// packages/react/src/optimistic.ts
|
|
411
416
|
function inFlightFetches(queries) {
|
|
412
417
|
const out = [];
|
|
413
418
|
for (const query of queries) {
|
|
@@ -543,7 +548,7 @@ var OptimisticLayers = class _OptimisticLayers {
|
|
|
543
548
|
}
|
|
544
549
|
};
|
|
545
550
|
|
|
546
|
-
// packages/
|
|
551
|
+
// packages/react/src/mutation-store.ts
|
|
547
552
|
import {
|
|
548
553
|
MutationObserver
|
|
549
554
|
} from "@tanstack/react-query";
|
|
@@ -776,9 +781,32 @@ function withIdempotencyKey(resource, name, info, args, keys) {
|
|
|
776
781
|
return withOptions(args, info, { idempotencyKey: key });
|
|
777
782
|
}
|
|
778
783
|
|
|
784
|
+
// packages/react/src/query-client.ts
|
|
785
|
+
import {
|
|
786
|
+
QueryClient as TanStackQueryClient
|
|
787
|
+
} from "@tanstack/react-query";
|
|
788
|
+
var QueryClient = class extends TanStackQueryClient {
|
|
789
|
+
constructor(config = {}) {
|
|
790
|
+
const { staleTime, gcTime, retry, networkMode, meta, defaultOptions, ...native } = config;
|
|
791
|
+
const shorthand = { staleTime, gcTime, retry, networkMode, meta };
|
|
792
|
+
for (const key of Object.keys(shorthand))
|
|
793
|
+
if (shorthand[key] === void 0) delete shorthand[key];
|
|
794
|
+
const hasShorthand = Object.keys(shorthand).length > 0;
|
|
795
|
+
super(
|
|
796
|
+
hasShorthand ? {
|
|
797
|
+
...native,
|
|
798
|
+
defaultOptions: {
|
|
799
|
+
...defaultOptions,
|
|
800
|
+
queries: { ...shorthand, ...defaultOptions?.queries }
|
|
801
|
+
}
|
|
802
|
+
} : { ...native, ...defaultOptions ? { defaultOptions } : {} }
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
};
|
|
806
|
+
|
|
779
807
|
export {
|
|
780
808
|
describeOperations,
|
|
781
|
-
createResourceKeys,
|
|
809
|
+
createResourceKeys2 as createResourceKeys,
|
|
782
810
|
createResourceQueryOptions,
|
|
783
811
|
buildResourceQueryOptions,
|
|
784
812
|
createResourceInfiniteQueryOptions,
|
|
@@ -790,5 +818,6 @@ export {
|
|
|
790
818
|
OptimisticLayers,
|
|
791
819
|
ResourceMutationStore,
|
|
792
820
|
validateConfig,
|
|
793
|
-
applyInvalidation
|
|
821
|
+
applyInvalidation,
|
|
822
|
+
QueryClient
|
|
794
823
|
};
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BUILTIN_CONVENTIONS,
|
|
3
|
+
MockError,
|
|
4
|
+
MockStore,
|
|
5
|
+
applyResourceSliceToListResult,
|
|
6
|
+
expandOperations,
|
|
7
|
+
runBuiltin
|
|
8
|
+
} from "./chunk-RYTQAJRH.js";
|
|
9
|
+
import {
|
|
10
|
+
InvalidSliceError,
|
|
11
|
+
MockApiError,
|
|
12
|
+
catalog,
|
|
13
|
+
isDatasetId,
|
|
14
|
+
listDatasetIds
|
|
15
|
+
} from "./chunk-JM47QQH6.js";
|
|
16
|
+
|
|
17
|
+
// packages/mocking/src/mock-types.ts
|
|
18
|
+
var MOCK_BUILTINS = [
|
|
19
|
+
"getAll",
|
|
20
|
+
"getById",
|
|
21
|
+
"getBySlug",
|
|
22
|
+
"getMany",
|
|
23
|
+
"getBy",
|
|
24
|
+
"findFirst",
|
|
25
|
+
"search",
|
|
26
|
+
"exists",
|
|
27
|
+
"count",
|
|
28
|
+
"create",
|
|
29
|
+
"update",
|
|
30
|
+
"patch",
|
|
31
|
+
"upsert",
|
|
32
|
+
"delete",
|
|
33
|
+
"createMany",
|
|
34
|
+
"deleteMany"
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
// packages/mocking/src/resource.ts
|
|
38
|
+
var CODES = {
|
|
39
|
+
MOCK_NOT_FOUND: "NOT_FOUND",
|
|
40
|
+
MOCK_DUPLICATE_ID: "DUPLICATE_ID",
|
|
41
|
+
MOCK_INVALID_INPUT: "INVALID_INPUT",
|
|
42
|
+
MOCK_UNSUPPORTED: "INVALID_CONFIG",
|
|
43
|
+
MOCK_LOAD: "UNKNOWN_DATASET"
|
|
44
|
+
};
|
|
45
|
+
function translate(error) {
|
|
46
|
+
if (error instanceof MockError) {
|
|
47
|
+
const code = error.code === "MOCK_INVALID_INPUT" && PAGING_MESSAGE.test(error.message) ? "INVALID_PAGINATION" : CODES[error.code] ?? "INVALID_INPUT";
|
|
48
|
+
const translated = new MockApiError(code, error.message);
|
|
49
|
+
translated.cause = error;
|
|
50
|
+
throw translated;
|
|
51
|
+
}
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
var PAGING_MESSAGE = /: (page|pageSize|cursor) must be/;
|
|
55
|
+
function createResource(name, type, options) {
|
|
56
|
+
const label = `Mock resource "${name}"`;
|
|
57
|
+
const entry = catalog[type];
|
|
58
|
+
const source = {
|
|
59
|
+
label,
|
|
60
|
+
idKey: entry.idKey,
|
|
61
|
+
load: async () => (await entry.load()).default,
|
|
62
|
+
// Catalog datasets are module-cached and never mutated: no private snapshot needed.
|
|
63
|
+
seed: options.seed,
|
|
64
|
+
immutable: true
|
|
65
|
+
};
|
|
66
|
+
const store = new MockStore(source);
|
|
67
|
+
const pagination = options.pagination === true ? "page" : options.pagination || void 0;
|
|
68
|
+
const fail = (code, message) => new MockApiError(code, `${label}: ${message}`);
|
|
69
|
+
const use = (fn) => async (...args) => {
|
|
70
|
+
try {
|
|
71
|
+
await store.ready();
|
|
72
|
+
return await fn(...args);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
return translate(error);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const run = (intent) => async (...args) => {
|
|
78
|
+
try {
|
|
79
|
+
const built = intent(...args);
|
|
80
|
+
const { data } = await runBuiltin(store, source, built, pagination);
|
|
81
|
+
return built.op === "delete" || built.op === "deleteMany" ? void 0 : data;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
return translate(error);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const getAllFn = run((query) => ({ op: "getAll", query }));
|
|
87
|
+
const builtins = {
|
|
88
|
+
// `slice` caps the list result (like Core); the store keeps every record.
|
|
89
|
+
getAll: async (query) => applyResourceSliceToListResult(await getAllFn(query), options.slice),
|
|
90
|
+
getById: run((id) => ({ op: "getById", id })),
|
|
91
|
+
getBySlug: run((slug) => ({ op: "getBySlug", slug })),
|
|
92
|
+
getMany: run((ids) => ({ op: "getMany", ids })),
|
|
93
|
+
getBy: run((field, value) => ({ op: "getBy", field, value })),
|
|
94
|
+
findFirst: run((query) => ({ op: "findFirst", query })),
|
|
95
|
+
search: run((query) => ({ op: "search", query })),
|
|
96
|
+
exists: run((id) => ({ op: "exists", id })),
|
|
97
|
+
count: run((query) => ({ op: "count", query })),
|
|
98
|
+
create: run((body) => ({ op: "create", body })),
|
|
99
|
+
update: run((id, body) => ({ op: "update", id, body })),
|
|
100
|
+
patch: run((id, body) => ({ op: "patch", id, body })),
|
|
101
|
+
upsert: run((id, body) => ({ op: "upsert", id, body })),
|
|
102
|
+
delete: run((id) => ({ op: "delete", id })),
|
|
103
|
+
createMany: run((body) => ({ op: "createMany", body })),
|
|
104
|
+
deleteMany: run((body) => ({ op: "deleteMany", body }))
|
|
105
|
+
};
|
|
106
|
+
const ctx = {
|
|
107
|
+
idKey: entry.idKey,
|
|
108
|
+
rows: async () => {
|
|
109
|
+
await store.ready().catch(translate);
|
|
110
|
+
return store.liveRows();
|
|
111
|
+
},
|
|
112
|
+
fail,
|
|
113
|
+
defaults: {
|
|
114
|
+
getAll: use(() => store.list()),
|
|
115
|
+
getById: use((id) => store.get(id)),
|
|
116
|
+
create: use((input) => store.create(input)),
|
|
117
|
+
update: use((id, input) => store.update(id, input)),
|
|
118
|
+
patch: use((id, input) => store.patch(id, input)),
|
|
119
|
+
delete: use((id) => store.delete(id))
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const custom = (fn) => async (...args) => {
|
|
123
|
+
try {
|
|
124
|
+
return await fn(ctx, ...args);
|
|
125
|
+
} finally {
|
|
126
|
+
store.reindex();
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const resource = {};
|
|
130
|
+
for (const [opName, definition] of Object.entries(options.operations)) {
|
|
131
|
+
if (typeof definition === "function") {
|
|
132
|
+
resource[opName] = custom(definition);
|
|
133
|
+
} else if (MOCK_BUILTINS.includes(opName)) {
|
|
134
|
+
resource[opName] = builtins[opName];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return resource;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// packages/mocking/src/slice.ts
|
|
141
|
+
function isPlainSliceRange(value) {
|
|
142
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).every((k) => k === "start" || k === "end");
|
|
143
|
+
}
|
|
144
|
+
function assertSliceConfig(slice, label) {
|
|
145
|
+
if (typeof slice === "number") {
|
|
146
|
+
if (!Number.isInteger(slice) || slice < 0) {
|
|
147
|
+
throw new InvalidSliceError(
|
|
148
|
+
`${label}: slice must be a non-negative integer (got ${String(slice)}).`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (!isPlainSliceRange(slice)) {
|
|
154
|
+
throw new InvalidSliceError(
|
|
155
|
+
`${label}: slice must be a non-negative integer or { start, end }.`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
const { start, end } = slice;
|
|
159
|
+
if (typeof start !== "number" || !Number.isInteger(start) || start < 0) {
|
|
160
|
+
throw new InvalidSliceError(
|
|
161
|
+
`${label}: slice.start must be a non-negative integer (got ${String(start)}).`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
if (typeof end !== "number" || !Number.isInteger(end) || end < 0) {
|
|
165
|
+
throw new InvalidSliceError(
|
|
166
|
+
`${label}: slice.end must be a non-negative integer (got ${String(end)}).`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
if (end < start) {
|
|
170
|
+
throw new InvalidSliceError(
|
|
171
|
+
`${label}: slice.end (${end}) must not be before slice.start (${start}).`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// packages/mocking/src/mock-api.ts
|
|
177
|
+
var SELECTION_KEYS = ["defaultOperations", "readOnly", "writeOnly", "operationGroups"];
|
|
178
|
+
var ROOT_OPTIONS = ["resources", ...SELECTION_KEYS];
|
|
179
|
+
var RESOURCE_OPTIONS = ["type", "slice", "seed", "pagination", "operations", ...SELECTION_KEYS];
|
|
180
|
+
var GROUPS = ["read", "write", "many", "extended"];
|
|
181
|
+
var EXPECTED_SHAPE = 'defineMockApi() expects { resources: { users: { type: "users-mock" } } } (or the shorthand { users: "users-mock" }).';
|
|
182
|
+
function isPlainObject(value) {
|
|
183
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
184
|
+
}
|
|
185
|
+
function isRootForm(config) {
|
|
186
|
+
const { resources } = config;
|
|
187
|
+
if (!isPlainObject(resources)) return false;
|
|
188
|
+
return typeof resources.type !== "string";
|
|
189
|
+
}
|
|
190
|
+
function assertSettings(settings, label) {
|
|
191
|
+
for (const key of ["defaultOperations", "readOnly", "writeOnly"]) {
|
|
192
|
+
const value = settings[key];
|
|
193
|
+
if (value !== void 0 && typeof value !== "boolean") {
|
|
194
|
+
throw new MockApiError(
|
|
195
|
+
"INVALID_CONFIG",
|
|
196
|
+
`${label}: ${key} must be a boolean (got ${String(value)}).`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (settings.readOnly === true && settings.writeOnly === true) {
|
|
201
|
+
throw new MockApiError(
|
|
202
|
+
"INVALID_CONFIG",
|
|
203
|
+
`${label}: readOnly and writeOnly cannot both be true.`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
const groups = settings.operationGroups;
|
|
207
|
+
if (groups === void 0) return;
|
|
208
|
+
if (!isPlainObject(groups)) {
|
|
209
|
+
throw new MockApiError("INVALID_CONFIG", `${label}: operationGroups must be an object.`);
|
|
210
|
+
}
|
|
211
|
+
for (const [group, choice] of Object.entries(groups)) {
|
|
212
|
+
if (!GROUPS.includes(group)) {
|
|
213
|
+
throw new MockApiError(
|
|
214
|
+
"INVALID_CONFIG",
|
|
215
|
+
`${label}: unknown operation group "${group}". Supported groups: ${GROUPS.join(", ")}.`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
if (choice !== void 0 && typeof choice !== "boolean") {
|
|
219
|
+
throw new MockApiError(
|
|
220
|
+
"INVALID_CONFIG",
|
|
221
|
+
`${label}: operationGroups.${group} must be a boolean (got ${String(choice)}).`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function assertOperations(operations, label) {
|
|
227
|
+
if (operations === void 0) return;
|
|
228
|
+
if (!isPlainObject(operations)) {
|
|
229
|
+
throw new MockApiError(
|
|
230
|
+
"INVALID_CONFIG",
|
|
231
|
+
`${label}: operations must be an object of { name: true | false | function }.`
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
for (const [name, value] of Object.entries(operations)) {
|
|
235
|
+
if (value === void 0 || value === false || typeof value === "function") continue;
|
|
236
|
+
if (value === true) {
|
|
237
|
+
if (Object.hasOwn(BUILTIN_CONVENTIONS, name)) continue;
|
|
238
|
+
throw new MockApiError(
|
|
239
|
+
"INVALID_CONFIG",
|
|
240
|
+
`${label}: unknown builtin operation "${name}". \`true\` only enables a builtin; give a custom operation a function.`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
throw new MockApiError(
|
|
244
|
+
"INVALID_CONFIG",
|
|
245
|
+
`${label}: operation "${name}" must be true, false, or a function. HTTP forms ({ method, path, \u2026 }) are not supported by a standalone mock.`
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function assertSeed(seed, label) {
|
|
250
|
+
if (!isPlainObject(seed)) {
|
|
251
|
+
throw new MockApiError("INVALID_CONFIG", `${label}: seed must be { limit?, offset? }.`);
|
|
252
|
+
}
|
|
253
|
+
for (const [key, value] of Object.entries(seed)) {
|
|
254
|
+
if (key !== "limit" && key !== "offset") {
|
|
255
|
+
throw new MockApiError(
|
|
256
|
+
"INVALID_CONFIG",
|
|
257
|
+
`${label}: seed has unknown option "${key}". Supported: limit, offset.`
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
if (value !== void 0 && (typeof value !== "number" || !Number.isInteger(value) || value < 0)) {
|
|
261
|
+
throw new MockApiError(
|
|
262
|
+
"INVALID_CONFIG",
|
|
263
|
+
`${label}: seed.${key} must be a non-negative integer (got ${String(value)}).`
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function assertResource(name, resource) {
|
|
269
|
+
const label = `Mock resource "${name}"`;
|
|
270
|
+
if (!isPlainObject(resource)) {
|
|
271
|
+
throw new MockApiError(
|
|
272
|
+
"INVALID_CONFIG",
|
|
273
|
+
`${label} must be a dataset id string or an object like { type: "users-mock" }.`
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
for (const key of Object.keys(resource)) {
|
|
277
|
+
if (!RESOURCE_OPTIONS.includes(key)) {
|
|
278
|
+
throw new MockApiError(
|
|
279
|
+
"INVALID_CONFIG",
|
|
280
|
+
`${label} has unsupported option "${key}". Supported options: ${RESOURCE_OPTIONS.join(", ")}.`
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const { type, slice, seed, pagination, operations } = resource;
|
|
285
|
+
if (!isDatasetId(type)) {
|
|
286
|
+
throw new MockApiError(
|
|
287
|
+
"UNKNOWN_DATASET",
|
|
288
|
+
`${label}: ${type === void 0 ? 'missing "type"' : `unknown dataset ${JSON.stringify(type)}`}. Available: ${listDatasetIds().join(", ")}.`
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
if (slice !== void 0) assertSliceConfig(slice, label);
|
|
292
|
+
if (seed !== void 0) assertSeed(seed, label);
|
|
293
|
+
if (pagination !== void 0 && typeof pagination !== "boolean" && pagination !== "page" && pagination !== "cursor") {
|
|
294
|
+
throw new MockApiError(
|
|
295
|
+
"INVALID_CONFIG",
|
|
296
|
+
`${label}: pagination must be true, false, "page" or "cursor" (got ${String(pagination)}).`
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
assertSettings(resource, label);
|
|
300
|
+
assertOperations(operations, label);
|
|
301
|
+
}
|
|
302
|
+
function pickSettings(source) {
|
|
303
|
+
const out = {};
|
|
304
|
+
for (const key of SELECTION_KEYS) {
|
|
305
|
+
if (source[key] !== void 0) out[key] = source[key];
|
|
306
|
+
}
|
|
307
|
+
return out;
|
|
308
|
+
}
|
|
309
|
+
function mergeSettings(root, resource) {
|
|
310
|
+
const merged = { ...root, ...resource };
|
|
311
|
+
if (root.operationGroups || resource.operationGroups) {
|
|
312
|
+
merged.operationGroups = { ...root.operationGroups, ...resource.operationGroups };
|
|
313
|
+
}
|
|
314
|
+
return merged;
|
|
315
|
+
}
|
|
316
|
+
function defineMockApi(config) {
|
|
317
|
+
if (!isPlainObject(config)) throw new MockApiError("INVALID_CONFIG", EXPECTED_SHAPE);
|
|
318
|
+
let root = {};
|
|
319
|
+
let resources;
|
|
320
|
+
if (isRootForm(config)) {
|
|
321
|
+
for (const key of Object.keys(config)) {
|
|
322
|
+
if (!ROOT_OPTIONS.includes(key)) {
|
|
323
|
+
throw new MockApiError(
|
|
324
|
+
"INVALID_CONFIG",
|
|
325
|
+
`defineMockApi() has unsupported option "${key}". Supported options: ${ROOT_OPTIONS.join(", ")}.`
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
assertSettings(config, "defineMockApi()");
|
|
330
|
+
root = pickSettings(config);
|
|
331
|
+
resources = config.resources;
|
|
332
|
+
} else {
|
|
333
|
+
resources = config;
|
|
334
|
+
}
|
|
335
|
+
const entries = Object.entries(resources);
|
|
336
|
+
if (entries.length === 0) {
|
|
337
|
+
throw new MockApiError("INVALID_CONFIG", "defineMockApi() needs at least one resource.");
|
|
338
|
+
}
|
|
339
|
+
const api = {};
|
|
340
|
+
for (const [name, input] of entries) {
|
|
341
|
+
const resource = typeof input === "string" ? { type: input } : input;
|
|
342
|
+
assertResource(name, resource);
|
|
343
|
+
const raw = resource;
|
|
344
|
+
const settings = mergeSettings(root, pickSettings(raw));
|
|
345
|
+
api[name] = createResource(name, resource.type, {
|
|
346
|
+
slice: resource.slice,
|
|
347
|
+
seed: resource.seed,
|
|
348
|
+
pagination: resource.pagination,
|
|
349
|
+
operations: expandOperations(
|
|
350
|
+
resource.operations,
|
|
351
|
+
settings
|
|
352
|
+
)
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
return api;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export {
|
|
359
|
+
defineMockApi
|
|
360
|
+
};
|