@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,137 @@
|
|
|
1
|
+
# HTTP API Implementation Guideline
|
|
2
|
+
|
|
3
|
+
We don't follow REST. We only use `POST` method in the APIs. The document ID and other fields should be sent in the request's **body**.
|
|
4
|
+
|
|
5
|
+
## Endpoints
|
|
6
|
+
|
|
7
|
+
```plain
|
|
8
|
+
create POST /<version>/<collection>/create
|
|
9
|
+
update POST /<version>/<collection>/update
|
|
10
|
+
details POST /<version>/<collection>/details
|
|
11
|
+
list POST /<version>/<collection>/list
|
|
12
|
+
delete POST /<version>/<collection>/delete
|
|
13
|
+
batch create POST /<version>/<collection>/batchCreate
|
|
14
|
+
batch update POST /<version>/<collection>/batchUpdate
|
|
15
|
+
batch details POST /<version>/<collection>/batchDetails
|
|
16
|
+
batch delete POST /<version>/<collection>/batchDelete
|
|
17
|
+
batch action POST /<version>/<collection>/batch<action>
|
|
18
|
+
action POST /<version>/<collection>/<action>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Request
|
|
22
|
+
### API query param convention
|
|
23
|
+
The following table shows the common parameters to be used in API request.
|
|
24
|
+
|
|
25
|
+
| **Field** | **Type** | **Description** |
|
|
26
|
+
|-----------|-----------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
27
|
+
| id | Any | the ID of the resource |
|
|
28
|
+
| ids | Any[] | the list of IDs of the resource |
|
|
29
|
+
| query | String | will be globally searched in the resource |
|
|
30
|
+
| searchOn | String\[\] | the fields on which the "query" can be searched on (if the "query" is present) |
|
|
31
|
+
| filter | Object | an object with predefined keys (filters) |
|
|
32
|
+
| page | Object | an object with two (limit, offset) keys that are used for pagination |
|
|
33
|
+
| sort | Object\[{ field: String, order: 'asc' \| 'desc' }\] | an ordered list of keys that indicates how the list should be sorted |
|
|
34
|
+
| extend | String\[\] | a list of related objects which should be added to each item in the list. Singular or Plural form depends on the response which is an array or a single object. |
|
|
35
|
+
| join | String\[\] | a list of un-related objects which should be added to each item in the list |
|
|
36
|
+
| mask | Object | specifies what fields should be included in the response documents. It's an **object** with the singular name of the documents. |
|
|
37
|
+
| notFilter | Object | an object with predefined keys (not filters) |
|
|
38
|
+
|
|
39
|
+
## Response
|
|
40
|
+
It should always be **an object** with the name of the resources it returns. If the resource is an array, it's plural and if it's a single object, it's singular.
|
|
41
|
+
In the listing APIs, a field named `total` can be presented which indicates the count of the main resource in the DB.
|
|
42
|
+
### Structure
|
|
43
|
+
If the API returns a single resource
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"success": true,
|
|
48
|
+
"status": 200,
|
|
49
|
+
"message": "The request succeeded.",
|
|
50
|
+
"data": { "[RESOURCE]": RESOURCE }
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
If the API returns an array of resources
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"success": true,
|
|
59
|
+
"status": 200,
|
|
60
|
+
"message": "The request succeeded.",
|
|
61
|
+
"data": {
|
|
62
|
+
"[RESOURCE]s": RESOURCE[],
|
|
63
|
+
"total": NUMBER
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
If the API returns a resource and extends other resources
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"success": true,
|
|
73
|
+
"status": 200,
|
|
74
|
+
"message": "The request succeeded.",
|
|
75
|
+
"data": {
|
|
76
|
+
"[RESOURCE]": RESOURCE,
|
|
77
|
+
"[EXTENDED_RESOURCE]": EXTENDED_RESOURCE,
|
|
78
|
+
"[EXTENDED_RESOURCE]s": EXTENDED_RESOURCE[]
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
If the API returns an array of resources and extends other resources
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"success": true,
|
|
88
|
+
"status": 200,
|
|
89
|
+
"message": "The request succeeded.",
|
|
90
|
+
"data": {
|
|
91
|
+
"[RESOURCE]s": RESOURCE[],
|
|
92
|
+
"total": NUMBER,
|
|
93
|
+
"[EXTENDED_RESOURCE]": EXTENDED_RESOURCE,
|
|
94
|
+
"[EXTENDED_RESOURCE]s": EXTENDED_RESOURCE[]
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
If the API throws an error (data may include some other fields related to the error)
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"success": false,
|
|
104
|
+
"status": 5XX | 4XX,
|
|
105
|
+
"errorReason": "REASON",
|
|
106
|
+
"message": "MESSAGE",
|
|
107
|
+
"data": {
|
|
108
|
+
"traceId": "ID",
|
|
109
|
+
"[METADATA]": METADATA,
|
|
110
|
+
...
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**NOTE:** If the API is not defined for retrieving data (not details and list), its response may have a specific structure.
|
|
116
|
+
|
|
117
|
+
### **Status codes**
|
|
118
|
+
|
|
119
|
+
```plain
|
|
120
|
+
----- Success -----
|
|
121
|
+
200 - The request is successfully done, and data is returned in response
|
|
122
|
+
202 - THe request is accepted (it doesn't mean done because maybe it's async or background process. The requester can be notified in 2 ways (polling, socket))
|
|
123
|
+
204 - The request is successfully done, and there is no data to return in response
|
|
124
|
+
|
|
125
|
+
----- Client Errors -----
|
|
126
|
+
400 - validation failed
|
|
127
|
+
401 - unauthorized (the JWT, api-key, etc. is not valid)
|
|
128
|
+
403 - have no permission to call the endpoint (auth token is valid but permission is not granted)
|
|
129
|
+
404 - The ID is not found
|
|
130
|
+
406 - the request logically is not allowed
|
|
131
|
+
409 - the resource already exists
|
|
132
|
+
423 - the resource is locked
|
|
133
|
+
429 - too many requests (this is rate-limiting status)
|
|
134
|
+
|
|
135
|
+
----- Server Errors -----
|
|
136
|
+
500 - an unknown internal error occurred
|
|
137
|
+
```
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Naming Guideline
|
|
2
|
+
|
|
3
|
+
## Variables & Classes
|
|
4
|
+
|
|
5
|
+
1. All imported modules (third-party NPM libraries or local modules) names should be added with pascal-case.
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
import Moment from 'moment'
|
|
9
|
+
import Fs from 'fs'
|
|
10
|
+
import Lodash from 'lodash'
|
|
11
|
+
import { find as Find } from 'lodash' // not recommended but conventionally is correct
|
|
12
|
+
import lodash from 'lodash' // wrong
|
|
13
|
+
import { find } from 'lodash' // wrong
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
2. All variables should be in camel-case even if they contain abbreviations. For example **apiKey**.
|
|
17
|
+
3. The variables which contain a list of objects with the same type should be named in plural form. For example, an array of **hostel** objects should be named **hostels**.
|
|
18
|
+
4. The class/enum/interface name should be a pascal-case.
|
|
19
|
+
5. For boolean variables, you need to start with **is**, **has**, or **does** prefixes. For example **isMale** and **hasChild**.
|
|
20
|
+
|
|
21
|
+
## Methods
|
|
22
|
+
|
|
23
|
+
Names are a combination of words, we have 6 basic actions:
|
|
24
|
+
1. get - when you want to get a single object
|
|
25
|
+
2. list - when you list a few objects
|
|
26
|
+
3. create - when you create an object
|
|
27
|
+
4. update - when you update an existing object (The updated properties should exist)
|
|
28
|
+
5. set - when you update an existing object (You don’t know if the properties exist or not)
|
|
29
|
+
6. remove - when you remove an existing object
|
|
30
|
+
As an example, if you are in a class called User and you want to get a user object your method name is:
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
class User {
|
|
34
|
+
get() {}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
And if you are in the same class but want to get a users profile and get a list of permissions
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
class User {
|
|
42
|
+
get() {},
|
|
43
|
+
getProfile() {},
|
|
44
|
+
listPermissions() {}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
As you can see we do not postfix the method with its own namespace but anything else will be prefixed by their name.
|
|
49
|
+
If you are doing multiple **create**, **update**, and **remove** in a method you should prefix it with **batch**.
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
batchCreate () {}
|
|
53
|
+
batchRemove () {}
|
|
54
|
+
batchUpdate () {}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
If it is a background job or a delayed job to process actions it is postfixed with "job".
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
// a job to create an object
|
|
61
|
+
createJob () {}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
// a job to create many objects in batch
|
|
65
|
+
batchCreateJob () {}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
If you have methods that are doing two actions, such as (create or update) or (create and delete), we order the names by the sequence of the actions.
|
|
69
|
+
If you are using the OR operation between actions, they should be ordered by the (**get, list, create, update, set, remove**).
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
class User {
|
|
73
|
+
//create a user or update a user based on a condition
|
|
74
|
+
createOrUpdate () {}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
//first remove actions in batch then update user
|
|
78
|
+
batchRemoveActionsAndUpdate () {}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
//Remove actions in batch in background then update user
|
|
82
|
+
batchRemoveActionsJobAndUpdate () {}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
If there are methods that are answering a question and returning a boolean, they should be prefixed by "is", "has" or "does".
|
|
87
|
+
|
|
88
|
+
```plain
|
|
89
|
+
isMale() {}
|
|
90
|
+
hasChild() {}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
If there are methods that add or remove items in a field of type array, they should be prefixed with "add" or "remove".
|
|
94
|
+
|
|
95
|
+
```plain
|
|
96
|
+
addImage() {}
|
|
97
|
+
removeCard() {}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
For any other method that doesn’t match the previous rules, the name of the method should start with action.
|
|
101
|
+
|
|
102
|
+
```plain
|
|
103
|
+
calculateDebt() {} //debtCalculation() is wrong
|
|
104
|
+
notifyTravelers() {} //travelersNotification() is wrong
|
|
105
|
+
batchPublish() {} //batchPublishing() is wrong
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Directories & Files
|
|
109
|
+
|
|
110
|
+
Directories should be named in camel-case style, and files should be named in pascal-case. Each file should export a class, function, or object with a name exactly matching the file name.
|
|
111
|
+
|
|
112
|
+
## Pub/Sub Topics & Subscriptions
|
|
113
|
+
|
|
114
|
+
### **Topic name**
|
|
115
|
+
The topic name should represent an event inside the publisher service. For example, if a reservation is updated inside the microservice, the topic name for this event would be: "**reservationUpdated"**. The topic name should be in the past tense and camel-case.
|
|
116
|
+
### **Subscription name**
|
|
117
|
+
The subscription name is a composition of multiple parts. All parts in the name should be camel-case:
|
|
118
|
+
**`<topic name>-<microservice name>-sub`**
|
|
119
|
+
For example**,** if the **pms** microservice wants to subscribe to the "**reservationUpdated"** topic, its subscription name would be "**reservationUpdated-pms-sub**".
|
|
120
|
+
|
|
121
|
+
Add `dev-` and `test-` prefixes for development and test environments. For example:
|
|
122
|
+
- Topic Name: `dev-reservationUpdated`
|
|
123
|
+
- Subscription Name: `dev-reservationUpdated-pms-sub`
|
|
124
|
+
|
|
125
|
+
## Redis Keys & Redlock resource names
|
|
126
|
+
|
|
127
|
+
A Redis key or Redlock resource name should be:
|
|
128
|
+
- Unique
|
|
129
|
+
- Predictable
|
|
130
|
+
- Not overlapping (for example, with other microservices)
|
|
131
|
+
- Can be a combination of multiple identifiers and literals, separated with a ":".
|
|
132
|
+
- Follow this convention:
|
|
133
|
+
- Redis key: `<key>:<microservice name>`
|
|
134
|
+
- Redlock resource name: `<name>:<microservice name>:lock`
|
|
135
|
+
|
|
136
|
+
Examples:
|
|
137
|
+
```javascript
|
|
138
|
+
// microservice name is "bunny"
|
|
139
|
+
const redisKey = `passportNumber:${userId}:bunny`
|
|
140
|
+
const redlockResourceName = `order:${orderId}:bunny:lock`
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Collections & Models
|
|
144
|
+
|
|
145
|
+
1. The database collection, table, or index should be in the plural form.
|
|
146
|
+
```
|
|
147
|
+
# Bad 😖
|
|
148
|
+
user
|
|
149
|
+
staff
|
|
150
|
+
hostel
|
|
151
|
+
|
|
152
|
+
# Good 😌
|
|
153
|
+
users
|
|
154
|
+
staffs
|
|
155
|
+
hostels
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
2. The firestore collections should be prefixed with the environment short name.
|
|
159
|
+
```
|
|
160
|
+
# Bad 😖
|
|
161
|
+
users
|
|
162
|
+
staffs
|
|
163
|
+
hostels
|
|
164
|
+
|
|
165
|
+
# Good 😌
|
|
166
|
+
dev_users
|
|
167
|
+
stg_staffs
|
|
168
|
+
prd_hostels
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
3. The models should be in the singular form
|
|
172
|
+
```
|
|
173
|
+
# Bad 😖
|
|
174
|
+
Users
|
|
175
|
+
Staffs
|
|
176
|
+
Hostels
|
|
177
|
+
|
|
178
|
+
# Good 😌
|
|
179
|
+
User
|
|
180
|
+
Staff
|
|
181
|
+
Hostel
|
|
182
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gokiteam/goki-dev",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Unified local development platform for Goki services",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./client/dist/index.js",
|
|
7
|
+
"types": "./client/dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./client/dist/index.d.ts",
|
|
11
|
+
"import": "./client/dist/index.js",
|
|
12
|
+
"require": "./client/dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"typesVersions": {
|
|
16
|
+
"*": {
|
|
17
|
+
".": [
|
|
18
|
+
"./client/dist/index.d.ts"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"bin": {
|
|
23
|
+
"goki-dev": "./bin/goki-dev.js",
|
|
24
|
+
"goki-dev-mcp": "./bin/mcp-server.js",
|
|
25
|
+
"goki-secrets": "./bin/secrets-cli.js"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"bin/",
|
|
29
|
+
"cli/",
|
|
30
|
+
"src/",
|
|
31
|
+
"client/dist/",
|
|
32
|
+
"config.development",
|
|
33
|
+
"config.test",
|
|
34
|
+
"guidelines/",
|
|
35
|
+
"patterns/"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"start": "dotenv -e config.development node src/Server.js",
|
|
39
|
+
"dev": "dotenv -e config.development node src/Server.js",
|
|
40
|
+
"dev:watch": "dotenv -e config.development node --watch src/Server.js",
|
|
41
|
+
"dev:ui": "concurrently \"npm run dev\" \"cd ui && npm start\" --names \"backend,frontend\" --prefix-colors \"blue,green\"",
|
|
42
|
+
"ui:start": "cd ui && npm start",
|
|
43
|
+
"ui:build": "cd ui && npm run build",
|
|
44
|
+
"build:client": "cd client && npx tsc && echo '{\"type\":\"commonjs\"}' > dist/package.json",
|
|
45
|
+
"prepublishOnly": "npm run build:client",
|
|
46
|
+
"test": "dotenv -e config.test mocha 'tests/**/*.test.js'",
|
|
47
|
+
"test:api": "dotenv -e config.test mocha 'tests/api/**/*.test.js'",
|
|
48
|
+
"test:emulation": "dotenv -e config.test mocha 'tests/emulation/**/*.test.js'",
|
|
49
|
+
"test:integration": "dotenv -e config.test mocha 'tests/integration/**/*.test.js' --timeout 10000",
|
|
50
|
+
"test:all": "npm run test:integration",
|
|
51
|
+
"test:e2e": "playwright test",
|
|
52
|
+
"test:e2e:ui": "playwright test tests/e2e/ui",
|
|
53
|
+
"test:e2e:workflows": "playwright test tests/e2e/workflows",
|
|
54
|
+
"test:e2e:headed": "playwright test --headed",
|
|
55
|
+
"test:e2e:debug": "playwright test --debug",
|
|
56
|
+
"lint": "standard --fix",
|
|
57
|
+
"docker:up": "docker-compose up -d",
|
|
58
|
+
"docker:up:build": "docker-compose up -d --build",
|
|
59
|
+
"docker:down": "docker-compose down",
|
|
60
|
+
"docker:logs": "docker-compose logs -f",
|
|
61
|
+
"docker:services": "docker-compose -f docker-compose.services.yml up -d",
|
|
62
|
+
"mcp": "node bin/mcp-server.js",
|
|
63
|
+
"cleanup:jobs": "node scripts/cleanup-bull-jobs.js"
|
|
64
|
+
},
|
|
65
|
+
"keywords": [
|
|
66
|
+
"goki",
|
|
67
|
+
"dev-tools",
|
|
68
|
+
"pubsub",
|
|
69
|
+
"mqtt",
|
|
70
|
+
"emulator",
|
|
71
|
+
"local-development"
|
|
72
|
+
],
|
|
73
|
+
"author": "Goki Team",
|
|
74
|
+
"license": "UNLICENSED",
|
|
75
|
+
"engines": {
|
|
76
|
+
"node": ">=18.0.0"
|
|
77
|
+
},
|
|
78
|
+
"publishConfig": {
|
|
79
|
+
"access": "public"
|
|
80
|
+
},
|
|
81
|
+
"dependencies": {
|
|
82
|
+
"@gokiteam/koa": "^3.0.6",
|
|
83
|
+
"@gokiteam/oops": "*",
|
|
84
|
+
"@google-cloud/functions-framework": "^5.0.2",
|
|
85
|
+
"@koa/cors": "^5.0.0",
|
|
86
|
+
"@modelcontextprotocol/sdk": "^1.27.0",
|
|
87
|
+
"@portalteam/protocols": "latest",
|
|
88
|
+
"@zowe/secrets-for-zowe-sdk": "^8.29.4",
|
|
89
|
+
"aedes": "^0.51.0",
|
|
90
|
+
"axios": "^1.6.0",
|
|
91
|
+
"better-sqlite3": "^12.6.2",
|
|
92
|
+
"bull": "^3.29.3",
|
|
93
|
+
"chalk": "^5.3.0",
|
|
94
|
+
"commander": "^11.0.0",
|
|
95
|
+
"concurrently": "^9.2.1",
|
|
96
|
+
"dockerode": "^3.3.5",
|
|
97
|
+
"dotenv": "^16.4.0",
|
|
98
|
+
"execa": "^8.0.1",
|
|
99
|
+
"firebase-admin": "^13.6.1",
|
|
100
|
+
"http-proxy": "^1.18.1",
|
|
101
|
+
"inquirer": "^9.2.0",
|
|
102
|
+
"ioredis": "^5.9.2",
|
|
103
|
+
"joi": "^17.13.0",
|
|
104
|
+
"jsonpath-plus": "^10.4.0",
|
|
105
|
+
"koa": "^2.15.0",
|
|
106
|
+
"koa-bodyparser": "^4.4.1",
|
|
107
|
+
"koa-router": "^12.0.1",
|
|
108
|
+
"koa-static": "^5.0.0",
|
|
109
|
+
"macos-touchid": "^1.0.3",
|
|
110
|
+
"moment": "^2.30.0",
|
|
111
|
+
"mqtt-packet": "^9.0.0",
|
|
112
|
+
"node-cron": "^4.2.1",
|
|
113
|
+
"ora": "^7.0.1",
|
|
114
|
+
"pg": "^8.18.0",
|
|
115
|
+
"socket.io": "^4.7.0",
|
|
116
|
+
"uuid": "^9.0.1"
|
|
117
|
+
},
|
|
118
|
+
"devDependencies": {
|
|
119
|
+
"@google-cloud/logging": "^11.2.1",
|
|
120
|
+
"@playwright/test": "^1.58.1",
|
|
121
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
122
|
+
"chai": "^5.1.0",
|
|
123
|
+
"dotenv-cli": "^7.4.0",
|
|
124
|
+
"eventsource": "^4.1.0",
|
|
125
|
+
"mocha": "^10.3.0",
|
|
126
|
+
"mqtt": "^5.15.0",
|
|
127
|
+
"playwright": "^1.58.1",
|
|
128
|
+
"standard": "^17.1.0",
|
|
129
|
+
"supertest": "^6.3.0",
|
|
130
|
+
"typescript": "^5.9.3"
|
|
131
|
+
},
|
|
132
|
+
"standard": {
|
|
133
|
+
"env": [
|
|
134
|
+
"mocha"
|
|
135
|
+
]
|
|
136
|
+
},
|
|
137
|
+
"packageManager": "yarn@3.8.7+sha512.bbe7e310ff7fd20dc63b111110f96fe18192234bb0d4f10441fa6b85d2b644c8923db8fbe6d7886257ace948440ab1f83325ad02af457a1806cdc97f03d2508e"
|
|
138
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
## API Controller definition
|
|
2
|
+
For each API endpoints:
|
|
3
|
+
- There should be a controller function which is a koa middleware
|
|
4
|
+
- The controller exports data from request, then calls the endpoint logic, then attaches response/status to koa context
|
|
5
|
+
|
|
6
|
+
The common way of defining controller is like:
|
|
7
|
+
```javascript
|
|
8
|
+
import { Logic } from './Logic.js'
|
|
9
|
+
|
|
10
|
+
export const Controllers = {
|
|
11
|
+
// other api endpoints controllers...
|
|
12
|
+
async create (ctx) {
|
|
13
|
+
const { traceId } = ctx.custom
|
|
14
|
+
const { body: data } = ctx.request
|
|
15
|
+
ctx.body = await Logic.create({ data, traceId })
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
If the endpoint doesn't return data on API response:
|
|
21
|
+
```javascript
|
|
22
|
+
import { Logic } from './Logic.js'
|
|
23
|
+
|
|
24
|
+
export const Controllers = {
|
|
25
|
+
// other api endpoints controllers...
|
|
26
|
+
async create (ctx) {
|
|
27
|
+
const { traceId } = ctx.custom
|
|
28
|
+
const { body: data } = ctx.request
|
|
29
|
+
ctx.status = 204
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
If the endpoint returns data on response with a status other than 200:
|
|
35
|
+
```javascript
|
|
36
|
+
import { Logic } from './Logic.js'
|
|
37
|
+
|
|
38
|
+
export const Controllers = {
|
|
39
|
+
// other api endpoints controllers...
|
|
40
|
+
async create (ctx) {
|
|
41
|
+
const { traceId } = ctx.custom
|
|
42
|
+
const { body: data } = ctx.request
|
|
43
|
+
ctx.body = await Logic.create({ data, traceId })
|
|
44
|
+
ctx.status = 202
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
If the response should bypass the default response formatting rules:
|
|
50
|
+
```javascript
|
|
51
|
+
import { Logic } from './Logic.js'
|
|
52
|
+
|
|
53
|
+
export const Controllers = {
|
|
54
|
+
// other api endpoints controllers...
|
|
55
|
+
async create (ctx) {
|
|
56
|
+
const { traceId } = ctx.custom
|
|
57
|
+
const { body: data } = ctx.request
|
|
58
|
+
ctx.body = await Logic.create({ data, traceId })
|
|
59
|
+
ctx.custom.isRawBody = true
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
## API Logic definition
|
|
2
|
+
For each API endpoint:
|
|
3
|
+
- There is logic function in "src/api/[collection]/Logic.js"
|
|
4
|
+
- The logic implementation includes (in order)
|
|
5
|
+
- Checking for all logical edge cases and throw Oops errors with proper reasons
|
|
6
|
+
- Check if the supplied entity IDs in the request exist
|
|
7
|
+
- Check value boundaries, logical relationships, limits enforced by configs, etc.
|
|
8
|
+
- The edge cases should be added to "src/api/[collection]/Schemas.js" as error responses
|
|
9
|
+
- All reasons should be defined in "src/enums/ErrorReason.js" enum
|
|
10
|
+
- DB operations, including ACID transaction
|
|
11
|
+
- Call shared logic from `src/logic` directory if required
|
|
12
|
+
- Publish pub/sub messages if required
|
|
13
|
+
- If there is an operator in the request, add
|
|
14
|
+
- Return data for controller to be set in API response
|
|
15
|
+
|
|
16
|
+
Coding pattern:
|
|
17
|
+
```javascript
|
|
18
|
+
import { ErrorReason } from '../../enums/ErrorReason.js'
|
|
19
|
+
import { TryPublishUserCreatedMessage } from '../../logic/TryPublishUserCreatedMessage.js'
|
|
20
|
+
import { User } from '../../models/User.js'
|
|
21
|
+
|
|
22
|
+
export const Logic = {
|
|
23
|
+
// other endpoints logic implementation...
|
|
24
|
+
async create (params) {
|
|
25
|
+
const { data, traceId } = params || {}
|
|
26
|
+
const { firstName, lastName, email } = data
|
|
27
|
+
const logicName = Logic.create.name
|
|
28
|
+
const exists = await User.doesExistCompositeId({ email })
|
|
29
|
+
if (exists) {
|
|
30
|
+
throw Oops.notAcceptable('The email already exists')
|
|
31
|
+
.reason(ErrorReason.userAlreadyExists)
|
|
32
|
+
.resource(logicName)
|
|
33
|
+
.meta({ traceId, email })
|
|
34
|
+
}
|
|
35
|
+
const user = new User({ firstName, lastName, email })
|
|
36
|
+
await user.create()
|
|
37
|
+
return { user: user.mask() }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
If an endpoint needs to update multiple models atomically, use transactions:
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
import { PostgresTransactionHandler } from '@gokiteam/odm'
|
|
46
|
+
import { PostgresPool } from '../../singletons/PostgresPool.js'
|
|
47
|
+
import { Device } from '../../models/Device.js'
|
|
48
|
+
import { DeviceStatus } from '../../models/DeviceStatus.js'
|
|
49
|
+
|
|
50
|
+
export const Logic = {
|
|
51
|
+
// other endpoints logic implementation...
|
|
52
|
+
async create (params) {
|
|
53
|
+
const { data, traceId } = params || {}
|
|
54
|
+
const { propertyId, macAddress, model } = data
|
|
55
|
+
const logicName = Logic.create.name
|
|
56
|
+
const exists = await Device.doesExist(macAddress)
|
|
57
|
+
if (exists) {
|
|
58
|
+
throw Oops.notAcceptable('Device already exists')
|
|
59
|
+
.reason(ErrorReason.deviceAlreadyExists)
|
|
60
|
+
.resource(logicName)
|
|
61
|
+
.meta({ traceId, macAddress })
|
|
62
|
+
}
|
|
63
|
+
const device = new Device({ propertyId, macAddress, model })
|
|
64
|
+
const deviceStatus = new DeviceStatus({ deviceId: macAddress })
|
|
65
|
+
const handler = new PostgresTransactionHandler(PostgresPool)
|
|
66
|
+
await handler
|
|
67
|
+
.create(device)
|
|
68
|
+
.create(deviceStatus)
|
|
69
|
+
.run()
|
|
70
|
+
return { device: device.mask() }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
For more transaction patterns, refer to `patterns/transactions/Patterns.md`.
|
|
76
|
+
|
|
77
|
+
If a request includes an "operator" data, a pub/sub message should be published like this:
|
|
78
|
+
|
|
79
|
+
```javascript
|
|
80
|
+
import Moment from 'moment'
|
|
81
|
+
import { v4 as IdGenerator } from 'uuid'
|
|
82
|
+
|
|
83
|
+
import { Application } from '../../configs/Application.js'
|
|
84
|
+
import { ErrorReason } from '../../enums/ErrorReason.js'
|
|
85
|
+
import { TryPublishBatchEventLogRequired } from '../../logic/TryPublishBatchEventLogRequired.js'
|
|
86
|
+
import { User } from '../../models/User.js'
|
|
87
|
+
|
|
88
|
+
export const Logic = {
|
|
89
|
+
// other endpoints logic implementation...
|
|
90
|
+
async create (params) {
|
|
91
|
+
const { data, traceId } = params || {}
|
|
92
|
+
const { propertyId, firstName, lastName, email, operator } = data
|
|
93
|
+
const logicName = Logic.create.name
|
|
94
|
+
const exists = await User.doesExistCompositeId({ email })
|
|
95
|
+
if (exists) {
|
|
96
|
+
throw Oops.notAcceptable('The email already exists')
|
|
97
|
+
.reason(ErrorReason.userAlreadyExists)
|
|
98
|
+
.resource(logicName)
|
|
99
|
+
.meta({ traceId, email })
|
|
100
|
+
}
|
|
101
|
+
const user = new User({ firstName, lastName, email })
|
|
102
|
+
await user.create()
|
|
103
|
+
TryPublishBatchEventLogRequired({
|
|
104
|
+
propertyId,
|
|
105
|
+
records: [
|
|
106
|
+
{
|
|
107
|
+
operator,
|
|
108
|
+
targetId: user.id,
|
|
109
|
+
targetType: EventTargetType.user,
|
|
110
|
+
data: { new: user.mask(), old: {} },
|
|
111
|
+
event: Event.userCreated
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
traceId
|
|
115
|
+
})
|
|
116
|
+
return { user: user.mask() }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
For complex Logic files with shared logic between multiple endpoints, use internal methods object:
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
const methods = {
|
|
125
|
+
async createDevice (params) {
|
|
126
|
+
const { propertyId, macAddress, model, supportedProtocols } = params || {}
|
|
127
|
+
const logicName = methods.createDevice.name
|
|
128
|
+
// ... shared logic implementation
|
|
129
|
+
const device = new Device({ propertyId, macAddress, model })
|
|
130
|
+
const deviceStatus = new DeviceStatus({ deviceId: macAddress })
|
|
131
|
+
const handler = new PostgresTransactionHandler(PostgresPool)
|
|
132
|
+
await handler.create(device).create(deviceStatus).run()
|
|
133
|
+
return { device: device.mask(), deviceStatus: deviceStatus.mask() }
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export const Logic = {
|
|
138
|
+
// other endpoints logic implementation...
|
|
139
|
+
async create (params) {
|
|
140
|
+
const { data, traceId } = params || {}
|
|
141
|
+
return methods.createDevice({ ...data, traceId })
|
|
142
|
+
},
|
|
143
|
+
async createWithModel (params) {
|
|
144
|
+
const { data, traceId } = params || {}
|
|
145
|
+
const { model } = data
|
|
146
|
+
return methods.createDevice({ ...data, model, traceId })
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
This pattern is useful when:
|
|
152
|
+
- Multiple endpoints share similar logic
|
|
153
|
+
- You need to call the same logic from different entry points
|
|
154
|
+
- You want to keep the exported Logic object clean and focused on endpoint routing
|