msw 2.14.2 → 2.14.4
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/lib/browser/index.js +88 -38
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/index.mjs +88 -38
- package/lib/browser/index.mjs.map +1 -1
- package/lib/core/{HttpResponse-aGiIzO91.d.ts → HttpResponse-BFS34nkx.d.ts} +16 -0
- package/lib/core/{HttpResponse-CxHR1nNN.d.mts → HttpResponse-CQwYpuKo.d.mts} +16 -0
- package/lib/core/HttpResponse.d.mts +1 -1
- package/lib/core/HttpResponse.d.ts +1 -1
- package/lib/core/experimental/compat.d.mts +1 -1
- package/lib/core/experimental/compat.d.ts +1 -1
- package/lib/core/experimental/define-network.d.mts +1 -1
- package/lib/core/experimental/define-network.d.ts +1 -1
- package/lib/core/experimental/frames/http-frame.d.mts +1 -1
- package/lib/core/experimental/frames/http-frame.d.ts +1 -1
- package/lib/core/experimental/frames/network-frame.d.mts +1 -1
- package/lib/core/experimental/frames/network-frame.d.ts +1 -1
- package/lib/core/experimental/frames/websocket-frame.d.mts +1 -1
- package/lib/core/experimental/frames/websocket-frame.d.ts +1 -1
- package/lib/core/experimental/handlers-controller.d.mts +1 -1
- package/lib/core/experimental/handlers-controller.d.ts +1 -1
- package/lib/core/experimental/index.d.mts +1 -1
- package/lib/core/experimental/index.d.ts +1 -1
- package/lib/core/experimental/on-unhandled-frame.d.mts +1 -1
- package/lib/core/experimental/on-unhandled-frame.d.ts +1 -1
- package/lib/core/experimental/setup-api.d.mts +1 -1
- package/lib/core/experimental/setup-api.d.ts +1 -1
- package/lib/core/experimental/sources/interceptor-source.d.mts +1 -1
- package/lib/core/experimental/sources/interceptor-source.d.ts +1 -1
- package/lib/core/experimental/sources/interceptor-source.js +14 -10
- package/lib/core/experimental/sources/interceptor-source.js.map +1 -1
- package/lib/core/experimental/sources/interceptor-source.mjs +14 -10
- package/lib/core/experimental/sources/interceptor-source.mjs.map +1 -1
- package/lib/core/experimental/sources/network-source.d.mts +1 -1
- package/lib/core/experimental/sources/network-source.d.ts +1 -1
- package/lib/core/getResponse.d.mts +1 -1
- package/lib/core/getResponse.d.ts +1 -1
- package/lib/core/graphql.d.mts +1 -1
- package/lib/core/graphql.d.ts +1 -1
- package/lib/core/handlers/GraphQLHandler.d.mts +1 -1
- package/lib/core/handlers/GraphQLHandler.d.ts +1 -1
- package/lib/core/handlers/HttpHandler.d.mts +1 -1
- package/lib/core/handlers/HttpHandler.d.ts +1 -1
- package/lib/core/handlers/RequestHandler.d.mts +1 -1
- package/lib/core/handlers/RequestHandler.d.ts +1 -1
- package/lib/core/handlers/RequestHandler.js +82 -1
- package/lib/core/handlers/RequestHandler.js.map +1 -1
- package/lib/core/handlers/RequestHandler.mjs +82 -1
- package/lib/core/handlers/RequestHandler.mjs.map +1 -1
- package/lib/core/http.d.mts +1 -1
- package/lib/core/http.d.ts +1 -1
- package/lib/core/index.d.mts +1 -1
- package/lib/core/index.d.ts +1 -1
- package/lib/core/passthrough.d.mts +1 -1
- package/lib/core/passthrough.d.ts +1 -1
- package/lib/core/sse.d.mts +1 -1
- package/lib/core/sse.d.ts +1 -1
- package/lib/core/utils/HttpResponse/decorators.d.mts +1 -1
- package/lib/core/utils/HttpResponse/decorators.d.ts +1 -1
- package/lib/core/utils/executeHandlers.d.mts +1 -1
- package/lib/core/utils/executeHandlers.d.ts +1 -1
- package/lib/core/utils/handleRequest.d.mts +1 -1
- package/lib/core/utils/handleRequest.d.ts +1 -1
- package/lib/core/utils/internal/attachSiblingHandlers.d.mts +1 -1
- package/lib/core/utils/internal/attachSiblingHandlers.d.ts +1 -1
- package/lib/core/utils/internal/isHandlerKind.d.mts +1 -1
- package/lib/core/utils/internal/isHandlerKind.d.ts +1 -1
- package/lib/core/utils/internal/parseGraphQLRequest.d.mts +1 -1
- package/lib/core/utils/internal/parseGraphQLRequest.d.ts +1 -1
- package/lib/core/utils/internal/parseMultipartData.d.mts +1 -1
- package/lib/core/utils/internal/parseMultipartData.d.ts +1 -1
- package/lib/core/ws/handleWebSocketEvent.d.mts +1 -1
- package/lib/core/ws/handleWebSocketEvent.d.ts +1 -1
- package/lib/iife/index.js +183 -49
- package/lib/iife/index.js.map +1 -1
- package/lib/mockServiceWorker.js +1 -1
- package/package.json +5 -4
- package/src/browser/sources/service-worker-source.ts +17 -12
- package/src/core/experimental/sources/interceptor-source.ts +20 -10
- package/src/core/handlers/RequestHandler.ts +144 -10
package/lib/mockServiceWorker.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - Please do NOT modify this file.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
const PACKAGE_VERSION = '2.14.
|
|
10
|
+
const PACKAGE_VERSION = '2.14.4'
|
|
11
11
|
const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82'
|
|
12
12
|
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
|
|
13
13
|
const activeClientIds = new Set()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "msw",
|
|
3
|
-
"version": "2.14.
|
|
3
|
+
"version": "2.14.4",
|
|
4
4
|
"description": "Seamless REST/GraphQL API mocking library for browser and Node.js.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./lib/core/index.js",
|
|
@@ -233,7 +233,7 @@
|
|
|
233
233
|
"outvariant": "^1.4.3",
|
|
234
234
|
"path-to-regexp": "^6.3.0",
|
|
235
235
|
"picocolors": "^1.1.1",
|
|
236
|
-
"rettime": "^0.11.
|
|
236
|
+
"rettime": "^0.11.11",
|
|
237
237
|
"statuses": "^2.0.2",
|
|
238
238
|
"strict-event-emitter": "^0.5.1",
|
|
239
239
|
"tough-cookie": "^6.0.1",
|
|
@@ -286,7 +286,7 @@
|
|
|
286
286
|
"vitest-environment-miniflare": "^2.14.4",
|
|
287
287
|
"webpack": "^5.106.2",
|
|
288
288
|
"webpack-http-server": "^0.5.0",
|
|
289
|
-
"msw": "^2.14.
|
|
289
|
+
"msw": "^2.14.4"
|
|
290
290
|
},
|
|
291
291
|
"peerDependencies": {
|
|
292
292
|
"typescript": ">= 4.8.x"
|
|
@@ -313,7 +313,7 @@
|
|
|
313
313
|
"build": "pnpm clean && cross-env NODE_ENV=production tsup && pnpm patch:dts",
|
|
314
314
|
"patch:dts": "node \"./config/scripts/patch-ts.js\"",
|
|
315
315
|
"publint": "publint",
|
|
316
|
-
"test": "pnpm test:unit && pnpm test:node && pnpm test:browser && pnpm test:native",
|
|
316
|
+
"test": "pnpm test:unit && pnpm test:node && pnpm test:browser && pnpm test:native && pnpm test:memory",
|
|
317
317
|
"test:unit": "vitest",
|
|
318
318
|
"test:node": "vitest --config=./test/node/vitest.config.ts",
|
|
319
319
|
"test:native": "vitest --config=./test/native/vitest.config.ts",
|
|
@@ -322,6 +322,7 @@
|
|
|
322
322
|
"test:modules:browser": "playwright test -c ./test/modules/browser/playwright.config.ts",
|
|
323
323
|
"test:e2e": "vitest run --config=./test/e2e/vitest.config.ts",
|
|
324
324
|
"test:ts": "vitest --config=./test/typings/vitest.config.ts",
|
|
325
|
+
"test:memory": "vitest --config=./test/memory/vitest.config.ts",
|
|
325
326
|
"release": "release publish",
|
|
326
327
|
"postinstall": "node -e \"import('./config/scripts/postinstall.js').catch(() => void 0)\"",
|
|
327
328
|
"knip": "knip"
|
|
@@ -330,6 +330,7 @@ Please consider using a custom "serviceWorker.url" option to point to the actual
|
|
|
330
330
|
|
|
331
331
|
async #handleResponse(event: WorkerChannelResponseEvent): Promise<void> {
|
|
332
332
|
const { request, response, isMockedResponse } = event.data
|
|
333
|
+
const frame = this.#frames.get(request.id)
|
|
333
334
|
|
|
334
335
|
/**
|
|
335
336
|
* CORS requests with `mode: "no-cors"` result in "opaque" responses.
|
|
@@ -340,10 +341,10 @@ Please consider using a custom "serviceWorker.url" option to point to the actual
|
|
|
340
341
|
*/
|
|
341
342
|
if (response.type?.includes('opaque')) {
|
|
342
343
|
this.#frames.delete(request.id)
|
|
344
|
+
frame?.events.removeAllListeners()
|
|
343
345
|
return
|
|
344
346
|
}
|
|
345
347
|
|
|
346
|
-
const frame = this.#frames.get(request.id)
|
|
347
348
|
this.#frames.delete(request.id)
|
|
348
349
|
|
|
349
350
|
/**
|
|
@@ -379,17 +380,21 @@ Please consider using a custom "serviceWorker.url" option to point to the actual
|
|
|
379
380
|
},
|
|
380
381
|
)
|
|
381
382
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
383
|
+
try {
|
|
384
|
+
frame.events.emit(
|
|
385
|
+
new ResponseEvent(
|
|
386
|
+
isMockedResponse ? 'response:mocked' : 'response:bypass',
|
|
387
|
+
{
|
|
388
|
+
requestId: frame.data.id,
|
|
389
|
+
request: fetchRequest,
|
|
390
|
+
response: fetchResponse,
|
|
391
|
+
isMockedResponse,
|
|
392
|
+
},
|
|
393
|
+
),
|
|
394
|
+
)
|
|
395
|
+
} finally {
|
|
396
|
+
frame.events.removeAllListeners()
|
|
397
|
+
}
|
|
393
398
|
}
|
|
394
399
|
|
|
395
400
|
#defaultFindWorker: FindWorker = (workerUrl, mockServiceWorkerUrl) => {
|
|
@@ -87,16 +87,26 @@ export class InterceptorSource extends NetworkSource {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
queueMicrotask(() => {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
90
|
+
try {
|
|
91
|
+
httpFrame.events.emit(
|
|
92
|
+
new ResponseEvent(
|
|
93
|
+
isMockedResponse ? 'response:mocked' : 'response:bypass',
|
|
94
|
+
{
|
|
95
|
+
requestId,
|
|
96
|
+
request,
|
|
97
|
+
response,
|
|
98
|
+
},
|
|
99
|
+
),
|
|
100
|
+
)
|
|
101
|
+
} finally {
|
|
102
|
+
/**
|
|
103
|
+
* @note Remove any listeners from this frame.
|
|
104
|
+
* Past this point, it won't emit anything. The removal is crucial
|
|
105
|
+
* to prevent "rettime" from keeping the abort cleanup listeners internally.
|
|
106
|
+
* @see https://github.com/mswjs/msw/issues/2735
|
|
107
|
+
*/
|
|
108
|
+
httpFrame.events.removeAllListeners()
|
|
109
|
+
}
|
|
100
110
|
})
|
|
101
111
|
}
|
|
102
112
|
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type DefaultUnsafeFetchResponse,
|
|
14
14
|
} from '../HttpResponse'
|
|
15
15
|
import type { GraphQLRequestBody } from './GraphQLHandler'
|
|
16
|
+
import { devUtils } from '../utils/internal/devUtils'
|
|
16
17
|
import { getRawSetCookie } from '../utils/HttpResponse/decorators'
|
|
17
18
|
|
|
18
19
|
export type DefaultRequestMultipartBody = Record<
|
|
@@ -90,8 +91,22 @@ export type ResponseResolverInfo<
|
|
|
90
91
|
> = {
|
|
91
92
|
request: StrictRequest<RequestBodyType>
|
|
92
93
|
requestId: string
|
|
94
|
+
/**
|
|
95
|
+
* Schedule a callback to run after this response resolver completes.
|
|
96
|
+
* Handy for cleaning up the side effects introduced in the resolver.
|
|
97
|
+
* @example
|
|
98
|
+
* sse('/', ({ client, finalize }) => {
|
|
99
|
+
* const interval = setInterval(() => client.send({ data: 'ping' }))
|
|
100
|
+
* finalize(() => clearInterval(interval))
|
|
101
|
+
* })
|
|
102
|
+
*/
|
|
103
|
+
finalize: ResponseResolverFinalizeFunction
|
|
93
104
|
} & ResolverExtraInfo
|
|
94
105
|
|
|
106
|
+
type ResponseResolverFinalizeFunction = (
|
|
107
|
+
callback: () => MaybePromise<void>,
|
|
108
|
+
) => void
|
|
109
|
+
|
|
95
110
|
export type ResponseResolver<
|
|
96
111
|
ResolverExtraInfo extends Record<string, unknown> = Record<string, unknown>,
|
|
97
112
|
RequestBodyType extends DefaultBodyType = DefaultBodyType,
|
|
@@ -149,7 +164,9 @@ export abstract class RequestHandler<
|
|
|
149
164
|
MaybeAsyncResponseResolverReturnType<any>
|
|
150
165
|
>
|
|
151
166
|
private resolverIteratorResult?: Response | HttpResponse<any>
|
|
167
|
+
private resolverIteratorCleanups?: Array<() => MaybePromise<void>>
|
|
152
168
|
private options?: HandlerOptions
|
|
169
|
+
private scheduledCleanups: Map<string, Array<() => MaybePromise<void>>>
|
|
153
170
|
|
|
154
171
|
public info: HandlerInfo & RequestHandlerInternalInfo
|
|
155
172
|
|
|
@@ -162,6 +179,7 @@ export abstract class RequestHandler<
|
|
|
162
179
|
constructor(args: RequestHandlerArgs<HandlerInfo, HandlerOptions>) {
|
|
163
180
|
this.resolver = args.resolver
|
|
164
181
|
this.options = args.options
|
|
182
|
+
this.scheduledCleanups = new Map()
|
|
165
183
|
|
|
166
184
|
const callFrame = getCallFrame(new Error())
|
|
167
185
|
|
|
@@ -180,9 +198,12 @@ export abstract class RequestHandler<
|
|
|
180
198
|
* from a clean state.
|
|
181
199
|
*/
|
|
182
200
|
protected reset(): void {
|
|
201
|
+
this.scheduledCleanups.clear()
|
|
202
|
+
|
|
183
203
|
const iterator = this.resolverIterator
|
|
184
204
|
this.resolverIterator = undefined
|
|
185
205
|
this.resolverIteratorResult = undefined
|
|
206
|
+
this.resolverIteratorCleanups = undefined
|
|
186
207
|
|
|
187
208
|
if (typeof iterator?.return === 'function') {
|
|
188
209
|
void Promise.resolve(iterator.return())
|
|
@@ -332,21 +353,39 @@ export abstract class RequestHandler<
|
|
|
332
353
|
parsedResult,
|
|
333
354
|
})
|
|
334
355
|
|
|
356
|
+
const listenerController = new AbortController()
|
|
357
|
+
|
|
358
|
+
args.request.signal.addEventListener(
|
|
359
|
+
'abort',
|
|
360
|
+
() => this.runScheduledCleanups(args.requestId),
|
|
361
|
+
{
|
|
362
|
+
once: true,
|
|
363
|
+
signal: listenerController.signal,
|
|
364
|
+
},
|
|
365
|
+
)
|
|
366
|
+
|
|
335
367
|
const mockedResponsePromise = (
|
|
336
368
|
executeResolver({
|
|
337
369
|
...resolverExtras,
|
|
370
|
+
finalize: (callback) => {
|
|
371
|
+
this.scheduleCleanup(args.requestId, callback)
|
|
372
|
+
},
|
|
338
373
|
requestId: args.requestId,
|
|
339
374
|
request: args.request,
|
|
340
|
-
}) as Promise<Response
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
375
|
+
}) as Promise<Response>
|
|
376
|
+
)
|
|
377
|
+
.catch((errorOrResponse) => {
|
|
378
|
+
// Allow throwing a Response instance in a response resolver.
|
|
379
|
+
if (errorOrResponse instanceof Response) {
|
|
380
|
+
return errorOrResponse
|
|
381
|
+
}
|
|
346
382
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
383
|
+
// Otherwise, throw the error as-is.
|
|
384
|
+
throw errorOrResponse
|
|
385
|
+
})
|
|
386
|
+
.finally(() => {
|
|
387
|
+
listenerController.abort()
|
|
388
|
+
})
|
|
350
389
|
|
|
351
390
|
const mockedResponse = await mockedResponsePromise
|
|
352
391
|
|
|
@@ -371,12 +410,40 @@ export abstract class RequestHandler<
|
|
|
371
410
|
): ResponseResolver<ResolverExtras> {
|
|
372
411
|
return async (info): Promise<ResponseResolverReturnType<any>> => {
|
|
373
412
|
if (!this.resolverIterator) {
|
|
374
|
-
|
|
413
|
+
let result
|
|
414
|
+
|
|
415
|
+
try {
|
|
416
|
+
result = await resolver(info)
|
|
417
|
+
} catch (error) {
|
|
418
|
+
// Ensure cleanup if the resolver throws, regardless of the returned type.
|
|
419
|
+
await this.runScheduledCleanups(info.requestId)
|
|
420
|
+
|
|
421
|
+
// Re-throw the error for the "run()" method to handle (e.g. thrown responses).
|
|
422
|
+
throw error
|
|
423
|
+
}
|
|
375
424
|
|
|
376
425
|
if (!isIterable(result)) {
|
|
426
|
+
// Otherwise, run the cleanup immediately if it's a plain response resolver.
|
|
427
|
+
await this.runScheduledCleanups(info.requestId)
|
|
377
428
|
return result
|
|
378
429
|
}
|
|
379
430
|
|
|
431
|
+
/**
|
|
432
|
+
* @note Carry over any previously registered cleanups onto the iterator cleanups.
|
|
433
|
+
* This is only relevant if "finalize()" is called in a regular resolver that
|
|
434
|
+
* returns an iterator.
|
|
435
|
+
* @example
|
|
436
|
+
* http.get('/', async ({ finalize }) => {
|
|
437
|
+
* finalize(cleanup)
|
|
438
|
+
* return (async function*() {})()
|
|
439
|
+
* })
|
|
440
|
+
*/
|
|
441
|
+
const existingCleanups = this.scheduledCleanups.get(info.requestId)
|
|
442
|
+
if (existingCleanups != null && existingCleanups.length > 0) {
|
|
443
|
+
this.resolverIteratorCleanups = existingCleanups
|
|
444
|
+
this.scheduledCleanups.delete(info.requestId)
|
|
445
|
+
}
|
|
446
|
+
|
|
380
447
|
this.resolverIterator =
|
|
381
448
|
Symbol.iterator in result
|
|
382
449
|
? result[Symbol.iterator]()
|
|
@@ -398,6 +465,8 @@ export abstract class RequestHandler<
|
|
|
398
465
|
// only after it's been completely exhausted.
|
|
399
466
|
this.isUsed = true
|
|
400
467
|
|
|
468
|
+
await this.runScheduledCleanups(info.requestId)
|
|
469
|
+
|
|
401
470
|
// Clone the previously stored response so it can be read
|
|
402
471
|
// when receiving it repeatedly from the "done" generator.
|
|
403
472
|
return this.resolverIteratorResult?.clone()
|
|
@@ -421,6 +490,71 @@ export abstract class RequestHandler<
|
|
|
421
490
|
parsedResult: args.parsedResult,
|
|
422
491
|
}
|
|
423
492
|
}
|
|
493
|
+
|
|
494
|
+
private scheduleCleanup(
|
|
495
|
+
requestId: string,
|
|
496
|
+
callback: () => MaybePromise<void>,
|
|
497
|
+
): void {
|
|
498
|
+
if (this.resolverIterator) {
|
|
499
|
+
;(this.resolverIteratorCleanups ||= []).unshift(callback)
|
|
500
|
+
return
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const cleanups = this.scheduledCleanups.get(requestId) || []
|
|
504
|
+
cleanups.unshift(callback)
|
|
505
|
+
this.scheduledCleanups.set(requestId, cleanups)
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
private async exhaustCleanups(
|
|
509
|
+
cleanups: Array<() => MaybePromise<void>>,
|
|
510
|
+
): Promise<void> {
|
|
511
|
+
const errors: Array<Error> = []
|
|
512
|
+
|
|
513
|
+
for (const cleanup of cleanups) {
|
|
514
|
+
try {
|
|
515
|
+
await cleanup()
|
|
516
|
+
} catch (error) {
|
|
517
|
+
if (error instanceof Error) {
|
|
518
|
+
errors.push(error)
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (errors.length > 0) {
|
|
524
|
+
devUtils.error(
|
|
525
|
+
'Failed to execute cleanup for request handler "%s"',
|
|
526
|
+
this.info.header,
|
|
527
|
+
new AggregateError(
|
|
528
|
+
errors,
|
|
529
|
+
`Failed to execute cleanup for request handler "${this.info.header}"`,
|
|
530
|
+
),
|
|
531
|
+
)
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
private async runScheduledCleanups(requestId: string): Promise<void> {
|
|
536
|
+
if (
|
|
537
|
+
this.resolverIterator &&
|
|
538
|
+
this.resolverIteratorCleanups != null &&
|
|
539
|
+
this.resolverIteratorCleanups.length > 0
|
|
540
|
+
) {
|
|
541
|
+
try {
|
|
542
|
+
await this.exhaustCleanups(this.resolverIteratorCleanups)
|
|
543
|
+
} finally {
|
|
544
|
+
this.resolverIteratorCleanups = undefined
|
|
545
|
+
}
|
|
546
|
+
return
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const cleanups = this.scheduledCleanups.get(requestId)
|
|
550
|
+
|
|
551
|
+
if (!cleanups || cleanups.length == 0) {
|
|
552
|
+
return
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
await this.exhaustCleanups(cleanups)
|
|
556
|
+
this.scheduledCleanups.delete(requestId)
|
|
557
|
+
}
|
|
424
558
|
}
|
|
425
559
|
|
|
426
560
|
/**
|