@mestra-dev/contract 0.0.11 → 0.0.12
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 +3 -3
- package/dist/index.d.ts +251 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,10 +34,10 @@ Always use `import type` so the bundler never pulls server code.
|
|
|
34
34
|
|
|
35
35
|
## Build (maintainers)
|
|
36
36
|
|
|
37
|
-
Requires local `services/
|
|
37
|
+
Requires local `services/core` and `services/utils` sources plus core `node_modules` for type resolution. Those packages are **not** published with this contract.
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
|
-
cd services/
|
|
40
|
+
cd services/hc
|
|
41
41
|
bun install
|
|
42
42
|
bun run build
|
|
43
43
|
```
|
|
@@ -47,7 +47,7 @@ Output: `dist/index.d.ts` (self-contained; depends on `hono` types only).
|
|
|
47
47
|
## Publish to npmjs
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
|
-
cd services/
|
|
50
|
+
cd services/hc
|
|
51
51
|
bun run build
|
|
52
52
|
bun pm version patch # or: minor / major
|
|
53
53
|
bun publish --access public
|
package/dist/index.d.ts
CHANGED
|
@@ -20,8 +20,8 @@ declare const routes: import("hono/hono-base").HonoBase<{
|
|
|
20
20
|
readonly openapi: "3.0.3";
|
|
21
21
|
readonly info: {
|
|
22
22
|
readonly title: "Auth Service API";
|
|
23
|
-
readonly version: "1.5.
|
|
24
|
-
readonly description: "OpenAPI specification for the Auth, Integrations, Workspace, Tasks, Templates, Tags, Comments, Assignees, Chat, Events, Billings, and
|
|
23
|
+
readonly version: "1.5.2";
|
|
24
|
+
readonly description: "OpenAPI specification for the Auth, Integrations, Workspace, Tasks, Templates, Tags, Comments, Assignees, Chat, Events, Billings, Files, and Notifications endpoints exposed by the k3-core platform.";
|
|
25
25
|
};
|
|
26
26
|
readonly servers: readonly [
|
|
27
27
|
{
|
|
@@ -96,7 +96,7 @@ declare const routes: import("hono/hono-base").HonoBase<{
|
|
|
96
96
|
},
|
|
97
97
|
{
|
|
98
98
|
readonly name: "Notifications";
|
|
99
|
-
readonly description: "Cursor-based incremental notification sync
|
|
99
|
+
readonly description: "Cursor-based incremental notification sync, plus mark-read and archive. Clients store the sync cursor and pass it on the next call; read/archive bump updated_at so changes propagate via sync.";
|
|
100
100
|
}
|
|
101
101
|
];
|
|
102
102
|
readonly components: {
|
|
@@ -223,6 +223,17 @@ declare const routes: import("hono/hono-base").HonoBase<{
|
|
|
223
223
|
};
|
|
224
224
|
};
|
|
225
225
|
};
|
|
226
|
+
readonly NotificationType: {
|
|
227
|
+
readonly type: "string";
|
|
228
|
+
readonly enum: readonly [
|
|
229
|
+
"info",
|
|
230
|
+
"warning",
|
|
231
|
+
"success",
|
|
232
|
+
"failure"
|
|
233
|
+
];
|
|
234
|
+
readonly default: "info";
|
|
235
|
+
readonly description: "Notification severity (`notification_type` Postgres enum).";
|
|
236
|
+
};
|
|
226
237
|
readonly Notification: {
|
|
227
238
|
readonly type: "object";
|
|
228
239
|
readonly required: readonly [
|
|
@@ -258,9 +269,7 @@ declare const routes: import("hono/hono-base").HonoBase<{
|
|
|
258
269
|
readonly maxLength: 256;
|
|
259
270
|
};
|
|
260
271
|
readonly type: {
|
|
261
|
-
readonly
|
|
262
|
-
readonly maxLength: 50;
|
|
263
|
-
readonly description: "Notification category (application-defined).";
|
|
272
|
+
readonly $ref: "#/components/schemas/NotificationType";
|
|
264
273
|
};
|
|
265
274
|
readonly read: {
|
|
266
275
|
readonly type: "boolean";
|
|
@@ -364,10 +373,30 @@ declare const routes: import("hono/hono-base").HonoBase<{
|
|
|
364
373
|
readonly description: "Human readable error message.";
|
|
365
374
|
};
|
|
366
375
|
readonly error: {
|
|
367
|
-
readonly type: "object";
|
|
368
376
|
readonly nullable: true;
|
|
369
|
-
readonly description: "
|
|
370
|
-
readonly
|
|
377
|
+
readonly description: "String message or Zod validation details, depending on the failure.";
|
|
378
|
+
readonly oneOf: readonly [
|
|
379
|
+
{
|
|
380
|
+
readonly type: "string";
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
readonly type: "object";
|
|
384
|
+
readonly additionalProperties: true;
|
|
385
|
+
}
|
|
386
|
+
];
|
|
387
|
+
};
|
|
388
|
+
};
|
|
389
|
+
};
|
|
390
|
+
readonly NotificationIdRequest: {
|
|
391
|
+
readonly type: "object";
|
|
392
|
+
readonly required: readonly [
|
|
393
|
+
"notificationId"
|
|
394
|
+
];
|
|
395
|
+
readonly properties: {
|
|
396
|
+
readonly notificationId: {
|
|
397
|
+
readonly type: "string";
|
|
398
|
+
readonly format: "uuid";
|
|
399
|
+
readonly description: "Notification identifier belonging to the authenticated user.";
|
|
371
400
|
};
|
|
372
401
|
};
|
|
373
402
|
};
|
|
@@ -3072,7 +3101,7 @@ declare const routes: import("hono/hono-base").HonoBase<{
|
|
|
3072
3101
|
};
|
|
3073
3102
|
};
|
|
3074
3103
|
readonly 400: {
|
|
3075
|
-
readonly description: "Missing code or token
|
|
3104
|
+
readonly description: "Missing `code`, or Google token exchange failed. `error` is a string such as Google's `error_description` (e.g. missing client_secret / invalid_request).";
|
|
3076
3105
|
readonly content: {
|
|
3077
3106
|
readonly "application/json": {
|
|
3078
3107
|
readonly schema: {
|
|
@@ -3082,7 +3111,7 @@ declare const routes: import("hono/hono-base").HonoBase<{
|
|
|
3082
3111
|
};
|
|
3083
3112
|
};
|
|
3084
3113
|
readonly 500: {
|
|
3085
|
-
readonly description: "
|
|
3114
|
+
readonly description: "`GOOGLE_CLIENT_SECRET is not configured`, or failed to issue session tokens after a successful Google exchange.";
|
|
3086
3115
|
readonly content: {
|
|
3087
3116
|
readonly "application/json": {
|
|
3088
3117
|
readonly schema: {
|
|
@@ -3186,7 +3215,7 @@ declare const routes: import("hono/hono-base").HonoBase<{
|
|
|
3186
3215
|
};
|
|
3187
3216
|
};
|
|
3188
3217
|
readonly 400: {
|
|
3189
|
-
readonly description: "Missing
|
|
3218
|
+
readonly description: "`Missing code or state`, `Invalid state`, `Invalid or expired state`, or Google token exchange failure (`error` string from Google / OAuth helper).";
|
|
3190
3219
|
readonly content: {
|
|
3191
3220
|
readonly "application/json": {
|
|
3192
3221
|
readonly schema: {
|
|
@@ -3212,7 +3241,7 @@ declare const routes: import("hono/hono-base").HonoBase<{
|
|
|
3212
3241
|
};
|
|
3213
3242
|
};
|
|
3214
3243
|
readonly 500: {
|
|
3215
|
-
readonly description: "
|
|
3244
|
+
readonly description: "`GOOGLE_CLIENT_SECRET is not configured`, or calendar creation / watch registration / persistence failed (e.g. invalid watch response).";
|
|
3216
3245
|
readonly content: {
|
|
3217
3246
|
readonly "application/json": {
|
|
3218
3247
|
readonly schema: {
|
|
@@ -4574,7 +4603,7 @@ declare const routes: import("hono/hono-base").HonoBase<{
|
|
|
4574
4603
|
"Workspace"
|
|
4575
4604
|
];
|
|
4576
4605
|
readonly summary: "Create workspace invitation";
|
|
4577
|
-
readonly description: "Creates an invitation for
|
|
4606
|
+
readonly description: "Creates an invitation for an email to join a workspace with specified resources and role. Works for emails that are not yet registered: a pending `invitations` row is stored and a collaborator is materialized when that email creates an account (OTP signup or OAuth). If the user already exists, a pending collaborator with policies is created immediately (same accept flow as before). Always returns the invite `token` for the frontend to share. Requires write access to crm::general. Pending invitations for unknown emails count toward the workspace collaborator seat limit.";
|
|
4578
4607
|
readonly security: readonly [
|
|
4579
4608
|
never
|
|
4580
4609
|
];
|
|
@@ -4594,7 +4623,7 @@ declare const routes: import("hono/hono-base").HonoBase<{
|
|
|
4594
4623
|
readonly email: {
|
|
4595
4624
|
readonly type: "string";
|
|
4596
4625
|
readonly format: "email";
|
|
4597
|
-
readonly description: "Email
|
|
4626
|
+
readonly description: "Email to invite (registered or not).";
|
|
4598
4627
|
};
|
|
4599
4628
|
readonly workspace: {
|
|
4600
4629
|
readonly type: "string";
|
|
@@ -4624,13 +4653,29 @@ declare const routes: import("hono/hono-base").HonoBase<{
|
|
|
4624
4653
|
readonly content: {
|
|
4625
4654
|
readonly "application/json": {
|
|
4626
4655
|
readonly schema: {
|
|
4627
|
-
readonly
|
|
4656
|
+
readonly type: "object";
|
|
4657
|
+
readonly required: readonly [
|
|
4658
|
+
"success",
|
|
4659
|
+
"token"
|
|
4660
|
+
];
|
|
4661
|
+
readonly properties: {
|
|
4662
|
+
readonly success: {
|
|
4663
|
+
readonly type: "boolean";
|
|
4664
|
+
readonly const: true;
|
|
4665
|
+
};
|
|
4666
|
+
readonly token: {
|
|
4667
|
+
readonly type: "string";
|
|
4668
|
+
readonly minLength: 64;
|
|
4669
|
+
readonly maxLength: 64;
|
|
4670
|
+
readonly description: "Invite token for GET /workspace/invite/{workspace}/{token} after the invitee authenticates.";
|
|
4671
|
+
};
|
|
4672
|
+
};
|
|
4628
4673
|
};
|
|
4629
4674
|
};
|
|
4630
4675
|
};
|
|
4631
4676
|
};
|
|
4632
4677
|
readonly 400: {
|
|
4633
|
-
readonly description: "Invalid request payload, max collaborators reached,
|
|
4678
|
+
readonly description: "Invalid request payload, max collaborators reached, invitation already pending, user is already a collaborator, or resource not found.";
|
|
4634
4679
|
readonly content: {
|
|
4635
4680
|
readonly "application/json": {
|
|
4636
4681
|
readonly schema: {
|
|
@@ -5128,13 +5173,13 @@ declare const routes: import("hono/hono-base").HonoBase<{
|
|
|
5128
5173
|
};
|
|
5129
5174
|
};
|
|
5130
5175
|
};
|
|
5131
|
-
readonly "/workspace/leave/{workspace}
|
|
5176
|
+
readonly "/workspace/leave/{workspace}": {
|
|
5132
5177
|
readonly get: {
|
|
5133
5178
|
readonly tags: readonly [
|
|
5134
5179
|
"Workspace"
|
|
5135
5180
|
];
|
|
5136
|
-
readonly summary: "
|
|
5137
|
-
readonly description: "Removes the
|
|
5181
|
+
readonly summary: "Leave a workspace";
|
|
5182
|
+
readonly description: "Removes the authenticated user from the workspace. Workspace owners cannot leave; they must delete the workspace instead (`Owner cannot leave the workspace, Delete the workspace instead`).";
|
|
5138
5183
|
readonly security: readonly [
|
|
5139
5184
|
never
|
|
5140
5185
|
];
|
|
@@ -5148,21 +5193,11 @@ declare const routes: import("hono/hono-base").HonoBase<{
|
|
|
5148
5193
|
readonly type: "string";
|
|
5149
5194
|
readonly format: "uuid";
|
|
5150
5195
|
};
|
|
5151
|
-
},
|
|
5152
|
-
{
|
|
5153
|
-
readonly name: "collaboratorId";
|
|
5154
|
-
readonly in: "path";
|
|
5155
|
-
readonly required: true;
|
|
5156
|
-
readonly description: "Collaborator identifier to remove.";
|
|
5157
|
-
readonly schema: {
|
|
5158
|
-
readonly type: "string";
|
|
5159
|
-
readonly format: "uuid";
|
|
5160
|
-
};
|
|
5161
5196
|
}
|
|
5162
5197
|
];
|
|
5163
5198
|
readonly responses: {
|
|
5164
5199
|
readonly 200: {
|
|
5165
|
-
readonly description: "
|
|
5200
|
+
readonly description: "Left the workspace successfully.";
|
|
5166
5201
|
readonly content: {
|
|
5167
5202
|
readonly "application/json": {
|
|
5168
5203
|
readonly schema: {
|
|
@@ -5172,7 +5207,7 @@ declare const routes: import("hono/hono-base").HonoBase<{
|
|
|
5172
5207
|
};
|
|
5173
5208
|
};
|
|
5174
5209
|
readonly 400: {
|
|
5175
|
-
readonly description: "Invalid workspace id,
|
|
5210
|
+
readonly description: "Invalid workspace id, caller is not a collaborator, or caller is the workspace owner.";
|
|
5176
5211
|
readonly content: {
|
|
5177
5212
|
readonly "application/json": {
|
|
5178
5213
|
readonly schema: {
|
|
@@ -10083,6 +10118,126 @@ declare const routes: import("hono/hono-base").HonoBase<{
|
|
|
10083
10118
|
};
|
|
10084
10119
|
};
|
|
10085
10120
|
};
|
|
10121
|
+
readonly "/notifications/read": {
|
|
10122
|
+
readonly post: {
|
|
10123
|
+
readonly tags: readonly [
|
|
10124
|
+
"Notifications"
|
|
10125
|
+
];
|
|
10126
|
+
readonly summary: "Mark a notification as read";
|
|
10127
|
+
readonly description: "Sets `read=true` and bumps `updated_at` for a notification owned by the authenticated user so the change is picked up by `/notifications/sync`. Requires a valid access token (no workspace policy check).";
|
|
10128
|
+
readonly security: readonly [
|
|
10129
|
+
never
|
|
10130
|
+
];
|
|
10131
|
+
readonly requestBody: {
|
|
10132
|
+
readonly required: true;
|
|
10133
|
+
readonly content: {
|
|
10134
|
+
readonly "application/json": {
|
|
10135
|
+
readonly schema: {
|
|
10136
|
+
readonly $ref: "#/components/schemas/NotificationIdRequest";
|
|
10137
|
+
};
|
|
10138
|
+
};
|
|
10139
|
+
};
|
|
10140
|
+
};
|
|
10141
|
+
readonly responses: {
|
|
10142
|
+
readonly 200: {
|
|
10143
|
+
readonly description: "Notification marked as read.";
|
|
10144
|
+
readonly content: {
|
|
10145
|
+
readonly "application/json": {
|
|
10146
|
+
readonly schema: {
|
|
10147
|
+
readonly $ref: "#/components/schemas/SimpleSuccess";
|
|
10148
|
+
};
|
|
10149
|
+
};
|
|
10150
|
+
};
|
|
10151
|
+
};
|
|
10152
|
+
readonly 400: {
|
|
10153
|
+
readonly description: "Invalid request payload.";
|
|
10154
|
+
readonly content: {
|
|
10155
|
+
readonly "application/json": {
|
|
10156
|
+
readonly schema: {
|
|
10157
|
+
readonly $ref: "#/components/schemas/ErrorResponse";
|
|
10158
|
+
};
|
|
10159
|
+
};
|
|
10160
|
+
};
|
|
10161
|
+
};
|
|
10162
|
+
readonly 401: {
|
|
10163
|
+
readonly description: "Unauthorized - Missing or invalid access token.";
|
|
10164
|
+
};
|
|
10165
|
+
readonly 403: {
|
|
10166
|
+
readonly description: "Forbidden - Token issuer is not 'access'.";
|
|
10167
|
+
};
|
|
10168
|
+
readonly 404: {
|
|
10169
|
+
readonly description: "Notification not found for this user.";
|
|
10170
|
+
readonly content: {
|
|
10171
|
+
readonly "application/json": {
|
|
10172
|
+
readonly schema: {
|
|
10173
|
+
readonly $ref: "#/components/schemas/ErrorResponse";
|
|
10174
|
+
};
|
|
10175
|
+
};
|
|
10176
|
+
};
|
|
10177
|
+
};
|
|
10178
|
+
};
|
|
10179
|
+
};
|
|
10180
|
+
};
|
|
10181
|
+
readonly "/notifications/archive": {
|
|
10182
|
+
readonly post: {
|
|
10183
|
+
readonly tags: readonly [
|
|
10184
|
+
"Notifications"
|
|
10185
|
+
];
|
|
10186
|
+
readonly summary: "Archive a notification";
|
|
10187
|
+
readonly description: "Sets `archive=true` and bumps `updated_at` for a notification owned by the authenticated user so the change is picked up by `/notifications/sync`. Requires a valid access token (no workspace policy check).";
|
|
10188
|
+
readonly security: readonly [
|
|
10189
|
+
never
|
|
10190
|
+
];
|
|
10191
|
+
readonly requestBody: {
|
|
10192
|
+
readonly required: true;
|
|
10193
|
+
readonly content: {
|
|
10194
|
+
readonly "application/json": {
|
|
10195
|
+
readonly schema: {
|
|
10196
|
+
readonly $ref: "#/components/schemas/NotificationIdRequest";
|
|
10197
|
+
};
|
|
10198
|
+
};
|
|
10199
|
+
};
|
|
10200
|
+
};
|
|
10201
|
+
readonly responses: {
|
|
10202
|
+
readonly 200: {
|
|
10203
|
+
readonly description: "Notification archived.";
|
|
10204
|
+
readonly content: {
|
|
10205
|
+
readonly "application/json": {
|
|
10206
|
+
readonly schema: {
|
|
10207
|
+
readonly $ref: "#/components/schemas/SimpleSuccess";
|
|
10208
|
+
};
|
|
10209
|
+
};
|
|
10210
|
+
};
|
|
10211
|
+
};
|
|
10212
|
+
readonly 400: {
|
|
10213
|
+
readonly description: "Invalid request payload.";
|
|
10214
|
+
readonly content: {
|
|
10215
|
+
readonly "application/json": {
|
|
10216
|
+
readonly schema: {
|
|
10217
|
+
readonly $ref: "#/components/schemas/ErrorResponse";
|
|
10218
|
+
};
|
|
10219
|
+
};
|
|
10220
|
+
};
|
|
10221
|
+
};
|
|
10222
|
+
readonly 401: {
|
|
10223
|
+
readonly description: "Unauthorized - Missing or invalid access token.";
|
|
10224
|
+
};
|
|
10225
|
+
readonly 403: {
|
|
10226
|
+
readonly description: "Forbidden - Token issuer is not 'access'.";
|
|
10227
|
+
};
|
|
10228
|
+
readonly 404: {
|
|
10229
|
+
readonly description: "Notification not found for this user.";
|
|
10230
|
+
readonly content: {
|
|
10231
|
+
readonly "application/json": {
|
|
10232
|
+
readonly schema: {
|
|
10233
|
+
readonly $ref: "#/components/schemas/ErrorResponse";
|
|
10234
|
+
};
|
|
10235
|
+
};
|
|
10236
|
+
};
|
|
10237
|
+
};
|
|
10238
|
+
};
|
|
10239
|
+
};
|
|
10240
|
+
};
|
|
10086
10241
|
readonly "/chat/start": {
|
|
10087
10242
|
readonly post: {
|
|
10088
10243
|
readonly tags: readonly [
|
|
@@ -10622,6 +10777,7 @@ declare const routes: import("hono/hono-base").HonoBase<{
|
|
|
10622
10777
|
input: {};
|
|
10623
10778
|
output: {
|
|
10624
10779
|
success: true;
|
|
10780
|
+
token: string;
|
|
10625
10781
|
};
|
|
10626
10782
|
outputFormat: "json";
|
|
10627
10783
|
status: 201;
|
|
@@ -12271,6 +12427,14 @@ declare const routes: import("hono/hono-base").HonoBase<{
|
|
|
12271
12427
|
};
|
|
12272
12428
|
outputFormat: "json";
|
|
12273
12429
|
status: 400;
|
|
12430
|
+
} | {
|
|
12431
|
+
input: {};
|
|
12432
|
+
output: {
|
|
12433
|
+
success: false;
|
|
12434
|
+
error: string;
|
|
12435
|
+
};
|
|
12436
|
+
outputFormat: "json";
|
|
12437
|
+
status: 500;
|
|
12274
12438
|
} | {
|
|
12275
12439
|
input: {};
|
|
12276
12440
|
output: {
|
|
@@ -15763,7 +15927,6 @@ declare const routes: import("hono/hono-base").HonoBase<{
|
|
|
15763
15927
|
output: {
|
|
15764
15928
|
success: false;
|
|
15765
15929
|
error: string;
|
|
15766
|
-
details: string;
|
|
15767
15930
|
};
|
|
15768
15931
|
outputFormat: "json";
|
|
15769
15932
|
status: 500;
|
|
@@ -15845,7 +16008,7 @@ declare const routes: import("hono/hono-base").HonoBase<{
|
|
|
15845
16008
|
workspaceId: string;
|
|
15846
16009
|
title: string;
|
|
15847
16010
|
message: string;
|
|
15848
|
-
type:
|
|
16011
|
+
type: "success" | "info" | "warning" | "failure";
|
|
15849
16012
|
read: boolean;
|
|
15850
16013
|
archive: boolean;
|
|
15851
16014
|
}[];
|
|
@@ -15856,5 +16019,59 @@ declare const routes: import("hono/hono-base").HonoBase<{
|
|
|
15856
16019
|
status: 200;
|
|
15857
16020
|
};
|
|
15858
16021
|
};
|
|
16022
|
+
} & {
|
|
16023
|
+
"/read": {
|
|
16024
|
+
$post: {
|
|
16025
|
+
input: {};
|
|
16026
|
+
output: {
|
|
16027
|
+
success: false;
|
|
16028
|
+
error: never;
|
|
16029
|
+
};
|
|
16030
|
+
outputFormat: "json";
|
|
16031
|
+
status: 400;
|
|
16032
|
+
} | {
|
|
16033
|
+
input: {};
|
|
16034
|
+
output: {
|
|
16035
|
+
success: false;
|
|
16036
|
+
error: string;
|
|
16037
|
+
};
|
|
16038
|
+
outputFormat: "json";
|
|
16039
|
+
status: 404;
|
|
16040
|
+
} | {
|
|
16041
|
+
input: {};
|
|
16042
|
+
output: {
|
|
16043
|
+
success: true;
|
|
16044
|
+
};
|
|
16045
|
+
outputFormat: "json";
|
|
16046
|
+
status: 200;
|
|
16047
|
+
};
|
|
16048
|
+
};
|
|
16049
|
+
} & {
|
|
16050
|
+
"/archive": {
|
|
16051
|
+
$post: {
|
|
16052
|
+
input: {};
|
|
16053
|
+
output: {
|
|
16054
|
+
success: false;
|
|
16055
|
+
error: never;
|
|
16056
|
+
};
|
|
16057
|
+
outputFormat: "json";
|
|
16058
|
+
status: 400;
|
|
16059
|
+
} | {
|
|
16060
|
+
input: {};
|
|
16061
|
+
output: {
|
|
16062
|
+
success: false;
|
|
16063
|
+
error: string;
|
|
16064
|
+
};
|
|
16065
|
+
outputFormat: "json";
|
|
16066
|
+
status: 404;
|
|
16067
|
+
} | {
|
|
16068
|
+
input: {};
|
|
16069
|
+
output: {
|
|
16070
|
+
success: true;
|
|
16071
|
+
};
|
|
16072
|
+
outputFormat: "json";
|
|
16073
|
+
status: 200;
|
|
16074
|
+
};
|
|
16075
|
+
};
|
|
15859
16076
|
}, "/notifications">, "/", "/">;
|
|
15860
16077
|
export type AppType = typeof routes;
|