@sockethub/schemas 3.0.0-alpha.11 → 3.0.0-alpha.13

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.
Files changed (60) hide show
  1. package/dist/activity-stream-helper.d.ts +5 -0
  2. package/dist/activity-stream-helper.js +36 -0
  3. package/dist/activity-stream-helper.js.map +10 -0
  4. package/dist/context.d.ts +56 -0
  5. package/dist/context.js +80 -0
  6. package/dist/context.js.map +10 -0
  7. package/dist/contexts/platform/dummy/v1.jsonld +8 -0
  8. package/dist/contexts/platform/error/v1.jsonld +8 -0
  9. package/dist/contexts/platform/feeds/v1.jsonld +8 -0
  10. package/dist/contexts/platform/irc/v1.jsonld +8 -0
  11. package/dist/contexts/platform/metadata/v1.jsonld +8 -0
  12. package/dist/contexts/platform/sockethub%3Ainternal/v1.jsonld +8 -0
  13. package/dist/contexts/platform/xmpp/v1.jsonld +8 -0
  14. package/dist/contexts/v1.jsonld +7 -0
  15. package/dist/helpers/error-parser.d.ts +13 -0
  16. package/dist/helpers/objects.d.ts +122 -0
  17. package/dist/index.d.ts +9 -0
  18. package/dist/index.js +459 -216
  19. package/dist/index.js.map +12 -11
  20. package/dist/maps/context-map.json +35 -0
  21. package/dist/object-types.d.ts +2 -0
  22. package/dist/object-types.js +296 -0
  23. package/dist/object-types.js.map +12 -0
  24. package/dist/schemas/activity-stream.d.ts +161 -0
  25. package/dist/schemas/platform/dummy/v1/credentials.schema.json +1 -0
  26. package/dist/schemas/platform/dummy/v1/messages.schema.json +22 -0
  27. package/dist/schemas/platform/error/v1/credentials.schema.json +1 -0
  28. package/dist/schemas/platform/error/v1/messages.schema.json +1 -0
  29. package/dist/schemas/platform/feeds/v1/credentials.schema.json +1 -0
  30. package/dist/schemas/platform/feeds/v1/messages.schema.json +27 -0
  31. package/dist/schemas/platform/irc/v1/credentials.schema.json +118 -0
  32. package/dist/schemas/platform/irc/v1/messages.schema.json +103 -0
  33. package/dist/schemas/platform/metadata/v1/credentials.schema.json +1 -0
  34. package/dist/schemas/platform/metadata/v1/messages.schema.json +14 -0
  35. package/dist/schemas/platform/sockethub%3Ainternal/v1/credentials.schema.json +1 -0
  36. package/dist/schemas/platform/sockethub%3Ainternal/v1/messages.schema.json +1 -0
  37. package/dist/schemas/platform/xmpp/v1/credentials.schema.json +43 -0
  38. package/dist/schemas/platform/xmpp/v1/messages.schema.json +129 -0
  39. package/dist/schemas/platform.d.ts +62 -0
  40. package/dist/schemas/sockethub-config.d.ts +261 -0
  41. package/{src/types.ts → dist/types.d.ts} +25 -46
  42. package/dist/validator.d.ts +26 -0
  43. package/package.json +17 -7
  44. package/src/helpers/error-parser.ts +0 -113
  45. package/src/helpers/objects.ts +0 -227
  46. package/src/index.test.data.credentials.ts +0 -71
  47. package/src/index.test.data.objects.ts +0 -241
  48. package/src/index.test.data.platform.ts +0 -45
  49. package/src/index.test.data.streams.ts +0 -498
  50. package/src/index.test.ts +0 -99
  51. package/src/index.ts +0 -29
  52. package/src/schemas/activity-object.ts +0 -19
  53. package/src/schemas/activity-stream.ts +0 -54
  54. package/src/schemas/json/activity-object.json +0 -356
  55. package/src/schemas/json/activity-stream.json +0 -469
  56. package/src/schemas/json/platform.json +0 -28
  57. package/src/schemas/json/sockethub-config.json +0 -222
  58. package/src/schemas/platform.ts +0 -47
  59. package/src/schemas/sockethub-config.ts +0 -206
  60. package/src/validator.ts +0 -87
@@ -0,0 +1,261 @@
1
+ export declare const SockethubConfigSchema: {
2
+ $id: string;
3
+ description: string;
4
+ type: string;
5
+ required: string[];
6
+ additionalProperties: boolean;
7
+ properties: {
8
+ $schema: {
9
+ type: string;
10
+ };
11
+ examples: {
12
+ type: string;
13
+ default: boolean;
14
+ };
15
+ info: {
16
+ type: string;
17
+ default: boolean;
18
+ };
19
+ logging: {
20
+ type: string;
21
+ additionalProperties: boolean;
22
+ properties: {
23
+ level: {
24
+ type: string;
25
+ enum: string[];
26
+ default: string;
27
+ };
28
+ fileLevel: {
29
+ type: string;
30
+ enum: string[];
31
+ default: string;
32
+ };
33
+ file: {
34
+ type: string;
35
+ default: string;
36
+ };
37
+ };
38
+ };
39
+ platforms: {
40
+ type: string;
41
+ items: {
42
+ type: string;
43
+ };
44
+ default: string[];
45
+ };
46
+ packageConfig: {
47
+ type: string;
48
+ properties: {
49
+ "@sockethub/platform-dummy": {
50
+ type: string;
51
+ additionalProperties: boolean;
52
+ properties: {
53
+ greeting: {
54
+ type: string;
55
+ default: string;
56
+ };
57
+ };
58
+ };
59
+ "@sockethub/platform-feeds": {
60
+ type: string;
61
+ additionalProperties: boolean;
62
+ properties: {
63
+ connectTimeoutMs: {
64
+ type: string;
65
+ };
66
+ allowPrivateAddresses: {
67
+ type: string;
68
+ };
69
+ concurrency: {
70
+ type: string;
71
+ minimum: number;
72
+ };
73
+ };
74
+ };
75
+ "@sockethub/platform-irc": {
76
+ type: string;
77
+ additionalProperties: boolean;
78
+ properties: {
79
+ connectTimeoutMs: {
80
+ type: string;
81
+ };
82
+ };
83
+ };
84
+ "@sockethub/platform-xmpp": {
85
+ type: string;
86
+ additionalProperties: boolean;
87
+ properties: {
88
+ connectTimeoutMs: {
89
+ type: string;
90
+ };
91
+ };
92
+ };
93
+ "@sockethub/platform-metadata": {
94
+ type: string;
95
+ additionalProperties: boolean;
96
+ properties: {
97
+ allowPrivateAddresses: {
98
+ type: string;
99
+ };
100
+ concurrency: {
101
+ type: string;
102
+ minimum: number;
103
+ };
104
+ };
105
+ };
106
+ };
107
+ };
108
+ credentialCheck: {
109
+ type: string;
110
+ additionalProperties: boolean;
111
+ properties: {
112
+ reconnectIpSource: {
113
+ type: string;
114
+ enum: string[];
115
+ default: string;
116
+ };
117
+ proxyHeader: {
118
+ type: string;
119
+ default: string;
120
+ };
121
+ };
122
+ };
123
+ public: {
124
+ type: string;
125
+ additionalProperties: boolean;
126
+ properties: {
127
+ protocol: {
128
+ type: string;
129
+ default: string;
130
+ };
131
+ host: {
132
+ type: string;
133
+ default: string;
134
+ };
135
+ port: {
136
+ type: string;
137
+ default: number;
138
+ };
139
+ path: {
140
+ type: string;
141
+ default: string;
142
+ };
143
+ };
144
+ };
145
+ rateLimiter: {
146
+ type: string;
147
+ additionalProperties: boolean;
148
+ properties: {
149
+ windowMs: {
150
+ type: string;
151
+ default: number;
152
+ };
153
+ maxRequests: {
154
+ type: string;
155
+ default: number;
156
+ };
157
+ blockDurationMs: {
158
+ type: string;
159
+ default: number;
160
+ };
161
+ maxConnectionsPerIp: {
162
+ type: string;
163
+ minimum: number;
164
+ default: number;
165
+ };
166
+ };
167
+ };
168
+ limits: {
169
+ type: string;
170
+ additionalProperties: boolean;
171
+ properties: {
172
+ maxPlatformInstances: {
173
+ type: string;
174
+ minimum: number;
175
+ default: number;
176
+ };
177
+ };
178
+ };
179
+ redis: {
180
+ type: string;
181
+ additionalProperties: boolean;
182
+ properties: {
183
+ url: {
184
+ type: string;
185
+ default: string;
186
+ };
187
+ connectTimeout: {
188
+ type: string;
189
+ default: number;
190
+ description: string;
191
+ };
192
+ disconnectTimeout: {
193
+ type: string;
194
+ default: number;
195
+ description: string;
196
+ };
197
+ maxRetriesPerRequest: {
198
+ type: string[];
199
+ default: number | null;
200
+ description: string;
201
+ };
202
+ };
203
+ };
204
+ sentry: {
205
+ type: string;
206
+ additionalProperties: boolean;
207
+ properties: {
208
+ dsn: {
209
+ type: string;
210
+ default: string;
211
+ };
212
+ traceSampleRate: {
213
+ type: string;
214
+ default: number;
215
+ };
216
+ };
217
+ };
218
+ sockethub: {
219
+ type: string;
220
+ additionalProperties: boolean;
221
+ properties: {
222
+ port: {
223
+ type: string;
224
+ default: number;
225
+ };
226
+ host: {
227
+ type: string;
228
+ default: string;
229
+ };
230
+ path: {
231
+ type: string;
232
+ default: string;
233
+ };
234
+ cors: {
235
+ type: string;
236
+ additionalProperties: boolean;
237
+ properties: {
238
+ origin: {
239
+ type: string;
240
+ default: string;
241
+ };
242
+ };
243
+ };
244
+ };
245
+ };
246
+ platformHeartbeat: {
247
+ type: string;
248
+ additionalProperties: boolean;
249
+ properties: {
250
+ intervalMs: {
251
+ type: string;
252
+ default: number;
253
+ };
254
+ timeoutMs: {
255
+ type: string;
256
+ default: number;
257
+ };
258
+ };
259
+ };
260
+ };
261
+ };
@@ -1,78 +1,58 @@
1
1
  export interface ASCollection {
2
- context: string;
2
+ "@context": JsonLdContext;
3
3
  id?: string;
4
4
  summary: string;
5
5
  type: "collection";
6
6
  totalItems: number;
7
7
  items: Array<ActivityStream>;
8
8
  }
9
-
9
+ export type JsonLdContext = Array<string>;
10
10
  export interface ActivityStream {
11
+ "@context": JsonLdContext;
11
12
  id?: string;
12
13
  type: string;
13
- context: string;
14
14
  actor: ActivityActor;
15
15
  object?: ActivityObject;
16
16
  target?: ActivityActor;
17
17
  error?: string;
18
18
  }
19
-
20
19
  export interface InternalActivityStream extends ActivityStream {
21
20
  sessionSecret?: string;
22
21
  }
23
-
24
22
  export interface BaseActivityObject {
25
23
  id?: string;
26
24
  type: string;
27
25
  }
28
-
29
26
  export interface ActivityActor extends BaseActivityObject {
30
27
  id: string;
31
28
  type: string;
32
29
  name?: string;
33
30
  [key: string]: unknown;
34
31
  }
35
-
36
32
  export interface ActivityObject extends BaseActivityObject {
37
33
  content?: string;
38
34
  [key: string]: unknown;
39
35
  }
40
-
41
36
  type ErrorMsg = string | Error;
42
-
43
- export type PlatformCallback = (
44
- err?: ErrorMsg,
45
- data?: ActivityStream | ASCollection,
46
- ) => void;
47
-
37
+ export type PlatformCallback = (err?: ErrorMsg, data?: ActivityStream | ASCollection) => void;
48
38
  export type CallbackInterface = (err?: ErrorMsg, data?: unknown) => void;
49
-
50
39
  export type MiddlewareCallback = (data: ActivityStream | Error) => void;
51
-
52
40
  export type CompletedJobHandler = (data: ActivityStream | undefined) => void;
53
-
54
- export type PlatformSendToClient = (
55
- msg: ActivityStream,
56
- special?: string,
57
- ) => void;
58
-
41
+ export type PlatformSendToClient = (msg: ActivityStream, special?: string) => void;
59
42
  export type PlatformUpdateActor = (credentials: object) => Promise<void>;
60
-
61
43
  export interface Logger {
62
44
  error(message: string, meta?: object): void;
63
45
  warn(message: string, meta?: object): void;
64
46
  info(message: string, meta?: object): void;
65
47
  debug(message: string, meta?: object): void;
66
48
  }
67
-
68
49
  export interface PlatformSession {
69
50
  log: Logger;
70
51
  sendToClient: PlatformSendToClient;
71
52
  updateActor: PlatformUpdateActor;
72
53
  }
73
-
74
54
  export interface CredentialsObject {
75
- context: string;
55
+ "@context": JsonLdContext;
76
56
  type: "credentials";
77
57
  actor: ActivityActor;
78
58
  object: {
@@ -81,7 +61,6 @@ export interface CredentialsObject {
81
61
  };
82
62
  target?: ActivityActor;
83
63
  }
84
-
85
64
  /**
86
65
  * Platform configuration discriminated union.
87
66
  *
@@ -100,17 +79,23 @@ export interface CredentialsObject {
100
79
  * - Example: IRC, XMPP
101
80
  */
102
81
  export type PlatformConfig = StatelessPlatformConfig | PersistentPlatformConfig;
103
-
104
82
  interface BasePlatformConfig {
105
83
  connectTimeoutMs?: number;
84
+ allowPrivateAddresses?: boolean;
106
85
  }
107
-
108
86
  /** Configuration for stateless platforms that start/stop per-request */
109
87
  export interface StatelessPlatformConfig extends BasePlatformConfig {
110
88
  persist: false;
111
89
  requireCredentials?: string[];
90
+ /**
91
+ * Number of jobs the platform's worker processes in parallel. Stateless
92
+ * platforms are shared by every session on the server, so serial
93
+ * processing lets one slow job (e.g. a hanging feed fetch) stall all
94
+ * other clients. Defaults to 10; override per-platform via
95
+ * `packageConfig`.
96
+ */
97
+ concurrency?: number;
112
98
  }
113
-
114
99
  /**
115
100
  * Configuration for persistent platforms with long-running connections.
116
101
  * These always require credentials because persistent state is actor-specific.
@@ -120,10 +105,13 @@ export interface PersistentPlatformConfig extends BasePlatformConfig {
120
105
  /** Message types requiring credentials (must be non-empty for persistent platforms) */
121
106
  requireCredentials: string[];
122
107
  }
123
-
124
108
  export interface PlatformSchemaStruct {
125
109
  name: string;
126
110
  version: string;
111
+ contextUrl: string;
112
+ contextVersion: string;
113
+ schemaVersion: string;
114
+ messageConstraints?: object;
127
115
  credentials?: object;
128
116
  messages?: {
129
117
  required?: string[];
@@ -133,12 +121,13 @@ export interface PlatformSchemaStruct {
133
121
  };
134
122
  };
135
123
  };
124
+ responses?: object;
136
125
  }
137
-
138
126
  export interface PlatformConstructor {
139
- new (session: PlatformSession): { log: Logger };
127
+ new (session: PlatformSession): {
128
+ log: Logger;
129
+ };
140
130
  }
141
-
142
131
  /**
143
132
  * Base interface for all Sockethub platforms.
144
133
  * Platforms implement protocol-specific logic for ActivityStreams messages.
@@ -160,7 +149,6 @@ export interface PlatformInterface {
160
149
  isInitialized(): boolean;
161
150
  cleanup(cb: PlatformCallback): void;
162
151
  }
163
-
164
152
  /**
165
153
  * Interface for persistent platforms with long-running connections.
166
154
  *
@@ -178,15 +166,6 @@ export interface PersistentPlatformInterface extends PlatformInterface {
178
166
  */
179
167
  credentialsHash: string | undefined;
180
168
  get config(): PersistentPlatformConfig;
181
- connect(
182
- job: ActivityStream,
183
- credentials: CredentialsObject,
184
- done: PlatformCallback,
185
- ): void;
186
- }
187
-
188
- export interface PlatformSession {
189
- log: Logger;
190
- sendToClient: PlatformSendToClient;
191
- updateActor: PlatformUpdateActor;
169
+ connect(job: ActivityStream, credentials: CredentialsObject, done: PlatformCallback): void;
192
170
  }
171
+ export {};
@@ -0,0 +1,26 @@
1
+ import { type Schema } from "ajv";
2
+ import { type ValidationErrorOptions } from "./helpers/error-parser.js";
3
+ import type { ActivityStream } from "./types.js";
4
+ export declare function registerSystemPlatformContexts(): void;
5
+ export declare function setValidationErrorOptions(options: ValidationErrorOptions): void;
6
+ /**
7
+ * Validate a (fully-materialized) Sockethub server config object against the
8
+ * canonical config schema. Returns "" when valid, otherwise a human-readable
9
+ * error string. Used by the server at startup so a mis-typed or unknown config
10
+ * value — including inside a platform's `packageConfig` entry — fails loudly
11
+ * rather than reaching a platform wrong-typed.
12
+ */
13
+ export declare function validateSockethubConfig(config: unknown): string;
14
+ export declare function validateActivityStream(msg: ActivityStream): string;
15
+ /**
16
+ * Validate an outbound platform response against that platform's `responses`
17
+ * schema. Returns "" when the platform has no responses schema registered (yet),
18
+ * so outbound validation can be adopted platform-by-platform.
19
+ */
20
+ export declare function validateActivityStreamResponse(msg: ActivityStream): string;
21
+ export declare function validateCredentials(msg: ActivityStream): string;
22
+ export declare function validatePlatformSchema(schema: Schema): string;
23
+ export declare function addPlatformSchema(schema: Schema, platform_type: string): void;
24
+ export declare function getPlatformSchema(platform_type: string): import("ajv/dist/core.js").AnyValidateFunction<unknown>;
25
+ export declare function addPlatformContext(platformId: string, platformContextUrl: string): void;
26
+ export declare function resolvePlatformId(msg: ActivityStream): string | null;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sockethub/schemas",
3
3
  "description": "JSON schema files for validating Sockethub Activity Streams",
4
- "version": "3.0.0-alpha.11",
4
+ "version": "3.0.0-alpha.13",
5
5
  "type": "module",
6
6
  "private": false,
7
7
  "author": "Nick Jennings <nick@silverbucket.net>",
@@ -12,20 +12,29 @@
12
12
  "main": "dist/index.js",
13
13
  "exports": {
14
14
  ".": {
15
- "bun": "./src/index.ts",
15
+ "types": "./dist/index.d.ts",
16
16
  "import": "./dist/index.js",
17
17
  "default": "./dist/index.js"
18
18
  },
19
+ "./context": {
20
+ "types": "./dist/context.d.ts",
21
+ "import": "./dist/context.js",
22
+ "default": "./dist/context.js"
23
+ },
24
+ "./object-types": {
25
+ "types": "./dist/object-types.d.ts",
26
+ "import": "./dist/object-types.js",
27
+ "default": "./dist/object-types.js"
28
+ },
19
29
  "./schemas/json/sockethub-config.json": {
20
- "default": "./src/schemas/json/sockethub-config.json"
30
+ "default": "./dist/schemas/json/sockethub-config.json"
21
31
  }
22
32
  },
23
33
  "files": [
24
- "src/",
25
34
  "dist/"
26
35
  ],
27
36
  "engines": {
28
- "bun": ">=1.2"
37
+ "node": ">=20"
29
38
  },
30
39
  "keywords": [
31
40
  "sockethub",
@@ -44,7 +53,7 @@
44
53
  "@types/bun": "latest"
45
54
  },
46
55
  "scripts": {
47
- "build": "bun ./scripts/export-json-schema.ts && bun build src/index.ts --outdir dist --target node --format esm --sourcemap=external",
56
+ "build": "bun ./scripts/export-json-schema.ts && bun ./scripts/export-canonical-assets.ts && bun build src/index.ts src/object-types.ts --outdir dist --target node --format esm --sourcemap=external && bun build src/context.ts src/activity-stream-helper.ts --outdir dist --target browser --format esm --sourcemap=external",
48
57
  "clean": "rm -rf dist",
49
58
  "clean:deps": "rm -rf node_modules"
50
59
  },
@@ -53,5 +62,6 @@
53
62
  "ajv-formats": "^2.1.1",
54
63
  "ajv-formats-draft2019": "^1.6.1"
55
64
  },
56
- "gitHead": "c243fa9e76c688ce5ffcf524400b1dd27dcce615"
65
+ "gitHead": "864733e5b34449ef39542eceb4ff11aa308081bc",
66
+ "types": "./dist/index.d.ts"
57
67
  }
@@ -1,113 +0,0 @@
1
- import type { ErrorObject } from "ajv";
2
- import type { ActivityObject, ActivityStream } from "../types.js";
3
- import { ObjectTypesList } from "./objects.js";
4
-
5
- interface TypeBreakdown {
6
- actor: Array<string>;
7
- object: Array<string>;
8
- target: Array<string>;
9
- }
10
-
11
- function parseMsg(error: ErrorObject): string {
12
- let err = `${
13
- error.instancePath ? error.instancePath : "activity stream"
14
- }: ${error.message}`;
15
- if (error.keyword === "additionalProperties") {
16
- err += `: ${error.params.additionalProperty}`;
17
- } else if (error.keyword === "enum") {
18
- err += `: ${error.params.allowedValues.join(", ")}`;
19
- }
20
- return err;
21
- }
22
-
23
- function getTypeList(msg: ActivityStream | ActivityObject): Array<string> {
24
- let types = [];
25
- types.push(msg?.type);
26
- for (const prop in msg) {
27
- if (msg[prop]?.type) {
28
- types = [...types, ...getTypeList(msg[prop])];
29
- }
30
- }
31
- return types;
32
- }
33
-
34
- function getSchemaType(error: ErrorObject): string {
35
- const schemaTypeRes = error.schemaPath.match(/#\/\w+\/\w+\/([\w-]+)\//);
36
- return schemaTypeRes ? schemaTypeRes[1] : undefined;
37
- }
38
-
39
- function getErrType(error: ErrorObject): string {
40
- const errTypeRes = error.instancePath.match(/\/(\w+)/);
41
- return errTypeRes ? errTypeRes[1] : undefined;
42
- }
43
-
44
- function getPartsCount(error: ErrorObject, types: TypeBreakdown): number {
45
- const schemaType = getSchemaType(error);
46
- const errType = getErrType(error);
47
- if (!errType) {
48
- return -1;
49
- }
50
- if (!types[errType]) {
51
- return -1;
52
- }
53
- if (!types[errType].includes(schemaType)) {
54
- return -1;
55
- }
56
- const parts = error.instancePath.split("/");
57
- return parts.length;
58
- }
59
-
60
- function getTypes(msg: ActivityStream): TypeBreakdown {
61
- return {
62
- actor: getTypeList(msg.actor),
63
- target: getTypeList(msg.target),
64
- object: getTypeList(msg.context ? msg.object : msg),
65
- };
66
- }
67
-
68
- /**
69
- * Traverses the errors array from ajv, and makes a series of filtering decisions to
70
- * try to arrive at the most useful error.
71
- * @param msg
72
- * @param errors
73
- * @returns {string}
74
- */
75
- export default function getErrorMessage(
76
- msg,
77
- errors: Array<ErrorObject>,
78
- ): string {
79
- const types = getTypes(msg);
80
- let deepest_entry = 0;
81
- let highest_depth = -1;
82
-
83
- for (let i = 0; i < errors.length; i++) {
84
- const partsCount = getPartsCount(errors[i], types);
85
- if (partsCount > highest_depth) {
86
- highest_depth = partsCount;
87
- deepest_entry = i;
88
- }
89
- }
90
-
91
- return highest_depth >= 0
92
- ? parseMsg(errors[deepest_entry])
93
- : composeFinalError(errors[errors.length - 1]);
94
- }
95
-
96
- function composeFinalError(error) {
97
- // if we have yet to build an error message, assume this is an invalid type value (oneOf),
98
- // try to build a list of valid types
99
- let msg: string;
100
- if (error.keyword === "oneOf") {
101
- msg =
102
- `${error.instancePath}: ${error.message}: ` +
103
- `${ObjectTypesList.join(", ")}`;
104
- } else {
105
- msg = `${
106
- error.instancePath ? error.instancePath : "activity stream"
107
- }: ${error.message}`;
108
- }
109
- if ("additionalProperty" in error.params) {
110
- msg += `: ${error.params.additionalProperty}`;
111
- }
112
- return msg;
113
- }