@warp-drive/holodeck 0.1.0-beta.1 → 0.1.0-beta.3
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/CHANGELOG.md +139 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +154 -210
- package/dist/index.js.map +1 -0
- package/dist/mock.d.ts +89 -0
- package/dist/mock.d.ts.map +1 -0
- package/dist/mock.js +179 -133
- package/dist/mock.js.map +1 -0
- package/package.json +23 -28
- package/server/bun.js +31 -23
- package/server/node.js +42 -33
- package/server/utils.js +55 -0
- package/declarations/index.d.ts +0 -52
- package/declarations/mock.d.ts +0 -85
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# @warp-drive/holodeck Changelog
|
|
2
|
+
|
|
3
|
+
## v5.8.0 (2025-10-07)
|
|
4
|
+
|
|
5
|
+
#### :rocket: Enhancement
|
|
6
|
+
|
|
7
|
+
* [#10121](https://github.com/warp-drive-data/warp-drive/pull/10121) feat: legacy ModelFragments support ([@RobbieTheWagner](https://github.com/RobbieTheWagner))
|
|
8
|
+
|
|
9
|
+
#### :house: Internal
|
|
10
|
+
|
|
11
|
+
* [#10377](https://github.com/warp-drive-data/warp-drive/pull/10377) chore: refactor holodeck to use node ([@runspired](https://github.com/runspired))
|
|
12
|
+
* [#10376](https://github.com/warp-drive-data/warp-drive/pull/10376) chore: prepare holodeck for publishing ([@runspired](https://github.com/runspired))
|
|
13
|
+
* [#10375](https://github.com/warp-drive-data/warp-drive/pull/10375) chore: make non-GET holodeck replay work ([@runspired](https://github.com/runspired))
|
|
14
|
+
* [#10373](https://github.com/warp-drive-data/warp-drive/pull/10373) chore: add holodeck client support for PUT/PATCH/POST/DELETE ([@runspired](https://github.com/runspired))
|
|
15
|
+
* [#10372](https://github.com/warp-drive-data/warp-drive/pull/10372) Missing Context Param in Code Example for setTestId ([@lucasxzh](https://github.com/lucasxzh))
|
|
16
|
+
* [#10349](https://github.com/warp-drive-data/warp-drive/pull/10349) chore: update all emberjs/data links to warp-drive-data/warp-drive ([@runspired](https://github.com/runspired))
|
|
17
|
+
|
|
18
|
+
#### Committers: (3)
|
|
19
|
+
|
|
20
|
+
Robbie Wagner ([@RobbieTheWagner](https://github.com/RobbieTheWagner))
|
|
21
|
+
Chris Thoburn ([@runspired](https://github.com/runspired))
|
|
22
|
+
[@lucasxzh](https://github.com/lucasxzh)
|
|
23
|
+
|
|
24
|
+
## v5.7.0 (2025-08-26)
|
|
25
|
+
|
|
26
|
+
#### :house: Internal
|
|
27
|
+
|
|
28
|
+
* [#10279](https://github.com/warp-drive-data/warp-drive/pull/10279) chore: add support for LegacyNetworkHandler to holodeck ([@runspired](https://github.com/runspired))
|
|
29
|
+
* [#10226](https://github.com/warp-drive-data/warp-drive/pull/10226) chore: use https for diagnostic by default, print url last ([@runspired](https://github.com/runspired))
|
|
30
|
+
* [#10124](https://github.com/warp-drive-data/warp-drive/pull/10124) chore: mark a few types as private ([@runspired](https://github.com/runspired))
|
|
31
|
+
|
|
32
|
+
#### Committers: (1)
|
|
33
|
+
|
|
34
|
+
Chris Thoburn ([@runspired](https://github.com/runspired))
|
|
35
|
+
|
|
36
|
+
## v5.6.0 (2025-07-01)
|
|
37
|
+
|
|
38
|
+
#### :memo: Documentation
|
|
39
|
+
|
|
40
|
+
* [#9991](https://github.com/warp-drive-data/warp-drive/pull/9991) chore: improve output of typedoc ([@runspired](https://github.com/runspired))
|
|
41
|
+
|
|
42
|
+
#### :house: Internal
|
|
43
|
+
|
|
44
|
+
* [#10042](https://github.com/warp-drive-data/warp-drive/pull/10042) chore: upgrade to vite7 ([@runspired](https://github.com/runspired))
|
|
45
|
+
* [#10040](https://github.com/warp-drive-data/warp-drive/pull/10040) chore: isolated declarations ([@runspired](https://github.com/runspired))
|
|
46
|
+
|
|
47
|
+
#### Committers: (1)
|
|
48
|
+
|
|
49
|
+
Chris Thoburn ([@runspired](https://github.com/runspired))
|
|
50
|
+
|
|
51
|
+
## v5.4.0 (2025-04-11)
|
|
52
|
+
|
|
53
|
+
#### :memo: Documentation
|
|
54
|
+
|
|
55
|
+
* [#9648](https://github.com/warp-drive-data/warp-drive/pull/9648) docs: cleanup usage pattern for RequestManager configuration on store ([@runspired](https://github.com/runspired))
|
|
56
|
+
|
|
57
|
+
#### :rocket: Enhancement
|
|
58
|
+
|
|
59
|
+
* [#9530](https://github.com/warp-drive-data/warp-drive/pull/9530) feat: <:idle> state for Requests ([@runspired](https://github.com/runspired))
|
|
60
|
+
* [#9503](https://github.com/warp-drive-data/warp-drive/pull/9503) feat: request deduping & <Request /> invalidate subscriptions ([@runspired](https://github.com/runspired))
|
|
61
|
+
|
|
62
|
+
#### :bug: Bug Fix
|
|
63
|
+
|
|
64
|
+
* [#9530](https://github.com/warp-drive-data/warp-drive/pull/9530) feat: <:idle> state for Requests ([@runspired](https://github.com/runspired))
|
|
65
|
+
|
|
66
|
+
#### :house: Internal
|
|
67
|
+
|
|
68
|
+
* [#9759](https://github.com/warp-drive-data/warp-drive/pull/9759) chore: Improve contributing experience ([@runspired](https://github.com/runspired))
|
|
69
|
+
* [#9758](https://github.com/warp-drive-data/warp-drive/pull/9758) chore: update contributing docs ([@runspired](https://github.com/runspired))
|
|
70
|
+
* [#9752](https://github.com/warp-drive-data/warp-drive/pull/9752) chore: tooling upgrades to support WarpDrive package unification ([@runspired](https://github.com/runspired))
|
|
71
|
+
* [#9705](https://github.com/warp-drive-data/warp-drive/pull/9705) chore: make diagnostic shutdown safer, use bun for holodeck server ([@runspired](https://github.com/runspired))
|
|
72
|
+
* [#9699](https://github.com/warp-drive-data/warp-drive/pull/9699) chore: update to pnpm 10 ([@runspired](https://github.com/runspired))
|
|
73
|
+
* [#9629](https://github.com/warp-drive-data/warp-drive/pull/9629) fix: restore * versions and setup publish to not overwrite them ([@runspired](https://github.com/runspired))
|
|
74
|
+
* [#9620](https://github.com/warp-drive-data/warp-drive/pull/9620) Starwars ([@NullVoxPopuli](https://github.com/NullVoxPopuli))
|
|
75
|
+
* [#9543](https://github.com/warp-drive-data/warp-drive/pull/9543) Improvement: Setting Up The Project ([@leoeuclids](https://github.com/leoeuclids))
|
|
76
|
+
|
|
77
|
+
#### Committers: (3)
|
|
78
|
+
|
|
79
|
+
Chris Thoburn ([@runspired](https://github.com/runspired))
|
|
80
|
+
[@NullVoxPopuli](https://github.com/NullVoxPopuli)
|
|
81
|
+
Leo Euclides ([@leoeuclids](https://github.com/leoeuclids))
|
|
82
|
+
|
|
83
|
+
## v0.0.0-alpha.71 (2024-06-15)
|
|
84
|
+
|
|
85
|
+
#### :memo: Documentation
|
|
86
|
+
|
|
87
|
+
* [#9328](https://github.com/warp-drive-data/warp-drive/pull/9328) chore: update READMEs with status and dist tag info ([@runspired](https://github.com/runspired))
|
|
88
|
+
|
|
89
|
+
#### :rocket: Enhancement
|
|
90
|
+
|
|
91
|
+
* [#9471](https://github.com/warp-drive-data/warp-drive/pull/9471) feat: npx warp-drive ([@runspired](https://github.com/runspired))
|
|
92
|
+
* [#9366](https://github.com/warp-drive-data/warp-drive/pull/9366) feat: typed Model ([@runspired](https://github.com/runspired))
|
|
93
|
+
* [#9260](https://github.com/warp-drive-data/warp-drive/pull/9260) feat: ember specific data utils ([@runspired](https://github.com/runspired))
|
|
94
|
+
|
|
95
|
+
#### :bug: Bug Fix
|
|
96
|
+
|
|
97
|
+
* [#9383](https://github.com/warp-drive-data/warp-drive/pull/9383) fix: ensure cache-handler clones full errors ([@runspired](https://github.com/runspired))
|
|
98
|
+
|
|
99
|
+
#### :house: Internal
|
|
100
|
+
|
|
101
|
+
* [#9292](https://github.com/warp-drive-data/warp-drive/pull/9292) feat: add new build-config package ([@runspired](https://github.com/runspired))
|
|
102
|
+
|
|
103
|
+
#### Committers: (1)
|
|
104
|
+
|
|
105
|
+
Chris Thoburn ([@runspired](https://github.com/runspired))
|
|
106
|
+
|
|
107
|
+
For the full project changelog see [https://github.com/warp-drive-data/warp-drive/blob/main/CHANGELOG.md](https://github.com/warp-drive-data/warp-drive/blob/main/CHANGELOG.md)
|
|
108
|
+
|
|
109
|
+
## v0.0.0-alpha.9 (2024-02-24)
|
|
110
|
+
|
|
111
|
+
#### :rocket: Enhancement
|
|
112
|
+
|
|
113
|
+
* [#8921](https://github.com/warp-drive-data/warp-drive/pull/8921) feat: Improved Fetch Errors ([@runspired](https://github.com/runspired))
|
|
114
|
+
|
|
115
|
+
#### :house: Internal
|
|
116
|
+
|
|
117
|
+
* [#9125](https://github.com/warp-drive-data/warp-drive/pull/9125) Configure ESLint for test packages ([@gitKrystan](https://github.com/gitKrystan))
|
|
118
|
+
* [#8994](https://github.com/warp-drive-data/warp-drive/pull/8994) chore: fix recursive pnpm on node 18.18 ([@runspired](https://github.com/runspired))
|
|
119
|
+
* [#9084](https://github.com/warp-drive-data/warp-drive/pull/9084) Add import types ([@gitKrystan](https://github.com/gitKrystan))
|
|
120
|
+
* [#8989](https://github.com/warp-drive-data/warp-drive/pull/8989) chore(private): concurrent mode ([@runspired](https://github.com/runspired))
|
|
121
|
+
* [#9058](https://github.com/warp-drive-data/warp-drive/pull/9058) Switch from eslint-plugin-prettier to running prettier directly ([@gitKrystan](https://github.com/gitKrystan))
|
|
122
|
+
* [#9057](https://github.com/warp-drive-data/warp-drive/pull/9057) Add eslint-plugin-n to eslint config for node files ([@gitKrystan](https://github.com/gitKrystan))
|
|
123
|
+
* [#9055](https://github.com/warp-drive-data/warp-drive/pull/9055) Fix ESLint for VSCode ([@gitKrystan](https://github.com/gitKrystan))
|
|
124
|
+
* [#9051](https://github.com/warp-drive-data/warp-drive/pull/9051) chore: use references for tsc, add checks to schema-record, bun to run scripts ([@runspired](https://github.com/runspired))
|
|
125
|
+
* [#9032](https://github.com/warp-drive-data/warp-drive/pull/9032) chore(types): split out lint and type commands to be per-package ([@runspired](https://github.com/runspired))
|
|
126
|
+
* [#9050](https://github.com/warp-drive-data/warp-drive/pull/9050) chore: use composite mode for tsc ([@runspired](https://github.com/runspired))
|
|
127
|
+
* [#9049](https://github.com/warp-drive-data/warp-drive/pull/9049) chore: incremental tsc builds ([@runspired](https://github.com/runspired))
|
|
128
|
+
* [#9046](https://github.com/warp-drive-data/warp-drive/pull/9046) chore: reduce number of things turbo builds for build ([@runspired](https://github.com/runspired))
|
|
129
|
+
* [#9025](https://github.com/warp-drive-data/warp-drive/pull/9025) chore: reconfigure request package type location ([@runspired](https://github.com/runspired))
|
|
130
|
+
* [#8972](https://github.com/warp-drive-data/warp-drive/pull/8972) chore: use new test runner for request tests ([@runspired](https://github.com/runspired))
|
|
131
|
+
* [#8931](https://github.com/warp-drive-data/warp-drive/pull/8931) chore: package infra for schema-record ([@runspired](https://github.com/runspired))
|
|
132
|
+
* [#8912](https://github.com/warp-drive-data/warp-drive/pull/8912) chore: docs for holodeck ([@runspired](https://github.com/runspired))
|
|
133
|
+
* [#8906](https://github.com/warp-drive-data/warp-drive/pull/8906) feat: expand mock-server capabilities, add to main tests ([@runspired](https://github.com/runspired))
|
|
134
|
+
|
|
135
|
+
#### Committers: (2)
|
|
136
|
+
|
|
137
|
+
Chris Thoburn ([@runspired](https://github.com/runspired))
|
|
138
|
+
Krystan HuffMenne ([@gitKrystan](https://github.com/gitKrystan))
|
|
139
|
+
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ScaffoldGenerator } from "./mock.js";
|
|
2
|
+
import { Handler, NextFn } from "@warp-drive/core/request";
|
|
3
|
+
import { RequestContext, StructuredDataDocument } from "@warp-drive/core/types/request";
|
|
4
|
+
import { Store } from "@warp-drive/legacy/store";
|
|
5
|
+
//#region src/index.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
declare function setConfig({ host }: {
|
|
10
|
+
host: string;
|
|
11
|
+
}): void;
|
|
12
|
+
/**
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
declare function setTestId(context: object, str: string | null): void;
|
|
16
|
+
/**
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
19
|
+
declare function setIsRecording(value: boolean): void;
|
|
20
|
+
/**
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
23
|
+
declare function getIsRecording(): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* A request handler that intercepts requests and routes them through
|
|
26
|
+
* the Holodeck mock server.
|
|
27
|
+
*
|
|
28
|
+
* This handler modifies the request URL to include test identifiers
|
|
29
|
+
* and manages request counts for accurate mocking.
|
|
30
|
+
*
|
|
31
|
+
* Requires that the test context be configured with a testId using `setTestId`.
|
|
32
|
+
*
|
|
33
|
+
* @param owner - the test context object used to retrieve the test ID.
|
|
34
|
+
*/
|
|
35
|
+
declare class MockServerHandler implements Handler {
|
|
36
|
+
owner: object;
|
|
37
|
+
constructor(owner: object);
|
|
38
|
+
request<T>(context: RequestContext, next: NextFn<T>): Promise<StructuredDataDocument<T>>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Creates an adapterFor function that wraps the provided adapterFor function
|
|
42
|
+
* to override the adapter's _fetchRequest method to route requests through
|
|
43
|
+
* the Holodeck mock server.
|
|
44
|
+
*
|
|
45
|
+
* @param owner - The test context object used to retrieve the test ID.
|
|
46
|
+
*/
|
|
47
|
+
declare function installAdapterFor(owner: object, store: Store): void;
|
|
48
|
+
/**
|
|
49
|
+
* Mock a request by sending the scaffold to the mock server.
|
|
50
|
+
*
|
|
51
|
+
* @public
|
|
52
|
+
*/
|
|
53
|
+
declare function mock(owner: object, generate: ScaffoldGenerator, isRecording?: boolean): Promise<void>;
|
|
54
|
+
//#endregion
|
|
55
|
+
export { MockServerHandler, getIsRecording, installAdapterFor, mock, setConfig, setIsRecording, setTestId };
|
|
56
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;iBAuIgB,YAAY;EAAU;;;;;iBAQtB,UAAU,iBAAiB;;;;iBA2C3B,eAAe;;;;iBAOf;;;;;;;;;;;;cAeH,6BAA6B;EACxC;EACA,YAAY;EAGZ,QAAc,GAAG,SAAS,gBAAgB,MAAM,OAAO,KAAK,QAAQ,uBAAuB;;;;;;;;;iBAkG7E,kBAAkB,eAAe,OAAO;;;;;;iBAuClC,KAAK,eAAe,UAAU,mBAAmB,wBAAwB"}
|
package/dist/index.js
CHANGED
|
@@ -1,237 +1,181 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getGlobalConfig, macroCondition } from "@embroider/macros";
|
|
2
2
|
|
|
3
|
+
//#region src/index.ts
|
|
3
4
|
/**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const TEST_IDS = new WeakMap();
|
|
8
|
-
let HOST =
|
|
9
|
-
|
|
5
|
+
* @module
|
|
6
|
+
* @mergeModuleWith <project>
|
|
7
|
+
*/
|
|
8
|
+
const TEST_IDS = /* @__PURE__ */ new WeakMap();
|
|
9
|
+
let HOST = "/";
|
|
10
10
|
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
host
|
|
16
|
-
}) {
|
|
17
|
-
HOST = host.endsWith('/') ? host : `${host}/`;
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
function setConfig({ host }) {
|
|
14
|
+
HOST = host.endsWith("/") ? host : `${host}/`;
|
|
18
15
|
}
|
|
19
|
-
|
|
20
16
|
/**
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
24
19
|
function setTestId(context, str) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
} else {
|
|
57
|
-
TEST_IDS.delete(context);
|
|
58
|
-
}
|
|
20
|
+
if (str && TEST_IDS.has(context)) throw new Error(`MockServerHandler is already configured with a testId.`);
|
|
21
|
+
if (str) TEST_IDS.set(context, {
|
|
22
|
+
id: str,
|
|
23
|
+
mock: {
|
|
24
|
+
GET: {},
|
|
25
|
+
PUT: {},
|
|
26
|
+
PATCH: {},
|
|
27
|
+
DELETE: {},
|
|
28
|
+
POST: {},
|
|
29
|
+
QUERY: {},
|
|
30
|
+
OPTIONS: {},
|
|
31
|
+
HEAD: {},
|
|
32
|
+
CONNECT: {},
|
|
33
|
+
TRACE: {}
|
|
34
|
+
},
|
|
35
|
+
request: {
|
|
36
|
+
GET: {},
|
|
37
|
+
PUT: {},
|
|
38
|
+
PATCH: {},
|
|
39
|
+
DELETE: {},
|
|
40
|
+
POST: {},
|
|
41
|
+
QUERY: {},
|
|
42
|
+
OPTIONS: {},
|
|
43
|
+
HEAD: {},
|
|
44
|
+
CONNECT: {},
|
|
45
|
+
TRACE: {}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
else TEST_IDS.delete(context);
|
|
59
49
|
}
|
|
60
|
-
const shouldRecord = SHOULD_RECORD ? true : false;
|
|
50
|
+
const shouldRecord = macroCondition(getGlobalConfig().WarpDrive.env.SHOULD_RECORD) ? true : false;
|
|
61
51
|
let IS_RECORDING = null;
|
|
62
|
-
|
|
63
52
|
/**
|
|
64
|
-
|
|
65
|
-
|
|
53
|
+
* @public
|
|
54
|
+
*/
|
|
66
55
|
function setIsRecording(value) {
|
|
67
|
-
|
|
56
|
+
IS_RECORDING = value === null ? value : Boolean(value);
|
|
68
57
|
}
|
|
69
|
-
|
|
70
58
|
/**
|
|
71
|
-
|
|
72
|
-
|
|
59
|
+
* @public
|
|
60
|
+
*/
|
|
73
61
|
function getIsRecording() {
|
|
74
|
-
|
|
62
|
+
return IS_RECORDING === null ? shouldRecord : IS_RECORDING;
|
|
75
63
|
}
|
|
76
|
-
|
|
77
64
|
/**
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
throw e;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
65
|
+
* A request handler that intercepts requests and routes them through
|
|
66
|
+
* the Holodeck mock server.
|
|
67
|
+
*
|
|
68
|
+
* This handler modifies the request URL to include test identifiers
|
|
69
|
+
* and manages request counts for accurate mocking.
|
|
70
|
+
*
|
|
71
|
+
* Requires that the test context be configured with a testId using `setTestId`.
|
|
72
|
+
*
|
|
73
|
+
* @param owner - the test context object used to retrieve the test ID.
|
|
74
|
+
*/
|
|
75
|
+
var MockServerHandler = class {
|
|
76
|
+
constructor(owner) {
|
|
77
|
+
this.owner = owner;
|
|
78
|
+
}
|
|
79
|
+
async request(context, next) {
|
|
80
|
+
const { request, queryForTest } = setupHolodeckFetch(this.owner, Object.assign({}, context.request));
|
|
81
|
+
try {
|
|
82
|
+
const future = next(request);
|
|
83
|
+
context.setStream(future.getStream());
|
|
84
|
+
return await future;
|
|
85
|
+
} catch (e) {
|
|
86
|
+
if (e instanceof Error && !(e instanceof DOMException)) e.message = e.message.replace(queryForTest, "");
|
|
87
|
+
throw e;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
109
91
|
function setupHolodeckFetch(owner, request) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
// and we don't want to trigger cors pre-flight
|
|
136
|
-
// we convert PUT to POST to keep the request in the
|
|
137
|
-
// "simple" cors category.
|
|
138
|
-
// if (request.method === 'PUT') {
|
|
139
|
-
// request.method = 'POST';
|
|
140
|
-
// }
|
|
141
|
-
|
|
142
|
-
const headers = new Headers(request.headers);
|
|
143
|
-
if (headers.has('Content-Type')) {
|
|
144
|
-
// under the rules of simple-cors, content-type can only be
|
|
145
|
-
// one of three things, none of which are what folks typically
|
|
146
|
-
// set this to. Since holodeck always expects body to be JSON
|
|
147
|
-
// this "just works".
|
|
148
|
-
headers.set('Content-Type', 'text/plain');
|
|
149
|
-
request.headers = headers;
|
|
150
|
-
}
|
|
151
|
-
return {
|
|
152
|
-
request,
|
|
153
|
-
queryForTest
|
|
154
|
-
};
|
|
92
|
+
const test = TEST_IDS.get(owner);
|
|
93
|
+
if (!test) throw new Error(`MockServerHandler is not configured with a testId. Use setTestId to set the testId for each test`);
|
|
94
|
+
const url = request.url;
|
|
95
|
+
const firstChar = url.includes("?") ? "&" : "?";
|
|
96
|
+
const method = request.method?.toUpperCase() ?? "GET";
|
|
97
|
+
if (!test.request[method]) {
|
|
98
|
+
console.log(`⚠️ Using custom HTTP method ${method} for response to request ${url}`);
|
|
99
|
+
test.request[method] = {};
|
|
100
|
+
}
|
|
101
|
+
if (!(url in test.request[method])) test.request[method][url] = 0;
|
|
102
|
+
const queryForTest = `${firstChar}__xTestId=${test.id}&__xTestRequestNumber=${test.request[method][url]++}`;
|
|
103
|
+
request.url = url + queryForTest;
|
|
104
|
+
request.method = method;
|
|
105
|
+
request.mode = "cors";
|
|
106
|
+
request.credentials = "omit";
|
|
107
|
+
request.referrerPolicy = "";
|
|
108
|
+
const headers = new Headers(request.headers);
|
|
109
|
+
if (headers.has("Content-Type")) {
|
|
110
|
+
headers.set("Content-Type", "text/plain");
|
|
111
|
+
request.headers = headers;
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
request,
|
|
115
|
+
queryForTest
|
|
116
|
+
};
|
|
155
117
|
}
|
|
156
118
|
function upgradeStore(store) {
|
|
157
|
-
|
|
158
|
-
throw new Error('Store is not compatible with Holodeck. Missing adapterFor method.');
|
|
159
|
-
}
|
|
119
|
+
if (typeof store.adapterFor !== "function") throw new Error("Store is not compatible with Holodeck. Missing adapterFor method.");
|
|
160
120
|
}
|
|
161
|
-
|
|
162
121
|
/**
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
122
|
+
* Creates an adapterFor function that wraps the provided adapterFor function
|
|
123
|
+
* to override the adapter's _fetchRequest method to route requests through
|
|
124
|
+
* the Holodeck mock server.
|
|
125
|
+
*
|
|
126
|
+
* @param owner - The test context object used to retrieve the test ID.
|
|
127
|
+
*/
|
|
169
128
|
function installAdapterFor(owner, store) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
return adapter;
|
|
191
|
-
}
|
|
192
|
-
store.adapterFor = holodeckAdapterFor;
|
|
129
|
+
upgradeStore(store);
|
|
130
|
+
const fn = store.adapterFor;
|
|
131
|
+
function holodeckAdapterFor(modelName, _allowMissing) {
|
|
132
|
+
const adapter = fn.call(this, modelName, _allowMissing);
|
|
133
|
+
if (adapter) {
|
|
134
|
+
if (!adapter.hasOverriddenFetch) {
|
|
135
|
+
adapter.hasOverriddenFetch = true;
|
|
136
|
+
adapter.useFetch = true;
|
|
137
|
+
const originalFetch = adapter._fetchRequest?.bind(adapter);
|
|
138
|
+
adapter._fetchRequest = function(options) {
|
|
139
|
+
if (!originalFetch) throw new Error(`Adapter ${String(modelName)} does not implement _fetchRequest`);
|
|
140
|
+
const { request } = setupHolodeckFetch(owner, options);
|
|
141
|
+
return originalFetch(request);
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return adapter;
|
|
146
|
+
}
|
|
147
|
+
store.adapterFor = holodeckAdapterFor;
|
|
193
148
|
}
|
|
194
|
-
|
|
195
149
|
/**
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
150
|
+
* Mock a request by sending the scaffold to the mock server.
|
|
151
|
+
*
|
|
152
|
+
* @public
|
|
153
|
+
*/
|
|
200
154
|
async function mock(owner, generate, isRecording) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
test.mock[mockMethod][mockUrl] = 0;
|
|
224
|
-
}
|
|
225
|
-
const testMockNum = test.mock[mockMethod][mockUrl]++;
|
|
226
|
-
const url = `${HOST}__record?__xTestId=${test.id}&__xTestRequestNumber=${testMockNum}`;
|
|
227
|
-
await fetch(url, {
|
|
228
|
-
method: 'POST',
|
|
229
|
-
body: JSON.stringify(requestToMock),
|
|
230
|
-
mode: 'cors',
|
|
231
|
-
credentials: 'omit',
|
|
232
|
-
referrerPolicy: ''
|
|
233
|
-
});
|
|
234
|
-
}
|
|
155
|
+
if (getIsRecording() || isRecording) {
|
|
156
|
+
const test = TEST_IDS.get(owner);
|
|
157
|
+
if (!test) throw new Error(`Cannot call "mock" before configuring a testId. Use setTestId to set the testId for each test`);
|
|
158
|
+
const requestToMock = generate();
|
|
159
|
+
const { url: mockUrl, method } = requestToMock;
|
|
160
|
+
if (!mockUrl || !method) throw new Error(`MockError: Cannot mock a request without providing a URL and Method`);
|
|
161
|
+
const mockMethod = method?.toUpperCase() ?? "GET";
|
|
162
|
+
if (!test.mock[mockMethod]) {
|
|
163
|
+
console.log(`⚠️ Using custom HTTP method ${mockMethod} for response to request ${mockUrl}`);
|
|
164
|
+
test.mock[mockMethod] = {};
|
|
165
|
+
}
|
|
166
|
+
if (!(mockUrl in test.mock[mockMethod])) test.mock[mockMethod][mockUrl] = 0;
|
|
167
|
+
const testMockNum = test.mock[mockMethod][mockUrl]++;
|
|
168
|
+
const url = `${HOST}__record?__xTestId=${test.id}&__xTestRequestNumber=${testMockNum}`;
|
|
169
|
+
await fetch(url, {
|
|
170
|
+
method: "POST",
|
|
171
|
+
body: JSON.stringify(requestToMock),
|
|
172
|
+
mode: "cors",
|
|
173
|
+
credentials: "omit",
|
|
174
|
+
referrerPolicy: ""
|
|
175
|
+
});
|
|
176
|
+
}
|
|
235
177
|
}
|
|
236
178
|
|
|
179
|
+
//#endregion
|
|
237
180
|
export { MockServerHandler, getIsRecording, installAdapterFor, mock, setConfig, setIsRecording, setTestId };
|
|
181
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["getGlobalConfig","macroCondition","TEST_IDS","WeakMap","HOST","setConfig","host","endsWith","setTestId","context","str","has","Error","set","id","mock","GET","PUT","PATCH","DELETE","POST","QUERY","OPTIONS","HEAD","CONNECT","TRACE","request","delete","shouldRecord","WarpDrive","env","SHOULD_RECORD","IS_RECORDING","setIsRecording","value","Boolean","getIsRecording","MockServerHandler","constructor","owner","next","queryForTest","setupHolodeckFetch","Object","assign","future","setStream","getStream","e","DOMException","message","replace","test","get","url","firstChar","includes","method","toUpperCase","console","log","mode","credentials","referrerPolicy","headers","Headers","upgradeAdapter","adapter","upgradeStore","store","adapterFor","installAdapterFor","fn","holodeckAdapterFor","modelName","_allowMissing","call","hasOverriddenFetch","useFetch","originalFetch","_fetchRequest","bind","options","String","generate","isRecording","requestToMock","mockUrl","mockMethod","testMockNum","fetch","body","JSON","stringify"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @module\n * @mergeModuleWith <project>\n */\nimport { SHOULD_RECORD } from '@warp-drive/core/build-config/env';\nimport type { Handler, NextFn } from '@warp-drive/core/request';\nimport type { HTTPMethod, RequestContext, RequestInfo, StructuredDataDocument } from '@warp-drive/core/types/request';\nimport type { MinimumAdapterInterface } from '@warp-drive/legacy/compat';\nimport type { Store } from '@warp-drive/legacy/store';\n\nimport type { ScaffoldGenerator } from './mock';\n\nconst TEST_IDS = new WeakMap<\n object,\n {\n id: string;\n /**\n * keeps track of the count of calls to record a mock\n */\n mock: {\n /**\n * For each GET, we keep track of the count\n * for a specific URL\n */\n GET: Record<string, number>;\n /**\n * For each PUT, we keep track of the count\n * for a specific URL\n */\n PUT: Record<string, number>;\n /**\n * For each PATCH, we keep track of the count\n * for a specific URL\n */\n PATCH: Record<string, number>;\n /**\n * For each DELETE, we keep track of the count\n * for a specific URL\n */\n DELETE: Record<string, number>;\n /**\n * For each POST, we keep track of the count\n * for a specific URL\n */\n POST: Record<string, number>;\n /**\n * For each OPTIONS, we keep track of the count\n * for a specific URL\n */\n OPTIONS: Record<string, number>;\n /**\n * For each QUERY, we keep track of the count\n * for a specific URL\n */\n QUERY: Record<string, number>;\n /**\n * for each HEAD, we keep track of the count\n * for a specific URL\n */\n HEAD: Record<string, number>;\n /**\n * for each CONNECT, we keep track of the count\n * for a specific URL\n */\n CONNECT: Record<string, number>;\n /**\n * for each TRACE, we keep track of the count\n * for a specific URL\n */\n TRACE: Record<string, number>;\n };\n /**\n * keeps track of the count of calls to make a request\n */\n request: {\n /**\n * For each GET, we keep track of the count\n * for a specific URL\n */\n GET: Record<string, number>;\n /**\n * For each PUT, we keep track of the count\n * for a specific URL\n */\n PUT: Record<string, number>;\n /**\n * For each PATCH, we keep track of the count\n * for a specific URL\n */\n PATCH: Record<string, number>;\n /**\n * For each DELETE, we keep track of the count\n * for a specific URL\n */\n DELETE: Record<string, number>;\n /**\n * For each POST, we keep track of the count\n * for a specific URL\n */\n POST: Record<string, number>;\n /**\n * For each OPTIONS, we keep track of the count\n * for a specific URL\n */\n OPTIONS: Record<string, number>;\n /**\n * For each QUERY, we keep track of the count\n * for a specific URL\n */\n QUERY: Record<string, number>;\n /**\n * for each HEAD, we keep track of the count\n * for a specific URL\n */\n HEAD: Record<string, number>;\n /**\n * for each CONNECT, we keep track of the count\n * for a specific URL\n */\n CONNECT: Record<string, number>;\n /**\n * for each TRACE, we keep track of the count\n * for a specific URL\n */\n TRACE: Record<string, number>;\n };\n }\n>();\n\nlet HOST = '/';\n\n/**\n * @public\n */\n\nexport function setConfig({ host }: { host: string }): void {\n HOST = host.endsWith('/') ? host : `${host}/`;\n}\n\n/**\n * @public\n */\n\nexport function setTestId(context: object, str: string | null): void {\n if (str && TEST_IDS.has(context)) {\n throw new Error(`MockServerHandler is already configured with a testId.`);\n }\n if (str) {\n TEST_IDS.set(context, {\n id: str,\n mock: {\n GET: {},\n PUT: {},\n PATCH: {},\n DELETE: {},\n POST: {},\n QUERY: {},\n OPTIONS: {},\n HEAD: {},\n CONNECT: {},\n TRACE: {},\n },\n request: {\n GET: {},\n PUT: {},\n PATCH: {},\n DELETE: {},\n POST: {},\n QUERY: {},\n OPTIONS: {},\n HEAD: {},\n CONNECT: {},\n TRACE: {},\n },\n });\n } else {\n TEST_IDS.delete(context);\n }\n}\n\nconst shouldRecord = SHOULD_RECORD ? true : false;\nlet IS_RECORDING: boolean | null = null;\n\n/**\n * @public\n */\nexport function setIsRecording(value: boolean): void {\n IS_RECORDING = value === null ? value : Boolean(value);\n}\n\n/**\n * @public\n */\nexport function getIsRecording(): boolean {\n return IS_RECORDING === null ? shouldRecord : IS_RECORDING;\n}\n\n/**\n * A request handler that intercepts requests and routes them through\n * the Holodeck mock server.\n *\n * This handler modifies the request URL to include test identifiers\n * and manages request counts for accurate mocking.\n *\n * Requires that the test context be configured with a testId using `setTestId`.\n *\n * @param owner - the test context object used to retrieve the test ID.\n */\nexport class MockServerHandler implements Handler {\n declare owner: object;\n constructor(owner: object) {\n this.owner = owner;\n }\n async request<T>(context: RequestContext, next: NextFn<T>): Promise<StructuredDataDocument<T>> {\n const { request, queryForTest } = setupHolodeckFetch(this.owner, Object.assign({}, context.request));\n\n try {\n const future = next(request);\n context.setStream(future.getStream());\n return await future;\n } catch (e) {\n if (e instanceof Error && !(e instanceof DOMException)) {\n e.message = e.message.replace(queryForTest, '');\n }\n throw e;\n }\n }\n}\n\nfunction setupHolodeckFetch(owner: object, request: RequestInfo): { request: RequestInfo; queryForTest: string } {\n const test = TEST_IDS.get(owner);\n if (!test) {\n throw new Error(`MockServerHandler is not configured with a testId. Use setTestId to set the testId for each test`);\n }\n\n const url = request.url!;\n const firstChar = url.includes('?') ? '&' : '?';\n const method = (request.method?.toUpperCase() ?? 'GET') as HTTPMethod;\n\n // enable custom methods\n if (!test.request[method]) {\n // oxlint-disable-next-line no-console\n console.log(`⚠️ Using custom HTTP method ${method} for response to request ${url}`);\n\n test.request[method] = {};\n }\n if (!(url in test.request[method])) {\n test.request[method][url] = 0;\n }\n\n const queryForTest = `${firstChar}__xTestId=${test.id}&__xTestRequestNumber=${test.request[method][url]++}`;\n request.url = url + queryForTest;\n request.method = method;\n\n request.mode = 'cors';\n request.credentials = 'omit';\n request.referrerPolicy = '';\n\n // since holodeck currently runs on a separate port\n // and we don't want to trigger cors pre-flight\n // we convert PUT to POST to keep the request in the\n // \"simple\" cors category.\n // if (request.method === 'PUT') {\n // request.method = 'POST';\n // }\n\n const headers = new Headers(request.headers);\n if (headers.has('Content-Type')) {\n // under the rules of simple-cors, content-type can only be\n // one of three things, none of which are what folks typically\n // set this to. Since holodeck always expects body to be JSON\n // this \"just works\".\n headers.set('Content-Type', 'text/plain');\n request.headers = headers;\n }\n\n return { request, queryForTest };\n}\n\ninterface HasAdapterForFn {\n adapterFor(this: Store, modelName: string): MinimumAdapterInterface;\n adapterFor(this: Store, modelName: string, _allowMissing?: true): MinimumAdapterInterface | undefined;\n}\n\n/*\n _fetchRequest(options: FetchRequestInit): Promise<Response> {\n const fetchFunction = fetch();\n\n return fetchFunction(options.url, options);\n }\n*/\ninterface PrivateAdapter {\n _fetchRequest(options: RequestInfo): Promise<Response>;\n hasOverriddenFetch: boolean;\n useFetch: boolean;\n}\n\nfunction upgradeAdapter(adapter: unknown): asserts adapter is PrivateAdapter {}\nfunction upgradeStore(store: Store): asserts store is Store & { adapterFor: HasAdapterForFn['adapterFor'] } {\n if (typeof store.adapterFor !== 'function') {\n throw new Error('Store is not compatible with Holodeck. Missing adapterFor method.');\n }\n}\n\n/**\n * Creates an adapterFor function that wraps the provided adapterFor function\n * to override the adapter's _fetchRequest method to route requests through\n * the Holodeck mock server.\n *\n * @param owner - The test context object used to retrieve the test ID.\n */\nexport function installAdapterFor(owner: object, store: Store): void {\n upgradeStore(store);\n const fn = store.adapterFor;\n function holodeckAdapterFor(\n this: Store,\n modelName: string,\n _allowMissing?: true\n ): MinimumAdapterInterface | undefined {\n const adapter = fn.call(this, modelName, _allowMissing);\n\n if (adapter) {\n upgradeAdapter(adapter);\n\n if (!adapter.hasOverriddenFetch) {\n adapter.hasOverriddenFetch = true;\n adapter.useFetch = true;\n const originalFetch = adapter._fetchRequest?.bind(adapter);\n\n adapter._fetchRequest = function (options: RequestInfo) {\n if (!originalFetch) {\n throw new Error(`Adapter ${String(modelName)} does not implement _fetchRequest`);\n }\n const { request } = setupHolodeckFetch(owner, options);\n\n return originalFetch(request);\n };\n }\n }\n\n return adapter;\n }\n store.adapterFor = holodeckAdapterFor as HasAdapterForFn['adapterFor'];\n}\n\n/**\n * Mock a request by sending the scaffold to the mock server.\n *\n * @public\n */\nexport async function mock(owner: object, generate: ScaffoldGenerator, isRecording?: boolean): Promise<void> {\n if (getIsRecording() || isRecording) {\n const test = TEST_IDS.get(owner);\n if (!test) {\n throw new Error(`Cannot call \"mock\" before configuring a testId. Use setTestId to set the testId for each test`);\n }\n const requestToMock = generate();\n const { url: mockUrl, method } = requestToMock;\n if (!mockUrl || !method) {\n throw new Error(`MockError: Cannot mock a request without providing a URL and Method`);\n }\n const mockMethod = (method?.toUpperCase() ?? 'GET') as HTTPMethod;\n\n // enable custom methods\n if (!test.mock[mockMethod]) {\n // oxlint-disable-next-line no-console\n console.log(`⚠️ Using custom HTTP method ${mockMethod} for response to request ${mockUrl}`);\n test.mock[mockMethod] = {};\n }\n if (!(mockUrl in test.mock[mockMethod])) {\n test.mock[mockMethod][mockUrl] = 0;\n }\n const testMockNum = test.mock[mockMethod][mockUrl]++;\n const url = `${HOST}__record?__xTestId=${test.id}&__xTestRequestNumber=${testMockNum}`;\n await fetch(url, {\n method: 'POST',\n body: JSON.stringify(requestToMock),\n mode: 'cors',\n credentials: 'omit',\n referrerPolicy: '',\n });\n }\n}\n"],"mappings":"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"}
|