@fraqjs/plugin-mock 0.1.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.
@@ -0,0 +1,203 @@
1
+ import { AnyApiCall, Context, ContextOptions, LogHandler, MilkyEventSource, MilkyEventSubscription, milky } from "@fraqjs/fraq";
2
+ //#region src/entity.d.ts
3
+ interface RandomFriendOverrides extends Partial<Omit<milky.FriendEntity, 'category'>> {
4
+ category?: Partial<milky.FriendCategoryEntity>;
5
+ }
6
+ interface RandomGroupOverrides extends Partial<milky.GroupEntity> {}
7
+ interface RandomGroupMemberOverrides extends Partial<milky.GroupMemberEntity> {}
8
+ declare function createRandomFriend(userId: number, overrides?: RandomFriendOverrides): milky.FriendEntity;
9
+ declare function createRandomGroup(groupId: number, overrides?: RandomGroupOverrides): milky.GroupEntity;
10
+ declare function createRandomGroupMember(groupId: number, userId: number, overrides?: RandomGroupMemberOverrides): milky.GroupMemberEntity;
11
+ //#endregion
12
+ //#region src/inbox.d.ts
13
+ type MockMessageScene = 'friend' | 'group' | 'temp';
14
+ interface MockMessageContext {
15
+ scene: MockMessageScene;
16
+ peerId: number;
17
+ senderId: number;
18
+ groupId?: number | undefined;
19
+ }
20
+ interface MockInboxOptions {
21
+ selfId?: number;
22
+ baseTime?: number;
23
+ sequenceStart?: number;
24
+ timeStepSeconds?: number;
25
+ conversationKey?(context: MockMessageContext): string;
26
+ }
27
+ interface MockMessageMetaOverrides {
28
+ messageSeq?: number;
29
+ time?: number;
30
+ }
31
+ interface MockFriendMessageOptions extends MockMessageMetaOverrides {
32
+ userId: number;
33
+ peerId?: number;
34
+ senderId?: number;
35
+ friend?: RandomFriendOverrides;
36
+ }
37
+ interface MockGroupMessageOptions extends MockMessageMetaOverrides {
38
+ groupId: number;
39
+ userId: number;
40
+ senderId?: number;
41
+ group?: RandomGroupOverrides;
42
+ groupMember?: RandomGroupMemberOverrides;
43
+ }
44
+ interface MockTempMessageOptions extends MockMessageMetaOverrides {
45
+ userId: number;
46
+ peerId?: number;
47
+ senderId?: number;
48
+ groupId?: number;
49
+ group?: RandomGroupOverrides | null;
50
+ }
51
+ interface MockMessageEventOverrides {
52
+ time?: number;
53
+ selfId?: number;
54
+ }
55
+ declare class MockInbox {
56
+ readonly selfId: number;
57
+ private readonly baseTime;
58
+ private readonly sequenceStart;
59
+ private readonly timeStepSeconds;
60
+ private readonly conversationKeyOf;
61
+ private readonly conversations;
62
+ private readonly allMessages;
63
+ private readonly friends;
64
+ private readonly groups;
65
+ private readonly groupMembers;
66
+ constructor(options?: MockInboxOptions);
67
+ conversationKey(context: MockMessageContext): string;
68
+ friendConversationKey(peerId: number, senderId?: number): string;
69
+ groupConversationKey(groupId: number, senderId?: number): string;
70
+ tempConversationKey(options: {
71
+ peerId?: number;
72
+ senderId: number;
73
+ groupId?: number;
74
+ }): string;
75
+ friend(options: MockFriendMessageOptions, segments: readonly milky.IncomingSegment_ZodInput[] | readonly milky.IncomingSegment[]): milky.IncomingFriendMessage;
76
+ group(options: MockGroupMessageOptions, segments: readonly milky.IncomingSegment_ZodInput[] | readonly milky.IncomingSegment[]): milky.IncomingGroupMessage;
77
+ temp(options: MockTempMessageOptions, segments: readonly milky.IncomingSegment_ZodInput[] | readonly milky.IncomingSegment[]): milky.IncomingTempMessage;
78
+ event(message: milky.IncomingMessage, overrides?: MockMessageEventOverrides): milky.MessageReceiveEvent;
79
+ friendEvent(options: MockFriendMessageOptions, segments: readonly milky.IncomingSegment_ZodInput[] | readonly milky.IncomingSegment[], overrides?: MockMessageEventOverrides): milky.MessageReceiveEvent;
80
+ groupEvent(options: MockGroupMessageOptions, segments: readonly milky.IncomingSegment_ZodInput[] | readonly milky.IncomingSegment[], overrides?: MockMessageEventOverrides): milky.MessageReceiveEvent;
81
+ tempEvent(options: MockTempMessageOptions, segments: readonly milky.IncomingSegment_ZodInput[] | readonly milky.IncomingSegment[], overrides?: MockMessageEventOverrides): milky.MessageReceiveEvent;
82
+ message(conversationKey: string, messageSeq: number): milky.IncomingMessage | undefined;
83
+ getMessage(input: milky.GetMessageInput_ZodInput): milky.GetMessageOutput;
84
+ getHistoryMessages(input: milky.GetHistoryMessagesInput_ZodInput): milky.GetHistoryMessagesOutput;
85
+ markMessageAsRead(input: milky.MarkMessageAsReadInput_ZodInput): milky.MarkMessageAsReadOutput;
86
+ getFriendInfo(input: milky.GetFriendInfoInput_ZodInput): milky.GetFriendInfoOutput;
87
+ getGroupInfo(input: milky.GetGroupInfoInput_ZodInput): milky.GetGroupInfoOutput;
88
+ getGroupMemberInfo(input: milky.GetGroupMemberInfoInput_ZodInput): milky.GetGroupMemberInfoOutput;
89
+ history(conversationKey?: string): milky.IncomingMessage[];
90
+ lastMessage(conversationKey?: string): milky.IncomingMessage | undefined;
91
+ reset(): void;
92
+ private allocateMessageMeta;
93
+ private getOrCreateConversationState;
94
+ private storeMessage;
95
+ private queryConversationKey;
96
+ }
97
+ declare function createMockInbox(options?: MockInboxOptions): MockInbox;
98
+ //#endregion
99
+ //#region src/service.d.ts
100
+ interface MockApiCall {
101
+ endpoint: string;
102
+ params?: unknown;
103
+ }
104
+ interface MockServiceOptions extends MockInboxOptions {
105
+ inbox?: MockInbox;
106
+ }
107
+ declare class MockService implements MilkyEventSource {
108
+ readonly name = "mock";
109
+ readonly inbox: MockInbox;
110
+ readonly apiCalls: MockApiCall[];
111
+ private onEvent;
112
+ private closeEventsResolver;
113
+ startEventCalls: number;
114
+ private nextStartError;
115
+ constructor(options?: MockServiceOptions);
116
+ start(handler: (event: milky.Event) => void | Promise<void>): Promise<MilkyEventSubscription>;
117
+ closeEvents(): void;
118
+ failNextStart(error: unknown): void;
119
+ emitEvent(event: milky.Event): Promise<void>;
120
+ receiveFriend(options: MockFriendMessageOptions, segments: readonly milky.IncomingSegment_ZodInput[] | readonly milky.IncomingSegment[]): Promise<milky.IncomingFriendMessage>;
121
+ receiveGroup(options: MockGroupMessageOptions, segments: readonly milky.IncomingSegment_ZodInput[] | readonly milky.IncomingSegment[]): Promise<milky.IncomingGroupMessage>;
122
+ receiveTemp(options: MockTempMessageOptions, segments: readonly milky.IncomingSegment_ZodInput[] | readonly milky.IncomingSegment[]): Promise<milky.IncomingTempMessage>;
123
+ handleApiCall(call: AnyApiCall): unknown;
124
+ reset(): void;
125
+ }
126
+ //#endregion
127
+ //#region src/logging.d.ts
128
+ declare function createSimpleLogHandler(): LogHandler;
129
+ //#endregion
130
+ //#region src/message.d.ts
131
+ type InmsgTemplateValue = string | number | boolean | milky.IncomingSegment_ZodInput;
132
+ interface IncomingReplySource {
133
+ message_seq: number;
134
+ sender_id: number;
135
+ sender_name?: string | null | undefined;
136
+ time: number;
137
+ segments: milky.IncomingSegment[] | milky.IncomingSegment_ZodInput[];
138
+ }
139
+ declare function inmsg(strings: TemplateStringsArray, ...values: InmsgTemplateValue[]): milky.IncomingSegment_ZodInput[];
140
+ declare namespace inseg {
141
+ function text(text: string): milky.IncomingTextSegment_ZodInput;
142
+ function mention(userId: number, name?: string): milky.IncomingMentionSegment_ZodInput;
143
+ function mentionAll(): milky.IncomingMentionAllSegment_ZodInput;
144
+ function face(faceId: number | string, options?: {
145
+ isLarge?: boolean;
146
+ }): milky.IncomingFaceSegment_ZodInput;
147
+ function reply(source: milky.IncomingMessage | IncomingReplySource): milky.IncomingReplySegment_ZodInput;
148
+ function image(options?: {
149
+ resourceId?: string;
150
+ tempUrl?: string;
151
+ width?: number;
152
+ height?: number;
153
+ summary?: string;
154
+ subType?: 'normal' | 'sticker';
155
+ }): milky.IncomingImageSegment_ZodInput;
156
+ function record(options?: {
157
+ resourceId?: string;
158
+ tempUrl?: string;
159
+ duration?: number;
160
+ }): milky.IncomingRecordSegment_ZodInput;
161
+ function video(options?: {
162
+ resourceId?: string;
163
+ tempUrl?: string;
164
+ width?: number;
165
+ height?: number;
166
+ duration?: number;
167
+ }): milky.IncomingVideoSegment_ZodInput;
168
+ function file(options?: {
169
+ fileId?: string;
170
+ fileName?: string;
171
+ fileSize?: number;
172
+ fileHash?: string | null;
173
+ }): milky.IncomingFileSegment_ZodInput;
174
+ function forward(options?: {
175
+ forwardId?: string;
176
+ title?: string;
177
+ preview?: string[];
178
+ summary?: string;
179
+ }): milky.IncomingForwardSegment_ZodInput;
180
+ function marketFace(options?: {
181
+ emojiPackageId?: number;
182
+ emojiId?: string;
183
+ key?: string;
184
+ summary?: string;
185
+ url?: string;
186
+ }): milky.IncomingMarketFaceSegment_ZodInput;
187
+ function lightApp(appName: string, jsonPayload: string): milky.IncomingLightAppSegment_ZodInput;
188
+ function xml(serviceId: number, xmlPayload: string): milky.IncomingXmlSegment_ZodInput;
189
+ function markdown(content: string): milky.IncomingMarkdownSegment_ZodInput;
190
+ }
191
+ //#endregion
192
+ //#region src/index.d.ts
193
+ interface MockPluginOptions extends MockServiceOptions {
194
+ service?: MockService;
195
+ }
196
+ declare const MockPlugin: import("@fraqjs/fraq").Plugin<[options?: MockPluginOptions | undefined], import("@fraqjs/fraq").Injection | undefined, import("@fraqjs/fraq").Injection | undefined>;
197
+ type MockContext = Context & {
198
+ readonly mock: MockService;
199
+ };
200
+ interface MockContextOptions extends MockServiceOptions, ContextOptions {}
201
+ declare function createMockContext(options?: MockContextOptions): MockContext;
202
+ //#endregion
203
+ export { IncomingReplySource, InmsgTemplateValue, MockApiCall, MockContext, MockContextOptions, MockFriendMessageOptions, MockGroupMessageOptions, MockInbox, MockInboxOptions, MockMessageContext, MockMessageEventOverrides, MockMessageMetaOverrides, MockMessageScene, MockPlugin, MockPlugin as default, MockPluginOptions, MockService, MockServiceOptions, MockTempMessageOptions, RandomFriendOverrides, RandomGroupMemberOverrides, RandomGroupOverrides, createMockContext, createMockInbox, createRandomFriend, createRandomGroup, createRandomGroupMember, createSimpleLogHandler, inmsg, inseg };
package/dist/index.mjs ADDED
@@ -0,0 +1,1070 @@
1
+ import { Context, definePlugin } from "@fraqjs/fraq";
2
+ //#region src/logging.ts
3
+ function createSimpleLogHandler() {
4
+ return ({ time, level, module, message, error }) => {
5
+ const timeStr = new Date(time).toISOString();
6
+ const levelStr = level.toUpperCase();
7
+ console.log(`[${timeStr} ${levelStr}] [${module}] ${message}`);
8
+ if (error) console.error(error);
9
+ };
10
+ }
11
+ //#endregion
12
+ //#region src/random.ts
13
+ const PCG32_MULTIPLIER = 6364136223846793005n;
14
+ const UINT64_MASK = (1n << 64n) - 1n;
15
+ const UINT32_MODULUS = 2 ** 32;
16
+ function rotateRight32(value, rotation) {
17
+ const amount = rotation & 31;
18
+ if (amount === 0) return value >>> 0;
19
+ return (value >>> amount | value << 32 - amount) >>> 0;
20
+ }
21
+ var PCG32 = class {
22
+ state;
23
+ increment;
24
+ constructor(seed, sequence = 1n) {
25
+ this.state = 0n;
26
+ this.increment = (sequence << 1n | 1n) & UINT64_MASK;
27
+ this.nextUint32();
28
+ this.state = this.state + seed & UINT64_MASK;
29
+ this.nextUint32();
30
+ }
31
+ nextUint32() {
32
+ const currentState = this.state;
33
+ this.state = currentState * PCG32_MULTIPLIER + this.increment & UINT64_MASK;
34
+ return rotateRight32(Number((currentState >> 18n ^ currentState) >> 27n & 4294967295n), Number(currentState >> 59n & 31n));
35
+ }
36
+ };
37
+ function assertSafeInteger$1(value, name) {
38
+ if (!Number.isSafeInteger(value)) throw new RangeError(`${name} must be a safe integer.`);
39
+ }
40
+ var SeededRandom = class {
41
+ generator;
42
+ constructor(seed) {
43
+ assertSafeInteger$1(seed, "seed");
44
+ this.generator = new PCG32(BigInt(seed >>> 0));
45
+ }
46
+ uint32() {
47
+ return this.generator.nextUint32();
48
+ }
49
+ float() {
50
+ return this.uint32() / UINT32_MODULUS;
51
+ }
52
+ bool(probability = .5) {
53
+ return this.float() < probability;
54
+ }
55
+ int(minOrMax, maxExclusive) {
56
+ const minInclusive = maxExclusive === void 0 ? 0 : minOrMax;
57
+ const actualMaxExclusive = maxExclusive ?? minOrMax;
58
+ assertSafeInteger$1(minInclusive, "minInclusive");
59
+ assertSafeInteger$1(actualMaxExclusive, "maxExclusive");
60
+ if (actualMaxExclusive <= minInclusive) throw new RangeError("maxExclusive must be greater than minInclusive.");
61
+ const span = actualMaxExclusive - minInclusive;
62
+ const limit = Math.floor(UINT32_MODULUS / span) * span;
63
+ let value = this.uint32();
64
+ while (value >= limit) value = this.uint32();
65
+ return minInclusive + value % span;
66
+ }
67
+ range(minInclusive, maxInclusive) {
68
+ assertSafeInteger$1(minInclusive, "minInclusive");
69
+ assertSafeInteger$1(maxInclusive, "maxInclusive");
70
+ if (maxInclusive < minInclusive) throw new RangeError("maxInclusive must be greater than or equal to minInclusive.");
71
+ return minInclusive + this.int(maxInclusive - minInclusive + 1);
72
+ }
73
+ pick(items) {
74
+ if (items.length === 0) throw new RangeError("items must not be empty.");
75
+ return items[this.int(items.length)];
76
+ }
77
+ weightedPick(items, weightOf) {
78
+ if (items.length === 0) throw new RangeError("items must not be empty.");
79
+ let totalWeight = 0;
80
+ for (const item of items) totalWeight += weightOf(item);
81
+ const target = this.float() * totalWeight;
82
+ let cumulative = 0;
83
+ for (const item of items) {
84
+ cumulative += weightOf(item);
85
+ if (target < cumulative) return item;
86
+ }
87
+ return items[items.length - 1];
88
+ }
89
+ };
90
+ //#endregion
91
+ //#region src/vocabulary.json
92
+ var friend = {
93
+ "firstNames": [
94
+ "Alice",
95
+ "Bob",
96
+ "Carol",
97
+ "Dave",
98
+ "Eve",
99
+ "Frank",
100
+ "Grace",
101
+ "Heidi",
102
+ "Ivan",
103
+ "Judy",
104
+ "Mallory",
105
+ "Niaj",
106
+ "Olivia",
107
+ "Peggy",
108
+ "Rupert",
109
+ "Sybil",
110
+ "Trent",
111
+ "Victor",
112
+ "Walter",
113
+ "Zoe"
114
+ ],
115
+ "lastNames": [
116
+ "Ash",
117
+ "Brook",
118
+ "Cedar",
119
+ "Dawn",
120
+ "Ember",
121
+ "Field",
122
+ "Grove",
123
+ "Harbor",
124
+ "Ivory",
125
+ "Jasper",
126
+ "Kite",
127
+ "Lake",
128
+ "Meadow",
129
+ "North",
130
+ "Oak",
131
+ "Pine",
132
+ "Quill",
133
+ "River",
134
+ "Stone",
135
+ "Vale"
136
+ ],
137
+ "nicknameSuffixes": [
138
+ "",
139
+ "",
140
+ "",
141
+ "Nova",
142
+ "Pixel",
143
+ "Echo",
144
+ "Skylark",
145
+ "Comet",
146
+ "Drift",
147
+ "Aurora"
148
+ ],
149
+ "remarks": [
150
+ "",
151
+ "",
152
+ "",
153
+ "Project lead",
154
+ "Design review",
155
+ "Ops contact",
156
+ "Weekend runner",
157
+ "Late-night debugger",
158
+ "Coffee strategist",
159
+ "Always online"
160
+ ],
161
+ "categoryNames": [
162
+ "General",
163
+ "Work",
164
+ "Friends",
165
+ "Study",
166
+ "Gaming",
167
+ "Family",
168
+ "Local",
169
+ "Archive"
170
+ ]
171
+ };
172
+ var group = {
173
+ "prefixes": [
174
+ "Northwind",
175
+ "Silver",
176
+ "Quiet",
177
+ "Golden",
178
+ "Blue",
179
+ "Rapid",
180
+ "Lucky",
181
+ "Open",
182
+ "Bright",
183
+ "Sunday"
184
+ ],
185
+ "topics": [
186
+ "Studio",
187
+ "Workshop",
188
+ "Lab",
189
+ "Guild",
190
+ "Circle",
191
+ "Squad",
192
+ "Forum",
193
+ "Club",
194
+ "Corner",
195
+ "Channel"
196
+ ],
197
+ "suffixes": [
198
+ "",
199
+ "",
200
+ "HQ",
201
+ "Room",
202
+ "Collective",
203
+ "Network",
204
+ "Union",
205
+ "Hub"
206
+ ],
207
+ "remarks": [
208
+ "",
209
+ "",
210
+ "Pinned for testing",
211
+ "Quiet hours after midnight",
212
+ "Temporary coordination group",
213
+ "Read-only on weekends"
214
+ ],
215
+ "descriptions": [
216
+ "A steady group for day-to-day coordination and practical discussion.",
217
+ "A relaxed place for updates, shared notes, and quick questions.",
218
+ "Used for planning, follow-up, and keeping small tasks moving.",
219
+ "A focused channel for experiments, reviews, and status checks.",
220
+ "A casual room where people trade ideas, links, and useful context."
221
+ ],
222
+ "questions": [
223
+ "",
224
+ "",
225
+ "What are you hoping to work on here?",
226
+ "Who invited you to join this group?",
227
+ "Share one sentence about your role in this space."
228
+ ],
229
+ "announcements": [
230
+ "Welcome aboard. Keep threads tidy and label follow-ups clearly.",
231
+ "Weekly review starts at 20:00. Bring blockers and next steps.",
232
+ "Please avoid duplicate questions; check the pinned notes first.",
233
+ "Files older than 30 days may be archived during cleanup.",
234
+ "Be kind, be direct, and leave enough context for the next person."
235
+ ]
236
+ };
237
+ var groupMember = {
238
+ "cardPrefixes": [
239
+ "",
240
+ "",
241
+ "Lead",
242
+ "Deputy",
243
+ "Night",
244
+ "Field",
245
+ "On-call",
246
+ "Guest",
247
+ "Blue",
248
+ "Quiet"
249
+ ],
250
+ "cardSuffixes": [
251
+ "",
252
+ "",
253
+ "Ops",
254
+ "Builder",
255
+ "Planner",
256
+ "Scout",
257
+ "Keeper",
258
+ "Pilot",
259
+ "Scribe",
260
+ "Watcher"
261
+ ],
262
+ "titles": [
263
+ "",
264
+ "",
265
+ "",
266
+ "Early Bird",
267
+ "Bug Hunter",
268
+ "Note Keeper",
269
+ "Night Shift",
270
+ "Fast Responder",
271
+ "Steady Hand",
272
+ "Build Whisperer"
273
+ ],
274
+ "levelLabels": [
275
+ "Sprout",
276
+ "Scout",
277
+ "Maker",
278
+ "Pilot",
279
+ "Anchor",
280
+ "Keeper",
281
+ "Guide",
282
+ "Expert"
283
+ ]
284
+ };
285
+ //#endregion
286
+ //#region src/entity.ts
287
+ const ROLE_WEIGHTS = [
288
+ {
289
+ role: "owner",
290
+ weight: 1
291
+ },
292
+ {
293
+ role: "admin",
294
+ weight: 6
295
+ },
296
+ {
297
+ role: "member",
298
+ weight: 93
299
+ }
300
+ ];
301
+ const SEX_WEIGHTS = [
302
+ {
303
+ sex: "male",
304
+ weight: 45
305
+ },
306
+ {
307
+ sex: "female",
308
+ weight: 45
309
+ },
310
+ {
311
+ sex: "unknown",
312
+ weight: 10
313
+ }
314
+ ];
315
+ const GROUP_CAPACITY_TIERS = [
316
+ 50,
317
+ 100,
318
+ 200,
319
+ 500,
320
+ 1e3,
321
+ 2e3
322
+ ];
323
+ const REFERENCE_TIME = Date.UTC(2025, 0, 1, 0, 0, 0) / 1e3;
324
+ const GROUP_CREATED_TIME_START = Date.UTC(2018, 0, 1, 0, 0, 0) / 1e3;
325
+ const GROUP_CREATED_TIME_END = Date.UTC(2024, 11, 31, 23, 59, 59) / 1e3;
326
+ function assertSeedInput(value, name) {
327
+ if (!Number.isSafeInteger(value)) throw new RangeError(`${name} must be a safe integer.`);
328
+ return value;
329
+ }
330
+ function mixSeed(namespace, ...values) {
331
+ let hash = 2166136261;
332
+ for (const char of namespace) {
333
+ hash ^= char.charCodeAt(0);
334
+ hash = Math.imul(hash, 16777619) >>> 0;
335
+ }
336
+ for (const value of values) {
337
+ let current = assertSeedInput(value, "seed input") >>> 0;
338
+ for (let index = 0; index < 4; index += 1) {
339
+ hash ^= current & 255;
340
+ hash = Math.imul(hash, 16777619) >>> 0;
341
+ current >>>= 8;
342
+ }
343
+ }
344
+ return hash;
345
+ }
346
+ function createRandom(namespace, ...values) {
347
+ return new SeededRandom(mixSeed(namespace, ...values));
348
+ }
349
+ function joinParts(...parts) {
350
+ return parts.filter(Boolean).join(" ");
351
+ }
352
+ function createUserProfile(userId) {
353
+ const random = createRandom("friend-profile", userId);
354
+ const firstName = random.pick(friend.firstNames);
355
+ const lastName = random.pick(friend.lastNames);
356
+ const nicknameSuffix = random.pick(friend.nicknameSuffixes);
357
+ const sex = random.weightedPick(SEX_WEIGHTS, (item) => item.weight).sex;
358
+ return {
359
+ user_id: userId,
360
+ nickname: joinParts(firstName, lastName, nicknameSuffix),
361
+ sex,
362
+ qid: `qid_${Math.abs(userId).toString(36).padStart(6, "0")}`
363
+ };
364
+ }
365
+ function createFriendCategory(userId, overrides) {
366
+ const categoryId = createRandom("friend-category", userId).int(friend.categoryNames.length);
367
+ return {
368
+ category_id: categoryId + 1,
369
+ category_name: friend.categoryNames[categoryId],
370
+ ...overrides
371
+ };
372
+ }
373
+ function createGroupBase(groupId) {
374
+ const random = createRandom("group", groupId);
375
+ const memberCount = random.range(12, 320);
376
+ const minCapacity = memberCount + random.range(0, 120);
377
+ const maxMemberCount = GROUP_CAPACITY_TIERS.find((capacity) => capacity >= minCapacity) ?? GROUP_CAPACITY_TIERS[GROUP_CAPACITY_TIERS.length - 1];
378
+ const createdTime = random.range(GROUP_CREATED_TIME_START, GROUP_CREATED_TIME_END);
379
+ return {
380
+ group_id: groupId,
381
+ group_name: joinParts(random.pick(group.prefixes), random.pick(group.topics), random.pick(group.suffixes)),
382
+ member_count: memberCount,
383
+ max_member_count: maxMemberCount,
384
+ remark: random.pick(group.remarks),
385
+ created_time: createdTime,
386
+ description: random.pick(group.descriptions),
387
+ question: random.pick(group.questions),
388
+ announcement: random.pick(group.announcements)
389
+ };
390
+ }
391
+ function createRandomFriend(userId, overrides = {}) {
392
+ assertSeedInput(userId, "userId");
393
+ const random = createRandom("friend", userId);
394
+ const friend$1 = {
395
+ ...createUserProfile(userId),
396
+ remark: random.pick(friend.remarks),
397
+ category: createFriendCategory(userId, overrides.category)
398
+ };
399
+ return {
400
+ ...friend$1,
401
+ ...overrides,
402
+ category: {
403
+ ...friend$1.category,
404
+ ...overrides.category
405
+ }
406
+ };
407
+ }
408
+ function createRandomGroup(groupId, overrides = {}) {
409
+ assertSeedInput(groupId, "groupId");
410
+ return {
411
+ ...createGroupBase(groupId),
412
+ ...overrides
413
+ };
414
+ }
415
+ function createRandomGroupMember(groupId, userId, overrides = {}) {
416
+ assertSeedInput(groupId, "groupId");
417
+ assertSeedInput(userId, "userId");
418
+ const random = createRandom("group-member", groupId, userId);
419
+ const profile = createUserProfile(userId);
420
+ const group = createGroupBase(groupId);
421
+ const role = random.weightedPick(ROLE_WEIGHTS, (item) => item.weight).role;
422
+ const joinTimeEnd = Math.max(group.created_time, REFERENCE_TIME - 86400);
423
+ const joinTime = random.range(group.created_time, joinTimeEnd);
424
+ const lastSentTime = random.range(joinTime, REFERENCE_TIME);
425
+ const shutUpEndTime = random.bool(.1) ? lastSentTime + random.range(300, 10080 * 60) : null;
426
+ const cardPrefix = random.pick(groupMember.cardPrefixes);
427
+ const cardSuffix = random.pick(groupMember.cardSuffixes);
428
+ const card = joinParts(cardPrefix, profile.nickname, cardSuffix);
429
+ const title = random.pick(groupMember.titles);
430
+ return {
431
+ user_id: userId,
432
+ nickname: profile.nickname,
433
+ sex: profile.sex,
434
+ group_id: groupId,
435
+ card,
436
+ title,
437
+ level: random.range(1, 100),
438
+ role,
439
+ join_time: joinTime,
440
+ last_sent_time: lastSentTime,
441
+ shut_up_end_time: shutUpEndTime,
442
+ ...overrides
443
+ };
444
+ }
445
+ //#endregion
446
+ //#region src/inbox.ts
447
+ const DEFAULT_BASE_TIME = Date.UTC(2025, 0, 1, 0, 0, 0) / 1e3;
448
+ function assertSafeInteger(value, name) {
449
+ if (!Number.isSafeInteger(value)) throw new RangeError(`${name} must be a safe integer.`);
450
+ }
451
+ function assertPositiveInteger(value, name) {
452
+ assertSafeInteger(value, name);
453
+ if (value <= 0) throw new RangeError(`${name} must be a positive integer.`);
454
+ }
455
+ function cloneSegments(segments) {
456
+ return segments.map((segment) => {
457
+ switch (segment.type) {
458
+ case "reply": return {
459
+ ...segment,
460
+ data: {
461
+ ...segment.data,
462
+ segments: cloneSegments(segment.data.segments)
463
+ }
464
+ };
465
+ case "forward":
466
+ case "image":
467
+ case "record":
468
+ case "video":
469
+ case "file":
470
+ case "market_face":
471
+ case "light_app":
472
+ case "xml":
473
+ case "markdown":
474
+ case "mention":
475
+ case "mention_all":
476
+ case "face":
477
+ case "text": return {
478
+ ...segment,
479
+ data: { ...segment.data }
480
+ };
481
+ }
482
+ });
483
+ }
484
+ function groupMemberKey(groupId, userId) {
485
+ return `${groupId}:${userId}`;
486
+ }
487
+ function defaultConversationKey(context) {
488
+ switch (context.scene) {
489
+ case "friend": return `friend:${context.peerId}`;
490
+ case "group": return `group:${context.groupId ?? context.peerId}`;
491
+ case "temp": return `temp:${context.groupId ?? context.peerId}:${context.senderId}`;
492
+ }
493
+ }
494
+ var MockInbox = class {
495
+ selfId;
496
+ baseTime;
497
+ sequenceStart;
498
+ timeStepSeconds;
499
+ conversationKeyOf;
500
+ conversations = /* @__PURE__ */ new Map();
501
+ allMessages = [];
502
+ friends = /* @__PURE__ */ new Map();
503
+ groups = /* @__PURE__ */ new Map();
504
+ groupMembers = /* @__PURE__ */ new Map();
505
+ constructor(options = {}) {
506
+ this.selfId = options.selfId ?? 1e4;
507
+ this.baseTime = options.baseTime ?? DEFAULT_BASE_TIME;
508
+ this.sequenceStart = options.sequenceStart ?? 1;
509
+ this.timeStepSeconds = options.timeStepSeconds ?? 1;
510
+ this.conversationKeyOf = options.conversationKey ?? defaultConversationKey;
511
+ assertPositiveInteger(this.selfId, "selfId");
512
+ assertSafeInteger(this.baseTime, "baseTime");
513
+ assertPositiveInteger(this.sequenceStart, "sequenceStart");
514
+ assertPositiveInteger(this.timeStepSeconds, "timeStepSeconds");
515
+ }
516
+ conversationKey(context) {
517
+ if (context.groupId !== void 0) assertPositiveInteger(context.groupId, "groupId");
518
+ assertPositiveInteger(context.peerId, "peerId");
519
+ assertPositiveInteger(context.senderId, "senderId");
520
+ return this.conversationKeyOf(context);
521
+ }
522
+ friendConversationKey(peerId, senderId = peerId) {
523
+ assertPositiveInteger(peerId, "peerId");
524
+ assertPositiveInteger(senderId, "senderId");
525
+ return this.conversationKey({
526
+ scene: "friend",
527
+ peerId,
528
+ senderId
529
+ });
530
+ }
531
+ groupConversationKey(groupId, senderId = groupId) {
532
+ assertPositiveInteger(groupId, "groupId");
533
+ assertPositiveInteger(senderId, "senderId");
534
+ return this.conversationKey({
535
+ scene: "group",
536
+ peerId: groupId,
537
+ senderId,
538
+ groupId
539
+ });
540
+ }
541
+ tempConversationKey(options) {
542
+ const peerId = options.peerId ?? options.senderId;
543
+ assertPositiveInteger(peerId, "peerId");
544
+ assertPositiveInteger(options.senderId, "senderId");
545
+ if (options.groupId !== void 0) assertPositiveInteger(options.groupId, "groupId");
546
+ return this.conversationKey({
547
+ scene: "temp",
548
+ peerId,
549
+ senderId: options.senderId,
550
+ groupId: options.groupId
551
+ });
552
+ }
553
+ friend(options, segments) {
554
+ assertPositiveInteger(options.userId, "userId");
555
+ const peerId = options.peerId ?? options.userId;
556
+ const senderId = options.senderId ?? options.userId;
557
+ assertPositiveInteger(peerId, "peerId");
558
+ assertPositiveInteger(senderId, "senderId");
559
+ const conversationKey = this.conversationKey({
560
+ scene: "friend",
561
+ peerId,
562
+ senderId
563
+ });
564
+ const { messageSeq, time } = this.allocateMessageMeta(conversationKey, options);
565
+ const message = {
566
+ message_scene: "friend",
567
+ peer_id: peerId,
568
+ message_seq: messageSeq,
569
+ sender_id: senderId,
570
+ time,
571
+ segments: cloneSegments(segments),
572
+ friend: createRandomFriend(options.userId, options.friend)
573
+ };
574
+ this.storeMessage(conversationKey, message);
575
+ return message;
576
+ }
577
+ group(options, segments) {
578
+ assertPositiveInteger(options.groupId, "groupId");
579
+ assertPositiveInteger(options.userId, "userId");
580
+ const senderId = options.senderId ?? options.userId;
581
+ assertPositiveInteger(senderId, "senderId");
582
+ const conversationKey = this.conversationKey({
583
+ scene: "group",
584
+ peerId: options.groupId,
585
+ senderId,
586
+ groupId: options.groupId
587
+ });
588
+ const { messageSeq, time } = this.allocateMessageMeta(conversationKey, options);
589
+ const message = {
590
+ message_scene: "group",
591
+ peer_id: options.groupId,
592
+ message_seq: messageSeq,
593
+ sender_id: senderId,
594
+ time,
595
+ segments: cloneSegments(segments),
596
+ group: createRandomGroup(options.groupId, options.group),
597
+ group_member: createRandomGroupMember(options.groupId, options.userId, options.groupMember)
598
+ };
599
+ this.storeMessage(conversationKey, message);
600
+ return message;
601
+ }
602
+ temp(options, segments) {
603
+ assertPositiveInteger(options.userId, "userId");
604
+ const peerId = options.peerId ?? options.userId;
605
+ const senderId = options.senderId ?? options.userId;
606
+ assertPositiveInteger(peerId, "peerId");
607
+ assertPositiveInteger(senderId, "senderId");
608
+ if (options.groupId !== void 0) assertPositiveInteger(options.groupId, "groupId");
609
+ const conversationKey = this.conversationKey({
610
+ scene: "temp",
611
+ peerId,
612
+ senderId,
613
+ groupId: options.groupId
614
+ });
615
+ const { messageSeq, time } = this.allocateMessageMeta(conversationKey, options);
616
+ const group = options.group === null ? null : options.groupId === void 0 ? void 0 : createRandomGroup(options.groupId, options.group);
617
+ const message = {
618
+ message_scene: "temp",
619
+ peer_id: peerId,
620
+ message_seq: messageSeq,
621
+ sender_id: senderId,
622
+ time,
623
+ segments: cloneSegments(segments),
624
+ group
625
+ };
626
+ this.storeMessage(conversationKey, message);
627
+ return message;
628
+ }
629
+ event(message, overrides) {
630
+ const eventTime = overrides?.time ?? message.time;
631
+ const selfId = overrides?.selfId ?? this.selfId;
632
+ assertSafeInteger(eventTime, "event time");
633
+ assertPositiveInteger(selfId, "event selfId");
634
+ return {
635
+ event_type: "message_receive",
636
+ time: eventTime,
637
+ self_id: selfId,
638
+ data: message
639
+ };
640
+ }
641
+ friendEvent(options, segments, overrides) {
642
+ return this.event(this.friend(options, segments), overrides);
643
+ }
644
+ groupEvent(options, segments, overrides) {
645
+ return this.event(this.group(options, segments), overrides);
646
+ }
647
+ tempEvent(options, segments, overrides) {
648
+ return this.event(this.temp(options, segments), overrides);
649
+ }
650
+ message(conversationKey, messageSeq) {
651
+ return this.conversations.get(conversationKey)?.bySeq.get(messageSeq);
652
+ }
653
+ getMessage(input) {
654
+ const conversationKey = this.queryConversationKey(input.message_scene, input.peer_id);
655
+ const message = this.message(conversationKey, input.message_seq);
656
+ if (!message) throw new Error(`Message not found (scene=${input.message_scene} peer=${input.peer_id} seq=${input.message_seq})`);
657
+ return { message };
658
+ }
659
+ getHistoryMessages(input) {
660
+ const conversationKey = this.queryConversationKey(input.message_scene, input.peer_id);
661
+ const limit = input.limit ?? 30;
662
+ const startMessageSeq = input.start_message_seq;
663
+ assertPositiveInteger(limit, "limit");
664
+ if (limit > 30) throw new RangeError("limit must be less than or equal to 30.");
665
+ const messages = this.history(conversationKey);
666
+ const endIndex = startMessageSeq == null ? messages.length : messages.findIndex((message) => message.message_seq > startMessageSeq);
667
+ const boundedMessages = endIndex === -1 ? messages : messages.slice(0, endIndex);
668
+ const startIndex = Math.max(0, boundedMessages.length - limit);
669
+ return {
670
+ messages: boundedMessages.slice(startIndex),
671
+ next_message_seq: boundedMessages[startIndex - 1]?.message_seq
672
+ };
673
+ }
674
+ markMessageAsRead(input) {
675
+ if (input.message_scene === "temp") return {};
676
+ const conversationKey = this.queryConversationKey(input.message_scene, input.peer_id);
677
+ const state = this.getOrCreateConversationState(conversationKey);
678
+ state.lastReadMessageSeq = input.message_seq;
679
+ return {};
680
+ }
681
+ getFriendInfo(input) {
682
+ assertPositiveInteger(input.user_id, "user_id");
683
+ return { friend: this.friends.get(input.user_id) ?? createRandomFriend(input.user_id) };
684
+ }
685
+ getGroupInfo(input) {
686
+ assertPositiveInteger(input.group_id, "group_id");
687
+ return { group: this.groups.get(input.group_id) ?? createRandomGroup(input.group_id) };
688
+ }
689
+ getGroupMemberInfo(input) {
690
+ assertPositiveInteger(input.group_id, "group_id");
691
+ assertPositiveInteger(input.user_id, "user_id");
692
+ const key = groupMemberKey(input.group_id, input.user_id);
693
+ return { member: this.groupMembers.get(key) ?? createRandomGroupMember(input.group_id, input.user_id) };
694
+ }
695
+ history(conversationKey) {
696
+ if (conversationKey === void 0) return [...this.allMessages];
697
+ return [...this.conversations.get(conversationKey)?.messages ?? []];
698
+ }
699
+ lastMessage(conversationKey) {
700
+ return this.history(conversationKey).at(-1);
701
+ }
702
+ reset() {
703
+ this.conversations.clear();
704
+ this.allMessages.length = 0;
705
+ this.friends.clear();
706
+ this.groups.clear();
707
+ this.groupMembers.clear();
708
+ }
709
+ allocateMessageMeta(conversationKey, overrides) {
710
+ const state = this.getOrCreateConversationState(conversationKey);
711
+ const messageSeq = overrides.messageSeq ?? state.nextSeq;
712
+ const time = overrides.time ?? state.nextTime;
713
+ assertPositiveInteger(messageSeq, "messageSeq");
714
+ assertSafeInteger(time, "time");
715
+ state.nextSeq = Math.max(state.nextSeq, messageSeq + 1);
716
+ state.nextTime = Math.max(state.nextTime, time + this.timeStepSeconds);
717
+ return {
718
+ messageSeq,
719
+ time
720
+ };
721
+ }
722
+ getOrCreateConversationState(conversationKey) {
723
+ let state = this.conversations.get(conversationKey);
724
+ if (!state) {
725
+ state = {
726
+ nextSeq: this.sequenceStart,
727
+ nextTime: this.baseTime,
728
+ messages: [],
729
+ bySeq: /* @__PURE__ */ new Map(),
730
+ lastReadMessageSeq: void 0
731
+ };
732
+ this.conversations.set(conversationKey, state);
733
+ }
734
+ return state;
735
+ }
736
+ storeMessage(conversationKey, message) {
737
+ const state = this.getOrCreateConversationState(conversationKey);
738
+ state.messages.push(message);
739
+ state.bySeq.set(message.message_seq, message);
740
+ this.allMessages.push(message);
741
+ switch (message.message_scene) {
742
+ case "friend":
743
+ this.friends.set(message.friend.user_id, message.friend);
744
+ break;
745
+ case "group":
746
+ this.groups.set(message.group.group_id, message.group);
747
+ this.groupMembers.set(groupMemberKey(message.group_member.group_id, message.group_member.user_id), message.group_member);
748
+ break;
749
+ case "temp":
750
+ if (message.group) this.groups.set(message.group.group_id, message.group);
751
+ break;
752
+ }
753
+ }
754
+ queryConversationKey(scene, peerId) {
755
+ assertPositiveInteger(peerId, "peer_id");
756
+ switch (scene) {
757
+ case "friend": return this.friendConversationKey(peerId);
758
+ case "group": return this.groupConversationKey(peerId);
759
+ case "temp": throw new Error("Temporary message queries are not supported.");
760
+ }
761
+ }
762
+ };
763
+ function createMockInbox(options) {
764
+ return new MockInbox(options);
765
+ }
766
+ //#endregion
767
+ //#region src/service.ts
768
+ var MockService = class {
769
+ name = "mock";
770
+ inbox;
771
+ apiCalls = [];
772
+ onEvent;
773
+ closeEventsResolver;
774
+ startEventCalls = 0;
775
+ nextStartError;
776
+ constructor(options = {}) {
777
+ this.inbox = options.inbox ?? new MockInbox(options);
778
+ }
779
+ async start(handler) {
780
+ this.startEventCalls += 1;
781
+ if (this.nextStartError) {
782
+ const error = this.nextStartError;
783
+ this.nextStartError = void 0;
784
+ throw error;
785
+ }
786
+ this.onEvent = handler;
787
+ return {
788
+ closed: new Promise((resolve) => {
789
+ this.closeEventsResolver = resolve;
790
+ }),
791
+ stop: () => {
792
+ this.closeEvents();
793
+ }
794
+ };
795
+ }
796
+ closeEvents() {
797
+ this.closeEventsResolver?.();
798
+ this.closeEventsResolver = void 0;
799
+ this.onEvent = void 0;
800
+ }
801
+ failNextStart(error) {
802
+ this.nextStartError = error;
803
+ }
804
+ async emitEvent(event) {
805
+ await this.onEvent?.(event);
806
+ }
807
+ async receiveFriend(options, segments) {
808
+ const event = this.inbox.friendEvent(options, segments);
809
+ await this.emitEvent(event);
810
+ return event.data;
811
+ }
812
+ async receiveGroup(options, segments) {
813
+ const event = this.inbox.groupEvent(options, segments);
814
+ await this.emitEvent(event);
815
+ return event.data;
816
+ }
817
+ async receiveTemp(options, segments) {
818
+ const event = this.inbox.tempEvent(options, segments);
819
+ await this.emitEvent(event);
820
+ return event.data;
821
+ }
822
+ handleApiCall(call) {
823
+ this.apiCalls.push({
824
+ endpoint: call.endpoint,
825
+ params: call.params
826
+ });
827
+ const params = call.params;
828
+ switch (call.endpoint) {
829
+ case "get_message": return this.inbox.getMessage(params);
830
+ case "get_history_messages": return this.inbox.getHistoryMessages(params);
831
+ case "mark_message_as_read": return this.inbox.markMessageAsRead(params);
832
+ case "get_friend_info": return this.inbox.getFriendInfo(params);
833
+ case "get_group_info": return this.inbox.getGroupInfo(params);
834
+ case "get_group_member_info": return this.inbox.getGroupMemberInfo(params);
835
+ default: return {};
836
+ }
837
+ }
838
+ reset() {
839
+ this.apiCalls.length = 0;
840
+ this.inbox.reset();
841
+ }
842
+ };
843
+ //#endregion
844
+ //#region src/message.ts
845
+ function inmsg(strings, ...values) {
846
+ let buffer = "";
847
+ const segments = [];
848
+ for (let index = 0; index < strings.length; index += 1) {
849
+ buffer += strings[index];
850
+ if (index >= values.length) continue;
851
+ const value = values[index];
852
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
853
+ buffer += value.toString();
854
+ continue;
855
+ }
856
+ if (buffer) {
857
+ segments.push(inseg.text(buffer));
858
+ buffer = "";
859
+ }
860
+ segments.push(value);
861
+ }
862
+ if (buffer) segments.push(inseg.text(buffer));
863
+ return trimBoundaryTextSegments(segments);
864
+ }
865
+ function trimBoundaryTextSegments(segments) {
866
+ const result = [...segments];
867
+ const first = result[0];
868
+ if (first?.type === "text") {
869
+ const text = first.data.text.trimStart();
870
+ if (text) result[0] = inseg.text(text);
871
+ else result.shift();
872
+ }
873
+ const lastIndex = result.length - 1;
874
+ const last = result[lastIndex];
875
+ if (last?.type === "text") {
876
+ const text = last.data.text.trimEnd();
877
+ if (text) result[lastIndex] = inseg.text(text);
878
+ else result.pop();
879
+ }
880
+ return result;
881
+ }
882
+ let inseg;
883
+ (function(_inseg) {
884
+ function text(text) {
885
+ return {
886
+ type: "text",
887
+ data: { text }
888
+ };
889
+ }
890
+ _inseg.text = text;
891
+ function mention(userId, name = `user_${userId}`) {
892
+ return {
893
+ type: "mention",
894
+ data: {
895
+ user_id: userId,
896
+ name
897
+ }
898
+ };
899
+ }
900
+ _inseg.mention = mention;
901
+ function mentionAll() {
902
+ return {
903
+ type: "mention_all",
904
+ data: {}
905
+ };
906
+ }
907
+ _inseg.mentionAll = mentionAll;
908
+ function face(faceId, options) {
909
+ return {
910
+ type: "face",
911
+ data: {
912
+ face_id: faceId.toString(),
913
+ is_large: options?.isLarge ?? false
914
+ }
915
+ };
916
+ }
917
+ _inseg.face = face;
918
+ function reply(source) {
919
+ const data = "message_scene" in source ? {
920
+ message_seq: source.message_seq,
921
+ sender_id: source.sender_id,
922
+ sender_name: void 0,
923
+ time: source.time,
924
+ segments: source.segments
925
+ } : source;
926
+ return {
927
+ type: "reply",
928
+ data: {
929
+ message_seq: data.message_seq,
930
+ sender_id: data.sender_id,
931
+ sender_name: data.sender_name,
932
+ time: data.time,
933
+ segments: [...data.segments]
934
+ }
935
+ };
936
+ }
937
+ _inseg.reply = reply;
938
+ function image(options) {
939
+ return {
940
+ type: "image",
941
+ data: {
942
+ resource_id: options?.resourceId ?? "mock-image",
943
+ temp_url: options?.tempUrl ?? "https://example.invalid/mock-image",
944
+ width: options?.width ?? 640,
945
+ height: options?.height ?? 480,
946
+ summary: options?.summary ?? "[image]",
947
+ sub_type: options?.subType ?? "normal"
948
+ }
949
+ };
950
+ }
951
+ _inseg.image = image;
952
+ function record(options) {
953
+ return {
954
+ type: "record",
955
+ data: {
956
+ resource_id: options?.resourceId ?? "mock-record",
957
+ temp_url: options?.tempUrl ?? "https://example.invalid/mock-record",
958
+ duration: options?.duration ?? 3
959
+ }
960
+ };
961
+ }
962
+ _inseg.record = record;
963
+ function video(options) {
964
+ return {
965
+ type: "video",
966
+ data: {
967
+ resource_id: options?.resourceId ?? "mock-video",
968
+ temp_url: options?.tempUrl ?? "https://example.invalid/mock-video",
969
+ width: options?.width ?? 1280,
970
+ height: options?.height ?? 720,
971
+ duration: options?.duration ?? 12
972
+ }
973
+ };
974
+ }
975
+ _inseg.video = video;
976
+ function file(options) {
977
+ return {
978
+ type: "file",
979
+ data: {
980
+ file_id: options?.fileId ?? "mock-file",
981
+ file_name: options?.fileName ?? "mock.txt",
982
+ file_size: options?.fileSize ?? 1024,
983
+ file_hash: options?.fileHash
984
+ }
985
+ };
986
+ }
987
+ _inseg.file = file;
988
+ function forward(options) {
989
+ return {
990
+ type: "forward",
991
+ data: {
992
+ forward_id: options?.forwardId ?? "mock-forward",
993
+ title: options?.title ?? "Chat History",
994
+ preview: options?.preview ?? ["Alice: ping", "Bob: pong"],
995
+ summary: options?.summary ?? "View forwarded messages"
996
+ }
997
+ };
998
+ }
999
+ _inseg.forward = forward;
1000
+ function marketFace(options) {
1001
+ return {
1002
+ type: "market_face",
1003
+ data: {
1004
+ emoji_package_id: options?.emojiPackageId ?? 1,
1005
+ emoji_id: options?.emojiId ?? "mock-market-face",
1006
+ key: options?.key ?? "mock-key",
1007
+ summary: options?.summary ?? "[market face]",
1008
+ url: options?.url ?? "https://example.invalid/mock-market-face"
1009
+ }
1010
+ };
1011
+ }
1012
+ _inseg.marketFace = marketFace;
1013
+ function lightApp(appName, jsonPayload) {
1014
+ return {
1015
+ type: "light_app",
1016
+ data: {
1017
+ app_name: appName,
1018
+ json_payload: jsonPayload
1019
+ }
1020
+ };
1021
+ }
1022
+ _inseg.lightApp = lightApp;
1023
+ function xml(serviceId, xmlPayload) {
1024
+ return {
1025
+ type: "xml",
1026
+ data: {
1027
+ service_id: serviceId,
1028
+ xml_payload: xmlPayload
1029
+ }
1030
+ };
1031
+ }
1032
+ _inseg.xml = xml;
1033
+ function markdown(content) {
1034
+ return {
1035
+ type: "markdown",
1036
+ data: { content }
1037
+ };
1038
+ }
1039
+ _inseg.markdown = markdown;
1040
+ })(inseg || (inseg = {}));
1041
+ //#endregion
1042
+ //#region src/index.ts
1043
+ function installMock(ctx, service) {
1044
+ ctx.hookApi((call) => service.handleApiCall(call));
1045
+ ctx.installEventSource(service);
1046
+ ctx.provide(MockService, service);
1047
+ return service;
1048
+ }
1049
+ const MockPlugin = definePlugin({
1050
+ name: "mock",
1051
+ provides: [MockService],
1052
+ apply(ctx, options) {
1053
+ installMock(ctx, options?.service ?? new MockService(options));
1054
+ }
1055
+ });
1056
+ const unreachableClient = new Proxy({}, { get(_target, prop) {
1057
+ if (typeof prop === "string" && prop.includes("_")) return () => {
1058
+ throw new Error(`[plugin-mock] API "${prop}" fell through to the stub client.`);
1059
+ };
1060
+ } });
1061
+ function createMockContext(options) {
1062
+ const ctx = Context.fromClient(unreachableClient, {
1063
+ ...options,
1064
+ logHandler: options?.logHandler ?? createSimpleLogHandler()
1065
+ });
1066
+ const mock = installMock(ctx, new MockService(options));
1067
+ return Object.assign(ctx, { mock });
1068
+ }
1069
+ //#endregion
1070
+ export { MockInbox, MockPlugin, MockPlugin as default, MockService, createMockContext, createMockInbox, createRandomFriend, createRandomGroup, createRandomGroupMember, createSimpleLogHandler, inmsg, inseg };
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@fraqjs/plugin-mock",
3
+ "type": "module",
4
+ "version": "0.1.0",
5
+ "description": "提供模拟 Milky 协议端的能力,帮助开发者编写插件测试",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "main": "dist/index.mjs",
10
+ "typings": "dist/index.d.mts",
11
+ "keywords": [],
12
+ "author": "fraqjs",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/fraqjs/fraq.git"
16
+ },
17
+ "homepage": "https://fraq.dev/docs/plugins/mock",
18
+ "license": "MIT",
19
+ "peerDependencies": {
20
+ "@fraqjs/fraq": "^0.14.0"
21
+ },
22
+ "engines": {
23
+ "node": ">=22"
24
+ },
25
+ "fraq": {
26
+ "category": "infrastructure"
27
+ },
28
+ "scripts": {
29
+ "build": "tsdown",
30
+ "test": "tsx --test test/*.test.ts"
31
+ }
32
+ }