@gokiteam/goki-dev 0.2.0
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 +478 -0
- package/bin/goki-dev.js +452 -0
- package/bin/mcp-server.js +16 -0
- package/bin/secrets-cli.js +302 -0
- package/cli/ComposeOverrideGenerator.js +226 -0
- package/cli/ComposeParser.js +73 -0
- package/cli/ConfigGenerator.js +304 -0
- package/cli/ConfigManager.js +46 -0
- package/cli/DatabaseManager.js +94 -0
- package/cli/DevToolsChecker.js +21 -0
- package/cli/DevToolsDir.js +66 -0
- package/cli/DevToolsManager.js +451 -0
- package/cli/DockerManager.js +138 -0
- package/cli/FunctionManager.js +95 -0
- package/cli/HttpProxyRewriter.js +91 -0
- package/cli/Logger.js +10 -0
- package/cli/McpConfigManager.js +123 -0
- package/cli/NgrokManager.js +431 -0
- package/cli/ProjectCLI.js +2322 -0
- package/cli/PubSubManager.js +129 -0
- package/cli/SnapshotManager.js +88 -0
- package/cli/UiFormatter.js +292 -0
- package/cli/WebhookUrlRewriter.js +32 -0
- package/cli/secrets/BiometricAuth.js +125 -0
- package/cli/secrets/SecretInjector.js +47 -0
- package/cli/secrets/SecretsConfig.js +141 -0
- package/cli/secrets/SecretsDoctor.js +384 -0
- package/cli/secrets/SecretsManager.js +255 -0
- package/client/dist/client.d.ts +332 -0
- package/client/dist/client.js +507 -0
- package/client/dist/helpers.d.ts +62 -0
- package/client/dist/helpers.js +122 -0
- package/client/dist/index.d.ts +59 -0
- package/client/dist/index.js +78 -0
- package/client/dist/package.json +1 -0
- package/client/dist/types.d.ts +280 -0
- package/client/dist/types.js +7 -0
- package/config.development +46 -0
- package/config.test +18 -0
- package/guidelines/CodingStyleGuideline.md +148 -0
- package/guidelines/CommentingGuideline.md +10 -0
- package/guidelines/HttpApiImplementationGuideline.md +137 -0
- package/guidelines/NamingGuideline.md +182 -0
- package/package.json +138 -0
- package/patterns/api/[collectionName]/Controllers.md +62 -0
- package/patterns/api/[collectionName]/Logic.md +154 -0
- package/patterns/api/[collectionName]/Permissions.md +81 -0
- package/patterns/api/[collectionName]/Router.md +83 -0
- package/patterns/api/[collectionName]/Schemas.md +197 -0
- package/patterns/configs/Patterns.md +7 -0
- package/patterns/enums/Patterns.md +24 -0
- package/patterns/errorHandling/Patterns.md +185 -0
- package/patterns/testing/Patterns.md +232 -0
- package/src/Server.js +238 -0
- package/src/api/dashboard/Controllers.js +9 -0
- package/src/api/dashboard/Logic.js +76 -0
- package/src/api/dashboard/Router.js +11 -0
- package/src/api/dashboard/Schemas.js +47 -0
- package/src/api/data/Controllers.js +26 -0
- package/src/api/data/Logic.js +188 -0
- package/src/api/data/Router.js +16 -0
- package/src/api/docker/Controllers.js +33 -0
- package/src/api/docker/Logic.js +268 -0
- package/src/api/docker/Router.js +15 -0
- package/src/api/docker/Schemas.js +80 -0
- package/src/api/docs/Controllers.js +15 -0
- package/src/api/docs/Logic.js +85 -0
- package/src/api/docs/Router.js +12 -0
- package/src/api/export/Controllers.js +30 -0
- package/src/api/export/Logic.js +143 -0
- package/src/api/export/Router.js +18 -0
- package/src/api/export/Schemas.js +104 -0
- package/src/api/firestore/Controllers.js +152 -0
- package/src/api/firestore/Logic.js +474 -0
- package/src/api/firestore/Router.js +23 -0
- package/src/api/functions/Controllers.js +261 -0
- package/src/api/functions/Logic.js +710 -0
- package/src/api/functions/Router.js +50 -0
- package/src/api/functions/Schemas.js +193 -0
- package/src/api/gateway/Controllers.js +72 -0
- package/src/api/gateway/Logic.js +74 -0
- package/src/api/gateway/Router.js +10 -0
- package/src/api/gateway/Schemas.js +19 -0
- package/src/api/health/Controllers.js +14 -0
- package/src/api/health/Logic.js +24 -0
- package/src/api/health/Router.js +12 -0
- package/src/api/httpTraffic/Controllers.js +29 -0
- package/src/api/httpTraffic/Logic.js +33 -0
- package/src/api/httpTraffic/Router.js +9 -0
- package/src/api/httpTraffic/Schemas.js +23 -0
- package/src/api/logging/Controllers.js +80 -0
- package/src/api/logging/Logic.js +461 -0
- package/src/api/logging/Router.js +24 -0
- package/src/api/logging/Schemas.js +43 -0
- package/src/api/mqtt/Controllers.js +17 -0
- package/src/api/mqtt/Logic.js +66 -0
- package/src/api/mqtt/Router.js +12 -0
- package/src/api/postgres/Controllers.js +97 -0
- package/src/api/postgres/Logic.js +221 -0
- package/src/api/postgres/Router.js +21 -0
- package/src/api/pubsub/Controllers.js +236 -0
- package/src/api/pubsub/Logic.js +732 -0
- package/src/api/pubsub/Router.js +41 -0
- package/src/api/pubsub/Schemas.js +355 -0
- package/src/api/redis/Controllers.js +63 -0
- package/src/api/redis/Logic.js +239 -0
- package/src/api/redis/Router.js +21 -0
- package/src/api/scheduler/Controllers.js +27 -0
- package/src/api/scheduler/Logic.js +49 -0
- package/src/api/scheduler/Router.js +16 -0
- package/src/api/services/Controllers.js +26 -0
- package/src/api/services/Logic.js +205 -0
- package/src/api/services/Router.js +14 -0
- package/src/api/services/Schemas.js +66 -0
- package/src/api/snapshots/Controllers.js +37 -0
- package/src/api/snapshots/Logic.js +797 -0
- package/src/api/snapshots/Router.js +15 -0
- package/src/api/snapshots/Schemas.js +23 -0
- package/src/api/webhooks/Controllers.js +49 -0
- package/src/api/webhooks/Logic.js +137 -0
- package/src/api/webhooks/Router.js +12 -0
- package/src/api/webhooks/Schemas.js +31 -0
- package/src/configs/Application.js +147 -0
- package/src/configs/Default.js +13 -0
- package/src/consumers/BlackboxLogsConsumer.js +235 -0
- package/src/consumers/DockerLogsConsumer.js +687 -0
- package/src/db/Tables.js +66 -0
- package/src/db/schemas/firestore.js +18 -0
- package/src/db/schemas/functions.js +65 -0
- package/src/db/schemas/httpTraffic.js +43 -0
- package/src/db/schemas/logging.js +74 -0
- package/src/db/schemas/migrations.js +64 -0
- package/src/db/schemas/mqtt.js +56 -0
- package/src/db/schemas/pubsub.js +90 -0
- package/src/db/schemas/pubsubRegistry.js +22 -0
- package/src/db/schemas/webhooks.js +28 -0
- package/src/emulation/awsiot/Controllers.js +91 -0
- package/src/emulation/awsiot/Logic.js +70 -0
- package/src/emulation/awsiot/Router.js +19 -0
- package/src/emulation/awsiot/Server.js +100 -0
- package/src/emulation/firestore/Server.js +136 -0
- package/src/emulation/logging/Controllers.js +212 -0
- package/src/emulation/logging/Logic.js +416 -0
- package/src/emulation/logging/Router.js +36 -0
- package/src/emulation/logging/Schemas.js +82 -0
- package/src/emulation/logging/Server.js +108 -0
- package/src/emulation/pubsub/Controllers.js +279 -0
- package/src/emulation/pubsub/DefaultTopics.js +162 -0
- package/src/emulation/pubsub/Logic.js +427 -0
- package/src/emulation/pubsub/README.md +309 -0
- package/src/emulation/pubsub/Router.js +33 -0
- package/src/emulation/pubsub/Server.js +104 -0
- package/src/emulation/pubsub/ShadowPoller.js +276 -0
- package/src/emulation/pubsub/ShadowSubscriptionManager.js +199 -0
- package/src/enums/ContainerNames.js +106 -0
- package/src/enums/ErrorReason.js +28 -0
- package/src/enums/FunctionStatuses.js +15 -0
- package/src/enums/FunctionTriggerTypes.js +15 -0
- package/src/enums/GatewayState.js +7 -0
- package/src/enums/ServiceNames.js +68 -0
- package/src/jobs/DatabaseMaintenance.js +184 -0
- package/src/jobs/MessageHistoryCleanup.js +152 -0
- package/src/mcp/ApiClient.js +25 -0
- package/src/mcp/Server.js +52 -0
- package/src/mcp/prompts/debugging.js +104 -0
- package/src/mcp/resources/platform.js +118 -0
- package/src/mcp/tools/data.js +84 -0
- package/src/mcp/tools/docker.js +166 -0
- package/src/mcp/tools/firestore.js +162 -0
- package/src/mcp/tools/functions.js +380 -0
- package/src/mcp/tools/httpTraffic.js +69 -0
- package/src/mcp/tools/logging.js +174 -0
- package/src/mcp/tools/mqtt.js +37 -0
- package/src/mcp/tools/postgres.js +130 -0
- package/src/mcp/tools/pubsub.js +316 -0
- package/src/mcp/tools/redis.js +146 -0
- package/src/mcp/tools/services.js +169 -0
- package/src/mcp/tools/snapshots.js +88 -0
- package/src/mcp/tools/webhooks.js +115 -0
- package/src/middleware/DevProxy.js +67 -0
- package/src/middleware/ErrorCatcher.js +35 -0
- package/src/middleware/HttpProxy.js +215 -0
- package/src/middleware/Reply.js +24 -0
- package/src/middleware/TraceId.js +9 -0
- package/src/middleware/WebhookProxy.js +234 -0
- package/src/protocols/mqtt/Broker.js +92 -0
- package/src/protocols/mqtt/Handlers.js +175 -0
- package/src/protocols/mqtt/PubSubBridge.js +162 -0
- package/src/protocols/mqtt/Server.js +116 -0
- package/src/runtime/FunctionRunner.js +179 -0
- package/src/services/AppGatewayService.js +582 -0
- package/src/singletons/FirestoreBroadcaster.js +367 -0
- package/src/singletons/FunctionTriggerDispatcher.js +456 -0
- package/src/singletons/FunctionsService.js +418 -0
- package/src/singletons/HttpProxy.js +224 -0
- package/src/singletons/LogBroadcaster.js +159 -0
- package/src/singletons/Logger.js +49 -0
- package/src/singletons/MemoryJsonStore.js +175 -0
- package/src/singletons/MessageBroadcaster.js +190 -0
- package/src/singletons/PostgresBroadcaster.js +367 -0
- package/src/singletons/PostgresClient.js +180 -0
- package/src/singletons/RedisClient.js +184 -0
- package/src/singletons/SqliteStore.js +480 -0
- package/src/singletons/TickService.js +151 -0
- package/src/singletons/WebhookProxy.js +223 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# API Permissions Pattern
|
|
2
|
+
|
|
3
|
+
For API gateway microservices, define permission combinations for each endpoint.
|
|
4
|
+
|
|
5
|
+
## Permission combination structure
|
|
6
|
+
|
|
7
|
+
Coding pattern:
|
|
8
|
+
```javascript
|
|
9
|
+
export const Permissions = {
|
|
10
|
+
create: [
|
|
11
|
+
['property:write', 'device:write']
|
|
12
|
+
],
|
|
13
|
+
update: [
|
|
14
|
+
['property:write', 'device:write']
|
|
15
|
+
],
|
|
16
|
+
details: [
|
|
17
|
+
['property:read', 'device:read']
|
|
18
|
+
],
|
|
19
|
+
list: [
|
|
20
|
+
['property:read', 'device:read']
|
|
21
|
+
],
|
|
22
|
+
delete: [
|
|
23
|
+
['property:write', 'device:write', 'device:delete']
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Router integration
|
|
29
|
+
|
|
30
|
+
Permissions are used in the middleware stack:
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
import KoaRouter from 'koa-router'
|
|
34
|
+
import { PermissionsEvaluator, JoiValidator } from '@gokiteam/koa'
|
|
35
|
+
|
|
36
|
+
import { Schemas } from './Schemas.js'
|
|
37
|
+
import { Controllers } from './Controllers.js'
|
|
38
|
+
import { Permissions } from './Permissions.js'
|
|
39
|
+
|
|
40
|
+
const Router = new KoaRouter()
|
|
41
|
+
const v1 = new KoaRouter({ prefix: '/v1/devices' })
|
|
42
|
+
|
|
43
|
+
v1.post(
|
|
44
|
+
'/create',
|
|
45
|
+
PermissionsEvaluator({ combinations: Permissions.create }),
|
|
46
|
+
JoiValidator({ schema: Schemas.create }),
|
|
47
|
+
Controllers.create
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
Router.use(v1.routes())
|
|
51
|
+
export { Router }
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Permission combinations
|
|
55
|
+
|
|
56
|
+
Each endpoint can have multiple permission combinations (OR logic):
|
|
57
|
+
|
|
58
|
+
```javascript
|
|
59
|
+
export const Permissions = {
|
|
60
|
+
create: [
|
|
61
|
+
['admin'], // Admin can create
|
|
62
|
+
['property:write', 'device:write'] // Or users with both permissions
|
|
63
|
+
],
|
|
64
|
+
delete: [
|
|
65
|
+
['admin'], // Only admin can delete
|
|
66
|
+
['property:owner', 'device:delete'] // Or property owners with delete permission
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Non-gateway microservices
|
|
72
|
+
|
|
73
|
+
For non-gateway microservices (internal services), no Permissions.js file is needed. The Router middleware stack is:
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
v1.post(
|
|
77
|
+
'/create',
|
|
78
|
+
JoiValidator({ schema: Schemas.create }),
|
|
79
|
+
Controllers.create
|
|
80
|
+
)
|
|
81
|
+
```
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# API Route definition
|
|
2
|
+
Each api endpoint:
|
|
3
|
+
- The routing rules should be defined in "src/api/[collection]/Router.js" file.
|
|
4
|
+
- The router is a koa router with a stack of middleware registered on each endpoint route.
|
|
5
|
+
- The router should be added to "src/HttpServer.js" router list.
|
|
6
|
+
|
|
7
|
+
The common scenario is to have a v1 router like:
|
|
8
|
+
```javascript
|
|
9
|
+
import KoaRouter from 'koa-router'
|
|
10
|
+
|
|
11
|
+
const Router = new KoaRouter()
|
|
12
|
+
const v1 = new KoaRouter({ prefix: '/v1/[collection]' })
|
|
13
|
+
|
|
14
|
+
// route defintions...
|
|
15
|
+
|
|
16
|
+
Router.use(v1.routes())
|
|
17
|
+
|
|
18
|
+
export { Router }
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
In some cases we might have other versions like:
|
|
22
|
+
```javascript
|
|
23
|
+
import KoaRouter from 'koa-router'
|
|
24
|
+
|
|
25
|
+
const Router = new KoaRouter()
|
|
26
|
+
const v1 = new KoaRouter({ prefix: '/v1/[collection]' })
|
|
27
|
+
const v2 = new KoaRouter({ prefix: '/v2/[collection]' })
|
|
28
|
+
|
|
29
|
+
// v1 route defintions...
|
|
30
|
+
|
|
31
|
+
// v2 route defintions
|
|
32
|
+
|
|
33
|
+
Router.use(v1.routes())
|
|
34
|
+
Router.use(v2.routes())
|
|
35
|
+
|
|
36
|
+
export { Router }
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
For each endpoint the common middleware stack is "path", "validation", "controller" like:
|
|
40
|
+
```javascript
|
|
41
|
+
import KoaRouter from 'koa-router'
|
|
42
|
+
|
|
43
|
+
import { Schemas } from './Schemas.js'
|
|
44
|
+
import { Controllers } from './Controllers.js'
|
|
45
|
+
|
|
46
|
+
const Router = new KoaRouter()
|
|
47
|
+
const v1 = new KoaRouter({ prefix: '/v1/[collection]' })
|
|
48
|
+
|
|
49
|
+
v1
|
|
50
|
+
.post(
|
|
51
|
+
'/create',
|
|
52
|
+
JoiValidator({ schema: Schemas.create }),
|
|
53
|
+
Controllers.create
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
Router.use(v1.routes())
|
|
57
|
+
|
|
58
|
+
export { Router }
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
For API gateway microservices, the common middleware stack is "path", "permission checks", "validation", "controller" like:
|
|
62
|
+
```javascript
|
|
63
|
+
import KoaRouter from 'koa-router'
|
|
64
|
+
|
|
65
|
+
import { Schemas } from './Schemas.js'
|
|
66
|
+
import { Controllers } from './Controllers.js'
|
|
67
|
+
import { Permissions } from './Permissions.js'
|
|
68
|
+
|
|
69
|
+
const Router = new KoaRouter()
|
|
70
|
+
const v1 = new KoaRouter({ prefix: '/v1/[collection]' })
|
|
71
|
+
|
|
72
|
+
v1
|
|
73
|
+
.post(
|
|
74
|
+
'/create',
|
|
75
|
+
PermissionsEvaluator({ combinations: Permissions.create }),
|
|
76
|
+
JoiValidator({ schema: Schemas.create }),
|
|
77
|
+
Controllers.create
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
Router.use(v1.routes())
|
|
81
|
+
|
|
82
|
+
export { Router }
|
|
83
|
+
```
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# API Schema definition and rules
|
|
2
|
+
Each API endpoint should have schemas for:
|
|
3
|
+
- success response
|
|
4
|
+
- error responses
|
|
5
|
+
- For each error reason being thrown from the related logic, specify Oops error meta object
|
|
6
|
+
|
|
7
|
+
For entities and shared objects, we define schemas inside "src/schemas" directory in the root of the project then use
|
|
8
|
+
them inside "src/api/[collection]/Schemas.js"
|
|
9
|
+
|
|
10
|
+
Coding pattern:
|
|
11
|
+
|
|
12
|
+
Consider the endpoint is `/v1/apps/activateConnection`.
|
|
13
|
+
|
|
14
|
+
The content of "src/schemas/App.js"
|
|
15
|
+
```javascript
|
|
16
|
+
import { Joi } from '@gokiteam/koa'
|
|
17
|
+
|
|
18
|
+
import { CategoryValues } from '../enums/Category.js'
|
|
19
|
+
import { PermissionValues } from '../enums/Permission.js'
|
|
20
|
+
import { AppStatusValues } from '../enums/AppStatus.js'
|
|
21
|
+
|
|
22
|
+
export const App = Joi.object().keys({
|
|
23
|
+
id: Joi.string().uuid(),
|
|
24
|
+
name: Joi.string(),
|
|
25
|
+
category: Joi.string().valid(...CategoryValues),
|
|
26
|
+
logo: Joi.object().keys({
|
|
27
|
+
url: Joi.string().uri(),
|
|
28
|
+
publicId: Joi.string()
|
|
29
|
+
}).allow(null),
|
|
30
|
+
summary: Joi.string(),
|
|
31
|
+
description: Joi.string(),
|
|
32
|
+
supportEmail: Joi.string().email(),
|
|
33
|
+
isPrivate: Joi.boolean(),
|
|
34
|
+
isCustom: Joi.boolean(),
|
|
35
|
+
status: Joi.string().valid(...AppStatusValues),
|
|
36
|
+
partnerId: Joi.string().uuid().allow(null),
|
|
37
|
+
externalLinks: Joi.array().items(Joi.object().keys({
|
|
38
|
+
title: Joi.string(),
|
|
39
|
+
url: Joi.string().uri()
|
|
40
|
+
})),
|
|
41
|
+
permissions: Joi.array().items(Joi.string().valid(...PermissionValues)),
|
|
42
|
+
createdAt: Joi.string(),
|
|
43
|
+
updatedAt: Joi.string()
|
|
44
|
+
})
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The content of "src/api/apps/Schemas.js"
|
|
48
|
+
```javascript
|
|
49
|
+
import { Joi } from '@gokiteam/koa'
|
|
50
|
+
|
|
51
|
+
import { ErrorReason } from '../../enums/ErrorReason.js'
|
|
52
|
+
import { App } from '../../schemas/App.js'
|
|
53
|
+
|
|
54
|
+
export const Schemas = {
|
|
55
|
+
// other endpoints schema definitions ...
|
|
56
|
+
activateConnection: {
|
|
57
|
+
request: {
|
|
58
|
+
body: {
|
|
59
|
+
id: Joi.string().uuid().required(),
|
|
60
|
+
partnerId: Joi.string().uuid().required(),
|
|
61
|
+
propertyId: Joi.string().uuid().required()
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
responses: {
|
|
65
|
+
success: { app: App },
|
|
66
|
+
[ErrorReason.invalidAppId]: {
|
|
67
|
+
traceId: Joi.string().required(),
|
|
68
|
+
id: Joi.string().uuid().required()
|
|
69
|
+
},
|
|
70
|
+
[ErrorReason.connectionDoesNotExist]: {
|
|
71
|
+
traceId: Joi.string().required(),
|
|
72
|
+
id: Joi.string().uuid().required(),
|
|
73
|
+
propertyId: Joi.string().uuid().required()
|
|
74
|
+
},
|
|
75
|
+
[ErrorReason.connectionIsAlreadyActive]: {
|
|
76
|
+
traceId: Joi.string().required(),
|
|
77
|
+
id: Joi.string().uuid().required(),
|
|
78
|
+
propertyId: Joi.string().uuid().required()
|
|
79
|
+
},
|
|
80
|
+
[ErrorReason.insufficientPermission]: {
|
|
81
|
+
traceId: Joi.string().required(),
|
|
82
|
+
id: Joi.string().uuid().required(),
|
|
83
|
+
propertyId: Joi.string().uuid().required()
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The summarized content of "src/api/apps/Logic.js"
|
|
91
|
+
```javascript
|
|
92
|
+
export const Logic = {
|
|
93
|
+
// other endpoint logic implementation ...
|
|
94
|
+
async activateConnection (params) {
|
|
95
|
+
// some code
|
|
96
|
+
if (!hasAccess) {
|
|
97
|
+
throw Oops.permissionDenied('You are not allowed to activate the connection')
|
|
98
|
+
.reason(ErrorReason.insufficientPermission).resource(logicName).meta({ traceId, partnerId, id })
|
|
99
|
+
}
|
|
100
|
+
// some code
|
|
101
|
+
if (!appExists || app.doc.partnerId !== partnerId) {
|
|
102
|
+
throw Oops.notFound('The given app ID does not exist!')
|
|
103
|
+
.reason(ErrorReason.invalidAppId).resource(logicName).meta({ traceId, id })
|
|
104
|
+
}
|
|
105
|
+
// some code
|
|
106
|
+
if (!exists) {
|
|
107
|
+
throw Oops.notAcceptable('There is no connection to activate!')
|
|
108
|
+
.reason(ErrorReason.connectionDoesNotExist).resource(logicName).meta({ traceId, id, propertyId })
|
|
109
|
+
}
|
|
110
|
+
if (connection.doc.status === ConnectionStatus.active) {
|
|
111
|
+
throw Oops.notAcceptable('The connection is already active!')
|
|
112
|
+
.reason(ErrorReason.connectionIsAlreadyActive).resource(logicName).meta({ traceId, id, propertyId })
|
|
113
|
+
}
|
|
114
|
+
// some code
|
|
115
|
+
return { app: app.mask() }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
If an API endpoint returns 204 status, the schema should be like:
|
|
121
|
+
```javascript
|
|
122
|
+
export const Schemas = {
|
|
123
|
+
// other endpoints schemas ...
|
|
124
|
+
emptyResponseEndpoint: {
|
|
125
|
+
request: { /* defintion */ },
|
|
126
|
+
responses: {
|
|
127
|
+
success: {},
|
|
128
|
+
// error reasons ...
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
If an API endpoint input is a file, the schema should be like:
|
|
135
|
+
```javascript
|
|
136
|
+
import { Default } from '../../configs/Default.js'
|
|
137
|
+
|
|
138
|
+
export const Schemas = {
|
|
139
|
+
// other endpoints schemas ...
|
|
140
|
+
createUsersWithCsv: {
|
|
141
|
+
request: {
|
|
142
|
+
csvFile: Joi.object().unknown().keys({
|
|
143
|
+
koaAddedFields: {
|
|
144
|
+
contentType: Joi.string().valid(Default.csvFile.correctMimType),
|
|
145
|
+
size: Joi.number()
|
|
146
|
+
.max(Default.csvFile.maxFileSizeMb)
|
|
147
|
+
.message(`The maximum supported file size is ${Default.csvFile.maxFileSizeMb}Mb`),
|
|
148
|
+
filename: Joi.string()
|
|
149
|
+
}
|
|
150
|
+
}).required()
|
|
151
|
+
},
|
|
152
|
+
responses: { /* definition */ }
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
If an API endpoint is a list endpoint with pagination, filtering, and sorting:
|
|
158
|
+
```javascript
|
|
159
|
+
import { Joi } from '@gokiteam/koa'
|
|
160
|
+
|
|
161
|
+
import { Device } from '../../schemas/Device.js'
|
|
162
|
+
import { ErrorReason } from '../../enums/ErrorReason.js'
|
|
163
|
+
|
|
164
|
+
export const Schemas = {
|
|
165
|
+
// other endpoints schema definitions ...
|
|
166
|
+
list: {
|
|
167
|
+
request: {
|
|
168
|
+
body: {
|
|
169
|
+
propertyId: Joi.string().uuid().required(),
|
|
170
|
+
query: Joi.string().optional(),
|
|
171
|
+
filter: Joi.object({
|
|
172
|
+
status: Joi.string().optional(),
|
|
173
|
+
model: Joi.number().optional()
|
|
174
|
+
}).optional(),
|
|
175
|
+
page: Joi.object({
|
|
176
|
+
limit: Joi.number().integer().min(1).max(100).default(20),
|
|
177
|
+
offset: Joi.number().integer().min(0).default(0)
|
|
178
|
+
}).optional(),
|
|
179
|
+
sort: Joi.array().items(Joi.object({
|
|
180
|
+
field: Joi.string().required(),
|
|
181
|
+
order: Joi.string().valid('asc', 'desc').required()
|
|
182
|
+
})).optional()
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
responses: {
|
|
186
|
+
success: {
|
|
187
|
+
devices: Joi.array().items(Device),
|
|
188
|
+
total: Joi.number().integer()
|
|
189
|
+
},
|
|
190
|
+
[ErrorReason.invalidPropertyId]: {
|
|
191
|
+
traceId: Joi.string().required(),
|
|
192
|
+
propertyId: Joi.string().uuid().required()
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Config definition rules
|
|
2
|
+
- All config should be added to the "src/configs" directory.
|
|
3
|
+
- All values should be defined as environment variables.
|
|
4
|
+
- The environment variables should be added to "config.development" and "config.test" files in the root of the project.
|
|
5
|
+
- The default values or constants should be added to "Default.js"
|
|
6
|
+
- All configs of application's dependent services and tools like databases, cache, search engine, cloud services, etc. should be added to "Application.js"
|
|
7
|
+
- All other microservice API base URLs should be added to "BaseUrls.js"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
## Enums definition patterns
|
|
2
|
+
|
|
3
|
+
We define enums when we see:
|
|
4
|
+
- Finite, Known Sets of Values
|
|
5
|
+
- Statuses or States
|
|
6
|
+
- Categories or Types
|
|
7
|
+
- Options
|
|
8
|
+
|
|
9
|
+
All enums should be defined as objects inside "src/enums" directory in the root of the project.
|
|
10
|
+
The file name should be same as the enum name.
|
|
11
|
+
|
|
12
|
+
Coding pattern:
|
|
13
|
+
|
|
14
|
+
The content of "src/enums/Gender.js":
|
|
15
|
+
```javascript
|
|
16
|
+
export const Gender = Object.freeze({
|
|
17
|
+
male: 'male',
|
|
18
|
+
female: 'female',
|
|
19
|
+
other: 'other'
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
export const GenderValues = Object.values(Gender)
|
|
23
|
+
|
|
24
|
+
```
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# Error Handling Patterns
|
|
2
|
+
|
|
3
|
+
## When to use Oops vs throw
|
|
4
|
+
|
|
5
|
+
### Use Oops for known business logic errors
|
|
6
|
+
- Validation failures
|
|
7
|
+
- Resource not found
|
|
8
|
+
- Permission denied
|
|
9
|
+
- Logical constraints violated
|
|
10
|
+
|
|
11
|
+
### Use throw for unexpected errors
|
|
12
|
+
- Unknown errors that should trigger retry
|
|
13
|
+
- System failures
|
|
14
|
+
- External service failures
|
|
15
|
+
|
|
16
|
+
### Use ProtocolError for device protocol errors
|
|
17
|
+
- Invalid protocol packets
|
|
18
|
+
- Encryption failures
|
|
19
|
+
- Device-specific errors
|
|
20
|
+
|
|
21
|
+
## Error structure pattern
|
|
22
|
+
|
|
23
|
+
All Oops errors should include:
|
|
24
|
+
- **reason**: from ErrorReason enum
|
|
25
|
+
- **resource**: function name (logicName)
|
|
26
|
+
- **meta**: traceId + relevant context
|
|
27
|
+
|
|
28
|
+
Coding pattern:
|
|
29
|
+
```javascript
|
|
30
|
+
import { Oops } from '@gokiteam/oops'
|
|
31
|
+
import { ErrorReason } from '../enums/ErrorReason.js'
|
|
32
|
+
|
|
33
|
+
export const Logic = {
|
|
34
|
+
async create (params) {
|
|
35
|
+
const { data, traceId } = params
|
|
36
|
+
const { email } = data
|
|
37
|
+
const logicName = Logic.create.name
|
|
38
|
+
const exists = await User.doesExistCompositeId({ email })
|
|
39
|
+
if (exists) {
|
|
40
|
+
throw Oops.notAcceptable('The email already exists')
|
|
41
|
+
.reason(ErrorReason.userAlreadyExists)
|
|
42
|
+
.resource(logicName)
|
|
43
|
+
.meta({ traceId, email })
|
|
44
|
+
}
|
|
45
|
+
// ... logic
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Common Oops error types
|
|
51
|
+
|
|
52
|
+
```javascript
|
|
53
|
+
// Not found (404)
|
|
54
|
+
throw Oops.notFound('Device not found')
|
|
55
|
+
.reason(ErrorReason.deviceNotFound)
|
|
56
|
+
.resource(logicName)
|
|
57
|
+
.meta({ traceId, macAddress })
|
|
58
|
+
|
|
59
|
+
// Invalid argument (400)
|
|
60
|
+
throw Oops.invalidArgument('Invalid MAC address format')
|
|
61
|
+
.reason(ErrorReason.invalidMacAddress)
|
|
62
|
+
.resource(logicName)
|
|
63
|
+
.meta({ traceId, macAddress })
|
|
64
|
+
|
|
65
|
+
// Not acceptable (406)
|
|
66
|
+
throw Oops.notAcceptable('Device already exists')
|
|
67
|
+
.reason(ErrorReason.deviceAlreadyExists)
|
|
68
|
+
.resource(logicName)
|
|
69
|
+
.meta({ traceId, macAddress })
|
|
70
|
+
|
|
71
|
+
// Permission denied (403)
|
|
72
|
+
throw Oops.permissionDenied('Insufficient permissions')
|
|
73
|
+
.reason(ErrorReason.insufficientPermission)
|
|
74
|
+
.resource(logicName)
|
|
75
|
+
.meta({ traceId, userId })
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Error chaining with .previous()
|
|
79
|
+
|
|
80
|
+
When catching and re-throwing errors, use `.previous()` to maintain error chain:
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
export const Logic = {
|
|
84
|
+
async create (params) {
|
|
85
|
+
const { data, traceId } = params
|
|
86
|
+
const { email } = data
|
|
87
|
+
const logicName = Logic.create.name
|
|
88
|
+
try {
|
|
89
|
+
const result = await externalService.createUser(email)
|
|
90
|
+
return { user: result }
|
|
91
|
+
} catch (error) {
|
|
92
|
+
throw Oops.internal('Failed to create user in external service')
|
|
93
|
+
.reason(ErrorReason.externalServiceFailed)
|
|
94
|
+
.resource(logicName)
|
|
95
|
+
.previous(error) // Chain the original error
|
|
96
|
+
.meta({ traceId, email })
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## ErrorReason enum usage
|
|
103
|
+
|
|
104
|
+
All error reasons should be defined in `src/enums/ErrorReason.js`:
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
export const ErrorReason = Object.freeze({
|
|
108
|
+
deviceNotFound: 'deviceNotFound',
|
|
109
|
+
deviceAlreadyExists: 'deviceAlreadyExists',
|
|
110
|
+
invalidMacAddress: 'invalidMacAddress',
|
|
111
|
+
userAlreadyExists: 'userAlreadyExists',
|
|
112
|
+
insufficientPermission: 'insufficientPermission'
|
|
113
|
+
// ... more reasons
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Logging patterns
|
|
118
|
+
|
|
119
|
+
### Info logging
|
|
120
|
+
```javascript
|
|
121
|
+
import { Logger } from '../singletons/Logger.js'
|
|
122
|
+
import { LogEvent } from '../enums/LogEvent.js'
|
|
123
|
+
|
|
124
|
+
Logger.log({
|
|
125
|
+
level: 'info',
|
|
126
|
+
message: 'Processing event',
|
|
127
|
+
data: { event: LogEvent.processingEvent, deviceId, traceId }
|
|
128
|
+
})
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Warning logging
|
|
132
|
+
```javascript
|
|
133
|
+
Logger.log({
|
|
134
|
+
level: 'warn',
|
|
135
|
+
message: 'Device not found',
|
|
136
|
+
data: { event: LogEvent.deviceNotFound, deviceId, traceId }
|
|
137
|
+
})
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Error logging
|
|
141
|
+
```javascript
|
|
142
|
+
import { ConvertErrorToLog } from '@gokiteam/utils'
|
|
143
|
+
|
|
144
|
+
Logger.log(ConvertErrorToLog({
|
|
145
|
+
error,
|
|
146
|
+
message: 'Failed to process event',
|
|
147
|
+
data: { event: LogEvent.eventProcessingFailed, traceId }
|
|
148
|
+
}))
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Subscriber error handling
|
|
152
|
+
|
|
153
|
+
### Known errors - don't retry
|
|
154
|
+
```javascript
|
|
155
|
+
async process ({ data, attributes }) {
|
|
156
|
+
const traceId = this.getTraceId({ attributes })
|
|
157
|
+
try {
|
|
158
|
+
// ... processing logic
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (error.isOops) {
|
|
161
|
+
const knownErrorReasons = [
|
|
162
|
+
ErrorReason.deviceNotFound,
|
|
163
|
+
ErrorReason.invalidMqttClient
|
|
164
|
+
]
|
|
165
|
+
if (knownErrorReasons.includes(error.data.reason)) {
|
|
166
|
+
// Don't retry - mark as failed
|
|
167
|
+
return { state: SubscriberResultState.processFailed, error: error.data }
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// Unknown error - let it throw to trigger retry
|
|
171
|
+
throw error
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Unknown errors - trigger retry
|
|
177
|
+
```javascript
|
|
178
|
+
async process ({ data, attributes }) {
|
|
179
|
+
const traceId = this.getTraceId({ attributes })
|
|
180
|
+
// Don't catch unknown errors - let them throw
|
|
181
|
+
// Pub/Sub will retry automatically
|
|
182
|
+
const result = await someOperation({ traceId })
|
|
183
|
+
return { state: SubscriberResultState.processedSuccessfully }
|
|
184
|
+
}
|
|
185
|
+
```
|