@graphql-box/worker-client 4.1.6 → 5.0.1
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 +1 -5
- package/dist/cjs/index.cjs +410 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/esm/index.mjs +401 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/types/cjs/constants.d.cts +6 -0
- package/dist/types/cjs/constants.d.cts.map +1 -0
- package/dist/types/cjs/debug/logRequest.d.cts +6 -0
- package/dist/types/cjs/debug/logRequest.d.cts.map +1 -0
- package/dist/types/cjs/debug/logSubscription.d.cts +6 -0
- package/dist/types/cjs/debug/logSubscription.d.cts.map +1 -0
- package/dist/types/cjs/helpers/isGraphqlBoxMessageRequestPayload.d.cts +4 -0
- package/dist/types/cjs/helpers/isGraphqlBoxMessageRequestPayload.d.cts.map +1 -0
- package/dist/types/cjs/helpers/operationNameRegex.d.cts +2 -0
- package/dist/types/cjs/helpers/operationNameRegex.d.cts.map +1 -0
- package/dist/types/cjs/index.d.cts +5 -0
- package/dist/types/cjs/index.d.cts.map +1 -0
- package/dist/types/cjs/main.d.cts +24 -0
- package/dist/types/cjs/main.d.cts.map +1 -0
- package/dist/types/cjs/registerWorker.d.cts +5 -0
- package/dist/types/cjs/registerWorker.d.cts.map +1 -0
- package/dist/types/cjs/types.d.cts +49 -0
- package/dist/types/cjs/types.d.cts.map +1 -0
- package/dist/types/esm/constants.d.ts +6 -0
- package/dist/types/esm/constants.d.ts.map +1 -0
- package/dist/types/esm/debug/logRequest.d.ts +6 -0
- package/dist/types/esm/debug/logRequest.d.ts.map +1 -0
- package/dist/types/esm/debug/logSubscription.d.ts +6 -0
- package/dist/types/esm/debug/logSubscription.d.ts.map +1 -0
- package/dist/types/esm/helpers/isGraphqlBoxMessageRequestPayload.d.ts +4 -0
- package/dist/types/esm/helpers/isGraphqlBoxMessageRequestPayload.d.ts.map +1 -0
- package/dist/types/esm/helpers/operationNameRegex.d.ts +2 -0
- package/dist/types/esm/helpers/operationNameRegex.d.ts.map +1 -0
- package/dist/types/esm/index.d.ts +5 -0
- package/dist/types/esm/index.d.ts.map +1 -0
- package/dist/types/esm/main.d.ts +24 -0
- package/dist/types/esm/main.d.ts.map +1 -0
- package/dist/types/esm/registerWorker.d.ts +5 -0
- package/dist/types/esm/registerWorker.d.ts.map +1 -0
- package/{lib/types/defs/index.d.ts → dist/types/esm/types.d.ts} +11 -11
- package/dist/types/esm/types.d.ts.map +1 -0
- package/dist/types/tsconfig.build.tsbuildinfo +1 -0
- package/package.json +57 -45
- package/src/constants.ts +5 -0
- package/src/debug/{log-request/index.ts → logRequest.ts} +30 -20
- package/src/debug/logSubscription.ts +50 -0
- package/src/helpers/isGraphqlBoxMessageRequestPayload.ts +7 -0
- package/src/helpers/operationNameRegex.test.ts +26 -14
- package/src/helpers/operationNameRegex.ts +2 -2
- package/src/index.ts +4 -3
- package/src/{main/index.ts → main.ts} +135 -108
- package/src/registerWorker.ts +107 -0
- package/src/{defs/index.ts → types.ts} +14 -9
- package/tsconfig.build.json +10 -0
- package/tsconfig.json +17 -0
- package/lib/browser/index.js +0 -2
- package/lib/browser/index.js.map +0 -1
- package/lib/browser/production.analysis.txt +0 -81
- package/lib/main/consts/index.js +0 -17
- package/lib/main/consts/index.js.map +0 -1
- package/lib/main/debug/log-request/index.js +0 -77
- package/lib/main/debug/log-request/index.js.map +0 -1
- package/lib/main/debug/log-subscription/index.js +0 -55
- package/lib/main/debug/log-subscription/index.js.map +0 -1
- package/lib/main/defs/index.js +0 -2
- package/lib/main/defs/index.js.map +0 -1
- package/lib/main/helpers/operationNameRegex.js +0 -14
- package/lib/main/helpers/operationNameRegex.js.map +0 -1
- package/lib/main/index.js +0 -41
- package/lib/main/index.js.map +0 -1
- package/lib/main/main/index.js +0 -249
- package/lib/main/main/index.js.map +0 -1
- package/lib/main/register-worker/index.js +0 -143
- package/lib/main/register-worker/index.js.map +0 -1
- package/lib/module/consts/index.mjs +0 -6
- package/lib/module/consts/index.mjs.map +0 -1
- package/lib/module/debug/log-request/index.mjs +0 -64
- package/lib/module/debug/log-request/index.mjs.map +0 -1
- package/lib/module/debug/log-subscription/index.mjs +0 -43
- package/lib/module/debug/log-subscription/index.mjs.map +0 -1
- package/lib/module/defs/index.mjs +0 -2
- package/lib/module/defs/index.mjs.map +0 -1
- package/lib/module/helpers/operationNameRegex.mjs +0 -5
- package/lib/module/helpers/operationNameRegex.mjs.map +0 -1
- package/lib/module/index.mjs +0 -4
- package/lib/module/index.mjs.map +0 -1
- package/lib/module/main/index.mjs +0 -236
- package/lib/module/main/index.mjs.map +0 -1
- package/lib/module/register-worker/index.mjs +0 -126
- package/lib/module/register-worker/index.mjs.map +0 -1
- package/lib/types/consts/index.d.ts +0 -6
- package/lib/types/consts/index.d.ts.map +0 -1
- package/lib/types/debug/log-request/index.d.ts +0 -2
- package/lib/types/debug/log-request/index.d.ts.map +0 -1
- package/lib/types/debug/log-subscription/index.d.ts +0 -2
- package/lib/types/debug/log-subscription/index.d.ts.map +0 -1
- package/lib/types/defs/index.d.ts.map +0 -1
- package/lib/types/helpers/operationNameRegex.d.ts +0 -3
- package/lib/types/helpers/operationNameRegex.d.ts.map +0 -1
- package/lib/types/helpers/operationNameRegex.test.d.ts +0 -2
- package/lib/types/helpers/operationNameRegex.test.d.ts.map +0 -1
- package/lib/types/index.d.ts +0 -4
- package/lib/types/index.d.ts.map +0 -1
- package/lib/types/main/index.d.ts +0 -32
- package/lib/types/main/index.d.ts.map +0 -1
- package/lib/types/register-worker/index.d.ts +0 -5
- package/lib/types/register-worker/index.d.ts.map +0 -1
- package/src/consts/index.ts +0 -7
- package/src/debug/log-subscription/index.ts +0 -41
- package/src/register-worker/index.ts +0 -95
package/package.json
CHANGED
|
@@ -1,62 +1,74 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graphql-box/worker-client",
|
|
3
|
-
"version": "4.1.6",
|
|
4
3
|
"description": "The GraphQL Box web worker client module.",
|
|
5
|
-
"
|
|
4
|
+
"version": "5.0.1",
|
|
5
|
+
"author": "Dylan Aubrey",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://github.com/badbatch/graphql-box",
|
|
8
|
-
"bugs": {
|
|
9
|
-
"url": "https://github.com/badbatch/graphql-box/issues"
|
|
10
|
-
},
|
|
11
8
|
"repository": {
|
|
9
|
+
"directory": "packages/worker-client",
|
|
12
10
|
"type": "git",
|
|
13
|
-
"url": "https://github.com/badbatch/graphql-box"
|
|
14
|
-
|
|
11
|
+
"url": "https://github.com/badbatch/graphql-box"
|
|
12
|
+
},
|
|
13
|
+
"bugs": "https://github.com/badbatch/graphql-box/issues",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/cjs/index.cjs",
|
|
16
|
+
"module": "./dist/esm/index.mjs",
|
|
17
|
+
"types": "./dist/types/cjs/index.d.cts",
|
|
18
|
+
"exports": {
|
|
19
|
+
"types": {
|
|
20
|
+
"import": "./dist/types/esm/index.d.ts",
|
|
21
|
+
"require": "./dist/types/cjs/index.d.cts"
|
|
22
|
+
},
|
|
23
|
+
"import": "./dist/esm/index.mjs",
|
|
24
|
+
"require": "./dist/cjs/index.cjs"
|
|
15
25
|
},
|
|
16
26
|
"publishConfig": {
|
|
17
27
|
"access": "public"
|
|
18
28
|
},
|
|
19
|
-
"main": "lib/main/index.js",
|
|
20
|
-
"module": "lib/module/index.mjs",
|
|
21
|
-
"browser": "lib/browser/index.js",
|
|
22
|
-
"types": "lib/types/index.d.ts",
|
|
23
|
-
"keywords": [
|
|
24
|
-
"graphql-box",
|
|
25
|
-
"graphql",
|
|
26
|
-
"client",
|
|
27
|
-
"server",
|
|
28
|
-
"isomorphic"
|
|
29
|
-
],
|
|
30
|
-
"scripts": {
|
|
31
|
-
"compile": "concurrently npm:compile:*",
|
|
32
|
-
"compile-watch": "concurrently npm:compile-watch:*",
|
|
33
|
-
"compile-watch:main": "yarn run compile:main --watch",
|
|
34
|
-
"compile-watch:types": "yarn run compile:types --watch",
|
|
35
|
-
"compile:browser": "cross-env BABEL_ENV=browser rollup -c ../../rollup.config.js",
|
|
36
|
-
"compile:main": "cross-env BABEL_ENV=main babel ./src --out-dir ./lib/main --extensions \".ts\" --source-maps --config-file ../../babel.config.js",
|
|
37
|
-
"compile:module": "cross-env BABEL_ENV=module babel ./src --out-dir ./lib/module --extensions \".ts\" --out-file-extension \".mjs\" --source-maps --config-file ../../babel.config.js",
|
|
38
|
-
"compile:types": "tsc --build",
|
|
39
|
-
"docs": "typedoc --includes ./src/main --out ./docs --options ../../typedoc.js",
|
|
40
|
-
"watch": "cross-env BABEL_ENV=browser rollup -c ../../rollup.config.js -w"
|
|
41
|
-
},
|
|
42
|
-
"peerDependencies": {
|
|
43
|
-
"@babel/runtime": "< 8",
|
|
44
|
-
"@graphql-box/core": "< 5",
|
|
45
|
-
"@graphql-box/helpers": "< 5",
|
|
46
|
-
"core-js": "< 4",
|
|
47
|
-
"lodash": "< 5"
|
|
48
|
-
},
|
|
49
29
|
"dependencies": {
|
|
50
|
-
"@
|
|
51
|
-
"
|
|
52
|
-
"@cachemap/core-worker": "^4.0.3",
|
|
53
|
-
"eventemitter3": "^4.0.0",
|
|
30
|
+
"@types/uuid": "^9.0.3",
|
|
31
|
+
"eventemitter3": "^5.0.1",
|
|
54
32
|
"iterall": "^1.3.0",
|
|
55
|
-
"uuid": "^
|
|
33
|
+
"uuid": "^9.0.1",
|
|
34
|
+
"@graphql-box/core": "5.0.0",
|
|
35
|
+
"@graphql-box/helpers": "5.0.0"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@babel/runtime": "<8",
|
|
39
|
+
"@cachemap/core": "<6",
|
|
40
|
+
"@cachemap/core-worker": "<6",
|
|
41
|
+
"core-js": "<4",
|
|
42
|
+
"graphql": "<17",
|
|
43
|
+
"lodash-es": "<5",
|
|
44
|
+
"@graphql-box/client": "5.0.1"
|
|
56
45
|
},
|
|
57
46
|
"devDependencies": {
|
|
58
|
-
"@
|
|
59
|
-
"@
|
|
60
|
-
"@
|
|
47
|
+
"@babel/runtime": "^7.20.13",
|
|
48
|
+
"@cachemap/core": "^5.0.6",
|
|
49
|
+
"@cachemap/core-worker": "^5.0.6",
|
|
50
|
+
"@types/lodash-es": "^4.14.191",
|
|
51
|
+
"core-js": "^3.27.2",
|
|
52
|
+
"cts-types": "^0.0.5",
|
|
53
|
+
"del-cli": "^5.1.0",
|
|
54
|
+
"graphql": "^16.8.1",
|
|
55
|
+
"lodash-es": "^4.17.21",
|
|
56
|
+
"@graphql-box/client": "5.0.1"
|
|
57
|
+
},
|
|
58
|
+
"keywords": [
|
|
59
|
+
"client",
|
|
60
|
+
"graphql",
|
|
61
|
+
"graphql-box",
|
|
62
|
+
"isomorphic",
|
|
63
|
+
"server"
|
|
64
|
+
],
|
|
65
|
+
"scripts": {
|
|
66
|
+
"build": "pnpm run clean:dist && pnpm run compile",
|
|
67
|
+
"clean:deps": "del-cli ./node_modules",
|
|
68
|
+
"clean:dist": "del-cli ./dist",
|
|
69
|
+
"compile": "pnpm run /^compile:.*/",
|
|
70
|
+
"compile:cjs": "MODULE_SYSTEM=cjs rollup -c ../../rollup.config.cjs",
|
|
71
|
+
"compile:esm": "rollup -c ../../rollup.config.cjs",
|
|
72
|
+
"compile:types": "tsc --project ./tsconfig.build.json && cts-types build dist/types/esm dist/types/cjs"
|
|
61
73
|
}
|
|
62
74
|
}
|
package/src/constants.ts
ADDED
|
@@ -1,20 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import {
|
|
2
|
+
type PartialRequestResult,
|
|
3
|
+
REQUEST_EXECUTED,
|
|
4
|
+
REQUEST_RESOLVED,
|
|
5
|
+
type RequestContext,
|
|
6
|
+
type RequestOptions,
|
|
7
|
+
} from '@graphql-box/core';
|
|
8
|
+
import { isAsyncIterable } from 'iterall';
|
|
9
|
+
import { operationNameRegex } from '../helpers/operationNameRegex.ts';
|
|
10
|
+
import { type WorkerClient } from '../main.ts';
|
|
11
|
+
|
|
12
|
+
type Descriptor = (
|
|
13
|
+
request: string,
|
|
14
|
+
options: RequestOptions,
|
|
15
|
+
context: RequestContext
|
|
16
|
+
) => Promise<PartialRequestResult | AsyncIterableIterator<PartialRequestResult | undefined>>;
|
|
17
|
+
|
|
18
|
+
export const logRequest = () => {
|
|
19
|
+
return (_target: WorkerClient, _propertyName: string, descriptor: TypedPropertyDescriptor<Descriptor>): void => {
|
|
11
20
|
const method = descriptor.value;
|
|
12
|
-
if (!method) return;
|
|
13
21
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
if (!method) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
descriptor.value = async function descriptorValue(...args: Parameters<Descriptor>): ReturnType<Descriptor> {
|
|
27
|
+
return new Promise(resolve => {
|
|
28
|
+
void (async () => {
|
|
29
|
+
const { debugManager, ...otherContext } = args[2];
|
|
18
30
|
|
|
19
31
|
if (!debugManager) {
|
|
20
32
|
resolve(await method.apply(this, args));
|
|
@@ -47,10 +59,8 @@ export default function logRequest() {
|
|
|
47
59
|
result,
|
|
48
60
|
stats: { duration, endTime, startTime },
|
|
49
61
|
});
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
return Promise.reject(error);
|
|
53
|
-
}
|
|
62
|
+
})();
|
|
63
|
+
});
|
|
54
64
|
};
|
|
55
65
|
};
|
|
56
|
-
}
|
|
66
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type PartialRequestResult,
|
|
3
|
+
type RequestContext,
|
|
4
|
+
type RequestOptions,
|
|
5
|
+
SUBSCRIPTION_EXECUTED,
|
|
6
|
+
} from '@graphql-box/core';
|
|
7
|
+
import { operationNameRegex } from '../helpers/operationNameRegex.ts';
|
|
8
|
+
import { type WorkerClient } from '../main.ts';
|
|
9
|
+
|
|
10
|
+
type Descriptor = (
|
|
11
|
+
request: string,
|
|
12
|
+
options: RequestOptions,
|
|
13
|
+
context: RequestContext
|
|
14
|
+
) => Promise<PartialRequestResult | AsyncIterableIterator<PartialRequestResult | undefined>>;
|
|
15
|
+
|
|
16
|
+
export const logSubscription = () => {
|
|
17
|
+
return (_target: WorkerClient, _propertyName: string, descriptor: TypedPropertyDescriptor<Descriptor>): void => {
|
|
18
|
+
const method = descriptor.value;
|
|
19
|
+
|
|
20
|
+
if (!method) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
descriptor.value = async function descriptorValue(...args: Parameters<Descriptor>): ReturnType<Descriptor> {
|
|
25
|
+
return new Promise(resolve => {
|
|
26
|
+
void (async () => {
|
|
27
|
+
const { debugManager, ...otherContext } = args[2];
|
|
28
|
+
|
|
29
|
+
if (!debugManager) {
|
|
30
|
+
resolve(await method.apply(this, args));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const derivedOperationName = operationNameRegex(args[0]);
|
|
35
|
+
const startTime = debugManager.now();
|
|
36
|
+
|
|
37
|
+
debugManager.log(SUBSCRIPTION_EXECUTED, {
|
|
38
|
+
context: { ...otherContext, operationName: derivedOperationName },
|
|
39
|
+
options: args[1],
|
|
40
|
+
request: args[0],
|
|
41
|
+
stats: { startTime },
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const result = await method.apply(this, args);
|
|
45
|
+
resolve(result);
|
|
46
|
+
})();
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type PostMessage as CachemapMessageRequestPayload } from '@cachemap/core-worker';
|
|
2
|
+
import { GRAPHQL_BOX } from '../constants.ts';
|
|
3
|
+
import type { MessageRequestPayload } from '../types.ts';
|
|
4
|
+
|
|
5
|
+
export const isGraphqlBoxMessageRequestPayload = (
|
|
6
|
+
payload: MessageRequestPayload | CachemapMessageRequestPayload
|
|
7
|
+
): payload is MessageRequestPayload => payload.type === GRAPHQL_BOX;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import operationNameRegex from
|
|
1
|
+
import { operationNameRegex } from './operationNameRegex.ts';
|
|
2
2
|
|
|
3
|
-
describe(
|
|
4
|
-
|
|
3
|
+
describe('operationNameRegex', () => {
|
|
4
|
+
describe('when request does not have operation type', () => {
|
|
5
5
|
const request = `
|
|
6
6
|
{
|
|
7
7
|
human(id: "1000") {
|
|
@@ -11,10 +11,12 @@ describe("operationNameRegex", () => {
|
|
|
11
11
|
}
|
|
12
12
|
`;
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
it('should return the correct value', () => {
|
|
15
|
+
expect(operationNameRegex(request)).toBe('');
|
|
16
|
+
});
|
|
15
17
|
});
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
describe('when request does not have operation name', () => {
|
|
18
20
|
const request = `
|
|
19
21
|
query {
|
|
20
22
|
human(id: "1000") {
|
|
@@ -24,10 +26,12 @@ describe("operationNameRegex", () => {
|
|
|
24
26
|
}
|
|
25
27
|
`;
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
it('should return the correct value', () => {
|
|
30
|
+
expect(operationNameRegex(request)).toBe('');
|
|
31
|
+
});
|
|
28
32
|
});
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
describe('when request has operation name but no arguments', () => {
|
|
31
35
|
const request = `
|
|
32
36
|
query HeroNameAndFriends {
|
|
33
37
|
hero(episode: $episode) {
|
|
@@ -39,10 +43,12 @@ describe("operationNameRegex", () => {
|
|
|
39
43
|
}
|
|
40
44
|
`;
|
|
41
45
|
|
|
42
|
-
|
|
46
|
+
it('should return the correct value', () => {
|
|
47
|
+
expect(operationNameRegex(request)).toBe('HeroNameAndFriends');
|
|
48
|
+
});
|
|
43
49
|
});
|
|
44
50
|
|
|
45
|
-
|
|
51
|
+
describe('when request has operation name and arguments', () => {
|
|
46
52
|
const request = `
|
|
47
53
|
query HeroNameAndFriends($episode: Episode = JEDI) {
|
|
48
54
|
hero(episode: $episode) {
|
|
@@ -54,10 +60,12 @@ describe("operationNameRegex", () => {
|
|
|
54
60
|
}
|
|
55
61
|
`;
|
|
56
62
|
|
|
57
|
-
|
|
63
|
+
it('should return the correct value', () => {
|
|
64
|
+
expect(operationNameRegex(request)).toBe('HeroNameAndFriends');
|
|
65
|
+
});
|
|
58
66
|
});
|
|
59
67
|
|
|
60
|
-
|
|
68
|
+
describe('when request is mutation', () => {
|
|
61
69
|
const request = `
|
|
62
70
|
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
|
|
63
71
|
createReview(episode: $ep, review: $review) {
|
|
@@ -67,10 +75,12 @@ describe("operationNameRegex", () => {
|
|
|
67
75
|
}
|
|
68
76
|
`;
|
|
69
77
|
|
|
70
|
-
|
|
78
|
+
it('should return the correct value', () => {
|
|
79
|
+
expect(operationNameRegex(request)).toBe('CreateReviewForEpisode');
|
|
80
|
+
});
|
|
71
81
|
});
|
|
72
82
|
|
|
73
|
-
|
|
83
|
+
describe('when request is subscription', () => {
|
|
74
84
|
const request = `
|
|
75
85
|
subscription OnCommentAdded($postID: ID!) {
|
|
76
86
|
commentAdded(postID: $postID) {
|
|
@@ -80,6 +90,8 @@ describe("operationNameRegex", () => {
|
|
|
80
90
|
}
|
|
81
91
|
`;
|
|
82
92
|
|
|
83
|
-
|
|
93
|
+
it('should return the correct value', () => {
|
|
94
|
+
expect(operationNameRegex(request)).toBe('OnCommentAdded');
|
|
95
|
+
});
|
|
84
96
|
});
|
|
85
97
|
});
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export
|
|
3
|
-
export
|
|
1
|
+
export * from './constants.ts';
|
|
2
|
+
export * from './main.ts';
|
|
3
|
+
export * from './registerWorker.ts';
|
|
4
|
+
export * from './types.ts';
|