@fabriktor/fx 0.0.31 → 0.0.33
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/billable/billable.d.ts +12 -10
- package/dist/src/billable/billable.js +1 -2
- package/dist/src/calls/calls.d.ts +5 -5
- package/dist/src/calls/calls.js +10 -4
- package/dist/src/chat/chat.d.ts +90 -0
- package/dist/src/chat/chat.js +194 -0
- package/dist/src/contacts/contacts.d.ts +67 -0
- package/dist/src/contacts/contacts.js +179 -0
- package/dist/src/core/err.d.ts +0 -10
- package/dist/src/core/err.js +0 -38
- package/dist/src/core/pull.d.ts +39 -0
- package/dist/src/core/pull.js +101 -0
- package/dist/src/feed/feed.d.ts +167 -0
- package/dist/src/feed/feed.js +642 -0
- package/dist/src/fx.d.ts +62 -8
- package/dist/src/fx.js +137 -9
- package/dist/src/index.d.ts +13 -0
- package/dist/src/index.js +13 -0
- package/dist/src/jobs/jobs.d.ts +74 -0
- package/dist/src/jobs/jobs.js +375 -0
- package/dist/src/marketplace/marketplace.d.ts +35 -0
- package/dist/src/marketplace/marketplace.js +159 -0
- package/dist/src/notifications/notifications.d.ts +20 -0
- package/dist/src/notifications/notifications.js +45 -0
- package/dist/src/payments/payments.d.ts +59 -0
- package/dist/src/payments/payments.js +266 -0
- package/dist/src/payrolls/payrolls.d.ts +52 -0
- package/dist/src/payrolls/payrolls.js +54 -0
- package/dist/src/privacy/privacy.d.ts +21 -0
- package/dist/src/privacy/privacy.js +78 -0
- package/dist/src/routes/routes.d.ts +22 -0
- package/dist/src/routes/routes.js +134 -0
- package/dist/src/session/session.d.ts +29 -13
- package/dist/src/session/session.js +21 -14
- package/dist/src/storage/storage.d.ts +79 -0
- package/dist/src/storage/storage.js +240 -0
- package/dist/src/timesheets/timesheets.d.ts +91 -0
- package/dist/src/timesheets/timesheets.js +407 -0
- package/dist/src/users/users.d.ts +15 -11
- package/dist/src/users/users.js +33 -7
- package/package.json +3 -3
|
@@ -1,28 +1,29 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { OperationResult, RPBillablePeriodsFromDates, YearSpan } from "@fabriktor/schema";
|
|
2
2
|
import type { BillableOperator, OperationResultOf } from "@fabriktor/client";
|
|
3
|
+
type GenerateBillPayload = Parameters<BillableOperator["generateBill"]>[0];
|
|
3
4
|
export type BillableFXOptions = {
|
|
4
5
|
billable: BillableOperator;
|
|
5
6
|
};
|
|
6
7
|
export type GenerateFixedEstimateOptions = {
|
|
7
|
-
in:
|
|
8
|
+
in: GenerateBillPayload;
|
|
8
9
|
};
|
|
9
10
|
export type AcceptFixedEstimateOptions = {
|
|
10
|
-
in:
|
|
11
|
+
in: GenerateBillPayload;
|
|
11
12
|
};
|
|
12
13
|
export type GenerateDynamicEstimateOptions = {
|
|
13
|
-
in:
|
|
14
|
+
in: GenerateBillPayload;
|
|
14
15
|
};
|
|
15
16
|
export type GenerateDynamicBillOptions = {
|
|
16
|
-
in:
|
|
17
|
+
in: GenerateBillPayload;
|
|
17
18
|
exclude_billed?: boolean;
|
|
18
19
|
};
|
|
19
20
|
export type MarkLocationAchievedOptions = {
|
|
20
|
-
in:
|
|
21
|
+
in: GenerateBillPayload;
|
|
21
22
|
};
|
|
22
|
-
export type YearSpanOptions =
|
|
23
|
-
export type PeriodsFromDatesOptions =
|
|
24
|
-
export type VendorInvoiceFromBillOptions =
|
|
25
|
-
export type SetItemQuantityOptions =
|
|
23
|
+
export type YearSpanOptions = Parameters<BillableOperator["yearSpan"]>[0];
|
|
24
|
+
export type PeriodsFromDatesOptions = Parameters<BillableOperator["periodsFromDates"]>[0];
|
|
25
|
+
export type VendorInvoiceFromBillOptions = Parameters<BillableOperator["vendorInvoiceFromBill"]>[0];
|
|
26
|
+
export type SetItemQuantityOptions = Parameters<BillableOperator["setItemQuantity"]>[0];
|
|
26
27
|
export interface BillableFXOperator {
|
|
27
28
|
generateFixedEstimate(opts: GenerateFixedEstimateOptions): Promise<OperationResult>;
|
|
28
29
|
acceptFixedEstimate(opts: AcceptFixedEstimateOptions): Promise<OperationResult>;
|
|
@@ -48,3 +49,4 @@ export declare class BillableFX implements BillableFXOperator {
|
|
|
48
49
|
setItemQuantity(opts: SetItemQuantityOptions): Promise<void>;
|
|
49
50
|
}
|
|
50
51
|
export declare function newBillableFX(opts: BillableFXOptions): BillableFX;
|
|
52
|
+
export {};
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import type { PLCallsMintVideoToken, PLNotificationsRelay } from "@fabriktor/schema";
|
|
2
1
|
import type { CallsOperator, NotificationsOperator } from "@fabriktor/client";
|
|
3
|
-
export type MintVideoTokenOptions =
|
|
2
|
+
export type MintVideoTokenOptions = Parameters<CallsOperator["mintVideoToken"]>[0];
|
|
3
|
+
export type RelayNotificationOptions = Parameters<NotificationsOperator["relayNotification"]>[0];
|
|
4
4
|
export type StartCallOptions = {
|
|
5
5
|
mint: MintVideoTokenOptions;
|
|
6
|
-
notification:
|
|
6
|
+
notification: RelayNotificationOptions;
|
|
7
7
|
};
|
|
8
8
|
export type AnswerCallOptions = {
|
|
9
9
|
mint: MintVideoTokenOptions;
|
|
10
|
-
self_notification:
|
|
10
|
+
self_notification: RelayNotificationOptions;
|
|
11
11
|
};
|
|
12
12
|
export type CancelCallOptions = {
|
|
13
|
-
notification:
|
|
13
|
+
notification: RelayNotificationOptions;
|
|
14
14
|
};
|
|
15
15
|
export type CallsFXOptions = {
|
|
16
16
|
calls: CallsOperator;
|
package/dist/src/calls/calls.js
CHANGED
|
@@ -3,7 +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 {
|
|
6
|
+
import { errResolveFailed } from "../core/err.js";
|
|
7
|
+
const resourceVideoToken = "video_token";
|
|
8
|
+
const msgUnableToMintVideoToken = "Unable to mint video token.";
|
|
7
9
|
export class CallsFX {
|
|
8
10
|
calls;
|
|
9
11
|
notifications;
|
|
@@ -26,10 +28,14 @@ export class CallsFX {
|
|
|
26
28
|
}
|
|
27
29
|
async requireVideoToken(opts) {
|
|
28
30
|
const out = await this.calls.mintVideoToken(opts);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
const token = out.value?.trim();
|
|
32
|
+
if (token === undefined || token === "") {
|
|
33
|
+
throw errResolveFailed({
|
|
34
|
+
resource: resourceVideoToken,
|
|
35
|
+
msg: msgUnableToMintVideoToken,
|
|
36
|
+
});
|
|
31
37
|
}
|
|
32
|
-
return
|
|
38
|
+
return token;
|
|
33
39
|
}
|
|
34
40
|
}
|
|
35
41
|
export function newCallsFX(opts) {
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { type Chat, type ChatRoom, type ChatRoomMember } from "@fabriktor/schema";
|
|
2
|
+
import type { ChatOperator, WSCloseHandler, WSConnection, WSErrorHandler, WSMessageHandler } from "@fabriktor/client";
|
|
3
|
+
export type ReactChatOptions = Parameters<ChatOperator["reactChat"]>[0];
|
|
4
|
+
export type ChatFXOptions = {
|
|
5
|
+
chat: ChatOperator;
|
|
6
|
+
};
|
|
7
|
+
export type LatestChatsOptions = {
|
|
8
|
+
chat_room_id: string;
|
|
9
|
+
page?: number;
|
|
10
|
+
per_page?: number;
|
|
11
|
+
};
|
|
12
|
+
export type LatestChatsCursor = {
|
|
13
|
+
chat_room_id: string;
|
|
14
|
+
next_page: number;
|
|
15
|
+
per_page: number;
|
|
16
|
+
done: boolean;
|
|
17
|
+
};
|
|
18
|
+
export type NextLatestChatsOptions = {
|
|
19
|
+
cursor: LatestChatsCursor;
|
|
20
|
+
};
|
|
21
|
+
export type LatestChatsPage = {
|
|
22
|
+
chats: Chat[];
|
|
23
|
+
page: number;
|
|
24
|
+
per_page: number;
|
|
25
|
+
total_count?: number;
|
|
26
|
+
last_page?: number;
|
|
27
|
+
has_more: boolean;
|
|
28
|
+
next_cursor: LatestChatsCursor;
|
|
29
|
+
};
|
|
30
|
+
export type ChatRoomMembersOptions = {
|
|
31
|
+
chat_room_id: string;
|
|
32
|
+
};
|
|
33
|
+
export type MyChatRoomMembersOptions = {
|
|
34
|
+
owner_id: string;
|
|
35
|
+
};
|
|
36
|
+
export type OpenChatRoomOptions = {
|
|
37
|
+
owner_id: string;
|
|
38
|
+
chat_room_id: string;
|
|
39
|
+
per_page?: number;
|
|
40
|
+
on_chat: WSMessageHandler<Chat>;
|
|
41
|
+
on_chat_room: WSMessageHandler<ChatRoom>;
|
|
42
|
+
on_error?: WSErrorHandler;
|
|
43
|
+
on_close?: WSCloseHandler;
|
|
44
|
+
};
|
|
45
|
+
export type OpenGlobalChatRoomsOptions = {
|
|
46
|
+
owner_id: string;
|
|
47
|
+
on_chat_room: WSMessageHandler<ChatRoom>;
|
|
48
|
+
on_error?: WSErrorHandler;
|
|
49
|
+
on_close?: WSCloseHandler;
|
|
50
|
+
};
|
|
51
|
+
export type ReactUploadedChatOptions = {
|
|
52
|
+
chat: Chat;
|
|
53
|
+
reaction: ReactChatOptions;
|
|
54
|
+
};
|
|
55
|
+
export type ChatRoomSession = {
|
|
56
|
+
latest: LatestChatsPage;
|
|
57
|
+
chats_ws: WSConnection;
|
|
58
|
+
chat_room_ws: WSConnection;
|
|
59
|
+
closeNormal(reason?: string): void;
|
|
60
|
+
closeLeavingPage(reason?: string): void;
|
|
61
|
+
};
|
|
62
|
+
export type GlobalChatRoomsSession = {
|
|
63
|
+
members: ChatRoomMember[];
|
|
64
|
+
chat_rooms_ws: WSConnection;
|
|
65
|
+
closeNormal(reason?: string): void;
|
|
66
|
+
closeLeavingPage(reason?: string): void;
|
|
67
|
+
};
|
|
68
|
+
export interface ChatFXOperator {
|
|
69
|
+
newLatestChatsCursor(opts: LatestChatsOptions): LatestChatsCursor;
|
|
70
|
+
latestChats(opts: LatestChatsOptions): Promise<LatestChatsPage>;
|
|
71
|
+
nextLatestChats(opts: NextLatestChatsOptions): Promise<LatestChatsPage>;
|
|
72
|
+
chatRoomMembers(opts: ChatRoomMembersOptions): Promise<ChatRoomMember[]>;
|
|
73
|
+
myChatRoomMembers(opts: MyChatRoomMembersOptions): Promise<ChatRoomMember[]>;
|
|
74
|
+
openChatRoom(opts: OpenChatRoomOptions): Promise<ChatRoomSession>;
|
|
75
|
+
openGlobalChatRooms(opts: OpenGlobalChatRoomsOptions): Promise<GlobalChatRoomsSession>;
|
|
76
|
+
reactUploadedChat(opts: ReactUploadedChatOptions): Promise<void>;
|
|
77
|
+
}
|
|
78
|
+
export declare class ChatFX implements ChatFXOperator {
|
|
79
|
+
private chat;
|
|
80
|
+
constructor(opts: ChatFXOptions);
|
|
81
|
+
newLatestChatsCursor(opts: LatestChatsOptions): LatestChatsCursor;
|
|
82
|
+
latestChats(opts: LatestChatsOptions): Promise<LatestChatsPage>;
|
|
83
|
+
nextLatestChats(opts: NextLatestChatsOptions): Promise<LatestChatsPage>;
|
|
84
|
+
chatRoomMembers(opts: ChatRoomMembersOptions): Promise<ChatRoomMember[]>;
|
|
85
|
+
myChatRoomMembers(opts: MyChatRoomMembersOptions): Promise<ChatRoomMember[]>;
|
|
86
|
+
openChatRoom(opts: OpenChatRoomOptions): Promise<ChatRoomSession>;
|
|
87
|
+
openGlobalChatRooms(opts: OpenGlobalChatRoomsOptions): Promise<GlobalChatRoomsSession>;
|
|
88
|
+
reactUploadedChat(opts: ReactUploadedChatOptions): Promise<void>;
|
|
89
|
+
}
|
|
90
|
+
export declare function newChatFX(opts: ChatFXOptions): ChatFX;
|
|
@@ -0,0 +1,194 @@
|
|
|
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, SearchResultResultsKey, } from "@fabriktor/schema";
|
|
7
|
+
import { errValidation } from "../core/err.js";
|
|
8
|
+
const firstPage = 1;
|
|
9
|
+
const defaultPerPage = 30;
|
|
10
|
+
const closeCodeNormal = 1000;
|
|
11
|
+
const closeCodeLeavingPage = 1001;
|
|
12
|
+
const msgChatObjectsUploading = "Cannot react to a chat while objects are still uploading.";
|
|
13
|
+
export class ChatFX {
|
|
14
|
+
chat;
|
|
15
|
+
constructor(opts) {
|
|
16
|
+
this.chat = opts.chat;
|
|
17
|
+
}
|
|
18
|
+
newLatestChatsCursor(opts) {
|
|
19
|
+
return {
|
|
20
|
+
chat_room_id: opts.chat_room_id,
|
|
21
|
+
next_page: normalizePage(opts.page),
|
|
22
|
+
per_page: normalizePerPage(opts.per_page),
|
|
23
|
+
done: false,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
async latestChats(opts) {
|
|
27
|
+
const page = normalizePage(opts.page);
|
|
28
|
+
const per_page = normalizePerPage(opts.per_page);
|
|
29
|
+
const raw = await this.chat.searchManyChats({
|
|
30
|
+
query: {
|
|
31
|
+
chat_room_id: opts.chat_room_id,
|
|
32
|
+
[SearchPageKey]: page,
|
|
33
|
+
[SearchPerPageKey]: per_page,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
return latestChatsPage({
|
|
37
|
+
chat_room_id: opts.chat_room_id,
|
|
38
|
+
page,
|
|
39
|
+
per_page,
|
|
40
|
+
raw,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async nextLatestChats(opts) {
|
|
44
|
+
if (opts.cursor.done) {
|
|
45
|
+
return emptyLatestChatsPage(opts.cursor);
|
|
46
|
+
}
|
|
47
|
+
return await this.latestChats({
|
|
48
|
+
chat_room_id: opts.cursor.chat_room_id,
|
|
49
|
+
page: opts.cursor.next_page,
|
|
50
|
+
per_page: opts.cursor.per_page,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
async chatRoomMembers(opts) {
|
|
54
|
+
const out = await this.chat.queryChatRoomMembers({
|
|
55
|
+
query: {
|
|
56
|
+
chat_room_id: opts.chat_room_id,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
return out.value ?? [];
|
|
60
|
+
}
|
|
61
|
+
async myChatRoomMembers(opts) {
|
|
62
|
+
const out = await this.chat.queryChatRoomMembers({
|
|
63
|
+
query: {
|
|
64
|
+
owner_id: opts.owner_id,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
return out.value ?? [];
|
|
68
|
+
}
|
|
69
|
+
async openChatRoom(opts) {
|
|
70
|
+
const latest = await this.latestChats({
|
|
71
|
+
chat_room_id: opts.chat_room_id,
|
|
72
|
+
per_page: opts.per_page,
|
|
73
|
+
});
|
|
74
|
+
const chats_ws = await this.chat.wsChats({
|
|
75
|
+
owner_id: opts.owner_id,
|
|
76
|
+
chat_room_id: opts.chat_room_id,
|
|
77
|
+
on_message: opts.on_chat,
|
|
78
|
+
on_error: opts.on_error,
|
|
79
|
+
on_close: opts.on_close,
|
|
80
|
+
});
|
|
81
|
+
const chat_room_ws = await this.chat.wsChatRoom({
|
|
82
|
+
id: opts.chat_room_id,
|
|
83
|
+
on_message: opts.on_chat_room,
|
|
84
|
+
on_error: opts.on_error,
|
|
85
|
+
on_close: opts.on_close,
|
|
86
|
+
});
|
|
87
|
+
return {
|
|
88
|
+
latest,
|
|
89
|
+
chats_ws,
|
|
90
|
+
chat_room_ws,
|
|
91
|
+
closeNormal(reason) {
|
|
92
|
+
closeWS([chats_ws, chat_room_ws], closeCodeNormal, reason);
|
|
93
|
+
},
|
|
94
|
+
closeLeavingPage(reason) {
|
|
95
|
+
closeWS([chats_ws, chat_room_ws], closeCodeLeavingPage, reason);
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
async openGlobalChatRooms(opts) {
|
|
100
|
+
const members = await this.myChatRoomMembers({
|
|
101
|
+
owner_id: opts.owner_id,
|
|
102
|
+
});
|
|
103
|
+
const chat_rooms_ws = await this.chat.wsMineChatRooms({
|
|
104
|
+
owner_id: opts.owner_id,
|
|
105
|
+
on_message: opts.on_chat_room,
|
|
106
|
+
on_error: opts.on_error,
|
|
107
|
+
on_close: opts.on_close,
|
|
108
|
+
});
|
|
109
|
+
return {
|
|
110
|
+
members,
|
|
111
|
+
chat_rooms_ws,
|
|
112
|
+
closeNormal(reason) {
|
|
113
|
+
closeWS([chat_rooms_ws], closeCodeNormal, reason);
|
|
114
|
+
},
|
|
115
|
+
closeLeavingPage(reason) {
|
|
116
|
+
closeWS([chat_rooms_ws], closeCodeLeavingPage, reason);
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
async reactUploadedChat(opts) {
|
|
121
|
+
if (opts.chat.has_objects && !opts.chat.objects_uploaded) {
|
|
122
|
+
throw errValidation({
|
|
123
|
+
field: "chat_id",
|
|
124
|
+
msg: msgChatObjectsUploading,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
await this.chat.reactChat(opts.reaction);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
export function newChatFX(opts) {
|
|
131
|
+
return new ChatFX(opts);
|
|
132
|
+
}
|
|
133
|
+
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
|
+
return {
|
|
146
|
+
chats,
|
|
147
|
+
page: opts.page,
|
|
148
|
+
per_page: opts.per_page,
|
|
149
|
+
total_count,
|
|
150
|
+
last_page,
|
|
151
|
+
has_more,
|
|
152
|
+
next_cursor: {
|
|
153
|
+
chat_room_id: opts.chat_room_id,
|
|
154
|
+
next_page: has_more ? opts.page + 1 : opts.page,
|
|
155
|
+
per_page: opts.per_page,
|
|
156
|
+
done: !has_more,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function emptyLatestChatsPage(cursor) {
|
|
161
|
+
return {
|
|
162
|
+
chats: [],
|
|
163
|
+
page: cursor.next_page,
|
|
164
|
+
per_page: cursor.per_page,
|
|
165
|
+
has_more: false,
|
|
166
|
+
next_cursor: cursor,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
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
|
+
function closeWS(conns, code, reason) {
|
|
179
|
+
for (const conn of conns) {
|
|
180
|
+
conn.close(code, reason);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function normalizePage(v) {
|
|
184
|
+
if (v === undefined || !Number.isFinite(v)) {
|
|
185
|
+
return firstPage;
|
|
186
|
+
}
|
|
187
|
+
return Math.max(firstPage, Math.trunc(v));
|
|
188
|
+
}
|
|
189
|
+
function normalizePerPage(v) {
|
|
190
|
+
if (v === undefined || !Number.isFinite(v)) {
|
|
191
|
+
return defaultPerPage;
|
|
192
|
+
}
|
|
193
|
+
return Math.max(1, Math.trunc(v));
|
|
194
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Friend, Hired, Manager, Na, OldHire, type Contact, type ID, type OperationResult } from "@fabriktor/schema";
|
|
2
|
+
import type { ContactsOperator } from "@fabriktor/client";
|
|
3
|
+
export type ContactRelation = typeof Na | typeof Friend | typeof OldHire | typeof Hired | typeof Manager;
|
|
4
|
+
export type ActiveContactRelation = typeof Friend | typeof Hired | typeof Manager;
|
|
5
|
+
export type ContactsFXOptions = {
|
|
6
|
+
contacts: ContactsOperator;
|
|
7
|
+
};
|
|
8
|
+
export type LoadContactIndexOptions = {
|
|
9
|
+
owner_id: string;
|
|
10
|
+
};
|
|
11
|
+
export type ContactIndex = {
|
|
12
|
+
contacts: Contact[];
|
|
13
|
+
peer_ids: Set<string>;
|
|
14
|
+
};
|
|
15
|
+
export type PresenceActiveHandler = (active_ids: ID[]) => void;
|
|
16
|
+
export type PresenceErrorHandler = (err: unknown) => void;
|
|
17
|
+
export type StartPresenceOptions = {
|
|
18
|
+
on_active: PresenceActiveHandler;
|
|
19
|
+
on_error?: PresenceErrorHandler;
|
|
20
|
+
};
|
|
21
|
+
export type PresenceSession = {
|
|
22
|
+
stop(): void;
|
|
23
|
+
};
|
|
24
|
+
export type SendFriendRequestOptions = {
|
|
25
|
+
contact_id: string;
|
|
26
|
+
};
|
|
27
|
+
export type RequestRelationUpgradeOptions = {
|
|
28
|
+
contact: Contact;
|
|
29
|
+
requested_relation: ActiveContactRelation;
|
|
30
|
+
};
|
|
31
|
+
export type DowngradeRelationOptions = {
|
|
32
|
+
contact: Contact;
|
|
33
|
+
relation: ActiveContactRelation;
|
|
34
|
+
};
|
|
35
|
+
export type AcceptContactRequestOptions = {
|
|
36
|
+
contact: Contact;
|
|
37
|
+
};
|
|
38
|
+
export type RejectContactRequestOptions = {
|
|
39
|
+
contact: Contact;
|
|
40
|
+
};
|
|
41
|
+
export type EndHireRelationOptions = {
|
|
42
|
+
contact: Contact;
|
|
43
|
+
};
|
|
44
|
+
export interface ContactsFXOperator {
|
|
45
|
+
loadContactIndex(opts: LoadContactIndexOptions): Promise<ContactIndex>;
|
|
46
|
+
startPresence(opts: StartPresenceOptions): Promise<PresenceSession>;
|
|
47
|
+
sendFriendRequest(opts: SendFriendRequestOptions): Promise<OperationResult>;
|
|
48
|
+
requestRelationUpgrade(opts: RequestRelationUpgradeOptions): Promise<OperationResult>;
|
|
49
|
+
downgradeRelation(opts: DowngradeRelationOptions): Promise<OperationResult>;
|
|
50
|
+
acceptContactRequest(opts: AcceptContactRequestOptions): Promise<OperationResult>;
|
|
51
|
+
rejectContactRequest(opts: RejectContactRequestOptions): Promise<OperationResult>;
|
|
52
|
+
endHireRelation(opts: EndHireRelationOptions): Promise<OperationResult>;
|
|
53
|
+
}
|
|
54
|
+
export declare class ContactsFX implements ContactsFXOperator {
|
|
55
|
+
private contacts;
|
|
56
|
+
constructor(opts: ContactsFXOptions);
|
|
57
|
+
loadContactIndex(opts: LoadContactIndexOptions): Promise<ContactIndex>;
|
|
58
|
+
startPresence(opts: StartPresenceOptions): Promise<PresenceSession>;
|
|
59
|
+
sendFriendRequest(opts: SendFriendRequestOptions): Promise<OperationResult>;
|
|
60
|
+
requestRelationUpgrade(opts: RequestRelationUpgradeOptions): Promise<OperationResult>;
|
|
61
|
+
downgradeRelation(opts: DowngradeRelationOptions): Promise<OperationResult>;
|
|
62
|
+
acceptContactRequest(opts: AcceptContactRequestOptions): Promise<OperationResult>;
|
|
63
|
+
rejectContactRequest(opts: RejectContactRequestOptions): Promise<OperationResult>;
|
|
64
|
+
endHireRelation(opts: EndHireRelationOptions): Promise<OperationResult>;
|
|
65
|
+
private replaceContact;
|
|
66
|
+
}
|
|
67
|
+
export declare function newContactsFX(opts: ContactsFXOptions): ContactsFX;
|
|
@@ -0,0 +1,179 @@
|
|
|
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 { Friend, Na, OldHire, } from "@fabriktor/schema";
|
|
7
|
+
import { errRequired, errValidation } from "../core/err.js";
|
|
8
|
+
const presenceIntervalMS = 120_000;
|
|
9
|
+
const msgOwnerIDRequired = "Owner ID is required.";
|
|
10
|
+
const msgContactIDRequired = "Contact ID is required.";
|
|
11
|
+
const msgRelationUpgradeInvalid = "Requested relation must be higher than the current relation.";
|
|
12
|
+
const msgRelationDowngradeInvalid = "Requested relation must be lower than the current relation.";
|
|
13
|
+
const msgPendingRelationRequired = "Contact request does not contain a pending relation.";
|
|
14
|
+
export class ContactsFX {
|
|
15
|
+
contacts;
|
|
16
|
+
constructor(opts) {
|
|
17
|
+
this.contacts = opts.contacts;
|
|
18
|
+
}
|
|
19
|
+
async loadContactIndex(opts) {
|
|
20
|
+
const owner_id = opts.owner_id.trim();
|
|
21
|
+
if (owner_id === "") {
|
|
22
|
+
throw errRequired({
|
|
23
|
+
field: "owner_id",
|
|
24
|
+
msg: msgOwnerIDRequired,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const out = await this.contacts.findContacts();
|
|
28
|
+
const contacts = out.value ?? [];
|
|
29
|
+
const peer_ids = new Set();
|
|
30
|
+
for (const contact of contacts) {
|
|
31
|
+
const peer_id = contactPeerID(contact, owner_id);
|
|
32
|
+
if (peer_id !== undefined) {
|
|
33
|
+
peer_ids.add(peer_id);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
contacts,
|
|
38
|
+
peer_ids,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
async startPresence(opts) {
|
|
42
|
+
const initial = await this.contacts.signal();
|
|
43
|
+
opts.on_active(initial.value ?? []);
|
|
44
|
+
let stopped = false;
|
|
45
|
+
let running = false;
|
|
46
|
+
const tick = async () => {
|
|
47
|
+
if (stopped || running) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
running = true;
|
|
51
|
+
try {
|
|
52
|
+
const out = await this.contacts.signal();
|
|
53
|
+
if (!stopped) {
|
|
54
|
+
opts.on_active(out.value ?? []);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
if (!stopped) {
|
|
59
|
+
opts.on_error?.(err);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
running = false;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const timer = setInterval(() => {
|
|
67
|
+
void tick();
|
|
68
|
+
}, presenceIntervalMS);
|
|
69
|
+
return {
|
|
70
|
+
stop() {
|
|
71
|
+
if (stopped) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
stopped = true;
|
|
75
|
+
clearInterval(timer);
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async sendFriendRequest(opts) {
|
|
80
|
+
const contact_id = opts.contact_id.trim();
|
|
81
|
+
if (contact_id === "") {
|
|
82
|
+
throw errRequired({
|
|
83
|
+
field: "contact_id",
|
|
84
|
+
msg: msgContactIDRequired,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return await this.contacts.insertOneContact({
|
|
88
|
+
in: {
|
|
89
|
+
contact_id,
|
|
90
|
+
is_pending: true,
|
|
91
|
+
relation: Na,
|
|
92
|
+
requested_relation: Friend,
|
|
93
|
+
is_archived: false,
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
async requestRelationUpgrade(opts) {
|
|
98
|
+
if (opts.requested_relation <= opts.contact.relation) {
|
|
99
|
+
throw errValidation({
|
|
100
|
+
field: "requested_relation",
|
|
101
|
+
msg: msgRelationUpgradeInvalid,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return await this.replaceContact(opts.contact, {
|
|
105
|
+
is_pending: true,
|
|
106
|
+
relation: opts.contact.relation,
|
|
107
|
+
requested_relation: opts.requested_relation,
|
|
108
|
+
is_archived: false,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
async downgradeRelation(opts) {
|
|
112
|
+
if (opts.relation >= opts.contact.relation) {
|
|
113
|
+
throw errValidation({
|
|
114
|
+
field: "relation",
|
|
115
|
+
msg: msgRelationDowngradeInvalid,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return await this.replaceContact(opts.contact, {
|
|
119
|
+
is_pending: false,
|
|
120
|
+
relation: opts.relation,
|
|
121
|
+
requested_relation: Na,
|
|
122
|
+
is_archived: false,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
async acceptContactRequest(opts) {
|
|
126
|
+
if (opts.contact.requested_relation === Na) {
|
|
127
|
+
throw errValidation({
|
|
128
|
+
field: "contact",
|
|
129
|
+
msg: msgPendingRelationRequired,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return await this.replaceContact(opts.contact, {
|
|
133
|
+
is_pending: false,
|
|
134
|
+
relation: opts.contact.requested_relation,
|
|
135
|
+
requested_relation: Na,
|
|
136
|
+
is_archived: false,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
async rejectContactRequest(opts) {
|
|
140
|
+
return await this.replaceContact(opts.contact, {
|
|
141
|
+
is_archived: true,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
async endHireRelation(opts) {
|
|
145
|
+
return await this.replaceContact(opts.contact, {
|
|
146
|
+
is_pending: false,
|
|
147
|
+
relation: OldHire,
|
|
148
|
+
requested_relation: Na,
|
|
149
|
+
is_archived: false,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
async replaceContact(contact, overrides) {
|
|
153
|
+
return await this.contacts.replaceOneContact({
|
|
154
|
+
id: contact.id,
|
|
155
|
+
in: {
|
|
156
|
+
contact_id: contact.contact_id,
|
|
157
|
+
is_pending: contact.is_pending,
|
|
158
|
+
relation: contact.relation,
|
|
159
|
+
requested_relation: contact.requested_relation,
|
|
160
|
+
is_archived: contact.is_archived,
|
|
161
|
+
...overrides,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
export function newContactsFX(opts) {
|
|
167
|
+
return new ContactsFX(opts);
|
|
168
|
+
}
|
|
169
|
+
function contactPeerID(contact, owner_id) {
|
|
170
|
+
const contact_owner_id = contact.owner_id.trim();
|
|
171
|
+
const contact_id = contact.contact_id.trim();
|
|
172
|
+
if (contact_owner_id === owner_id && contact_id !== "") {
|
|
173
|
+
return contact_id;
|
|
174
|
+
}
|
|
175
|
+
if (contact_id === owner_id && contact_owner_id !== "") {
|
|
176
|
+
return contact_owner_id;
|
|
177
|
+
}
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
package/dist/src/core/err.d.ts
CHANGED
|
@@ -29,11 +29,6 @@ export type ResolveFailedFXErrOptions<T extends object = Record<string, unknown>
|
|
|
29
29
|
field?: FieldOf<T>;
|
|
30
30
|
cause?: unknown;
|
|
31
31
|
};
|
|
32
|
-
export type UsernameInvalidFXErrOptions<T extends object = Record<string, unknown>> = {
|
|
33
|
-
field: FieldOf<T>;
|
|
34
|
-
msg: string;
|
|
35
|
-
cause?: unknown;
|
|
36
|
-
};
|
|
37
32
|
type FXErrRuntimeOptions = {
|
|
38
33
|
kind: FXErrKind;
|
|
39
34
|
msg: string;
|
|
@@ -52,11 +47,6 @@ export declare function errFX<T extends object = Record<string, unknown>>(opts:
|
|
|
52
47
|
export declare function errRequired<T extends object = Record<string, unknown>>(opts: RequiredFXErrOptions<T>): FXErr;
|
|
53
48
|
export declare function errValidation<T extends object = Record<string, unknown>>(opts: ValidationFXErrOptions<T>): FXErr;
|
|
54
49
|
export declare function errResolveFailed<T extends object = Record<string, unknown>>(opts: ResolveFailedFXErrOptions<T>): FXErr;
|
|
55
|
-
export declare function errUsernameResolveFailed<T extends object = Record<string, unknown>>(field?: FieldOf<T>): FXErr;
|
|
56
|
-
export declare function errVideoTokenMintFailed(): FXErr;
|
|
57
|
-
export declare function errUsernameInvalid<T extends object = Record<string, unknown>>(opts: UsernameInvalidFXErrOptions<T>): FXErr;
|
|
58
|
-
export declare function errPhoneRequired<T extends object = Record<string, unknown>>(field: FieldOf<T>): FXErr;
|
|
59
|
-
export declare function errPhoneCodeRequired<T extends object = Record<string, unknown>>(field: FieldOf<T>): FXErr;
|
|
60
50
|
export declare function errUnknown(cause: unknown): FXErr;
|
|
61
51
|
export declare function isFXErr(err: unknown): err is FXErr;
|
|
62
52
|
export declare function fxErrMessage(err: unknown): string;
|