@fabriktor/fx 0.0.37 → 0.0.39
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/dist/src/chat/chat.d.ts +21 -3
- package/dist/src/chat/chat.js +296 -76
- package/dist/src/core/search.d.ts +24 -0
- package/dist/src/core/search.js +84 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/memos/memos.d.ts +19 -0
- package/dist/src/memos/memos.js +24 -1
- package/package.json +3 -3
package/dist/src/chat/chat.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { type Chat, type ChatRoom, type ChatRoomMember } from "@fabriktor/schema";
|
|
2
1
|
import type { ChatOperator, WSCloseHandler, WSConnection, WSErrorHandler, WSMessageHandler } from "@fabriktor/client";
|
|
2
|
+
import { type Chat, type ChatRoom, type ChatRoomMember } from "@fabriktor/schema";
|
|
3
|
+
import { type SearchPage } from "../core/search.js";
|
|
4
|
+
type ChatInsertOptions = Parameters<ChatOperator["insertOneChat"]>[0];
|
|
5
|
+
export type CreateChatOptions = ChatInsertOptions;
|
|
3
6
|
export type ReactChatOptions = Parameters<ChatOperator["reactChat"]>[0];
|
|
4
7
|
export type ChatFXOptions = {
|
|
5
8
|
chat: ChatOperator;
|
|
@@ -27,6 +30,13 @@ export type LatestChatsPage = {
|
|
|
27
30
|
has_more: boolean;
|
|
28
31
|
next_cursor: LatestChatsCursor;
|
|
29
32
|
};
|
|
33
|
+
export type SearchChatRoomMembersOptions = {
|
|
34
|
+
chat_room_id: string;
|
|
35
|
+
q?: string;
|
|
36
|
+
page?: number;
|
|
37
|
+
per_page?: number;
|
|
38
|
+
};
|
|
39
|
+
export type SearchChatRoomMembersPage = SearchPage<ChatRoomMember>;
|
|
30
40
|
export type ChatRoomMembersOptions = {
|
|
31
41
|
chat_room_id: string;
|
|
32
42
|
};
|
|
@@ -53,22 +63,24 @@ export type ReactUploadedChatOptions = {
|
|
|
53
63
|
reaction: ReactChatOptions;
|
|
54
64
|
};
|
|
55
65
|
export type ChatRoomSession = {
|
|
56
|
-
latest: LatestChatsPage;
|
|
57
66
|
chats_ws: WSConnection;
|
|
58
67
|
chat_room_ws: WSConnection;
|
|
68
|
+
latest: LatestChatsPage;
|
|
59
69
|
closeNormal(reason?: string): void;
|
|
60
70
|
closeLeavingPage(reason?: string): void;
|
|
61
71
|
};
|
|
62
72
|
export type GlobalChatRoomsSession = {
|
|
63
|
-
members: ChatRoomMember[];
|
|
64
73
|
chat_rooms_ws: WSConnection;
|
|
74
|
+
members: ChatRoomMember[];
|
|
65
75
|
closeNormal(reason?: string): void;
|
|
66
76
|
closeLeavingPage(reason?: string): void;
|
|
67
77
|
};
|
|
68
78
|
export interface ChatFXOperator {
|
|
79
|
+
createChat(opts: CreateChatOptions): Promise<Chat>;
|
|
69
80
|
newLatestChatsCursor(opts: LatestChatsOptions): LatestChatsCursor;
|
|
70
81
|
latestChats(opts: LatestChatsOptions): Promise<LatestChatsPage>;
|
|
71
82
|
nextLatestChats(opts: NextLatestChatsOptions): Promise<LatestChatsPage>;
|
|
83
|
+
searchChatRoomMembers(opts: SearchChatRoomMembersOptions): Promise<SearchChatRoomMembersPage>;
|
|
72
84
|
chatRoomMembers(opts: ChatRoomMembersOptions): Promise<ChatRoomMember[]>;
|
|
73
85
|
myChatRoomMembers(opts: MyChatRoomMembersOptions): Promise<ChatRoomMember[]>;
|
|
74
86
|
openChatRoom(opts: OpenChatRoomOptions): Promise<ChatRoomSession>;
|
|
@@ -78,13 +90,19 @@ export interface ChatFXOperator {
|
|
|
78
90
|
export declare class ChatFX implements ChatFXOperator {
|
|
79
91
|
private chat;
|
|
80
92
|
constructor(opts: ChatFXOptions);
|
|
93
|
+
createChat(opts: CreateChatOptions): Promise<Chat>;
|
|
81
94
|
newLatestChatsCursor(opts: LatestChatsOptions): LatestChatsCursor;
|
|
82
95
|
latestChats(opts: LatestChatsOptions): Promise<LatestChatsPage>;
|
|
83
96
|
nextLatestChats(opts: NextLatestChatsOptions): Promise<LatestChatsPage>;
|
|
97
|
+
searchChatRoomMembers(opts: SearchChatRoomMembersOptions): Promise<SearchChatRoomMembersPage>;
|
|
84
98
|
chatRoomMembers(opts: ChatRoomMembersOptions): Promise<ChatRoomMember[]>;
|
|
85
99
|
myChatRoomMembers(opts: MyChatRoomMembersOptions): Promise<ChatRoomMember[]>;
|
|
86
100
|
openChatRoom(opts: OpenChatRoomOptions): Promise<ChatRoomSession>;
|
|
87
101
|
openGlobalChatRooms(opts: OpenGlobalChatRoomsOptions): Promise<GlobalChatRoomsSession>;
|
|
88
102
|
reactUploadedChat(opts: ReactUploadedChatOptions): Promise<void>;
|
|
103
|
+
private openChatRoomConnections;
|
|
104
|
+
private openGlobalChatRoomsConnection;
|
|
105
|
+
private findChat;
|
|
89
106
|
}
|
|
90
107
|
export declare function newChatFX(opts: ChatFXOptions): ChatFX;
|
|
108
|
+
export {};
|
package/dist/src/chat/chat.js
CHANGED
|
@@ -3,40 +3,50 @@
|
|
|
3
3
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
4
4
|
// not use this file except in compliance with the License. You may obtain
|
|
5
5
|
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
-
import {
|
|
7
|
-
import { errValidation } from "../core/err.js";
|
|
8
|
-
|
|
9
|
-
const defaultPerPage = 30;
|
|
6
|
+
import { ZeroID, } from "@fabriktor/schema";
|
|
7
|
+
import { errResolveFailed, errValidation } from "../core/err.js";
|
|
8
|
+
import { normalizeSearchPage, normalizeSearchPerPage, runSearch, } from "../core/search.js";
|
|
10
9
|
const closeCodeNormal = 1000;
|
|
11
10
|
const closeCodeLeavingPage = 1001;
|
|
11
|
+
const wsRefreshMilliseconds = 40 * 60 * 1000;
|
|
12
|
+
const wsRefreshRetryMilliseconds = 30 * 1000;
|
|
13
|
+
const resourceChatID = "chat_id";
|
|
14
|
+
const resourceChat = "chat";
|
|
15
|
+
const msgCreatedChatIDMissing = "Created chat response did not contain an ID.";
|
|
16
|
+
const msgChatMissing = "Chat could not be resolved after the mutation.";
|
|
12
17
|
const msgChatObjectsUploading = "Cannot react to a chat while objects are still uploading.";
|
|
13
18
|
export class ChatFX {
|
|
14
19
|
chat;
|
|
15
20
|
constructor(opts) {
|
|
16
21
|
this.chat = opts.chat;
|
|
17
22
|
}
|
|
23
|
+
async createChat(opts) {
|
|
24
|
+
const out = await this.chat.insertOneChat(opts);
|
|
25
|
+
const id = requiredOperationID(out, {
|
|
26
|
+
resource: resourceChatID,
|
|
27
|
+
msg: msgCreatedChatIDMissing,
|
|
28
|
+
});
|
|
29
|
+
return await this.findChat(id);
|
|
30
|
+
}
|
|
18
31
|
newLatestChatsCursor(opts) {
|
|
19
32
|
return {
|
|
20
33
|
chat_room_id: opts.chat_room_id,
|
|
21
|
-
next_page:
|
|
22
|
-
per_page:
|
|
34
|
+
next_page: normalizeSearchPage(opts.page),
|
|
35
|
+
per_page: normalizeSearchPerPage(opts.per_page),
|
|
23
36
|
done: false,
|
|
24
37
|
};
|
|
25
38
|
}
|
|
26
39
|
async latestChats(opts) {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
const raw = await this.chat.searchManyChats({
|
|
40
|
+
const raw = await runSearch({
|
|
41
|
+
search: (searchOpts) => this.chat.searchManyChats(searchOpts),
|
|
30
42
|
query: {
|
|
31
43
|
chat_room_id: opts.chat_room_id,
|
|
32
|
-
[SearchPageKey]: page,
|
|
33
|
-
[SearchPerPageKey]: per_page,
|
|
34
44
|
},
|
|
45
|
+
page: opts.page,
|
|
46
|
+
per_page: opts.per_page,
|
|
35
47
|
});
|
|
36
48
|
return latestChatsPage({
|
|
37
49
|
chat_room_id: opts.chat_room_id,
|
|
38
|
-
page,
|
|
39
|
-
per_page,
|
|
40
50
|
raw,
|
|
41
51
|
});
|
|
42
52
|
}
|
|
@@ -50,6 +60,17 @@ export class ChatFX {
|
|
|
50
60
|
per_page: opts.cursor.per_page,
|
|
51
61
|
});
|
|
52
62
|
}
|
|
63
|
+
async searchChatRoomMembers(opts) {
|
|
64
|
+
return await runSearch({
|
|
65
|
+
search: (searchOpts) => this.chat.searchManyChatRoomMembers(searchOpts),
|
|
66
|
+
query: {
|
|
67
|
+
chat_room_id: opts.chat_room_id,
|
|
68
|
+
},
|
|
69
|
+
q: opts.q,
|
|
70
|
+
page: opts.page,
|
|
71
|
+
per_page: opts.per_page,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
53
74
|
async chatRoomMembers(opts) {
|
|
54
75
|
const out = await this.chat.queryChatRoomMembers({
|
|
55
76
|
query: {
|
|
@@ -71,28 +92,66 @@ export class ChatFX {
|
|
|
71
92
|
chat_room_id: opts.chat_room_id,
|
|
72
93
|
per_page: opts.per_page,
|
|
73
94
|
});
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
95
|
+
const fx = this;
|
|
96
|
+
let current = await fx.openChatRoomConnections(opts);
|
|
97
|
+
let refresh_timer = null;
|
|
98
|
+
let closed = false;
|
|
99
|
+
current.activate();
|
|
100
|
+
scheduleRefresh();
|
|
101
|
+
async function refresh() {
|
|
102
|
+
refresh_timer = null;
|
|
103
|
+
if (closed) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const next = await fx.openChatRoomConnections(opts);
|
|
108
|
+
if (closed) {
|
|
109
|
+
next.close(closeCodeNormal);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const previous = current;
|
|
113
|
+
previous.deactivate();
|
|
114
|
+
current = next;
|
|
115
|
+
current.activate();
|
|
116
|
+
previous.close(closeCodeNormal);
|
|
117
|
+
scheduleRefresh();
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
scheduleRefresh(wsRefreshRetryMilliseconds);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function scheduleRefresh(delay = wsRefreshMilliseconds) {
|
|
124
|
+
if (closed) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
refresh_timer = setTimeout(() => {
|
|
128
|
+
void refresh();
|
|
129
|
+
}, delay);
|
|
130
|
+
}
|
|
131
|
+
function closeSession(code, reason) {
|
|
132
|
+
if (closed) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
closed = true;
|
|
136
|
+
if (refresh_timer !== null) {
|
|
137
|
+
clearTimeout(refresh_timer);
|
|
138
|
+
refresh_timer = null;
|
|
139
|
+
}
|
|
140
|
+
current.close(code, reason);
|
|
141
|
+
}
|
|
87
142
|
return {
|
|
88
143
|
latest,
|
|
89
|
-
chats_ws
|
|
90
|
-
|
|
144
|
+
get chats_ws() {
|
|
145
|
+
return current.chats_ws;
|
|
146
|
+
},
|
|
147
|
+
get chat_room_ws() {
|
|
148
|
+
return current.chat_room_ws;
|
|
149
|
+
},
|
|
91
150
|
closeNormal(reason) {
|
|
92
|
-
|
|
151
|
+
closeSession(closeCodeNormal, reason);
|
|
93
152
|
},
|
|
94
153
|
closeLeavingPage(reason) {
|
|
95
|
-
|
|
154
|
+
closeSession(closeCodeLeavingPage, reason);
|
|
96
155
|
},
|
|
97
156
|
};
|
|
98
157
|
}
|
|
@@ -100,20 +159,63 @@ export class ChatFX {
|
|
|
100
159
|
const members = await this.myChatRoomMembers({
|
|
101
160
|
owner_id: opts.owner_id,
|
|
102
161
|
});
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
162
|
+
const fx = this;
|
|
163
|
+
let current = await fx.openGlobalChatRoomsConnection(opts);
|
|
164
|
+
let refresh_timer = null;
|
|
165
|
+
let closed = false;
|
|
166
|
+
current.activate();
|
|
167
|
+
scheduleRefresh();
|
|
168
|
+
async function refresh() {
|
|
169
|
+
refresh_timer = null;
|
|
170
|
+
if (closed) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
const next = await fx.openGlobalChatRoomsConnection(opts);
|
|
175
|
+
if (closed) {
|
|
176
|
+
next.close(closeCodeNormal);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const previous = current;
|
|
180
|
+
previous.deactivate();
|
|
181
|
+
current = next;
|
|
182
|
+
current.activate();
|
|
183
|
+
previous.close(closeCodeNormal);
|
|
184
|
+
scheduleRefresh();
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
scheduleRefresh(wsRefreshRetryMilliseconds);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function scheduleRefresh(delay = wsRefreshMilliseconds) {
|
|
191
|
+
if (closed) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
refresh_timer = setTimeout(() => {
|
|
195
|
+
void refresh();
|
|
196
|
+
}, delay);
|
|
197
|
+
}
|
|
198
|
+
function closeSession(code, reason) {
|
|
199
|
+
if (closed) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
closed = true;
|
|
203
|
+
if (refresh_timer !== null) {
|
|
204
|
+
clearTimeout(refresh_timer);
|
|
205
|
+
refresh_timer = null;
|
|
206
|
+
}
|
|
207
|
+
current.close(code, reason);
|
|
208
|
+
}
|
|
109
209
|
return {
|
|
110
210
|
members,
|
|
111
|
-
chat_rooms_ws
|
|
211
|
+
get chat_rooms_ws() {
|
|
212
|
+
return current.chat_rooms_ws;
|
|
213
|
+
},
|
|
112
214
|
closeNormal(reason) {
|
|
113
|
-
|
|
215
|
+
closeSession(closeCodeNormal, reason);
|
|
114
216
|
},
|
|
115
217
|
closeLeavingPage(reason) {
|
|
116
|
-
|
|
218
|
+
closeSession(closeCodeLeavingPage, reason);
|
|
117
219
|
},
|
|
118
220
|
};
|
|
119
221
|
}
|
|
@@ -126,34 +228,156 @@ export class ChatFX {
|
|
|
126
228
|
}
|
|
127
229
|
await this.chat.reactChat(opts.reaction);
|
|
128
230
|
}
|
|
231
|
+
async openChatRoomConnections(opts) {
|
|
232
|
+
let active = false;
|
|
233
|
+
const chats_ws = await this.chat.wsChats({
|
|
234
|
+
owner_id: opts.owner_id,
|
|
235
|
+
chat_room_id: opts.chat_room_id,
|
|
236
|
+
on_message(v, ev) {
|
|
237
|
+
if (active) {
|
|
238
|
+
opts.on_chat(v, ev);
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
on_error: opts.on_error === undefined
|
|
242
|
+
? undefined
|
|
243
|
+
: (ev) => {
|
|
244
|
+
if (active) {
|
|
245
|
+
opts.on_error?.(ev);
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
on_close: opts.on_close === undefined
|
|
249
|
+
? undefined
|
|
250
|
+
: (ev) => {
|
|
251
|
+
if (active) {
|
|
252
|
+
opts.on_close?.(ev);
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
let chat_room_ws;
|
|
257
|
+
try {
|
|
258
|
+
chat_room_ws = await this.chat.wsChatRoom({
|
|
259
|
+
id: opts.chat_room_id,
|
|
260
|
+
on_message(v, ev) {
|
|
261
|
+
if (active) {
|
|
262
|
+
opts.on_chat_room(v, ev);
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
on_error: opts.on_error === undefined
|
|
266
|
+
? undefined
|
|
267
|
+
: (ev) => {
|
|
268
|
+
if (active) {
|
|
269
|
+
opts.on_error?.(ev);
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
on_close: opts.on_close === undefined
|
|
273
|
+
? undefined
|
|
274
|
+
: (ev) => {
|
|
275
|
+
if (active) {
|
|
276
|
+
opts.on_close?.(ev);
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
catch (cause) {
|
|
282
|
+
chats_ws.close(closeCodeNormal);
|
|
283
|
+
await Promise.allSettled([chats_ws.opened]);
|
|
284
|
+
throw cause;
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
await Promise.all([chats_ws.opened, chat_room_ws.opened]);
|
|
288
|
+
}
|
|
289
|
+
catch (cause) {
|
|
290
|
+
closeWS([chats_ws, chat_room_ws], closeCodeNormal);
|
|
291
|
+
throw cause;
|
|
292
|
+
}
|
|
293
|
+
return {
|
|
294
|
+
chats_ws,
|
|
295
|
+
chat_room_ws,
|
|
296
|
+
activate() {
|
|
297
|
+
active = true;
|
|
298
|
+
},
|
|
299
|
+
deactivate() {
|
|
300
|
+
active = false;
|
|
301
|
+
},
|
|
302
|
+
close(code, reason) {
|
|
303
|
+
closeWS([chats_ws, chat_room_ws], code, reason);
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
async openGlobalChatRoomsConnection(opts) {
|
|
308
|
+
let active = false;
|
|
309
|
+
const chat_rooms_ws = await this.chat.wsMineChatRooms({
|
|
310
|
+
owner_id: opts.owner_id,
|
|
311
|
+
on_message(v, ev) {
|
|
312
|
+
if (active) {
|
|
313
|
+
opts.on_chat_room(v, ev);
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
on_error: opts.on_error === undefined
|
|
317
|
+
? undefined
|
|
318
|
+
: (ev) => {
|
|
319
|
+
if (active) {
|
|
320
|
+
opts.on_error?.(ev);
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
on_close: opts.on_close === undefined
|
|
324
|
+
? undefined
|
|
325
|
+
: (ev) => {
|
|
326
|
+
if (active) {
|
|
327
|
+
opts.on_close?.(ev);
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
try {
|
|
332
|
+
await chat_rooms_ws.opened;
|
|
333
|
+
}
|
|
334
|
+
catch (cause) {
|
|
335
|
+
chat_rooms_ws.close(closeCodeNormal);
|
|
336
|
+
throw cause;
|
|
337
|
+
}
|
|
338
|
+
return {
|
|
339
|
+
chat_rooms_ws,
|
|
340
|
+
activate() {
|
|
341
|
+
active = true;
|
|
342
|
+
},
|
|
343
|
+
deactivate() {
|
|
344
|
+
active = false;
|
|
345
|
+
},
|
|
346
|
+
close(code, reason) {
|
|
347
|
+
chat_rooms_ws.close(code, reason);
|
|
348
|
+
},
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
async findChat(id) {
|
|
352
|
+
const out = await this.chat.findOneChat({
|
|
353
|
+
id,
|
|
354
|
+
});
|
|
355
|
+
const chat = out.value;
|
|
356
|
+
if (chat === undefined) {
|
|
357
|
+
throw errResolveFailed({
|
|
358
|
+
resource: resourceChat,
|
|
359
|
+
msg: msgChatMissing,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
return chat;
|
|
363
|
+
}
|
|
129
364
|
}
|
|
130
365
|
export function newChatFX(opts) {
|
|
131
366
|
return new ChatFX(opts);
|
|
132
367
|
}
|
|
133
368
|
function latestChatsPage(opts) {
|
|
134
|
-
const value = opts.raw.value;
|
|
135
|
-
const chats = value?.[SearchResultResultsKey] ?? [];
|
|
136
|
-
const total_count = value?.total_count;
|
|
137
|
-
const last_page = value?.last_page;
|
|
138
|
-
const has_more = hasMoreChats({
|
|
139
|
-
chats,
|
|
140
|
-
page: opts.page,
|
|
141
|
-
per_page: opts.per_page,
|
|
142
|
-
total_count,
|
|
143
|
-
last_page,
|
|
144
|
-
});
|
|
145
369
|
return {
|
|
146
|
-
chats,
|
|
147
|
-
page: opts.page,
|
|
148
|
-
per_page: opts.per_page,
|
|
149
|
-
total_count,
|
|
150
|
-
last_page,
|
|
151
|
-
has_more,
|
|
370
|
+
chats: opts.raw.results,
|
|
371
|
+
page: opts.raw.page,
|
|
372
|
+
per_page: opts.raw.per_page,
|
|
373
|
+
total_count: opts.raw.total_count,
|
|
374
|
+
last_page: opts.raw.last_page,
|
|
375
|
+
has_more: opts.raw.has_more,
|
|
152
376
|
next_cursor: {
|
|
153
377
|
chat_room_id: opts.chat_room_id,
|
|
154
|
-
next_page:
|
|
155
|
-
per_page: opts.per_page,
|
|
156
|
-
done:
|
|
378
|
+
next_page: opts.raw.next_page,
|
|
379
|
+
per_page: opts.raw.per_page,
|
|
380
|
+
done: opts.raw.done,
|
|
157
381
|
},
|
|
158
382
|
};
|
|
159
383
|
}
|
|
@@ -166,29 +390,25 @@ function emptyLatestChatsPage(cursor) {
|
|
|
166
390
|
next_cursor: cursor,
|
|
167
391
|
};
|
|
168
392
|
}
|
|
169
|
-
function hasMoreChats(opts) {
|
|
170
|
-
if (opts.last_page !== undefined) {
|
|
171
|
-
return opts.page < opts.last_page;
|
|
172
|
-
}
|
|
173
|
-
if (opts.total_count !== undefined) {
|
|
174
|
-
return opts.page * opts.per_page < opts.total_count;
|
|
175
|
-
}
|
|
176
|
-
return opts.chats.length >= opts.per_page;
|
|
177
|
-
}
|
|
178
393
|
function closeWS(conns, code, reason) {
|
|
179
394
|
for (const conn of conns) {
|
|
180
395
|
conn.close(code, reason);
|
|
181
396
|
}
|
|
182
397
|
}
|
|
183
|
-
function
|
|
184
|
-
|
|
185
|
-
|
|
398
|
+
function requiredOperationID(out, opts) {
|
|
399
|
+
const id = resolvedID(out.id);
|
|
400
|
+
if (id === undefined) {
|
|
401
|
+
throw errResolveFailed({
|
|
402
|
+
resource: opts.resource,
|
|
403
|
+
msg: opts.msg,
|
|
404
|
+
});
|
|
186
405
|
}
|
|
187
|
-
return
|
|
406
|
+
return id;
|
|
188
407
|
}
|
|
189
|
-
function
|
|
190
|
-
|
|
191
|
-
|
|
408
|
+
function resolvedID(value) {
|
|
409
|
+
const id = value?.trim();
|
|
410
|
+
if (id === undefined || id === "" || id === ZeroID) {
|
|
411
|
+
return undefined;
|
|
192
412
|
}
|
|
193
|
-
return
|
|
413
|
+
return id;
|
|
194
414
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { OperationResultOf, SearchManyOptionsCRUD, SearchResultOf } from "@fabriktor/client";
|
|
2
|
+
export type SearchQuery = NonNullable<SearchManyOptionsCRUD["query"]>;
|
|
3
|
+
export type SearchCall<T> = (opts: SearchManyOptionsCRUD) => Promise<OperationResultOf<SearchResultOf<T>>>;
|
|
4
|
+
export type RunSearchOptions<T> = {
|
|
5
|
+
search: SearchCall<T>;
|
|
6
|
+
query?: SearchQuery;
|
|
7
|
+
q?: string;
|
|
8
|
+
page?: number;
|
|
9
|
+
per_page?: number;
|
|
10
|
+
};
|
|
11
|
+
export type SearchPage<T> = {
|
|
12
|
+
results: T[];
|
|
13
|
+
page: number;
|
|
14
|
+
per_page: number;
|
|
15
|
+
total_count?: number;
|
|
16
|
+
last_page?: number;
|
|
17
|
+
has_more: boolean;
|
|
18
|
+
next_page: number;
|
|
19
|
+
done: boolean;
|
|
20
|
+
};
|
|
21
|
+
export declare function runSearch<T>(opts: RunSearchOptions<T>): Promise<SearchPage<T>>;
|
|
22
|
+
export declare function normalizeSearchPage(value?: number): number;
|
|
23
|
+
export declare function normalizeSearchPerPage(value?: number): number;
|
|
24
|
+
export declare function normalizeSearchQuery(value?: string): string | undefined;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Copyright (C) Fabriktor, Inc. 2025-present.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
4
|
+
// not use this file except in compliance with the License. You may obtain
|
|
5
|
+
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
import { SearchPageKey, SearchPerPageKey, SearchQueryKey, SearchResultResultsKey, } from "@fabriktor/schema";
|
|
7
|
+
const firstSearchPage = 1;
|
|
8
|
+
const defaultSearchPerPage = 30;
|
|
9
|
+
export async function runSearch(opts) {
|
|
10
|
+
const page = normalizeSearchPage(opts.page);
|
|
11
|
+
const per_page = normalizeSearchPerPage(opts.per_page);
|
|
12
|
+
const q = normalizeSearchQuery(opts.q);
|
|
13
|
+
const query = q === undefined
|
|
14
|
+
? {
|
|
15
|
+
...opts.query,
|
|
16
|
+
[SearchPageKey]: page,
|
|
17
|
+
[SearchPerPageKey]: per_page,
|
|
18
|
+
}
|
|
19
|
+
: {
|
|
20
|
+
...opts.query,
|
|
21
|
+
[SearchPageKey]: page,
|
|
22
|
+
[SearchPerPageKey]: per_page,
|
|
23
|
+
[SearchQueryKey]: q,
|
|
24
|
+
};
|
|
25
|
+
const raw = await opts.search({
|
|
26
|
+
query,
|
|
27
|
+
});
|
|
28
|
+
return resolveSearchPage({
|
|
29
|
+
raw,
|
|
30
|
+
page,
|
|
31
|
+
per_page,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export function normalizeSearchPage(value) {
|
|
35
|
+
if (value === undefined || !Number.isFinite(value)) {
|
|
36
|
+
return firstSearchPage;
|
|
37
|
+
}
|
|
38
|
+
return Math.max(firstSearchPage, Math.trunc(value));
|
|
39
|
+
}
|
|
40
|
+
export function normalizeSearchPerPage(value) {
|
|
41
|
+
if (value === undefined || !Number.isFinite(value)) {
|
|
42
|
+
return defaultSearchPerPage;
|
|
43
|
+
}
|
|
44
|
+
return Math.max(1, Math.trunc(value));
|
|
45
|
+
}
|
|
46
|
+
export function normalizeSearchQuery(value) {
|
|
47
|
+
const query = value?.trim();
|
|
48
|
+
if (!query) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
return query;
|
|
52
|
+
}
|
|
53
|
+
function resolveSearchPage(opts) {
|
|
54
|
+
const value = opts.raw.value;
|
|
55
|
+
const results = value?.[SearchResultResultsKey] ?? [];
|
|
56
|
+
const total_count = value?.total_count;
|
|
57
|
+
const last_page = value?.last_page;
|
|
58
|
+
const has_more = hasMoreSearchResults({
|
|
59
|
+
result_count: results.length,
|
|
60
|
+
page: opts.page,
|
|
61
|
+
per_page: opts.per_page,
|
|
62
|
+
total_count,
|
|
63
|
+
last_page,
|
|
64
|
+
});
|
|
65
|
+
return {
|
|
66
|
+
results,
|
|
67
|
+
page: opts.page,
|
|
68
|
+
per_page: opts.per_page,
|
|
69
|
+
total_count,
|
|
70
|
+
last_page,
|
|
71
|
+
has_more,
|
|
72
|
+
next_page: has_more ? opts.page + 1 : opts.page,
|
|
73
|
+
done: !has_more,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function hasMoreSearchResults(opts) {
|
|
77
|
+
if (opts.last_page !== undefined) {
|
|
78
|
+
return opts.page < opts.last_page;
|
|
79
|
+
}
|
|
80
|
+
if (opts.total_count !== undefined) {
|
|
81
|
+
return opts.page * opts.per_page < opts.total_count;
|
|
82
|
+
}
|
|
83
|
+
return opts.result_count >= opts.per_page;
|
|
84
|
+
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * from "./chat/chat.js";
|
|
|
5
5
|
export * from "./contacts/contacts.js";
|
|
6
6
|
export * from "./core/err.js";
|
|
7
7
|
export * from "./core/pull.js";
|
|
8
|
+
export * from "./core/search.js";
|
|
8
9
|
export * from "./feed/feed.js";
|
|
9
10
|
export * from "./fx.js";
|
|
10
11
|
export * from "./jobs/jobs.js";
|
package/dist/src/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export * from "./chat/chat.js";
|
|
|
5
5
|
export * from "./contacts/contacts.js";
|
|
6
6
|
export * from "./core/err.js";
|
|
7
7
|
export * from "./core/pull.js";
|
|
8
|
+
export * from "./core/search.js";
|
|
8
9
|
export * from "./feed/feed.js";
|
|
9
10
|
export * from "./fx.js";
|
|
10
11
|
export * from "./jobs/jobs.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { MemosOperator } from "@fabriktor/client";
|
|
2
2
|
import { type Memo, type MemoFolder } from "@fabriktor/schema";
|
|
3
|
+
import { type SearchPage } from "../core/search.js";
|
|
3
4
|
type MemoInsertOptions = Parameters<MemosOperator["insertOneMemo"]>[0];
|
|
4
5
|
type MemoReplaceOptions = Parameters<MemosOperator["replaceOneMemo"]>[0];
|
|
5
6
|
type MemoFolderInsertOptions = Parameters<MemosOperator["insertOneMemoFolder"]>[0];
|
|
@@ -8,11 +9,27 @@ export type CreateMemoOptions = MemoInsertOptions;
|
|
|
8
9
|
export type ReplaceMemoOptions = MemoReplaceOptions;
|
|
9
10
|
export type CreateMemoFolderOptions = MemoFolderInsertOptions;
|
|
10
11
|
export type ReplaceMemoFolderOptions = MemoFolderReplaceOptions;
|
|
12
|
+
export type SearchMemosOptions = {
|
|
13
|
+
owner_id: string;
|
|
14
|
+
q?: string;
|
|
15
|
+
page?: number;
|
|
16
|
+
per_page?: number;
|
|
17
|
+
};
|
|
18
|
+
export type SearchMemoFoldersOptions = {
|
|
19
|
+
owner_id: string;
|
|
20
|
+
q?: string;
|
|
21
|
+
page?: number;
|
|
22
|
+
per_page?: number;
|
|
23
|
+
};
|
|
24
|
+
export type SearchMemosPage = SearchPage<Memo>;
|
|
25
|
+
export type SearchMemoFoldersPage = SearchPage<MemoFolder>;
|
|
11
26
|
export interface MemosFXOperator {
|
|
12
27
|
createMemo(opts: CreateMemoOptions): Promise<Memo>;
|
|
13
28
|
replaceMemo(opts: ReplaceMemoOptions): Promise<Memo>;
|
|
14
29
|
createMemoFolder(opts: CreateMemoFolderOptions): Promise<MemoFolder>;
|
|
15
30
|
replaceMemoFolder(opts: ReplaceMemoFolderOptions): Promise<MemoFolder>;
|
|
31
|
+
searchMemos(opts: SearchMemosOptions): Promise<SearchMemosPage>;
|
|
32
|
+
searchMemoFolders(opts: SearchMemoFoldersOptions): Promise<SearchMemoFoldersPage>;
|
|
16
33
|
}
|
|
17
34
|
export type MemosFXOptions = {
|
|
18
35
|
memos: MemosOperator;
|
|
@@ -24,6 +41,8 @@ export declare class MemosFX implements MemosFXOperator {
|
|
|
24
41
|
replaceMemo(opts: ReplaceMemoOptions): Promise<Memo>;
|
|
25
42
|
createMemoFolder(opts: CreateMemoFolderOptions): Promise<MemoFolder>;
|
|
26
43
|
replaceMemoFolder(opts: ReplaceMemoFolderOptions): Promise<MemoFolder>;
|
|
44
|
+
searchMemos(opts: SearchMemosOptions): Promise<SearchMemosPage>;
|
|
45
|
+
searchMemoFolders(opts: SearchMemoFoldersOptions): Promise<SearchMemoFoldersPage>;
|
|
27
46
|
private findMemo;
|
|
28
47
|
private findMemoFolder;
|
|
29
48
|
}
|
package/dist/src/memos/memos.js
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
4
4
|
// not use this file except in compliance with the License. You may obtain
|
|
5
5
|
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
-
import { ZeroID, } from "@fabriktor/schema";
|
|
6
|
+
import { OwnerIDKey, ZeroID, } from "@fabriktor/schema";
|
|
7
7
|
import { errResolveFailed } from "../core/err.js";
|
|
8
|
+
import { runSearch } from "../core/search.js";
|
|
8
9
|
const resourceMemoID = "memo_id";
|
|
9
10
|
const resourceMemo = "memo";
|
|
10
11
|
const resourceMemoFolderID = "memo_folder_id";
|
|
@@ -42,6 +43,28 @@ export class MemosFX {
|
|
|
42
43
|
await this.memos.replaceOneMemoFolder(opts);
|
|
43
44
|
return await this.findMemoFolder(opts.id);
|
|
44
45
|
}
|
|
46
|
+
async searchMemos(opts) {
|
|
47
|
+
return await runSearch({
|
|
48
|
+
search: (searchOpts) => this.memos.searchManyMemos(searchOpts),
|
|
49
|
+
query: {
|
|
50
|
+
[OwnerIDKey]: opts.owner_id,
|
|
51
|
+
},
|
|
52
|
+
q: opts.q,
|
|
53
|
+
page: opts.page,
|
|
54
|
+
per_page: opts.per_page,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
async searchMemoFolders(opts) {
|
|
58
|
+
return await runSearch({
|
|
59
|
+
search: (searchOpts) => this.memos.searchManyMemoFolders(searchOpts),
|
|
60
|
+
query: {
|
|
61
|
+
[OwnerIDKey]: opts.owner_id,
|
|
62
|
+
},
|
|
63
|
+
q: opts.q,
|
|
64
|
+
page: opts.page,
|
|
65
|
+
per_page: opts.per_page,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
45
68
|
async findMemo(id) {
|
|
46
69
|
const out = await this.memos.findOneMemo({
|
|
47
70
|
id,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fabriktor/fx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.39",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"typescript": "^6.0.3"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@fabriktor/client": "0.0.
|
|
21
|
-
"@fabriktor/schema": "0.0.
|
|
20
|
+
"@fabriktor/client": "0.0.45",
|
|
21
|
+
"@fabriktor/schema": "0.0.32"
|
|
22
22
|
}
|
|
23
23
|
}
|