@venizia/ignis-docs 0.0.8 → 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 +7 -7
- package/content/best-practices/api-usage-examples.md +15 -12
- package/content/best-practices/architectural-patterns.md +70 -78
- package/content/best-practices/architecture-decisions.md +91 -60
- package/content/best-practices/code-style-standards/advanced-patterns.md +56 -44
- package/content/best-practices/code-style-standards/constants-configuration.md +11 -11
- package/content/best-practices/code-style-standards/control-flow.md +5 -2
- package/content/best-practices/code-style-standards/documentation.md +13 -13
- package/content/best-practices/code-style-standards/function-patterns.md +9 -10
- package/content/best-practices/code-style-standards/index.md +1 -1
- package/content/best-practices/code-style-standards/naming-conventions.md +10 -8
- package/content/best-practices/code-style-standards/route-definitions.md +30 -12
- package/content/best-practices/code-style-standards/tooling.md +8 -5
- package/content/best-practices/code-style-standards/type-safety.md +13 -12
- package/content/best-practices/common-pitfalls.md +56 -37
- package/content/best-practices/contribution-workflow.md +13 -14
- package/content/best-practices/data-modeling.md +46 -22
- package/content/best-practices/deployment-strategies.md +28 -27
- package/content/best-practices/error-handling.md +48 -24
- package/content/best-practices/index.md +5 -5
- package/content/best-practices/performance-optimization.md +40 -31
- package/content/best-practices/security-guidelines.md +52 -23
- package/content/best-practices/testing-strategies.md +65 -51
- package/content/best-practices/troubleshooting-tips.md +24 -24
- package/content/extensions/components/{swagger.md → api-reference.md} +40 -31
- package/content/extensions/components/authentication/api.md +19 -19
- package/content/extensions/components/authentication/errors.md +7 -7
- package/content/extensions/components/authentication/index.md +10 -8
- package/content/extensions/components/authentication/usage.md +101 -6
- package/content/extensions/components/authorization/api.md +45 -25
- package/content/extensions/components/authorization/errors.md +6 -6
- package/content/extensions/components/authorization/index.md +11 -10
- package/content/extensions/components/authorization/usage.md +21 -21
- package/content/extensions/components/health-check.md +1 -1
- package/content/extensions/components/index.md +5 -5
- package/content/extensions/components/mail/errors.md +15 -15
- package/content/extensions/components/mail/index.md +1 -2
- package/content/extensions/components/mail/usage.md +1 -1
- package/content/extensions/components/request-tracker.md +1 -1
- package/content/extensions/components/socket-io/api.md +9 -9
- package/content/extensions/components/socket-io/errors.md +5 -5
- package/content/extensions/components/socket-io/index.md +8 -8
- package/content/extensions/components/socket-io/usage.md +1 -1
- package/content/extensions/components/static-asset/api.md +17 -4
- package/content/extensions/components/static-asset/errors.md +4 -4
- package/content/extensions/components/static-asset/index.md +26 -28
- package/content/extensions/components/static-asset/usage.md +13 -12
- package/content/extensions/components/template/index.md +2 -2
- package/content/extensions/components/template/setup-page.md +1 -1
- package/content/extensions/components/websocket/api.md +3 -3
- package/content/extensions/components/websocket/errors.md +5 -5
- package/content/extensions/components/websocket/index.md +5 -5
- package/content/extensions/components/websocket/usage.md +3 -3
- package/content/extensions/helpers/cron/index.md +2 -2
- package/content/extensions/helpers/crypto/index.md +1 -1
- package/content/extensions/helpers/env/index.md +27 -12
- package/content/extensions/helpers/error/index.md +81 -25
- package/content/extensions/helpers/index.md +2 -3
- package/content/extensions/helpers/inversion/index.md +15 -7
- package/content/extensions/helpers/kafka/compile-binary.md +92 -0
- package/content/extensions/helpers/kafka/examples.md +1 -1
- package/content/extensions/helpers/kafka/index.md +3 -0
- package/content/extensions/helpers/logger/index.md +32 -2
- package/content/extensions/helpers/network/index.md +6 -0
- package/content/extensions/helpers/queue/index.md +14 -17
- package/content/extensions/helpers/redis/index.md +548 -323
- package/content/extensions/helpers/socket-io/index.md +14 -10
- package/content/extensions/helpers/storage/api.md +44 -8
- package/content/extensions/helpers/storage/index.md +43 -7
- package/content/extensions/helpers/template/index.md +6 -3
- package/content/extensions/helpers/types/index.md +11 -8
- package/content/extensions/helpers/websocket/api.md +9 -9
- package/content/extensions/helpers/websocket/index.md +7 -7
- package/content/extensions/helpers/worker-thread/index.md +2 -2
- package/content/extensions/index.md +3 -4
- package/content/extensions/src-details/mcp-server.md +18 -24
- package/content/guides/core-concepts/application/bootstrapping.md +11 -14
- package/content/guides/core-concepts/application/index.md +3 -3
- package/content/guides/core-concepts/components.md +19 -10
- package/content/guides/core-concepts/dependency-injection.md +6 -3
- package/content/guides/core-concepts/grpc-controllers.md +6 -5
- package/content/guides/core-concepts/persistent/datasources.md +42 -43
- package/content/guides/core-concepts/persistent/index.md +16 -7
- package/content/guides/core-concepts/persistent/models.md +24 -20
- package/content/guides/core-concepts/persistent/postgres-drivers.md +201 -0
- package/content/guides/core-concepts/persistent/repositories.md +40 -23
- package/content/guides/core-concepts/persistent/search-meilisearch.md +185 -0
- package/content/guides/core-concepts/persistent/search-typesense.md +431 -0
- package/content/guides/core-concepts/persistent/transactions.md +61 -25
- package/content/guides/core-concepts/rest-controllers.md +12 -9
- package/content/guides/core-concepts/services.md +330 -60
- package/content/guides/get-started/5-minute-quickstart.md +15 -15
- package/content/guides/get-started/philosophy.md +36 -36
- package/content/guides/get-started/setup.md +3 -3
- package/content/guides/index.md +3 -3
- package/content/guides/migrations/redis-helpers-migration.md +177 -0
- package/content/guides/migrations/scoped-rbac-migration.md +17 -17
- package/content/guides/migrations/unified-connectors-migration.md +113 -0
- package/content/guides/reference/glossary.md +19 -12
- package/content/guides/reference/mcp-docs-server.md +22 -18
- package/content/guides/tutorials/building-a-crud-api.md +37 -44
- package/content/guides/tutorials/complete-installation.md +17 -17
- package/content/guides/tutorials/ecommerce-api.md +163 -124
- package/content/guides/tutorials/realtime-chat.md +181 -135
- package/content/guides/tutorials/testing.md +65 -523
- package/content/index.md +2 -180
- package/content/public/apple-touch-icon.png +0 -0
- package/content/public/og-image.png +0 -0
- package/content/public/site.webmanifest +11 -0
- package/content/references/base/application.md +4 -5
- package/content/references/base/bootstrapping.md +18 -5
- package/content/references/base/components.md +149 -120
- package/content/references/base/connectors.md +178 -0
- package/content/references/base/controllers.md +41 -30
- package/content/references/base/datasources.md +163 -92
- package/content/references/base/dependency-injection.md +34 -22
- package/content/references/base/filter-system/application-usage.md +17 -14
- package/content/references/base/filter-system/array-operators.md +7 -2
- package/content/references/base/filter-system/comparison-operators.md +3 -0
- package/content/references/base/filter-system/default-filter.md +89 -71
- package/content/references/base/filter-system/fields-order-pagination.md +22 -22
- package/content/references/base/filter-system/index.md +6 -3
- package/content/references/base/filter-system/json-filtering.md +20 -1
- package/content/references/base/filter-system/list-operators.md +1 -1
- package/content/references/base/filter-system/logical-operators.md +33 -1
- package/content/references/base/filter-system/null-operators.md +30 -1
- package/content/references/base/filter-system/quick-reference.md +23 -4
- package/content/references/base/filter-system/tips.md +5 -5
- package/content/references/base/filter-system/use-cases.md +12 -12
- package/content/references/base/grpc-controllers.md +13 -13
- package/content/references/base/index.md +24 -12
- package/content/references/base/middlewares.md +265 -327
- package/content/references/base/models.md +63 -49
- package/content/references/base/providers.md +136 -130
- package/content/references/base/repositories/advanced.md +59 -58
- package/content/references/base/repositories/index.md +115 -91
- package/content/references/base/repositories/mixins.md +55 -291
- package/content/references/base/repositories/relations.md +54 -64
- package/content/references/base/repositories/soft-deletable.md +31 -30
- package/content/references/base/services.md +296 -93
- package/content/references/configuration/environment-variables.md +49 -31
- package/content/references/configuration/index.md +6 -6
- package/content/references/index.md +17 -12
- package/content/references/quick-reference.md +65 -106
- package/content/references/utilities/crypto.md +65 -23
- package/content/references/utilities/index.md +3 -3
- package/content/references/utilities/jsx.md +6 -4
- package/content/references/utilities/module.md +68 -20
- package/content/references/utilities/parse.md +4 -14
- package/content/references/utilities/promise.md +9 -7
- package/content/references/utilities/schema.md +5 -3
- package/dist/mcp-server/common/guards.d.ts +8 -0
- package/dist/mcp-server/common/guards.d.ts.map +1 -0
- package/dist/mcp-server/common/guards.js +14 -0
- package/dist/mcp-server/common/guards.js.map +1 -0
- package/dist/mcp-server/common/index.d.ts +1 -0
- package/dist/mcp-server/common/index.d.ts.map +1 -1
- package/dist/mcp-server/common/index.js +1 -0
- package/dist/mcp-server/common/index.js.map +1 -1
- package/dist/mcp-server/helpers/docs.helper.d.ts.map +1 -1
- package/dist/mcp-server/helpers/docs.helper.js +4 -2
- package/dist/mcp-server/helpers/docs.helper.js.map +1 -1
- package/dist/mcp-server/helpers/github.helper.js +1 -1
- package/dist/mcp-server/index.js +7 -2
- package/dist/mcp-server/index.js.map +1 -1
- package/dist/mcp-server/tools/base.tool.d.ts +6 -2
- package/dist/mcp-server/tools/base.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/base.tool.js.map +1 -1
- package/dist/mcp-server/tools/docs/search-documents.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/search-code.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/search-code.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/search-code.tool.js +4 -1
- package/dist/mcp-server/tools/github/search-code.tool.js.map +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js +3 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js.map +1 -1
- package/package.json +9 -9
- package/content/extensions/helpers/testing/index.md +0 -510
- package/content/references/base/middleware.md +0 -347
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Building a Real-Time Chat Application
|
|
2
2
|
|
|
3
|
-
This tutorial shows you how to build a real-time chat application with rooms, direct messages, typing indicators, and presence using Socket.IO
|
|
3
|
+
This tutorial shows you how to build a real-time chat application with rooms, direct messages, typing indicators, and presence using Socket.IO - powered by the `SocketIOComponent` and `SocketIOServerHelper`.
|
|
4
4
|
|
|
5
5
|
**What You'll Build:**
|
|
6
6
|
|
|
@@ -29,7 +29,7 @@ bun add hono @hono/zod-openapi @venizia/ignis @venizia/ignis-helpers
|
|
|
29
29
|
bun add drizzle-orm drizzle-zod pg
|
|
30
30
|
bun add -d typescript @types/bun @venizia/dev-configs drizzle-kit @types/pg
|
|
31
31
|
|
|
32
|
-
# For Bun runtime
|
|
32
|
+
# For Bun runtime - Socket.IO engine
|
|
33
33
|
bun add @socket.io/bun-engine
|
|
34
34
|
```
|
|
35
35
|
|
|
@@ -46,10 +46,10 @@ Models in IGNIS combine Drizzle ORM schemas with Entity classes.
|
|
|
46
46
|
// src/models/user.model.ts
|
|
47
47
|
import {
|
|
48
48
|
BaseEntity,
|
|
49
|
-
createRelations,
|
|
50
49
|
generateIdColumnDefs,
|
|
51
50
|
generateTzColumnDefs,
|
|
52
51
|
model,
|
|
52
|
+
TRelationConfig,
|
|
53
53
|
TTableObject,
|
|
54
54
|
} from '@venizia/ignis';
|
|
55
55
|
import { pgTable, varchar, text, timestamp, boolean } from 'drizzle-orm/pg-core';
|
|
@@ -64,18 +64,13 @@ export const userTable = pgTable('User', {
|
|
|
64
64
|
lastSeenAt: timestamp('last_seen_at'),
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
-
export const userRelations = createRelations({
|
|
68
|
-
source: userTable,
|
|
69
|
-
relations: [],
|
|
70
|
-
});
|
|
71
|
-
|
|
72
67
|
export type TUserSchema = typeof userTable;
|
|
73
68
|
export type TUser = TTableObject<TUserSchema>;
|
|
74
69
|
|
|
75
70
|
@model({ type: 'entity' })
|
|
76
71
|
export class User extends BaseEntity<typeof User.schema> {
|
|
77
72
|
static override schema = userTable;
|
|
78
|
-
static override relations = () =>
|
|
73
|
+
static override relations = (): TRelationConfig[] => [];
|
|
79
74
|
static override TABLE_NAME = 'User';
|
|
80
75
|
}
|
|
81
76
|
```
|
|
@@ -86,10 +81,11 @@ export class User extends BaseEntity<typeof User.schema> {
|
|
|
86
81
|
// src/models/room.model.ts
|
|
87
82
|
import {
|
|
88
83
|
BaseEntity,
|
|
89
|
-
createRelations,
|
|
90
84
|
generateIdColumnDefs,
|
|
91
85
|
generateTzColumnDefs,
|
|
92
86
|
model,
|
|
87
|
+
RelationTypes,
|
|
88
|
+
TRelationConfig,
|
|
93
89
|
TTableObject,
|
|
94
90
|
} from '@venizia/ignis';
|
|
95
91
|
import { pgTable, varchar, text, timestamp, boolean } from 'drizzle-orm/pg-core';
|
|
@@ -113,22 +109,6 @@ export const roomMemberTable = pgTable('RoomMember', {
|
|
|
113
109
|
lastReadAt: timestamp('last_read_at'),
|
|
114
110
|
});
|
|
115
111
|
|
|
116
|
-
export const roomRelations = createRelations({
|
|
117
|
-
source: roomTable,
|
|
118
|
-
relations: [
|
|
119
|
-
{ type: 'one', name: 'creator', target: () => userTable, fields: ['createdBy'], references: ['id'] },
|
|
120
|
-
{ type: 'many', name: 'members', target: () => roomMemberTable, fields: ['id'], references: ['roomId'] },
|
|
121
|
-
],
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
export const roomMemberRelations = createRelations({
|
|
125
|
-
source: roomMemberTable,
|
|
126
|
-
relations: [
|
|
127
|
-
{ type: 'one', name: 'room', target: () => roomTable, fields: ['roomId'], references: ['id'] },
|
|
128
|
-
{ type: 'one', name: 'user', target: () => userTable, fields: ['userId'], references: ['id'] },
|
|
129
|
-
],
|
|
130
|
-
});
|
|
131
|
-
|
|
132
112
|
export type TRoomSchema = typeof roomTable;
|
|
133
113
|
export type TRoom = TTableObject<TRoomSchema>;
|
|
134
114
|
export type TRoomMemberSchema = typeof roomMemberTable;
|
|
@@ -137,14 +117,44 @@ export type TRoomMember = TTableObject<TRoomMemberSchema>;
|
|
|
137
117
|
@model({ type: 'entity' })
|
|
138
118
|
export class Room extends BaseEntity<typeof Room.schema> {
|
|
139
119
|
static override schema = roomTable;
|
|
140
|
-
|
|
120
|
+
|
|
121
|
+
static override relations = (): TRelationConfig[] => [
|
|
122
|
+
{
|
|
123
|
+
name: 'creator',
|
|
124
|
+
type: RelationTypes.ONE,
|
|
125
|
+
schema: userTable,
|
|
126
|
+
metadata: { fields: [roomTable.createdBy], references: [userTable.id] },
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'members',
|
|
130
|
+
type: RelationTypes.MANY,
|
|
131
|
+
schema: roomMemberTable,
|
|
132
|
+
metadata: { fields: [roomTable.id], references: [roomMemberTable.roomId] },
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
|
|
141
136
|
static override TABLE_NAME = 'Room';
|
|
142
137
|
}
|
|
143
138
|
|
|
144
139
|
@model({ type: 'entity' })
|
|
145
140
|
export class RoomMember extends BaseEntity<typeof RoomMember.schema> {
|
|
146
141
|
static override schema = roomMemberTable;
|
|
147
|
-
|
|
142
|
+
|
|
143
|
+
static override relations = (): TRelationConfig[] => [
|
|
144
|
+
{
|
|
145
|
+
name: 'room',
|
|
146
|
+
type: RelationTypes.ONE,
|
|
147
|
+
schema: roomTable,
|
|
148
|
+
metadata: { fields: [roomMemberTable.roomId], references: [roomTable.id] },
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: 'user',
|
|
152
|
+
type: RelationTypes.ONE,
|
|
153
|
+
schema: userTable,
|
|
154
|
+
metadata: { fields: [roomMemberTable.userId], references: [userTable.id] },
|
|
155
|
+
},
|
|
156
|
+
];
|
|
157
|
+
|
|
148
158
|
static override TABLE_NAME = 'RoomMember';
|
|
149
159
|
}
|
|
150
160
|
```
|
|
@@ -155,10 +165,11 @@ export class RoomMember extends BaseEntity<typeof RoomMember.schema> {
|
|
|
155
165
|
// src/models/message.model.ts
|
|
156
166
|
import {
|
|
157
167
|
BaseEntity,
|
|
158
|
-
createRelations,
|
|
159
168
|
generateIdColumnDefs,
|
|
160
169
|
generateTzColumnDefs,
|
|
161
170
|
model,
|
|
171
|
+
RelationTypes,
|
|
172
|
+
TRelationConfig,
|
|
162
173
|
TTableObject,
|
|
163
174
|
} from '@venizia/ignis';
|
|
164
175
|
import { pgTable, text, timestamp, varchar } from 'drizzle-orm/pg-core';
|
|
@@ -189,22 +200,6 @@ export const directMessageTable = pgTable('DirectMessage', {
|
|
|
189
200
|
deletedAt: timestamp('deleted_at'),
|
|
190
201
|
});
|
|
191
202
|
|
|
192
|
-
export const messageRelations = createRelations({
|
|
193
|
-
source: messageTable,
|
|
194
|
-
relations: [
|
|
195
|
-
{ type: 'one', name: 'room', target: () => roomTable, fields: ['roomId'], references: ['id'] },
|
|
196
|
-
{ type: 'one', name: 'sender', target: () => userTable, fields: ['senderId'], references: ['id'] },
|
|
197
|
-
],
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
export const directMessageRelations = createRelations({
|
|
201
|
-
source: directMessageTable,
|
|
202
|
-
relations: [
|
|
203
|
-
{ type: 'one', name: 'sender', target: () => userTable, fields: ['senderId'], references: ['id'] },
|
|
204
|
-
{ type: 'one', name: 'receiver', target: () => userTable, fields: ['receiverId'], references: ['id'] },
|
|
205
|
-
],
|
|
206
|
-
});
|
|
207
|
-
|
|
208
203
|
export type TMessageSchema = typeof messageTable;
|
|
209
204
|
export type TMessage = TTableObject<TMessageSchema>;
|
|
210
205
|
export type TDirectMessageSchema = typeof directMessageTable;
|
|
@@ -213,14 +208,44 @@ export type TDirectMessage = TTableObject<TDirectMessageSchema>;
|
|
|
213
208
|
@model({ type: 'entity' })
|
|
214
209
|
export class Message extends BaseEntity<typeof Message.schema> {
|
|
215
210
|
static override schema = messageTable;
|
|
216
|
-
|
|
211
|
+
|
|
212
|
+
static override relations = (): TRelationConfig[] => [
|
|
213
|
+
{
|
|
214
|
+
name: 'room',
|
|
215
|
+
type: RelationTypes.ONE,
|
|
216
|
+
schema: roomTable,
|
|
217
|
+
metadata: { fields: [messageTable.roomId], references: [roomTable.id] },
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: 'sender',
|
|
221
|
+
type: RelationTypes.ONE,
|
|
222
|
+
schema: userTable,
|
|
223
|
+
metadata: { fields: [messageTable.senderId], references: [userTable.id] },
|
|
224
|
+
},
|
|
225
|
+
];
|
|
226
|
+
|
|
217
227
|
static override TABLE_NAME = 'Message';
|
|
218
228
|
}
|
|
219
229
|
|
|
220
230
|
@model({ type: 'entity' })
|
|
221
231
|
export class DirectMessage extends BaseEntity<typeof DirectMessage.schema> {
|
|
222
232
|
static override schema = directMessageTable;
|
|
223
|
-
|
|
233
|
+
|
|
234
|
+
static override relations = (): TRelationConfig[] => [
|
|
235
|
+
{
|
|
236
|
+
name: 'sender',
|
|
237
|
+
type: RelationTypes.ONE,
|
|
238
|
+
schema: userTable,
|
|
239
|
+
metadata: { fields: [directMessageTable.senderId], references: [userTable.id] },
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
name: 'receiver',
|
|
243
|
+
type: RelationTypes.ONE,
|
|
244
|
+
schema: userTable,
|
|
245
|
+
metadata: { fields: [directMessageTable.receiverId], references: [userTable.id] },
|
|
246
|
+
},
|
|
247
|
+
];
|
|
248
|
+
|
|
224
249
|
static override TABLE_NAME = 'DirectMessage';
|
|
225
250
|
}
|
|
226
251
|
```
|
|
@@ -243,7 +268,7 @@ import {
|
|
|
243
268
|
datasource,
|
|
244
269
|
ValueOrPromise,
|
|
245
270
|
} from '@venizia/ignis';
|
|
246
|
-
import {
|
|
271
|
+
import { NodePostgresDriver } from '@venizia/ignis/postgres/node-postgres';
|
|
247
272
|
import { Pool } from 'pg';
|
|
248
273
|
|
|
249
274
|
interface IDSConfigs {
|
|
@@ -254,7 +279,7 @@ interface IDSConfigs {
|
|
|
254
279
|
password: string;
|
|
255
280
|
}
|
|
256
281
|
|
|
257
|
-
@datasource({ driver:
|
|
282
|
+
@datasource({ driver: NodePostgresDriver })
|
|
258
283
|
export class PostgresDataSource extends BaseDataSource<IDSConfigs> {
|
|
259
284
|
constructor() {
|
|
260
285
|
super({
|
|
@@ -270,16 +295,18 @@ export class PostgresDataSource extends BaseDataSource<IDSConfigs> {
|
|
|
270
295
|
}
|
|
271
296
|
|
|
272
297
|
override configure(): ValueOrPromise<void> {
|
|
273
|
-
const schema = this.getSchema();
|
|
298
|
+
const schema = Object.keys(this.getSchema());
|
|
274
299
|
|
|
275
300
|
this.logger.debug(
|
|
276
301
|
'[configure] Auto-discovered schema | Schema + Relations (%s): %o',
|
|
277
|
-
|
|
278
|
-
|
|
302
|
+
schema.length,
|
|
303
|
+
schema,
|
|
279
304
|
);
|
|
280
305
|
|
|
281
|
-
|
|
282
|
-
|
|
306
|
+
// The client must land on this.client - a local would leave beginTransaction() with nothing
|
|
307
|
+
// to resolve a driver from, and it would throw `No driver and no client`. NodePostgresDriver
|
|
308
|
+
// named in @datasource above is what wires the driver and Drizzle connector from it.
|
|
309
|
+
this.client = new Pool(this.settings);
|
|
283
310
|
}
|
|
284
311
|
}
|
|
285
312
|
```
|
|
@@ -325,25 +352,27 @@ export class RoomRepository extends DefaultCRUDRepository<typeof Room.schema> {
|
|
|
325
352
|
|
|
326
353
|
async findByUser(opts: { userId: string }) {
|
|
327
354
|
const memberships = await this._memberRepo.find({
|
|
328
|
-
where: { userId: opts.userId },
|
|
329
|
-
include: { room: true },
|
|
355
|
+
filter: { where: { userId: opts.userId }, include: [{ relation: 'room' }] },
|
|
330
356
|
});
|
|
331
357
|
return memberships.map(m => m.room);
|
|
332
358
|
}
|
|
333
359
|
|
|
334
360
|
async isMember(opts: { roomId: string; userId: string }): Promise<boolean> {
|
|
335
361
|
const member = await this._memberRepo.findOne({
|
|
336
|
-
where: { roomId: opts.roomId, userId: opts.userId },
|
|
362
|
+
filter: { where: { roomId: opts.roomId, userId: opts.userId } },
|
|
337
363
|
});
|
|
338
364
|
return !!member;
|
|
339
365
|
}
|
|
340
366
|
|
|
341
367
|
async addMember(opts: { roomId: string; userId: string; role?: string }) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
368
|
+
const { data } = await this._memberRepo.create({
|
|
369
|
+
data: {
|
|
370
|
+
roomId: opts.roomId,
|
|
371
|
+
userId: opts.userId,
|
|
372
|
+
role: opts.role ?? 'member',
|
|
373
|
+
},
|
|
346
374
|
});
|
|
375
|
+
return data;
|
|
347
376
|
}
|
|
348
377
|
|
|
349
378
|
async removeMember(opts: { roomId: string; userId: string }) {
|
|
@@ -354,14 +383,13 @@ export class RoomRepository extends DefaultCRUDRepository<typeof Room.schema> {
|
|
|
354
383
|
|
|
355
384
|
async getMember(opts: { roomId: string; userId: string }) {
|
|
356
385
|
return this._memberRepo.findOne({
|
|
357
|
-
where: { roomId: opts.roomId, userId: opts.userId },
|
|
386
|
+
filter: { where: { roomId: opts.roomId, userId: opts.userId } },
|
|
358
387
|
});
|
|
359
388
|
}
|
|
360
389
|
|
|
361
390
|
async getMembers(opts: { roomId: string }) {
|
|
362
391
|
return this._memberRepo.find({
|
|
363
|
-
where: { roomId: opts.roomId },
|
|
364
|
-
include: { user: true },
|
|
392
|
+
filter: { where: { roomId: opts.roomId }, include: [{ relation: 'user' }] },
|
|
365
393
|
});
|
|
366
394
|
}
|
|
367
395
|
}
|
|
@@ -393,7 +421,8 @@ export class MessageRepository extends DefaultCRUDRepository<typeof Message.sche
|
|
|
393
421
|
}
|
|
394
422
|
|
|
395
423
|
async createDirectMessage(opts: { senderId: string; receiverId: string; content: string }) {
|
|
396
|
-
|
|
424
|
+
const { data } = await this._dmRepo.create({ data: opts });
|
|
425
|
+
return data;
|
|
397
426
|
}
|
|
398
427
|
|
|
399
428
|
async findDirectMessages(opts: {
|
|
@@ -403,26 +432,26 @@ export class MessageRepository extends DefaultCRUDRepository<typeof Message.sche
|
|
|
403
432
|
before?: string;
|
|
404
433
|
}) {
|
|
405
434
|
return this._dmRepo.find({
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
435
|
+
filter: {
|
|
436
|
+
where: {
|
|
437
|
+
or: [
|
|
438
|
+
{ senderId: opts.userId1, receiverId: opts.userId2 },
|
|
439
|
+
{ senderId: opts.userId2, receiverId: opts.userId1 },
|
|
440
|
+
],
|
|
441
|
+
},
|
|
442
|
+
order: ['createdAt DESC'],
|
|
443
|
+
limit: opts.limit ?? 50,
|
|
411
444
|
},
|
|
412
|
-
orderBy: { createdAt: 'desc' },
|
|
413
|
-
limit: opts.limit ?? 50,
|
|
414
445
|
});
|
|
415
446
|
}
|
|
416
447
|
|
|
417
448
|
async findConversations(opts: { userId: string }) {
|
|
418
449
|
// Get unique conversation partners
|
|
419
450
|
const sent = await this._dmRepo.find({
|
|
420
|
-
where: { senderId: opts.userId },
|
|
421
|
-
include: { receiver: true },
|
|
451
|
+
filter: { where: { senderId: opts.userId }, include: [{ relation: 'receiver' }] },
|
|
422
452
|
});
|
|
423
453
|
const received = await this._dmRepo.find({
|
|
424
|
-
where: { receiverId: opts.userId },
|
|
425
|
-
include: { sender: true },
|
|
454
|
+
filter: { where: { receiverId: opts.userId }, include: [{ relation: 'sender' }] },
|
|
426
455
|
});
|
|
427
456
|
|
|
428
457
|
// Combine and deduplicate
|
|
@@ -450,7 +479,8 @@ import {
|
|
|
450
479
|
BindingKeys,
|
|
451
480
|
BindingNamespaces,
|
|
452
481
|
} from '@venizia/ignis';
|
|
453
|
-
import {
|
|
482
|
+
import { getError } from '@venizia/ignis-helpers';
|
|
483
|
+
import { ISocketIOClient, SocketIOServerHelper } from '@venizia/ignis-helpers/socket-io';
|
|
454
484
|
import { Socket } from 'socket.io';
|
|
455
485
|
import { MessageRepository } from '../repositories/message.repository';
|
|
456
486
|
import { RoomRepository } from '../repositories/room.repository';
|
|
@@ -492,7 +522,7 @@ export class ChatService extends BaseService {
|
|
|
492
522
|
}
|
|
493
523
|
|
|
494
524
|
// ---------------------------------------------------------------------------
|
|
495
|
-
// SocketIOServerHelper
|
|
525
|
+
// SocketIOServerHelper - lazy getter (bound after server starts via post-start hook)
|
|
496
526
|
// ---------------------------------------------------------------------------
|
|
497
527
|
private get socketIOHelper(): SocketIOServerHelper {
|
|
498
528
|
if (!this._socketIOHelper) {
|
|
@@ -511,7 +541,7 @@ export class ChatService extends BaseService {
|
|
|
511
541
|
}
|
|
512
542
|
|
|
513
543
|
// ---------------------------------------------------------------------------
|
|
514
|
-
// Socket event handlers
|
|
544
|
+
// Socket event handlers - called from clientConnectedFn after authentication
|
|
515
545
|
// ---------------------------------------------------------------------------
|
|
516
546
|
registerClientHandlers(opts: { socket: Socket }) {
|
|
517
547
|
const logger = this.logger.for(this.registerClientHandlers.name);
|
|
@@ -699,7 +729,7 @@ export class ChatService extends BaseService {
|
|
|
699
729
|
// Room operations
|
|
700
730
|
// ---------------------------------------------------------------------------
|
|
701
731
|
async createRoom(opts: { name: string; description?: string; isPrivate?: boolean; createdBy: string }) {
|
|
702
|
-
const room = await this._roomRepo.create(opts);
|
|
732
|
+
const { data: room } = await this._roomRepo.create({ data: opts });
|
|
703
733
|
|
|
704
734
|
// Add creator as admin
|
|
705
735
|
await this._roomRepo.addMember({ roomId: room.id, userId: opts.createdBy, role: 'admin' });
|
|
@@ -708,7 +738,7 @@ export class ChatService extends BaseService {
|
|
|
708
738
|
}
|
|
709
739
|
|
|
710
740
|
async joinRoom(opts: { roomId: string; userId: string }) {
|
|
711
|
-
const room = await this._roomRepo.findById(opts.roomId);
|
|
741
|
+
const room = await this._roomRepo.findById({ id: opts.roomId });
|
|
712
742
|
if (!room) {
|
|
713
743
|
throw getError({ statusCode: 404, message: 'Room not found' });
|
|
714
744
|
}
|
|
@@ -742,14 +772,16 @@ export class ChatService extends BaseService {
|
|
|
742
772
|
throw getError({ statusCode: 403, message: 'Not a member of this room' });
|
|
743
773
|
}
|
|
744
774
|
|
|
745
|
-
const message = await this._messageRepo.create({
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
775
|
+
const { data: message } = await this._messageRepo.create({
|
|
776
|
+
data: {
|
|
777
|
+
roomId: opts.roomId,
|
|
778
|
+
senderId: opts.senderId,
|
|
779
|
+
content: opts.content,
|
|
780
|
+
type: opts.type ?? 'text',
|
|
781
|
+
},
|
|
750
782
|
});
|
|
751
783
|
|
|
752
|
-
const sender = await this._userRepo.findById(opts.senderId);
|
|
784
|
+
const sender = await this._userRepo.findById({ id: opts.senderId });
|
|
753
785
|
|
|
754
786
|
return {
|
|
755
787
|
...message,
|
|
@@ -763,7 +795,7 @@ export class ChatService extends BaseService {
|
|
|
763
795
|
}
|
|
764
796
|
|
|
765
797
|
async editMessage(opts: { messageId: string; userId: string; content: string }) {
|
|
766
|
-
const message = await this._messageRepo.findById(opts.messageId);
|
|
798
|
+
const message = await this._messageRepo.findById({ id: opts.messageId });
|
|
767
799
|
|
|
768
800
|
if (!message) {
|
|
769
801
|
throw getError({ statusCode: 404, message: 'Message not found' });
|
|
@@ -773,14 +805,17 @@ export class ChatService extends BaseService {
|
|
|
773
805
|
throw getError({ statusCode: 403, message: 'Cannot edit others messages' });
|
|
774
806
|
}
|
|
775
807
|
|
|
776
|
-
return this._messageRepo.updateById(
|
|
777
|
-
|
|
778
|
-
|
|
808
|
+
return this._messageRepo.updateById({
|
|
809
|
+
id: opts.messageId,
|
|
810
|
+
data: {
|
|
811
|
+
content: opts.content,
|
|
812
|
+
editedAt: new Date(),
|
|
813
|
+
},
|
|
779
814
|
});
|
|
780
815
|
}
|
|
781
816
|
|
|
782
817
|
async deleteMessage(opts: { messageId: string; userId: string }) {
|
|
783
|
-
const message = await this._messageRepo.findById(opts.messageId);
|
|
818
|
+
const message = await this._messageRepo.findById({ id: opts.messageId });
|
|
784
819
|
|
|
785
820
|
if (!message) {
|
|
786
821
|
throw getError({ statusCode: 404, message: 'Message not found' });
|
|
@@ -793,8 +828,11 @@ export class ChatService extends BaseService {
|
|
|
793
828
|
}
|
|
794
829
|
}
|
|
795
830
|
|
|
796
|
-
return this._messageRepo.updateById(
|
|
797
|
-
|
|
831
|
+
return this._messageRepo.updateById({
|
|
832
|
+
id: opts.messageId,
|
|
833
|
+
data: {
|
|
834
|
+
deletedAt: new Date(),
|
|
835
|
+
},
|
|
798
836
|
});
|
|
799
837
|
}
|
|
800
838
|
|
|
@@ -805,16 +843,18 @@ export class ChatService extends BaseService {
|
|
|
805
843
|
};
|
|
806
844
|
|
|
807
845
|
if (opts.before) {
|
|
808
|
-
const beforeMessage = await this._messageRepo.findById(opts.before);
|
|
846
|
+
const beforeMessage = await this._messageRepo.findById({ id: opts.before });
|
|
809
847
|
if (beforeMessage) {
|
|
810
848
|
where.createdAt = { lt: beforeMessage.createdAt };
|
|
811
849
|
}
|
|
812
850
|
}
|
|
813
851
|
|
|
814
852
|
return this._messageRepo.find({
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
853
|
+
filter: {
|
|
854
|
+
where,
|
|
855
|
+
order: ['createdAt DESC'],
|
|
856
|
+
limit: opts.limit ?? 50,
|
|
857
|
+
},
|
|
818
858
|
});
|
|
819
859
|
}
|
|
820
860
|
|
|
@@ -842,16 +882,22 @@ export class ChatService extends BaseService {
|
|
|
842
882
|
// Presence operations
|
|
843
883
|
// ---------------------------------------------------------------------------
|
|
844
884
|
async setOnline(opts: { userId: string }) {
|
|
845
|
-
await this._userRepo.updateById(
|
|
846
|
-
|
|
847
|
-
|
|
885
|
+
await this._userRepo.updateById({
|
|
886
|
+
id: opts.userId,
|
|
887
|
+
data: {
|
|
888
|
+
isOnline: true,
|
|
889
|
+
lastSeenAt: new Date(),
|
|
890
|
+
},
|
|
848
891
|
});
|
|
849
892
|
}
|
|
850
893
|
|
|
851
894
|
async setOffline(opts: { userId: string }) {
|
|
852
|
-
await this._userRepo.updateById(
|
|
853
|
-
|
|
854
|
-
|
|
895
|
+
await this._userRepo.updateById({
|
|
896
|
+
id: opts.userId,
|
|
897
|
+
data: {
|
|
898
|
+
isOnline: false,
|
|
899
|
+
lastSeenAt: new Date(),
|
|
900
|
+
},
|
|
855
901
|
});
|
|
856
902
|
}
|
|
857
903
|
|
|
@@ -948,7 +994,7 @@ import {
|
|
|
948
994
|
import {
|
|
949
995
|
applicationEnvironment,
|
|
950
996
|
type ISocketIOServerBaseOptions,
|
|
951
|
-
|
|
997
|
+
RedisSingleHelper,
|
|
952
998
|
SocketIOServerHelper,
|
|
953
999
|
} from '@venizia/ignis-helpers';
|
|
954
1000
|
import { ChatService } from './services/chat.service';
|
|
@@ -958,7 +1004,7 @@ import { RoomRepository, RoomMemberRepository } from './repositories/room.reposi
|
|
|
958
1004
|
import { MessageRepository, DirectMessageRepository } from './repositories/message.repository';
|
|
959
1005
|
|
|
960
1006
|
export class ChatApp extends BaseApplication {
|
|
961
|
-
private redisHelper:
|
|
1007
|
+
private redisHelper: RedisSingleHelper;
|
|
962
1008
|
|
|
963
1009
|
getAppInfo(): IApplicationInfo {
|
|
964
1010
|
return { name: 'chat-api', version: '1.0.0' };
|
|
@@ -990,9 +1036,9 @@ export class ChatApp extends BaseApplication {
|
|
|
990
1036
|
|
|
991
1037
|
// ---------------------------------------------------------------------------
|
|
992
1038
|
private setupSocketIO() {
|
|
993
|
-
// 1. Redis connection
|
|
1039
|
+
// 1. Redis connection - SocketIOServerHelper creates 3 duplicate connections
|
|
994
1040
|
// for adapter (pub/sub) and emitter automatically
|
|
995
|
-
this.redisHelper = new
|
|
1041
|
+
this.redisHelper = new RedisSingleHelper({
|
|
996
1042
|
name: 'chat-redis',
|
|
997
1043
|
host: process.env.APP_ENV_REDIS_HOST ?? 'localhost',
|
|
998
1044
|
port: +(process.env.APP_ENV_REDIS_PORT ?? 6379),
|
|
@@ -1000,11 +1046,11 @@ export class ChatApp extends BaseApplication {
|
|
|
1000
1046
|
autoConnect: false,
|
|
1001
1047
|
});
|
|
1002
1048
|
|
|
1003
|
-
this.bind<
|
|
1049
|
+
this.bind<RedisSingleHelper>({
|
|
1004
1050
|
key: SocketIOBindingKeys.REDIS_CONNECTION,
|
|
1005
1051
|
}).toValue(this.redisHelper);
|
|
1006
1052
|
|
|
1007
|
-
// 2. Authentication handler
|
|
1053
|
+
// 2. Authentication handler - called when a client emits 'authenticate'
|
|
1008
1054
|
// Receives the Socket.IO handshake (headers, query, auth object)
|
|
1009
1055
|
const authenticateFn: ISocketIOServerBaseOptions['authenticateFn'] = handshake => {
|
|
1010
1056
|
const token =
|
|
@@ -1024,7 +1070,7 @@ export class ChatApp extends BaseApplication {
|
|
|
1024
1070
|
key: SocketIOBindingKeys.AUTHENTICATE_HANDLER,
|
|
1025
1071
|
}).toValue(authenticateFn);
|
|
1026
1072
|
|
|
1027
|
-
// 3. Client connected handler
|
|
1073
|
+
// 3. Client connected handler - called AFTER successful authentication
|
|
1028
1074
|
// This is where you register custom event handlers on each socket
|
|
1029
1075
|
const clientConnectedFn: ISocketIOServerBaseOptions['clientConnectedFn'] = ({ socket }) => {
|
|
1030
1076
|
const chatService = this.get<ChatService>({
|
|
@@ -1041,7 +1087,7 @@ export class ChatApp extends BaseApplication {
|
|
|
1041
1087
|
key: SocketIOBindingKeys.CLIENT_CONNECTED_HANDLER,
|
|
1042
1088
|
}).toValue(clientConnectedFn);
|
|
1043
1089
|
|
|
1044
|
-
// 4. (Optional) Custom server options
|
|
1090
|
+
// 4. (Optional) Custom server options - override defaults
|
|
1045
1091
|
this.bind({
|
|
1046
1092
|
key: SocketIOBindingKeys.SERVER_OPTIONS,
|
|
1047
1093
|
}).toValue({
|
|
@@ -1054,7 +1100,7 @@ export class ChatApp extends BaseApplication {
|
|
|
1054
1100
|
},
|
|
1055
1101
|
});
|
|
1056
1102
|
|
|
1057
|
-
// 5. Register the component
|
|
1103
|
+
// 5. Register the component - that's it!
|
|
1058
1104
|
// SocketIOComponent handles:
|
|
1059
1105
|
// - Runtime detection (Node.js / Bun)
|
|
1060
1106
|
// - Post-start hook to create SocketIOServerHelper after server starts
|
|
@@ -1096,7 +1142,7 @@ Application Lifecycle
|
|
|
1096
1142
|
preConfigure()
|
|
1097
1143
|
├── Register repositories, services, controllers
|
|
1098
1144
|
└── setupSocketIO()
|
|
1099
|
-
├── Bind
|
|
1145
|
+
├── Bind RedisSingleHelper → REDIS_CONNECTION
|
|
1100
1146
|
├── Bind authenticateFn → AUTHENTICATE_HANDLER
|
|
1101
1147
|
├── Bind clientConnectedFn → CLIENT_CONNECTED_HANDLER
|
|
1102
1148
|
├── Bind server options → SERVER_OPTIONS
|
|
@@ -1104,12 +1150,12 @@ preConfigure()
|
|
|
1104
1150
|
|
|
1105
1151
|
initialize()
|
|
1106
1152
|
└── SocketIOComponent.binding()
|
|
1107
|
-
├── resolveBindings()
|
|
1108
|
-
├── RuntimeModules.detect()
|
|
1153
|
+
├── resolveBindings() - reads all bound values
|
|
1154
|
+
├── RuntimeModules.detect() - auto-detect Node.js or Bun
|
|
1109
1155
|
└── registerPostStartHook('socket-io-initialize')
|
|
1110
1156
|
|
|
1111
1157
|
start()
|
|
1112
|
-
├── startBunModule() / startNodeModule()
|
|
1158
|
+
├── startBunModule() / startNodeModule() - server starts
|
|
1113
1159
|
└── executePostStartHooks()
|
|
1114
1160
|
└── 'socket-io-initialize'
|
|
1115
1161
|
├── Create SocketIOServerHelper (with Redis adapter + emitter)
|
|
@@ -1215,7 +1261,7 @@ export class ChatController extends BaseRestController {
|
|
|
1215
1261
|
},
|
|
1216
1262
|
});
|
|
1217
1263
|
|
|
1218
|
-
// GET /chat/rooms
|
|
1264
|
+
// GET /chat/rooms/{roomId}/messages
|
|
1219
1265
|
this.bindRoute({ configs: ChatRoutes.GET_MESSAGES }).to({
|
|
1220
1266
|
handler: async (c: TRouteContext) => {
|
|
1221
1267
|
const roomId = c.req.param('roomId');
|
|
@@ -1241,7 +1287,7 @@ export class ChatController extends BaseRestController {
|
|
|
1241
1287
|
},
|
|
1242
1288
|
});
|
|
1243
1289
|
|
|
1244
|
-
// GET /chat/dm
|
|
1290
|
+
// GET /chat/dm/{userId}
|
|
1245
1291
|
this.bindRoute({ configs: ChatRoutes.GET_DIRECT_MESSAGES }).to({
|
|
1246
1292
|
handler: async (c: TRouteContext) => {
|
|
1247
1293
|
const currentUserId = c.get('userId');
|
|
@@ -1265,7 +1311,7 @@ export class ChatController extends BaseRestController {
|
|
|
1265
1311
|
|
|
1266
1312
|
## 8. Client Usage
|
|
1267
1313
|
|
|
1268
|
-
Clients must follow the `SocketIOServerHelper` authentication flow: **connect** → **emit `authenticate`** → **receive `authenticated`**
|
|
1314
|
+
Clients must follow the `SocketIOServerHelper` authentication flow: **connect** → **emit `authenticate`** → **receive `authenticated`** - then they're ready to send and receive events.
|
|
1269
1315
|
|
|
1270
1316
|
### JavaScript Client Example
|
|
1271
1317
|
|
|
@@ -1293,16 +1339,16 @@ class ChatClient {
|
|
|
1293
1339
|
}
|
|
1294
1340
|
|
|
1295
1341
|
// ---------------------------------------------------------------------------
|
|
1296
|
-
// Connection lifecycle
|
|
1342
|
+
// Connection lifecycle - follows SocketIOServerHelper's auth flow
|
|
1297
1343
|
// ---------------------------------------------------------------------------
|
|
1298
1344
|
private setupLifecycle() {
|
|
1299
|
-
// Step 1: Connected
|
|
1345
|
+
// Step 1: Connected - now send authenticate event
|
|
1300
1346
|
this._socket.on('connect', () => {
|
|
1301
1347
|
console.log('Connected | id:', this._socket.id);
|
|
1302
1348
|
this._socket.emit('authenticate');
|
|
1303
1349
|
});
|
|
1304
1350
|
|
|
1305
|
-
// Step 2: Authenticated
|
|
1351
|
+
// Step 2: Authenticated - ready to use
|
|
1306
1352
|
this._socket.on('authenticated', (data: { id: string; time: string }) => {
|
|
1307
1353
|
console.log('Authenticated | id:', data.id);
|
|
1308
1354
|
this._authenticated = true;
|
|
@@ -1317,7 +1363,7 @@ class ChatClient {
|
|
|
1317
1363
|
|
|
1318
1364
|
// Keep-alive ping from server (every 30s)
|
|
1319
1365
|
this._socket.on('ping', () => {
|
|
1320
|
-
// Server is checking we're alive
|
|
1366
|
+
// Server is checking we're alive - no action needed
|
|
1321
1367
|
});
|
|
1322
1368
|
|
|
1323
1369
|
this._socket.on('disconnect', (reason: string) => {
|
|
@@ -1327,7 +1373,7 @@ class ChatClient {
|
|
|
1327
1373
|
}
|
|
1328
1374
|
|
|
1329
1375
|
// ---------------------------------------------------------------------------
|
|
1330
|
-
// Custom event handlers
|
|
1376
|
+
// Custom event handlers - registered after authentication
|
|
1331
1377
|
// ---------------------------------------------------------------------------
|
|
1332
1378
|
private setupEventHandlers() {
|
|
1333
1379
|
this._socket.on('message:new', (message) => {
|
|
@@ -1405,7 +1451,7 @@ const chat = new ChatClient({ token: 'your-jwt-token' });
|
|
|
1405
1451
|
For server-to-server or microservice communication, use the built-in `SocketIOClientHelper`:
|
|
1406
1452
|
|
|
1407
1453
|
```typescript
|
|
1408
|
-
import { SocketIOClientHelper } from '@venizia/ignis-helpers';
|
|
1454
|
+
import { SocketIOClientHelper } from '@venizia/ignis-helpers/socket-io';
|
|
1409
1455
|
|
|
1410
1456
|
const client = new SocketIOClientHelper({
|
|
1411
1457
|
identifier: 'chat-service-client',
|
|
@@ -1446,7 +1492,7 @@ Redis scaling is **built-in** and **automatic** when using `SocketIOComponent`.
|
|
|
1446
1492
|
|
|
1447
1493
|
### How It Works
|
|
1448
1494
|
|
|
1449
|
-
When you bind a `
|
|
1495
|
+
When you bind a `RedisSingleHelper` (or any `AbstractRedisHelper` subclass) to `SocketIOBindingKeys.REDIS_CONNECTION`, the `SocketIOServerHelper` automatically:
|
|
1450
1496
|
|
|
1451
1497
|
1. Creates 3 duplicate Redis connections from your helper
|
|
1452
1498
|
2. Sets up `@socket.io/redis-adapter` for cross-instance pub/sub
|
|
@@ -1464,7 +1510,7 @@ Process A Redis Process B
|
|
|
1464
1510
|
|
|
1465
1511
|
### Multi-Instance Deployment
|
|
1466
1512
|
|
|
1467
|
-
Run multiple instances behind a load balancer
|
|
1513
|
+
Run multiple instances behind a load balancer - Redis keeps them in sync:
|
|
1468
1514
|
|
|
1469
1515
|
```bash
|
|
1470
1516
|
# Instance 1
|
|
@@ -1481,10 +1527,10 @@ All calls to `socketIOHelper.send()` go through the Redis emitter, so messages r
|
|
|
1481
1527
|
|
|
1482
1528
|
### Redis Configuration
|
|
1483
1529
|
|
|
1484
|
-
The only thing you need is a `
|
|
1530
|
+
The only thing you need is a `RedisSingleHelper` bound to the correct key (already done in the application setup):
|
|
1485
1531
|
|
|
1486
1532
|
```typescript
|
|
1487
|
-
this.redisHelper = new
|
|
1533
|
+
this.redisHelper = new RedisSingleHelper({
|
|
1488
1534
|
name: 'chat-redis',
|
|
1489
1535
|
host: process.env.APP_ENV_REDIS_HOST ?? 'localhost',
|
|
1490
1536
|
port: +(process.env.APP_ENV_REDIS_PORT ?? 6379),
|
|
@@ -1492,7 +1538,7 @@ this.redisHelper = new RedisHelper({
|
|
|
1492
1538
|
autoConnect: false,
|
|
1493
1539
|
});
|
|
1494
1540
|
|
|
1495
|
-
this.bind<
|
|
1541
|
+
this.bind<RedisSingleHelper>({
|
|
1496
1542
|
key: SocketIOBindingKeys.REDIS_CONNECTION,
|
|
1497
1543
|
}).toValue(this.redisHelper);
|
|
1498
1544
|
```
|
|
@@ -1507,7 +1553,7 @@ this.bind<RedisHelper>({
|
|
|
1507
1553
|
| Presence | `socketIOHelper.send()` broadcast on connect/disconnect |
|
|
1508
1554
|
| History | REST API with cursor-based pagination |
|
|
1509
1555
|
| Authentication | `SocketIOServerHelper` built-in flow (connect → authenticate → authenticated) |
|
|
1510
|
-
| Scaling | Redis adapter/emitter
|
|
1556
|
+
| Scaling | Redis adapter/emitter - automatic via `RedisSingleHelper` binding |
|
|
1511
1557
|
| Runtime | Auto-detected (Node.js or Bun) by `SocketIOComponent` |
|
|
1512
1558
|
|
|
1513
1559
|
## Next Steps
|
|
@@ -1520,6 +1566,6 @@ this.bind<RedisHelper>({
|
|
|
1520
1566
|
|
|
1521
1567
|
## See Also
|
|
1522
1568
|
|
|
1523
|
-
- [Socket.IO Component](/extensions/components/socket-io/)
|
|
1524
|
-
- [Socket.IO Helper](/extensions/helpers/socket-io/)
|
|
1525
|
-
- [Socket.IO Test Example](https://github.com/VENIZIA-AI/ignis/tree/main/examples/socket-io-test)
|
|
1569
|
+
- [Socket.IO Component](/extensions/components/socket-io/) - Component reference
|
|
1570
|
+
- [Socket.IO Helper](/extensions/helpers/socket-io/) - Server + Client helper API
|
|
1571
|
+
- [Socket.IO Test Example](https://github.com/VENIZIA-AI/ignis/tree/main/examples/socket-io-test) - Working example with automated test client
|