@fabriktor/fx 0.0.30 → 0.0.32

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.
@@ -3,12 +3,20 @@
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
+ export const SignInMode = {
7
+ Email: "email",
8
+ Username: "username",
9
+ };
6
10
  export class SessionFX {
7
11
  auth;
12
+ connections;
8
13
  users;
14
+ users_fx;
9
15
  constructor(opts) {
10
16
  this.auth = opts.auth;
17
+ this.connections = opts.connections;
11
18
  this.users = opts.users;
19
+ this.users_fx = opts.users_fx;
12
20
  }
13
21
  async startSignUp(opts) {
14
22
  await this.auth.signUp({
@@ -31,31 +39,44 @@ export class SessionFX {
31
39
  });
32
40
  }
33
41
  async signIn(opts) {
34
- const email = opts.mode === "email"
42
+ const email = opts.mode === SignInMode.Email
35
43
  ? opts.identifier
36
- : await this.users.resolveUsername({
44
+ : await this.users_fx.resolveUsername({
37
45
  username: opts.identifier,
38
46
  });
39
47
  await this.signInWithEmail({
40
48
  email,
41
49
  password: opts.password,
50
+ connection: opts.connection,
42
51
  });
43
52
  }
44
53
  async signInWithEmail(opts) {
45
- await this.auth.signIn(opts);
54
+ await this.auth.signIn({
55
+ email: opts.email,
56
+ password: opts.password,
57
+ });
58
+ await this.connections.insertOneConnection({
59
+ in: opts.connection,
60
+ });
46
61
  await this.users.sync();
47
62
  }
48
63
  async signOut() {
49
64
  await this.auth.signOut();
50
65
  }
51
66
  async forgotPassword(opts) {
52
- await this.users.forgotPassword(opts);
67
+ await this.users.forgotPassword({
68
+ email: opts.email,
69
+ });
53
70
  }
54
71
  async sendSignInLink(opts) {
55
- await this.users.sendSignInLink(opts);
72
+ await this.users.sendSignInLink({
73
+ email: opts.email,
74
+ });
56
75
  }
57
76
  async sendEmailVerification(opts) {
58
- await this.users.sendEmailVerification(opts);
77
+ await this.users.sendEmailVerification({
78
+ language: opts.language,
79
+ });
59
80
  }
60
81
  async checkEmailVerification() {
61
82
  await this.auth.reloadUser();
@@ -0,0 +1,70 @@
1
+ import { type Object as StorageObject, type PLStorageDownload, type RPStorageUpload } from "@fabriktor/schema";
2
+ import type { OperationResultOf, StorageCollection, StorageOperator, StorageUploadFile, UploadStorageOptions, OptionalField } from "@fabriktor/client";
3
+ export declare const StorageUploadModeration: {
4
+ readonly Clean: "clean";
5
+ readonly ContainsForbidden: "contains_forbidden";
6
+ readonly AllForbidden: "all_forbidden";
7
+ };
8
+ export type StorageUploadModeration = (typeof StorageUploadModeration)[keyof typeof StorageUploadModeration];
9
+ export declare const StorageDualUploadStatus: {
10
+ readonly ChatOnly: "chat_only";
11
+ readonly Complete: "complete";
12
+ readonly Partial: "partial";
13
+ };
14
+ export type StorageDualUploadStatus = (typeof StorageDualUploadStatus)[keyof typeof StorageDualUploadStatus];
15
+ export type StorageFXOptions = {
16
+ storage: StorageOperator;
17
+ };
18
+ export type UploadObjectsOptions = UploadStorageOptions & {
19
+ col: StorageCollection;
20
+ };
21
+ export type DownloadObjectsOptions = OptionalField<PLStorageDownload, "mime_prefix"> & {
22
+ col: StorageCollection;
23
+ };
24
+ export type UploadUserObjectsOptions = {
25
+ user_id: string;
26
+ uploader_id?: string;
27
+ full_quality?: boolean;
28
+ files: StorageUploadFile[];
29
+ };
30
+ export type UploadChatObjectsOptions = {
31
+ chat_id: string;
32
+ user_id?: string;
33
+ uploader_id?: string;
34
+ full_quality?: boolean;
35
+ files: StorageUploadFile[];
36
+ save_to_personal_box?: boolean;
37
+ };
38
+ export type StorageUploadResult = {
39
+ raw: OperationResultOf<RPStorageUpload>;
40
+ moderation: StorageUploadModeration;
41
+ contains_forbidden: boolean;
42
+ all_forbidden: boolean;
43
+ };
44
+ export type UploadChatObjectsResult = {
45
+ status: typeof StorageDualUploadStatus.ChatOnly;
46
+ chat: StorageUploadResult;
47
+ } | {
48
+ status: typeof StorageDualUploadStatus.Complete;
49
+ chat: StorageUploadResult;
50
+ personal_box: StorageUploadResult;
51
+ } | {
52
+ status: typeof StorageDualUploadStatus.Partial;
53
+ chat: StorageUploadResult;
54
+ personal_box_error: unknown;
55
+ };
56
+ export interface StorageFXOperator {
57
+ uploadObjects(opts: UploadObjectsOptions): Promise<StorageUploadResult>;
58
+ downloadObjects(opts: DownloadObjectsOptions): Promise<OperationResultOf<StorageObject[]>>;
59
+ uploadUserObjects(opts: UploadUserObjectsOptions): Promise<StorageUploadResult>;
60
+ uploadChatObjects(opts: UploadChatObjectsOptions): Promise<UploadChatObjectsResult>;
61
+ }
62
+ export declare class StorageFX implements StorageFXOperator {
63
+ private storage;
64
+ constructor(opts: StorageFXOptions);
65
+ uploadObjects(opts: UploadObjectsOptions): Promise<StorageUploadResult>;
66
+ downloadObjects(opts: DownloadObjectsOptions): Promise<OperationResultOf<StorageObject[]>>;
67
+ uploadUserObjects(opts: UploadUserObjectsOptions): Promise<StorageUploadResult>;
68
+ uploadChatObjects(opts: UploadChatObjectsOptions): Promise<UploadChatObjectsResult>;
69
+ }
70
+ export declare function newStorageFX(opts: StorageFXOptions): StorageFX;
@@ -0,0 +1,225 @@
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 { ColStorageChat, ColStorageUsers, } from "@fabriktor/schema";
7
+ import { errRequired, errValidation } from "../core/err.js";
8
+ const maxUploadFiles = 20;
9
+ const maxUploadBytes = 1024 * 1024 * 1024;
10
+ const mimePrefixAll = "";
11
+ const extensionHEIC = ".heic";
12
+ const extensionHEIF = ".heif";
13
+ const mimeImageHEIC = "image/heic";
14
+ const mimeImageHEIF = "image/heif";
15
+ const mimeImageHEICSequence = "image/heic-sequence";
16
+ const mimeImageHEIFSequence = "image/heif-sequence";
17
+ const msgOwnerIDRequired = "Owner ID is required.";
18
+ const msgChatIDRequired = "Chat ID is required.";
19
+ const msgUserIDRequired = "User ID is required.";
20
+ const msgUploadFilesRequired = "At least one file is required.";
21
+ const msgUploadTooManyFiles = "Upload supports at most 20 files.";
22
+ const msgUploadTooLarge = "Combined upload size cannot exceed 1 GB.";
23
+ const msgHEICUnsupported = "HEIC and HEIF files must be converted to JPEG or PNG before upload.";
24
+ export const StorageUploadModeration = {
25
+ Clean: "clean",
26
+ ContainsForbidden: "contains_forbidden",
27
+ AllForbidden: "all_forbidden",
28
+ };
29
+ export const StorageDualUploadStatus = {
30
+ ChatOnly: "chat_only",
31
+ Complete: "complete",
32
+ Partial: "partial",
33
+ };
34
+ export class StorageFX {
35
+ storage;
36
+ constructor(opts) {
37
+ this.storage = opts.storage;
38
+ }
39
+ async uploadObjects(opts) {
40
+ validateUpload(opts);
41
+ const out = await this.storage.upload(opts.col, {
42
+ owner_id: opts.owner_id,
43
+ uploader_id: opts.uploader_id,
44
+ full_quality: opts.full_quality,
45
+ files: opts.files,
46
+ });
47
+ return uploadResult(out);
48
+ }
49
+ async downloadObjects(opts) {
50
+ const owner_id = opts.owner_id.trim();
51
+ if (owner_id === "") {
52
+ throw errRequired({
53
+ field: "owner_id",
54
+ msg: msgOwnerIDRequired,
55
+ });
56
+ }
57
+ const { col, mime_prefix, ...input } = opts;
58
+ return await this.storage.download(col, {
59
+ ...input,
60
+ owner_id,
61
+ mime_prefix: mime_prefix?.trim() ?? mimePrefixAll,
62
+ });
63
+ }
64
+ async uploadUserObjects(opts) {
65
+ const user_id = opts.user_id.trim();
66
+ if (user_id === "") {
67
+ throw errRequired({
68
+ field: "user_id",
69
+ msg: msgUserIDRequired,
70
+ });
71
+ }
72
+ return await this.uploadObjects({
73
+ col: ColStorageUsers,
74
+ owner_id: user_id,
75
+ uploader_id: opts.uploader_id,
76
+ full_quality: opts.full_quality,
77
+ files: opts.files,
78
+ });
79
+ }
80
+ async uploadChatObjects(opts) {
81
+ const chat_id = opts.chat_id.trim();
82
+ if (chat_id === "") {
83
+ throw errRequired({
84
+ field: "chat_id",
85
+ msg: msgChatIDRequired,
86
+ });
87
+ }
88
+ let personal_box_user_id;
89
+ if (opts.save_to_personal_box) {
90
+ const user_id = opts.user_id?.trim() ?? "";
91
+ if (user_id === "") {
92
+ throw errRequired({
93
+ field: "user_id",
94
+ msg: msgUserIDRequired,
95
+ });
96
+ }
97
+ personal_box_user_id = user_id;
98
+ }
99
+ const chat = await this.uploadObjects({
100
+ col: ColStorageChat,
101
+ owner_id: chat_id,
102
+ uploader_id: opts.uploader_id,
103
+ full_quality: opts.full_quality,
104
+ files: opts.files,
105
+ });
106
+ if (personal_box_user_id === undefined) {
107
+ return {
108
+ status: StorageDualUploadStatus.ChatOnly,
109
+ chat,
110
+ };
111
+ }
112
+ try {
113
+ const personal_box = await this.uploadUserObjects({
114
+ user_id: personal_box_user_id,
115
+ uploader_id: opts.uploader_id,
116
+ full_quality: opts.full_quality,
117
+ files: opts.files,
118
+ });
119
+ return {
120
+ status: StorageDualUploadStatus.Complete,
121
+ chat,
122
+ personal_box,
123
+ };
124
+ }
125
+ catch (err) {
126
+ return {
127
+ status: StorageDualUploadStatus.Partial,
128
+ chat,
129
+ personal_box_error: err,
130
+ };
131
+ }
132
+ }
133
+ }
134
+ export function newStorageFX(opts) {
135
+ return new StorageFX(opts);
136
+ }
137
+ function validateUpload(opts) {
138
+ if (opts.owner_id.trim() === "") {
139
+ throw errRequired({
140
+ field: "owner_id",
141
+ msg: msgOwnerIDRequired,
142
+ });
143
+ }
144
+ if (opts.files.length === 0) {
145
+ throw errRequired({
146
+ field: "files",
147
+ msg: msgUploadFilesRequired,
148
+ });
149
+ }
150
+ if (opts.files.length > maxUploadFiles) {
151
+ throw errValidation({
152
+ field: "files",
153
+ msg: msgUploadTooManyFiles,
154
+ });
155
+ }
156
+ if (uploadSize(opts.files) > maxUploadBytes) {
157
+ throw errValidation({
158
+ field: "files",
159
+ msg: msgUploadTooLarge,
160
+ });
161
+ }
162
+ if (opts.files.some(isHEIC)) {
163
+ throw errValidation({
164
+ field: "files",
165
+ msg: msgHEICUnsupported,
166
+ });
167
+ }
168
+ }
169
+ function uploadSize(files) {
170
+ let out = 0;
171
+ for (const f of files) {
172
+ out += fileSize(f);
173
+ }
174
+ return out;
175
+ }
176
+ function fileSize(f) {
177
+ if ("blob" in f) {
178
+ return f.blob.size;
179
+ }
180
+ return f.size;
181
+ }
182
+ function isHEIC(f) {
183
+ const name = fileName(f).toLowerCase();
184
+ const mime = fileMIME(f).toLowerCase();
185
+ return (name.endsWith(extensionHEIC) ||
186
+ name.endsWith(extensionHEIF) ||
187
+ mime === mimeImageHEIC ||
188
+ mime === mimeImageHEIF ||
189
+ mime === mimeImageHEICSequence ||
190
+ mime === mimeImageHEIFSequence);
191
+ }
192
+ function fileName(f) {
193
+ if ("blob" in f) {
194
+ return f.filename;
195
+ }
196
+ return f.name;
197
+ }
198
+ function fileMIME(f) {
199
+ if ("blob" in f) {
200
+ return f.blob.type;
201
+ }
202
+ return f.type;
203
+ }
204
+ function uploadResult(raw) {
205
+ const contains_forbidden = raw.value?.contains_forbidden ?? false;
206
+ const all_forbidden = raw.value?.all_forbidden ?? false;
207
+ return {
208
+ raw,
209
+ moderation: uploadModeration({
210
+ contains_forbidden,
211
+ all_forbidden,
212
+ }),
213
+ contains_forbidden,
214
+ all_forbidden,
215
+ };
216
+ }
217
+ function uploadModeration(opts) {
218
+ if (opts.all_forbidden) {
219
+ return StorageUploadModeration.AllForbidden;
220
+ }
221
+ if (opts.contains_forbidden) {
222
+ return StorageUploadModeration.ContainsForbidden;
223
+ }
224
+ return StorageUploadModeration.Clean;
225
+ }
@@ -1,45 +1,26 @@
1
- import type { InUser, OperationResult, PLUsersVerifyEmail, PLUsersContactList, PLUsersForgotPassword, PLUsersResolveUsername, PLUsersSendCode, PLUsersSignInLink, PLUsersUsername, PLUsersVerifyCode, UpsertResult, User } from "@fabriktor/schema";
2
- import type { Op, TmpPhonesOperator, TmpUsernamesOperator, UsernameVerification, UsersOperator, GetByUIDOptions, SearchOptions } from "@fabriktor/client";
1
+ import type { PLUsersResolveUsername, PLUsersSendCode, PLUsersUsername, PLUsersVerifyCode } from "@fabriktor/schema";
2
+ import type { OperationResultOf, TmpPhonesOperator, TmpUsernamesOperator, UsernameVerification, UsersOperator } from "@fabriktor/client";
3
3
  export type UsersFXOptions = {
4
4
  users: UsersOperator;
5
5
  tmp_usernames: TmpUsernamesOperator;
6
6
  tmp_phones: TmpPhonesOperator;
7
7
  };
8
- export type BootstrapOptions = {
9
- in: InUser;
10
- };
11
8
  export interface UsersFXOperator {
12
- bootstrap(opts: BootstrapOptions): Promise<Op<UpsertResult>>;
13
- sync(): Promise<OperationResult>;
14
- sendEmailVerification(opts: PLUsersVerifyEmail): Promise<void>;
15
- forgotPassword(opts: PLUsersForgotPassword): Promise<void>;
16
- sendSignInLink(opts: PLUsersSignInLink): Promise<void>;
17
9
  resolveUsername(opts: PLUsersResolveUsername): Promise<string>;
18
- verifyUsername(opts: PLUsersUsername): Promise<Op<UsernameVerification>>;
19
- generateUsernames(opts: PLUsersUsername): Promise<Op<UsernameVerification>>;
10
+ verifyUsername(opts: PLUsersUsername): Promise<OperationResultOf<UsernameVerification>>;
11
+ generateUsernames(opts: PLUsersUsername): Promise<OperationResultOf<UsernameVerification>>;
20
12
  sendPhoneCode(opts: PLUsersSendCode): Promise<void>;
21
13
  verifyPhoneCode(opts: PLUsersVerifyCode): Promise<void>;
22
- match(opts: PLUsersContactList): Promise<Op<User[]>>;
23
- search(opts: SearchOptions): Promise<Op<User[]>>;
24
- getByUID(opts: GetByUIDOptions): Promise<Op<User[]>>;
25
14
  }
26
15
  export declare class UsersFX implements UsersFXOperator {
27
16
  private users;
28
17
  private tmp_usernames;
29
18
  private tmp_phones;
30
19
  constructor(opts: UsersFXOptions);
31
- bootstrap(opts: BootstrapOptions): Promise<Op<UpsertResult>>;
32
- sync(): Promise<OperationResult>;
33
- sendEmailVerification(opts: PLUsersVerifyEmail): Promise<void>;
34
- forgotPassword(opts: PLUsersForgotPassword): Promise<void>;
35
- sendSignInLink(opts: PLUsersSignInLink): Promise<void>;
36
20
  resolveUsername(opts: PLUsersResolveUsername): Promise<string>;
37
- verifyUsername(opts: PLUsersUsername): Promise<Op<UsernameVerification>>;
38
- generateUsernames(opts: PLUsersUsername): Promise<Op<UsernameVerification>>;
21
+ verifyUsername(opts: PLUsersUsername): Promise<OperationResultOf<UsernameVerification>>;
22
+ generateUsernames(opts: PLUsersUsername): Promise<OperationResultOf<UsernameVerification>>;
39
23
  sendPhoneCode(opts: PLUsersSendCode): Promise<void>;
40
24
  verifyPhoneCode(opts: PLUsersVerifyCode): Promise<void>;
41
- match(opts: PLUsersContactList): Promise<Op<User[]>>;
42
- search(opts: SearchOptions): Promise<Op<User[]>>;
43
- getByUID(opts: GetByUIDOptions): Promise<Op<User[]>>;
44
25
  }
45
26
  export declare function newUsersFX(opts: UsersFXOptions): UsersFX;
@@ -3,9 +3,13 @@
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 { errPhoneCodeRequired, errPhoneRequired, errUsernameInvalid, errUsernameResolveFailed, } from "../core/err.js";
6
+ import { errRequired, errResolveFailed, errValidation, } from "../core/err.js";
7
7
  import { normalizePhone, normalizePhoneCode, validatePhone, validatePhoneCode, } from "./phone.js";
8
8
  import { normalizeUsername, validateUsernameFormat } from "./username.js";
9
+ const msgUnableToResolveUsername = "Unable to resolve username.";
10
+ const msgPhoneRequired = "Phone number is required.";
11
+ const msgPhoneCodeRequired = "Verification code is required.";
12
+ const resourceUsername = "username";
9
13
  export class UsersFX {
10
14
  users;
11
15
  tmp_usernames;
@@ -15,58 +19,22 @@ export class UsersFX {
15
19
  this.tmp_usernames = opts.tmp_usernames;
16
20
  this.tmp_phones = opts.tmp_phones;
17
21
  }
18
- async bootstrap(opts) {
19
- return await this.users.bootstrap({
20
- in: opts.in,
21
- });
22
- }
23
- async sync() {
24
- return await this.users.sync();
25
- }
26
- async sendEmailVerification(opts) {
27
- return await this.users.sendEmailVerification(opts);
28
- }
29
- async forgotPassword(opts) {
30
- return await this.users.forgotPassword({
31
- email: opts.email,
32
- });
33
- }
34
- async sendSignInLink(opts) {
35
- return await this.users.sendSignInLink({
36
- email: opts.email,
37
- });
38
- }
39
- // public async resolveUsername(opts: PLUsersResolveUsername): Promise<string> {
40
- // const username = normalizeUsername(opts.username);
41
- // const out = await this.users.resolveUsername({
42
- // username,
43
- // });
44
- // if (out.value === undefined) {
45
- // throw errUsernameResolveFailed();
46
- // }
47
- // return out.value;
48
- // }
49
22
  async resolveUsername(opts) {
50
23
  const username = normalizeUsername(opts.username);
51
- console.log("[fx/users.resolveUsername] normalized", {
52
- input: opts.username,
53
- username,
54
- });
55
24
  const out = await this.users.resolveUsername({
56
25
  username,
57
26
  });
58
- console.log("[fx/users.resolveUsername] response", out);
59
27
  if (out.value === undefined ||
60
28
  out.value.trim() === "" ||
61
29
  !out.value.includes("@")) {
62
- throw errUsernameResolveFailed();
30
+ throw errUsernameResolveFailed("username");
63
31
  }
64
32
  return out.value;
65
33
  }
66
34
  async verifyUsername(opts) {
67
35
  const check = validateUsernameFormat(opts.to_validate);
68
36
  if (!check.valid) {
69
- throw errUsernameInvalid(check.error);
37
+ throw errUsernameInvalid("to_validate", check.error);
70
38
  }
71
39
  return await this.tmp_usernames.verify({
72
40
  words: opts.words,
@@ -82,7 +50,7 @@ export class UsersFX {
82
50
  async sendPhoneCode(opts) {
83
51
  const phone = normalizePhone(opts.phone);
84
52
  if (!validatePhone(phone)) {
85
- throw errPhoneRequired();
53
+ throw errPhoneRequired("phone");
86
54
  }
87
55
  return await this.tmp_phones.send({
88
56
  phone,
@@ -92,33 +60,42 @@ export class UsersFX {
92
60
  const phone = normalizePhone(opts.phone);
93
61
  const code = normalizePhoneCode(opts.code);
94
62
  if (!validatePhone(phone)) {
95
- throw errPhoneRequired();
63
+ throw errPhoneRequired("phone");
96
64
  }
97
65
  if (!validatePhoneCode(code)) {
98
- throw errPhoneCodeRequired();
66
+ throw errPhoneCodeRequired("code");
99
67
  }
100
68
  return await this.tmp_phones.verify({
101
69
  phone,
102
70
  code,
103
71
  });
104
72
  }
105
- async match(opts) {
106
- return await this.users.match({
107
- contacts: opts.contacts,
108
- });
109
- }
110
- async search(opts) {
111
- return await this.users.search({
112
- q: opts.q,
113
- limit: opts.limit,
114
- });
115
- }
116
- async getByUID(opts) {
117
- return await this.users.getByUID({
118
- uid: opts.uid,
119
- });
120
- }
121
73
  }
122
74
  export function newUsersFX(opts) {
123
75
  return new UsersFX(opts);
124
76
  }
77
+ function errUsernameResolveFailed(field) {
78
+ return errResolveFailed({
79
+ resource: resourceUsername,
80
+ field,
81
+ msg: msgUnableToResolveUsername,
82
+ });
83
+ }
84
+ function errUsernameInvalid(field, msg) {
85
+ return errValidation({
86
+ field,
87
+ msg,
88
+ });
89
+ }
90
+ function errPhoneRequired(field) {
91
+ return errRequired({
92
+ field,
93
+ msg: msgPhoneRequired,
94
+ });
95
+ }
96
+ function errPhoneCodeRequired(field) {
97
+ return errRequired({
98
+ field,
99
+ msg: msgPhoneCodeRequired,
100
+ });
101
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fabriktor/fx",
3
- "version": "0.0.30",
3
+ "version": "0.0.32",
4
4
  "description": "![Fabriktor Logo](./img/fabriktor-character.gif)",
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.25",
21
- "@fabriktor/schema": "^0.0.18"
20
+ "@fabriktor/client": "0.0.38",
21
+ "@fabriktor/schema": "0.0.29"
22
22
  }
23
23
  }