@objectstack/nestjs 2.0.1 → 2.0.2
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/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +6 -0
- package/package.json +11 -8
- package/src/__mocks__/runtime.ts +16 -0
- package/src/nestjs.test.ts +350 -0
- package/vitest.config.ts +16 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @objectstack/nestjs@2.0.
|
|
2
|
+
> @objectstack/nestjs@2.0.2 build /home/runner/work/spec/spec/packages/adapters/nestjs
|
|
3
3
|
> tsup --config ../../../tsup.config.ts
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
[33mCJS[39m [33mYou have emitDecoratorMetadata enabled but @swc/core was not installed, skipping swc plugin[39m
|
|
15
15
|
[32mESM[39m [1mdist/index.mjs [22m[32m6.87 KB[39m
|
|
16
16
|
[32mESM[39m [1mdist/index.mjs.map [22m[32m11.21 KB[39m
|
|
17
|
-
[32mESM[39m ⚡️ Build success in
|
|
17
|
+
[32mESM[39m ⚡️ Build success in 50ms
|
|
18
18
|
[32mCJS[39m [1mdist/index.js [22m[32m8.49 KB[39m
|
|
19
19
|
[32mCJS[39m [1mdist/index.js.map [22m[32m11.24 KB[39m
|
|
20
|
-
[32mCJS[39m ⚡️ Build success in
|
|
20
|
+
[32mCJS[39m ⚡️ Build success in 50ms
|
|
21
21
|
[34mDTS[39m Build start
|
|
22
|
-
[32mDTS[39m ⚡️ Build success in
|
|
22
|
+
[32mDTS[39m ⚡️ Build success in 6716ms
|
|
23
23
|
[32mDTS[39m [1mdist/index.d.mts [22m[32m8.75 KB[39m
|
|
24
24
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m8.75 KB[39m
|
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/nestjs",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"peerDependencies": {
|
|
8
|
-
"@nestjs/common": "^
|
|
9
|
-
"@nestjs/core": "^
|
|
10
|
-
"@objectstack/runtime": "2.0.
|
|
8
|
+
"@nestjs/common": "^11.1.13",
|
|
9
|
+
"@nestjs/core": "^11.1.13",
|
|
10
|
+
"@objectstack/runtime": "2.0.2"
|
|
11
11
|
},
|
|
12
12
|
"devDependencies": {
|
|
13
|
-
"@nestjs/common": "^
|
|
14
|
-
"@nestjs/core": "^
|
|
13
|
+
"@nestjs/common": "^11.1.13",
|
|
14
|
+
"@nestjs/core": "^11.1.13",
|
|
15
15
|
"typescript": "^5.0.0",
|
|
16
|
-
"
|
|
16
|
+
"vitest": "^4.0.18",
|
|
17
|
+
"@objectstack/runtime": "2.0.2"
|
|
17
18
|
},
|
|
18
19
|
"scripts": {
|
|
19
|
-
"build": "tsup --config ../../../tsup.config.ts"
|
|
20
|
+
"build": "tsup --config ../../../tsup.config.ts",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"test:watch": "vitest"
|
|
20
23
|
}
|
|
21
24
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Stub for @objectstack/runtime - resolved via vitest alias
|
|
2
|
+
import { vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
export class HttpDispatcher {
|
|
5
|
+
getDiscoveryInfo = vi.fn().mockReturnValue({ version: '1.0' });
|
|
6
|
+
handleGraphQL = vi.fn().mockResolvedValue({ data: {} });
|
|
7
|
+
handleAuth = vi.fn().mockResolvedValue({ handled: true, response: { status: 200, body: { ok: true } } });
|
|
8
|
+
handleMetadata = vi.fn().mockResolvedValue({ handled: true, response: { status: 200, body: [] } });
|
|
9
|
+
handleData = vi.fn().mockResolvedValue({ handled: true, response: { status: 200, body: [] } });
|
|
10
|
+
handleStorage = vi.fn().mockResolvedValue({ handled: true, response: { status: 200, body: {} } });
|
|
11
|
+
|
|
12
|
+
constructor(_kernel: any) {}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type ObjectKernel = any;
|
|
16
|
+
export type HttpDispatcherResult = any;
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
4
|
+
|
|
5
|
+
// Mock NestJS decorators as no-ops
|
|
6
|
+
vi.mock('@nestjs/common', () => {
|
|
7
|
+
const classDecorator = () => (target: any) => target;
|
|
8
|
+
const methodDecorator = () => (_target: any, _key: string, descriptor: PropertyDescriptor) => descriptor;
|
|
9
|
+
const paramDecorator = () => () => (_target: any, _key: string, _index: number) => {};
|
|
10
|
+
return {
|
|
11
|
+
Module: classDecorator,
|
|
12
|
+
Global: () => (target: any) => target,
|
|
13
|
+
Controller: (_prefix?: string) => (target: any) => target,
|
|
14
|
+
Injectable: () => (target: any) => target,
|
|
15
|
+
Inject: (_token: any) => (_target: any, _key: string | undefined, _index: number) => {},
|
|
16
|
+
DynamicModule: class {},
|
|
17
|
+
Post: methodDecorator,
|
|
18
|
+
Get: methodDecorator,
|
|
19
|
+
All: methodDecorator,
|
|
20
|
+
Body: paramDecorator,
|
|
21
|
+
Query: paramDecorator,
|
|
22
|
+
Req: paramDecorator,
|
|
23
|
+
Res: paramDecorator,
|
|
24
|
+
createParamDecorator: (_fn: any) => () => (_target: any, _key: string, _index: number) => {},
|
|
25
|
+
ExecutionContext: class {},
|
|
26
|
+
Provider: class {},
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
import {
|
|
31
|
+
OBJECT_KERNEL,
|
|
32
|
+
ObjectStackModule,
|
|
33
|
+
ObjectStackService,
|
|
34
|
+
ObjectStackController,
|
|
35
|
+
DiscoveryController,
|
|
36
|
+
} from './index.js';
|
|
37
|
+
|
|
38
|
+
// --- Helpers ---
|
|
39
|
+
|
|
40
|
+
function createMockKernel() {
|
|
41
|
+
return { id: 'test-kernel' } as any;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function createMockRes() {
|
|
45
|
+
const res: any = {
|
|
46
|
+
_status: 200,
|
|
47
|
+
_body: null,
|
|
48
|
+
_headers: {} as Record<string, string>,
|
|
49
|
+
_redirectUrl: null as string | null,
|
|
50
|
+
status(code: number) { res._status = code; return res; },
|
|
51
|
+
json(body: any) { res._body = body; return res; },
|
|
52
|
+
send(body: any) { res._body = body; return res; },
|
|
53
|
+
setHeader(k: string, v: string) { res._headers[k] = v; return res; },
|
|
54
|
+
redirect(url: string) { res._redirectUrl = url; return res; },
|
|
55
|
+
};
|
|
56
|
+
return res;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// --- Tests ---
|
|
60
|
+
|
|
61
|
+
describe('OBJECT_KERNEL constant', () => {
|
|
62
|
+
it('is exported as a string token', () => {
|
|
63
|
+
expect(OBJECT_KERNEL).toBe('OBJECT_KERNEL');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('ObjectStackModule', () => {
|
|
68
|
+
it('forRoot returns a DynamicModule with correct shape', () => {
|
|
69
|
+
const kernel = createMockKernel();
|
|
70
|
+
const mod = ObjectStackModule.forRoot(kernel);
|
|
71
|
+
|
|
72
|
+
expect(mod).toBeDefined();
|
|
73
|
+
expect(mod.module).toBe(ObjectStackModule);
|
|
74
|
+
expect(mod.controllers).toContain(ObjectStackController);
|
|
75
|
+
expect(mod.controllers).toContain(DiscoveryController);
|
|
76
|
+
expect(mod.providers).toHaveLength(2);
|
|
77
|
+
expect(mod.exports).toHaveLength(2);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('forRoot provides the kernel under OBJECT_KERNEL token', () => {
|
|
81
|
+
const kernel = createMockKernel();
|
|
82
|
+
const mod = ObjectStackModule.forRoot(kernel);
|
|
83
|
+
|
|
84
|
+
const kernelProvider = (mod.providers as any[])?.find(
|
|
85
|
+
(p: any) => p.provide === OBJECT_KERNEL,
|
|
86
|
+
);
|
|
87
|
+
expect(kernelProvider).toBeDefined();
|
|
88
|
+
expect(kernelProvider.useValue).toBe(kernel);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('forRoot exports ObjectStackService', () => {
|
|
92
|
+
const kernel = createMockKernel();
|
|
93
|
+
const mod = ObjectStackModule.forRoot(kernel);
|
|
94
|
+
|
|
95
|
+
expect(mod.exports).toContain(ObjectStackService);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('ObjectStackService', () => {
|
|
100
|
+
let service: ObjectStackService;
|
|
101
|
+
let kernel: any;
|
|
102
|
+
|
|
103
|
+
beforeEach(() => {
|
|
104
|
+
kernel = createMockKernel();
|
|
105
|
+
service = new ObjectStackService(kernel);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('creates an HttpDispatcher on construction', () => {
|
|
109
|
+
expect(service.dispatcher).toBeDefined();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('getKernel returns the injected kernel', () => {
|
|
113
|
+
expect(service.getKernel()).toBe(kernel);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('ObjectStackController', () => {
|
|
118
|
+
let controller: ObjectStackController;
|
|
119
|
+
let service: ObjectStackService;
|
|
120
|
+
let res: ReturnType<typeof createMockRes>;
|
|
121
|
+
|
|
122
|
+
beforeEach(() => {
|
|
123
|
+
const kernel = createMockKernel();
|
|
124
|
+
service = new ObjectStackService(kernel);
|
|
125
|
+
controller = new ObjectStackController(service);
|
|
126
|
+
res = createMockRes();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('has all expected route handler methods', () => {
|
|
130
|
+
expect(typeof controller.discovery).toBe('function');
|
|
131
|
+
expect(typeof controller.graphql).toBe('function');
|
|
132
|
+
expect(typeof controller.auth).toBe('function');
|
|
133
|
+
expect(typeof controller.metadata).toBe('function');
|
|
134
|
+
expect(typeof controller.data).toBe('function');
|
|
135
|
+
expect(typeof controller.storage).toBe('function');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('discovery()', () => {
|
|
139
|
+
it('returns discovery info from the dispatcher', () => {
|
|
140
|
+
const result = controller.discovery();
|
|
141
|
+
expect(result).toEqual({ data: { version: '1.0' } });
|
|
142
|
+
expect(service.dispatcher.getDiscoveryInfo).toHaveBeenCalledWith('/api');
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe('graphql()', () => {
|
|
147
|
+
it('dispatches to handleGraphQL and returns json', async () => {
|
|
148
|
+
const req = { headers: {} };
|
|
149
|
+
const body = { query: '{ objects { name } }' };
|
|
150
|
+
|
|
151
|
+
await controller.graphql(body, req, res);
|
|
152
|
+
|
|
153
|
+
expect(service.dispatcher.handleGraphQL).toHaveBeenCalledWith(body, { request: req });
|
|
154
|
+
expect(res._body).toEqual({ data: {} });
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('handles errors from handleGraphQL', async () => {
|
|
158
|
+
(service.dispatcher.handleGraphQL as any).mockRejectedValueOnce(
|
|
159
|
+
Object.assign(new Error('GQL Error'), { statusCode: 400 }),
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
await controller.graphql({}, {}, res);
|
|
163
|
+
|
|
164
|
+
expect(res._status).toBe(400);
|
|
165
|
+
expect(res._body.success).toBe(false);
|
|
166
|
+
expect(res._body.error.message).toBe('GQL Error');
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('auth()', () => {
|
|
171
|
+
it('dispatches to handleAuth with extracted path', async () => {
|
|
172
|
+
const req = { params: { 0: 'login' }, url: '/api/auth/login', method: 'POST' };
|
|
173
|
+
const body = { username: 'admin' };
|
|
174
|
+
|
|
175
|
+
await controller.auth(req, res, body);
|
|
176
|
+
|
|
177
|
+
expect(service.dispatcher.handleAuth).toHaveBeenCalledWith(
|
|
178
|
+
'login', 'POST', body, { request: req, response: res },
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('falls back to URL parsing for path extraction', async () => {
|
|
183
|
+
const req = { params: {}, url: '/api/auth/callback?code=abc', method: 'GET' };
|
|
184
|
+
|
|
185
|
+
await controller.auth(req, res, {});
|
|
186
|
+
|
|
187
|
+
expect(service.dispatcher.handleAuth).toHaveBeenCalledWith(
|
|
188
|
+
'callback', 'GET', {}, { request: req, response: res },
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('metadata()', () => {
|
|
194
|
+
it('dispatches to handleMetadata with extracted path', async () => {
|
|
195
|
+
const req = { params: { 0: '' }, url: '/api/meta/objects', method: 'GET' };
|
|
196
|
+
|
|
197
|
+
await controller.metadata(req, res, undefined);
|
|
198
|
+
|
|
199
|
+
expect(service.dispatcher.handleMetadata).toHaveBeenCalledWith(
|
|
200
|
+
'/objects', { request: req }, 'GET', undefined,
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('data()', () => {
|
|
206
|
+
it('dispatches to handleData with extracted path', async () => {
|
|
207
|
+
const req = { params: { 0: '' }, url: '/api/data/account', method: 'GET' };
|
|
208
|
+
const query = { limit: '10' };
|
|
209
|
+
|
|
210
|
+
await controller.data(req, res, {}, query);
|
|
211
|
+
|
|
212
|
+
expect(service.dispatcher.handleData).toHaveBeenCalledWith(
|
|
213
|
+
'/account', 'GET', {}, query, { request: req },
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe('storage()', () => {
|
|
219
|
+
it('dispatches to handleStorage with extracted path', async () => {
|
|
220
|
+
const req = { params: { 0: '' }, url: '/api/storage/files/test.png', method: 'GET', file: null, files: {} };
|
|
221
|
+
|
|
222
|
+
await controller.storage(req, res);
|
|
223
|
+
|
|
224
|
+
expect(service.dispatcher.handleStorage).toHaveBeenCalledWith(
|
|
225
|
+
'/files/test.png', 'GET', undefined, { request: req },
|
|
226
|
+
);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
describe('normalizeResponse (via handlers)', () => {
|
|
231
|
+
it('returns 404 when result is not handled', async () => {
|
|
232
|
+
(service.dispatcher.handleAuth as any).mockResolvedValueOnce({ handled: false });
|
|
233
|
+
const req = { params: { 0: 'noop' }, url: '/api/auth/noop', method: 'GET' };
|
|
234
|
+
|
|
235
|
+
await controller.auth(req, res, {});
|
|
236
|
+
|
|
237
|
+
expect(res._status).toBe(404);
|
|
238
|
+
expect(res._body.error.code).toBe(404);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('sets custom headers from response', async () => {
|
|
242
|
+
(service.dispatcher.handleData as any).mockResolvedValueOnce({
|
|
243
|
+
handled: true,
|
|
244
|
+
response: { status: 201, body: { id: 1 }, headers: { 'X-Custom': 'yes' } },
|
|
245
|
+
});
|
|
246
|
+
const req = { params: {}, url: '/api/data/account', method: 'POST' };
|
|
247
|
+
|
|
248
|
+
await controller.data(req, res, {}, {});
|
|
249
|
+
|
|
250
|
+
expect(res._status).toBe(201);
|
|
251
|
+
expect(res._headers['X-Custom']).toBe('yes');
|
|
252
|
+
expect(res._body).toEqual({ id: 1 });
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('handles redirect results', async () => {
|
|
256
|
+
(service.dispatcher.handleAuth as any).mockResolvedValueOnce({
|
|
257
|
+
handled: true,
|
|
258
|
+
result: { type: 'redirect', url: 'https://example.com/callback' },
|
|
259
|
+
});
|
|
260
|
+
const req = { params: { 0: 'oauth' }, url: '/api/auth/oauth', method: 'GET' };
|
|
261
|
+
|
|
262
|
+
await controller.auth(req, res, {});
|
|
263
|
+
|
|
264
|
+
expect(res._redirectUrl).toBe('https://example.com/callback');
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('handles stream results', async () => {
|
|
268
|
+
const pipeFn = vi.fn();
|
|
269
|
+
(service.dispatcher.handleStorage as any).mockResolvedValueOnce({
|
|
270
|
+
handled: true,
|
|
271
|
+
result: {
|
|
272
|
+
type: 'stream',
|
|
273
|
+
stream: { pipe: pipeFn },
|
|
274
|
+
headers: { 'Content-Type': 'application/octet-stream' },
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
const req = { params: {}, url: '/api/storage/download', method: 'GET', file: null, files: {} };
|
|
278
|
+
|
|
279
|
+
await controller.storage(req, res);
|
|
280
|
+
|
|
281
|
+
expect(pipeFn).toHaveBeenCalledWith(res);
|
|
282
|
+
expect(res._headers['Content-Type']).toBe('application/octet-stream');
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('handles generic result objects with 200 status', async () => {
|
|
286
|
+
(service.dispatcher.handleData as any).mockResolvedValueOnce({
|
|
287
|
+
handled: true,
|
|
288
|
+
result: { foo: 'bar' },
|
|
289
|
+
});
|
|
290
|
+
const req = { params: {}, url: '/api/data/x', method: 'GET' };
|
|
291
|
+
|
|
292
|
+
await controller.data(req, res, {}, {});
|
|
293
|
+
|
|
294
|
+
expect(res._status).toBe(200);
|
|
295
|
+
expect(res._body).toEqual({ foo: 'bar' });
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('handles Response-like objects', async () => {
|
|
299
|
+
const mockHeaders = new Map([['content-type', 'text/plain']]);
|
|
300
|
+
(service.dispatcher.handleData as any).mockResolvedValueOnce({
|
|
301
|
+
handled: true,
|
|
302
|
+
result: {
|
|
303
|
+
status: 203,
|
|
304
|
+
headers: mockHeaders,
|
|
305
|
+
text: vi.fn().mockResolvedValue('hello world'),
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
const req = { params: {}, url: '/api/data/x', method: 'GET' };
|
|
309
|
+
|
|
310
|
+
await controller.data(req, res, {}, {});
|
|
311
|
+
|
|
312
|
+
expect(res._status).toBe(203);
|
|
313
|
+
expect(res._body).toBe('hello world');
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
describe('handleError', () => {
|
|
318
|
+
it('uses statusCode from error if available', async () => {
|
|
319
|
+
(service.dispatcher.handleGraphQL as any).mockRejectedValueOnce(
|
|
320
|
+
Object.assign(new Error('Forbidden'), { statusCode: 403, details: { reason: 'no access' } }),
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
await controller.graphql({}, {}, res);
|
|
324
|
+
|
|
325
|
+
expect(res._status).toBe(403);
|
|
326
|
+
expect(res._body.error.message).toBe('Forbidden');
|
|
327
|
+
expect(res._body.error.details).toEqual({ reason: 'no access' });
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('defaults to 500 when no statusCode', async () => {
|
|
331
|
+
(service.dispatcher.handleGraphQL as any).mockRejectedValueOnce(new Error('Unexpected'));
|
|
332
|
+
|
|
333
|
+
await controller.graphql({}, {}, res);
|
|
334
|
+
|
|
335
|
+
expect(res._status).toBe(500);
|
|
336
|
+
expect(res._body.error.code).toBe(500);
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
describe('DiscoveryController', () => {
|
|
342
|
+
it('redirects to /api', () => {
|
|
343
|
+
const controller = new DiscoveryController();
|
|
344
|
+
const res = createMockRes();
|
|
345
|
+
|
|
346
|
+
controller.discover(res);
|
|
347
|
+
|
|
348
|
+
expect(res._redirectUrl).toBe('/api');
|
|
349
|
+
});
|
|
350
|
+
});
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
|
|
3
|
+
import { defineConfig } from 'vitest/config';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
test: {
|
|
8
|
+
globals: true,
|
|
9
|
+
environment: 'node',
|
|
10
|
+
},
|
|
11
|
+
resolve: {
|
|
12
|
+
alias: {
|
|
13
|
+
'@objectstack/runtime': path.resolve(__dirname, 'src/__mocks__/runtime.ts'),
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
});
|