@theelena/shared 1.0.1 → 1.0.2
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/package.json +8 -5
- package/src/events/base.ts +153 -0
- package/src/events/index.ts +3 -0
- package/src/events/notification.events.ts +23 -0
- package/src/events/order.events.ts +25 -0
- package/src/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theelena/shared",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"main": "./index.
|
|
6
|
-
"types": "./index.ts",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"types": "./src/index.ts",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": {
|
|
9
9
|
"types": "./src/index.ts",
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
|
-
"
|
|
21
|
-
]
|
|
20
|
+
"src"
|
|
21
|
+
],
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"typescript": "^5"
|
|
24
|
+
}
|
|
22
25
|
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event scope types for channel routing
|
|
3
|
+
*/
|
|
4
|
+
export type EventScope = "restaurant" | "user" | "global";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Context required for channel resolution
|
|
8
|
+
*/
|
|
9
|
+
export interface EventContext {
|
|
10
|
+
restaurantId: string;
|
|
11
|
+
userId: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Serialized event format for SSE transmission
|
|
16
|
+
*/
|
|
17
|
+
export interface SerializedEvent<TData = unknown> {
|
|
18
|
+
type: string;
|
|
19
|
+
data: TData;
|
|
20
|
+
timestamp: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Channel name generators for event routing
|
|
25
|
+
*/
|
|
26
|
+
export const EventChannels = {
|
|
27
|
+
restaurant: (restaurantId: string) => `restaurant:${restaurantId}`,
|
|
28
|
+
user: (userId: string) => `user:${userId}`,
|
|
29
|
+
global: () => "global",
|
|
30
|
+
} as const;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Base class for all application events
|
|
34
|
+
*
|
|
35
|
+
* Scope is automatically deduced from event type prefix:
|
|
36
|
+
* - 'order.*', 'product.*', 'sale.*', 'customer.*' → restaurant scope
|
|
37
|
+
* - 'notification.*', 'system.*' → user scope (owner messages)
|
|
38
|
+
* - default → global scope
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* class OrderCreatedEvent extends BaseEvent<OrderCreatedData> {
|
|
43
|
+
* readonly type = 'order.created' as const;
|
|
44
|
+
* }
|
|
45
|
+
*
|
|
46
|
+
* const event = new OrderCreatedEvent({ orderId: '123', ... });
|
|
47
|
+
* const channel = event.getChannel({ restaurantId: 'abc', userId: 'xyz' });
|
|
48
|
+
* // Returns: 'restaurant:abc'
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export abstract class BaseEvent<TData = unknown> {
|
|
52
|
+
/**
|
|
53
|
+
* Event type identifier - used for routing and filtering
|
|
54
|
+
* Convention: '{domain}.{action}' e.g., 'order.created', 'notification.sent'
|
|
55
|
+
*/
|
|
56
|
+
abstract readonly type: string;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Event payload data
|
|
60
|
+
*/
|
|
61
|
+
readonly data: TData;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Event creation timestamp (milliseconds since epoch)
|
|
65
|
+
*/
|
|
66
|
+
readonly timestamp: number;
|
|
67
|
+
|
|
68
|
+
constructor(data: TData) {
|
|
69
|
+
this.data = data;
|
|
70
|
+
this.timestamp = Date.now();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Deduce scope from event type prefix
|
|
75
|
+
* Override in subclass if custom scope logic is needed
|
|
76
|
+
*/
|
|
77
|
+
protected getScope(): EventScope {
|
|
78
|
+
const domain = this.type.split(".")[0] ?? "";
|
|
79
|
+
|
|
80
|
+
const restaurantDomains = ["order", "product", "sale", "customer", "table", "category"];
|
|
81
|
+
if (restaurantDomains.includes(domain)) {
|
|
82
|
+
return "restaurant";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const userDomains = ["notification", "system", "alert"];
|
|
86
|
+
if (userDomains.includes(domain)) {
|
|
87
|
+
return "user";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return "global";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get the Redis channel for this event based on context
|
|
95
|
+
*/
|
|
96
|
+
getChannel(context: EventContext): string {
|
|
97
|
+
const scope = this.getScope();
|
|
98
|
+
|
|
99
|
+
switch (scope) {
|
|
100
|
+
case "restaurant":
|
|
101
|
+
return EventChannels.restaurant(context.restaurantId);
|
|
102
|
+
case "user":
|
|
103
|
+
return EventChannels.user(context.userId);
|
|
104
|
+
case "global":
|
|
105
|
+
return EventChannels.global();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get all channels this event should be published to
|
|
111
|
+
* By default, only returns the primary channel
|
|
112
|
+
* Override for multi-channel publishing
|
|
113
|
+
*/
|
|
114
|
+
getChannels(context: EventContext): string[] {
|
|
115
|
+
return [this.getChannel(context)];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Serialize event for transmission
|
|
120
|
+
*/
|
|
121
|
+
toJSON(): SerializedEvent<TData> {
|
|
122
|
+
return {
|
|
123
|
+
type: this.type,
|
|
124
|
+
data: this.data,
|
|
125
|
+
timestamp: this.timestamp,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Create event instance from serialized data
|
|
131
|
+
* Must be implemented by concrete event classes for deserialization
|
|
132
|
+
*/
|
|
133
|
+
static fromJSON<T extends BaseEvent>(
|
|
134
|
+
this: new (data: any) => T,
|
|
135
|
+
json: SerializedEvent
|
|
136
|
+
): T {
|
|
137
|
+
return new this(json.data);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Type helper to extract data type from event class
|
|
143
|
+
*/
|
|
144
|
+
export type EventData<E extends BaseEvent> = E extends BaseEvent<infer D>
|
|
145
|
+
? D
|
|
146
|
+
: never;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Type helper for event class constructor
|
|
150
|
+
*/
|
|
151
|
+
export type EventClass<E extends BaseEvent = BaseEvent> = new (
|
|
152
|
+
data: EventData<E>
|
|
153
|
+
) => E;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { BaseEvent } from "./base";
|
|
2
|
+
|
|
3
|
+
export interface SystemNotificationData {
|
|
4
|
+
title: string;
|
|
5
|
+
message: string;
|
|
6
|
+
severity: "info" | "warning" | "error" | "success";
|
|
7
|
+
actionUrl?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface AlertData {
|
|
11
|
+
alertId: string;
|
|
12
|
+
alertType: string;
|
|
13
|
+
message: string;
|
|
14
|
+
metadata?: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class SystemNotificationEvent extends BaseEvent<SystemNotificationData> {
|
|
18
|
+
readonly type = "system.notification" as const;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class AlertEvent extends BaseEvent<AlertData> {
|
|
22
|
+
readonly type = "alert.triggered" as const;
|
|
23
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { BaseEvent } from "./base";
|
|
2
|
+
|
|
3
|
+
export interface OrderCreatedData {
|
|
4
|
+
orderId: string;
|
|
5
|
+
orderNumber: string;
|
|
6
|
+
orderType: "takeout" | "dine_in" | "reservation";
|
|
7
|
+
customerName?: string;
|
|
8
|
+
tableName?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface OrderStatusChangedData {
|
|
12
|
+
orderId: string;
|
|
13
|
+
orderNumber: string;
|
|
14
|
+
oldStatus: string;
|
|
15
|
+
newStatus: string;
|
|
16
|
+
updatedBy: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class OrderCreatedEvent extends BaseEvent<OrderCreatedData> {
|
|
20
|
+
readonly type = "order.created" as const;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class OrderStatusChangedEvent extends BaseEvent<OrderStatusChangedData> {
|
|
24
|
+
readonly type = "order.status_changed" as const;
|
|
25
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./events";
|