appwrite-utils-cli 1.3.4 → 1.4.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.
- package/dist/adapters/AdapterFactory.d.ts +87 -0
- package/dist/adapters/AdapterFactory.js +217 -0
- package/dist/adapters/DatabaseAdapter.d.ts +217 -0
- package/dist/adapters/DatabaseAdapter.js +50 -0
- package/dist/adapters/LegacyAdapter.d.ts +49 -0
- package/dist/adapters/LegacyAdapter.js +382 -0
- package/dist/adapters/TablesDBAdapter.d.ts +55 -0
- package/dist/adapters/TablesDBAdapter.js +302 -0
- package/dist/adapters/index.d.ts +11 -0
- package/dist/adapters/index.js +12 -0
- package/dist/collections/attributes.js +43 -24
- package/dist/collections/methods.d.ts +4 -3
- package/dist/collections/methods.js +34 -14
- package/dist/config/yamlConfig.d.ts +40 -437
- package/dist/config/yamlConfig.js +8 -2
- package/dist/databases/setup.js +2 -2
- package/dist/main.js +0 -0
- package/dist/migrations/appwriteToX.d.ts +26 -37
- package/dist/migrations/comprehensiveTransfer.js +4 -4
- package/dist/migrations/dataLoader.d.ts +124 -1484
- package/dist/migrations/dataLoader.js +2 -1
- package/dist/migrations/relationships.d.ts +2 -3
- package/dist/migrations/relationships.js +1 -1
- package/dist/migrations/services/UserMappingService.js +1 -1
- package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +24 -279
- package/dist/migrations/yaml/YamlImportConfigLoader.js +7 -2
- package/dist/schemas/authUser.d.ts +7 -47
- package/dist/schemas/authUser.js +1 -1
- package/dist/shared/jsonSchemaGenerator.d.ts +0 -2
- package/dist/shared/jsonSchemaGenerator.js +4 -17
- package/dist/shared/migrationHelpers.d.ts +17 -119
- package/dist/shared/operationQueue.js +16 -7
- package/dist/shared/schemaGenerator.js +2 -17
- package/dist/storage/schemas.d.ts +149 -296
- package/dist/users/methods.d.ts +2 -2
- package/dist/utils/configMigration.js +0 -1
- package/dist/utils/getClientFromConfig.d.ts +26 -0
- package/dist/utils/getClientFromConfig.js +37 -0
- package/dist/utils/loadConfigs.js +0 -2
- package/dist/utils/schemaStrings.js +2 -17
- package/dist/utils/setupFiles.js +2 -0
- package/dist/utils/versionDetection.d.ts +56 -0
- package/dist/utils/versionDetection.js +217 -0
- package/dist/utils/yamlConverter.d.ts +0 -1
- package/dist/utils/yamlConverter.js +0 -2
- package/dist/utilsController.js +2 -0
- package/package.json +3 -2
- package/src/adapters/AdapterFactory.ts +296 -0
- package/src/adapters/DatabaseAdapter.ts +290 -0
- package/src/adapters/LegacyAdapter.ts +667 -0
- package/src/adapters/TablesDBAdapter.ts +429 -0
- package/src/adapters/index.ts +37 -0
- package/src/collections/attributes.ts +351 -157
- package/src/collections/methods.ts +43 -28
- package/src/config/yamlConfig.ts +8 -2
- package/src/databases/setup.ts +2 -2
- package/src/migrations/afterImportActions.ts +2 -2
- package/src/migrations/comprehensiveTransfer.ts +4 -0
- package/src/migrations/dataLoader.ts +2 -1
- package/src/migrations/relationships.ts +1 -1
- package/src/migrations/services/UserMappingService.ts +1 -1
- package/src/migrations/yaml/YamlImportConfigLoader.ts +7 -2
- package/src/schemas/authUser.ts +1 -1
- package/src/shared/jsonSchemaGenerator.ts +4 -19
- package/src/shared/operationQueue.ts +20 -13
- package/src/shared/schemaGenerator.ts +2 -16
- package/src/types/node-appwrite-tablesdb.d.ts +44 -0
- package/src/users/methods.ts +2 -2
- package/src/utils/configMigration.ts +0 -1
- package/src/utils/getClientFromConfig.ts +56 -0
- package/src/utils/loadConfigs.ts +0 -2
- package/src/utils/schemaStrings.ts +2 -16
- package/src/utils/setupFiles.ts +2 -0
- package/src/utils/versionDetection.ts +265 -0
- package/src/utils/yamlConverter.ts +0 -2
- package/src/utilsController.ts +2 -0
- package/dist/functions/openapi.d.ts +0 -4
- package/dist/functions/openapi.js +0 -60
- package/dist/migrations/attributes.d.ts +0 -4
- package/dist/migrations/attributes.js +0 -301
- package/dist/migrations/backup.d.ts +0 -687
- package/dist/migrations/backup.js +0 -175
- package/dist/migrations/collections.d.ts +0 -22
- package/dist/migrations/collections.js +0 -347
- package/dist/migrations/converters.d.ts +0 -46
- package/dist/migrations/converters.js +0 -139
- package/dist/migrations/databases.d.ts +0 -2
- package/dist/migrations/databases.js +0 -28
- package/dist/migrations/dbHelpers.d.ts +0 -5
- package/dist/migrations/dbHelpers.js +0 -57
- package/dist/migrations/helper.d.ts +0 -3
- package/dist/migrations/helper.js +0 -21
- package/dist/migrations/indexes.d.ts +0 -4
- package/dist/migrations/indexes.js +0 -19
- package/dist/migrations/logging.d.ts +0 -10
- package/dist/migrations/logging.js +0 -46
- package/dist/migrations/migrationHelper.d.ts +0 -173
- package/dist/migrations/migrationHelper.js +0 -130
- package/dist/migrations/openapi.d.ts +0 -4
- package/dist/migrations/openapi.js +0 -60
- package/dist/migrations/queue.d.ts +0 -13
- package/dist/migrations/queue.js +0 -79
- package/dist/migrations/schemaStrings.d.ts +0 -14
- package/dist/migrations/schemaStrings.js +0 -478
- package/dist/migrations/setupDatabase.d.ts +0 -6
- package/dist/migrations/setupDatabase.js +0 -115
- package/dist/migrations/storage.d.ts +0 -10
- package/dist/migrations/storage.js +0 -340
- package/dist/migrations/users.d.ts +0 -16
- package/dist/migrations/users.js +0 -276
- package/dist/migrations/validationRules.d.ts +0 -43
- package/dist/migrations/validationRules.js +0 -42
- package/dist/shared/attributeManager.d.ts +0 -17
- package/dist/shared/attributeManager.js +0 -272
- package/src/functions/openapi.ts +0 -83
- package/src/shared/attributeManager.ts +0 -428
package/dist/migrations/users.js
DELETED
@@ -1,276 +0,0 @@
|
|
1
|
-
import { AppwriteException, Databases, ID, Query, Users, } from "node-appwrite";
|
2
|
-
import { AuthUserSchema, } from "../schemas/authUser.js";
|
3
|
-
import { logger } from "./logging.js";
|
4
|
-
import { splitIntoBatches } from "./migrationHelper.js";
|
5
|
-
import { getAppwriteClient, tryAwaitWithRetry, } from "../utils/helperFunctions.js";
|
6
|
-
import { isUndefined } from "es-toolkit/compat";
|
7
|
-
import { isEmpty } from "es-toolkit/compat";
|
8
|
-
export class UsersController {
|
9
|
-
config;
|
10
|
-
users;
|
11
|
-
static userFields = [
|
12
|
-
"email",
|
13
|
-
"name",
|
14
|
-
"password",
|
15
|
-
"phone",
|
16
|
-
"labels",
|
17
|
-
"prefs",
|
18
|
-
"userId",
|
19
|
-
"$createdAt",
|
20
|
-
"$updatedAt",
|
21
|
-
];
|
22
|
-
constructor(config, db) {
|
23
|
-
this.config = config;
|
24
|
-
this.users = new Users(this.config.appwriteClient);
|
25
|
-
}
|
26
|
-
async wipeUsers() {
|
27
|
-
const allUsers = await this.getAllUsers();
|
28
|
-
console.log("Deleting all users...");
|
29
|
-
const createBatches = (finalData, batchSize) => {
|
30
|
-
const finalBatches = [];
|
31
|
-
for (let i = 0; i < finalData.length; i += batchSize) {
|
32
|
-
finalBatches.push(finalData.slice(i, i + batchSize));
|
33
|
-
}
|
34
|
-
return finalBatches;
|
35
|
-
};
|
36
|
-
let usersDeleted = 0;
|
37
|
-
if (allUsers.length > 0) {
|
38
|
-
const batchedUserPromises = createBatches(allUsers, 25); // Batch size of 25
|
39
|
-
for (const batch of batchedUserPromises) {
|
40
|
-
console.log(`Deleting ${batch.length} users...`);
|
41
|
-
await Promise.all(batch.map((user) => tryAwaitWithRetry(async () => await this.users.delete(user.$id))));
|
42
|
-
usersDeleted += batch.length;
|
43
|
-
if (usersDeleted % 100 === 0) {
|
44
|
-
console.log(`Deleted ${usersDeleted} users...`);
|
45
|
-
}
|
46
|
-
}
|
47
|
-
}
|
48
|
-
else {
|
49
|
-
console.log("No users to delete");
|
50
|
-
}
|
51
|
-
}
|
52
|
-
async getAllUsers() {
|
53
|
-
const allUsers = [];
|
54
|
-
const users = await tryAwaitWithRetry(async () => await this.users.list([Query.limit(200)]));
|
55
|
-
if (users.users.length === 0) {
|
56
|
-
return [];
|
57
|
-
}
|
58
|
-
if (users.users.length === 200) {
|
59
|
-
let lastDocumentId = users.users[users.users.length - 1].$id;
|
60
|
-
allUsers.push(...users.users);
|
61
|
-
while (lastDocumentId) {
|
62
|
-
const moreUsers = await tryAwaitWithRetry(async () => await this.users.list([
|
63
|
-
Query.limit(200),
|
64
|
-
Query.cursorAfter(lastDocumentId),
|
65
|
-
]));
|
66
|
-
allUsers.push(...moreUsers.users);
|
67
|
-
if (moreUsers.users.length < 200) {
|
68
|
-
break;
|
69
|
-
}
|
70
|
-
lastDocumentId = moreUsers.users[moreUsers.users.length - 1].$id;
|
71
|
-
}
|
72
|
-
}
|
73
|
-
else {
|
74
|
-
allUsers.push(...users.users);
|
75
|
-
}
|
76
|
-
return allUsers;
|
77
|
-
}
|
78
|
-
async createUsersAndReturn(items) {
|
79
|
-
const users = await Promise.all(items.map((item) => this.createUserAndReturn(item)));
|
80
|
-
return users;
|
81
|
-
}
|
82
|
-
async createUserAndReturn(item) {
|
83
|
-
try {
|
84
|
-
const user = await tryAwaitWithRetry(async () => {
|
85
|
-
const createdUser = await this.users.create(item.userId || ID.unique(), item.email || undefined, item.phone && item.phone.length < 15 && item.phone.startsWith("+")
|
86
|
-
? item.phone
|
87
|
-
: undefined, `changeMe${item.email?.toLowerCase()}` || `changeMePlease`, item.name || undefined);
|
88
|
-
if (item.labels) {
|
89
|
-
await this.users.updateLabels(createdUser.$id, item.labels);
|
90
|
-
}
|
91
|
-
if (item.prefs) {
|
92
|
-
await this.users.updatePrefs(createdUser.$id, item.prefs);
|
93
|
-
}
|
94
|
-
return createdUser;
|
95
|
-
}); // Set throwError to true since we want to handle errors
|
96
|
-
return user;
|
97
|
-
}
|
98
|
-
catch (e) {
|
99
|
-
if (e instanceof Error) {
|
100
|
-
logger.error("FAILED CREATING USER: ", e.message, item);
|
101
|
-
}
|
102
|
-
}
|
103
|
-
}
|
104
|
-
async createAndCheckForUserAndReturn(item) {
|
105
|
-
let userToReturn = undefined;
|
106
|
-
try {
|
107
|
-
// Attempt to find an existing user by email or phone.
|
108
|
-
let foundUsers = [];
|
109
|
-
if (item.email) {
|
110
|
-
const foundUsersByEmail = await this.users.list([
|
111
|
-
Query.equal("email", item.email),
|
112
|
-
]);
|
113
|
-
foundUsers = foundUsersByEmail.users;
|
114
|
-
}
|
115
|
-
if (item.phone) {
|
116
|
-
const foundUsersByPhone = await this.users.list([
|
117
|
-
Query.equal("phone", item.phone),
|
118
|
-
]);
|
119
|
-
foundUsers = foundUsers.length
|
120
|
-
? foundUsers.concat(foundUsersByPhone.users)
|
121
|
-
: foundUsersByPhone.users;
|
122
|
-
}
|
123
|
-
userToReturn = foundUsers[0] || undefined;
|
124
|
-
if (!userToReturn) {
|
125
|
-
userToReturn = await this.users.create(item.userId || ID.unique(), item.email || undefined, item.phone && item.phone.length < 15 && item.phone.startsWith("+")
|
126
|
-
? item.phone
|
127
|
-
: undefined, item.password?.toLowerCase() ||
|
128
|
-
`changeMe${item.email?.toLowerCase()}` ||
|
129
|
-
`changeMePlease`, item.name || undefined);
|
130
|
-
}
|
131
|
-
else {
|
132
|
-
// Update user details as necessary, ensuring email uniqueness if attempting an update.
|
133
|
-
if (item.email &&
|
134
|
-
item.email !== userToReturn.email &&
|
135
|
-
!isEmpty(item.email) &&
|
136
|
-
!isUndefined(item.email)) {
|
137
|
-
const emailExists = await this.users.list([
|
138
|
-
Query.equal("email", item.email),
|
139
|
-
]);
|
140
|
-
if (emailExists.users.length === 0) {
|
141
|
-
userToReturn = await this.users.updateEmail(userToReturn.$id, item.email);
|
142
|
-
}
|
143
|
-
else {
|
144
|
-
console.log("Email update skipped: Email already exists.");
|
145
|
-
}
|
146
|
-
}
|
147
|
-
if (item.password) {
|
148
|
-
userToReturn = await this.users.updatePassword(userToReturn.$id, item.password.toLowerCase());
|
149
|
-
}
|
150
|
-
if (item.name && item.name !== userToReturn.name) {
|
151
|
-
userToReturn = await this.users.updateName(userToReturn.$id, item.name);
|
152
|
-
}
|
153
|
-
if (item.phone &&
|
154
|
-
item.phone !== userToReturn.phone &&
|
155
|
-
item.phone.length < 15 &&
|
156
|
-
item.phone.startsWith("+") &&
|
157
|
-
(isUndefined(userToReturn.phone) || isEmpty(userToReturn.phone))) {
|
158
|
-
const userFoundWithPhone = await this.users.list([
|
159
|
-
Query.equal("phone", item.phone),
|
160
|
-
]);
|
161
|
-
if (userFoundWithPhone.total === 0) {
|
162
|
-
userToReturn = await this.users.updatePhone(userToReturn.$id, item.phone);
|
163
|
-
}
|
164
|
-
}
|
165
|
-
}
|
166
|
-
if (item.$createdAt && item.$updatedAt) {
|
167
|
-
console.log("$createdAt and $updatedAt are not yet supported, sorry about that!");
|
168
|
-
}
|
169
|
-
if (item.labels && item.labels.length) {
|
170
|
-
userToReturn = await this.users.updateLabels(userToReturn.$id, item.labels);
|
171
|
-
}
|
172
|
-
if (item.prefs && Object.keys(item.prefs).length) {
|
173
|
-
await this.users.updatePrefs(userToReturn.$id, item.prefs);
|
174
|
-
userToReturn.prefs = item.prefs;
|
175
|
-
}
|
176
|
-
return userToReturn;
|
177
|
-
}
|
178
|
-
catch (error) {
|
179
|
-
return userToReturn;
|
180
|
-
}
|
181
|
-
}
|
182
|
-
async getUserIdByEmailOrPhone(email, phone) {
|
183
|
-
if (!email && !phone) {
|
184
|
-
return undefined;
|
185
|
-
}
|
186
|
-
if (email && phone) {
|
187
|
-
const foundUsersByEmail = await this.users.list([
|
188
|
-
// @ts-ignore
|
189
|
-
Query.or([Query.equal("email", email), Query.equal("phone", phone)]),
|
190
|
-
]);
|
191
|
-
if (foundUsersByEmail.users.length > 0) {
|
192
|
-
return foundUsersByEmail.users[0]?.$id;
|
193
|
-
}
|
194
|
-
}
|
195
|
-
else if (email) {
|
196
|
-
const foundUsersByEmail = await this.users.list([
|
197
|
-
Query.equal("email", email),
|
198
|
-
]);
|
199
|
-
if (foundUsersByEmail.users.length > 0) {
|
200
|
-
return foundUsersByEmail.users[0]?.$id;
|
201
|
-
}
|
202
|
-
else {
|
203
|
-
if (!phone) {
|
204
|
-
return undefined;
|
205
|
-
}
|
206
|
-
else {
|
207
|
-
const foundUsersByPhone = await this.users.list([
|
208
|
-
Query.equal("phone", phone),
|
209
|
-
]);
|
210
|
-
if (foundUsersByPhone.users.length > 0) {
|
211
|
-
return foundUsersByPhone.users[0]?.$id;
|
212
|
-
}
|
213
|
-
else {
|
214
|
-
return undefined;
|
215
|
-
}
|
216
|
-
}
|
217
|
-
}
|
218
|
-
}
|
219
|
-
if (phone) {
|
220
|
-
const foundUsersByPhone = await this.users.list([
|
221
|
-
Query.equal("phone", phone),
|
222
|
-
]);
|
223
|
-
if (foundUsersByPhone.users.length > 0) {
|
224
|
-
return foundUsersByPhone.users[0]?.$id;
|
225
|
-
}
|
226
|
-
else {
|
227
|
-
return undefined;
|
228
|
-
}
|
229
|
-
}
|
230
|
-
}
|
231
|
-
transferUsersBetweenDbsLocalToRemote = async (endpoint, projectId, apiKey) => {
|
232
|
-
const localUsers = this.users;
|
233
|
-
const client = getAppwriteClient(endpoint, projectId, apiKey);
|
234
|
-
const remoteUsers = new Users(client);
|
235
|
-
let fromUsers = await localUsers.list([Query.limit(50)]);
|
236
|
-
if (fromUsers.users.length === 0) {
|
237
|
-
console.log(`No users found`);
|
238
|
-
return;
|
239
|
-
}
|
240
|
-
else if (fromUsers.users.length < 50) {
|
241
|
-
console.log(`Transferring ${fromUsers.users.length} users to remote`);
|
242
|
-
const batchedPromises = fromUsers.users.map((user) => {
|
243
|
-
return tryAwaitWithRetry(async () => {
|
244
|
-
const toCreateObject = {
|
245
|
-
...user,
|
246
|
-
};
|
247
|
-
delete toCreateObject.$id;
|
248
|
-
delete toCreateObject.$createdAt;
|
249
|
-
delete toCreateObject.$updatedAt;
|
250
|
-
await remoteUsers.create(user.$id, user.email, user.phone, user.password, user.name);
|
251
|
-
});
|
252
|
-
});
|
253
|
-
await Promise.all(batchedPromises);
|
254
|
-
}
|
255
|
-
else {
|
256
|
-
while (fromUsers.users.length === 50) {
|
257
|
-
fromUsers = await localUsers.list([
|
258
|
-
Query.limit(50),
|
259
|
-
Query.cursorAfter(fromUsers.users[fromUsers.users.length - 1].$id),
|
260
|
-
]);
|
261
|
-
const batchedPromises = fromUsers.users.map((user) => {
|
262
|
-
return tryAwaitWithRetry(async () => {
|
263
|
-
const toCreateObject = {
|
264
|
-
...user,
|
265
|
-
};
|
266
|
-
delete toCreateObject.$id;
|
267
|
-
delete toCreateObject.$createdAt;
|
268
|
-
delete toCreateObject.$updatedAt;
|
269
|
-
await remoteUsers.create(user.$id, user.email, user.phone, user.password, user.name);
|
270
|
-
});
|
271
|
-
});
|
272
|
-
await Promise.all(batchedPromises);
|
273
|
-
}
|
274
|
-
}
|
275
|
-
};
|
276
|
-
}
|
@@ -1,43 +0,0 @@
|
|
1
|
-
export interface ValidationRules {
|
2
|
-
[key: string]: (value: any, ...args: any[]) => boolean;
|
3
|
-
}
|
4
|
-
export declare const validationRules: {
|
5
|
-
isNumber: (value: any) => boolean;
|
6
|
-
isString: (value: any) => boolean;
|
7
|
-
isBoolean: (value: any) => boolean;
|
8
|
-
isArray: (value: any) => boolean;
|
9
|
-
isObject: (value: any) => boolean;
|
10
|
-
isNull: (value: any) => boolean;
|
11
|
-
isValidEmail: (value: string) => boolean;
|
12
|
-
isValidPhone: (value: string) => boolean;
|
13
|
-
isValidPassword: (value: string) => boolean;
|
14
|
-
isValidUrl: (value: string) => boolean;
|
15
|
-
isValidHex: (value: string) => boolean;
|
16
|
-
isValidHexColor: (value: string) => boolean;
|
17
|
-
isValidHexAlpha: (value: string) => boolean;
|
18
|
-
isValidDate: (value: string) => boolean;
|
19
|
-
isValidTime: (value: string) => boolean;
|
20
|
-
isNullish: (value: any) => boolean;
|
21
|
-
isUndefined: (value: any) => boolean;
|
22
|
-
isDefined: (value: any) => boolean;
|
23
|
-
isDate: (value: any) => boolean;
|
24
|
-
isEmpty: (value: any) => boolean;
|
25
|
-
isInteger: (value: any) => boolean;
|
26
|
-
isFloat: (value: any) => boolean;
|
27
|
-
isArrayLike: (value: any) => boolean;
|
28
|
-
isArrayLikeObject: (value: any) => boolean;
|
29
|
-
isFunction: (value: any) => boolean;
|
30
|
-
isLength: (value: any) => boolean;
|
31
|
-
isMap: (value: any) => boolean;
|
32
|
-
isSet: (value: any) => boolean;
|
33
|
-
isRegExp: (value: any) => boolean;
|
34
|
-
isSymbol: (value: any) => boolean;
|
35
|
-
isObjectLike: (value: any) => boolean;
|
36
|
-
isPlainObject: (value: any) => boolean;
|
37
|
-
isSafeInteger: (value: any) => boolean;
|
38
|
-
isTypedArray: (value: any) => boolean;
|
39
|
-
isEqual: (value: any, other: any) => boolean;
|
40
|
-
isMatch: (object: any, source: any) => boolean;
|
41
|
-
has: (object: any, path: string) => boolean;
|
42
|
-
get: (object: any, path: string, defaultValue: any) => any;
|
43
|
-
};
|
@@ -1,42 +0,0 @@
|
|
1
|
-
import { isNumber, isString, isBoolean, isArray, isPlainObject, isNull, isUndefined, isDate, isEmpty, isInteger, isArrayLike, isArrayLikeObject, isFunction, isLength, isMap, isSet, isRegExp, isSymbol, isObjectLike, isSafeInteger, isTypedArray, isEqual, isMatch, has, get, } from "es-toolkit/compat";
|
2
|
-
export const validationRules = {
|
3
|
-
isNumber: (value) => isNumber(value),
|
4
|
-
isString: (value) => isString(value),
|
5
|
-
isBoolean: (value) => isBoolean(value),
|
6
|
-
isArray: (value) => isArray(value),
|
7
|
-
isObject: (value) => isPlainObject(value) && !isArray(value) && !isFunction(value),
|
8
|
-
isNull: (value) => isNull(value),
|
9
|
-
isValidEmail: (value) => value.match(/^[\w\-\.]+@([\w-]+\.)+[\w-]{2,}$/) !== null,
|
10
|
-
isValidPhone: (value) => value.match(/^[+]?[(]?[0-9]{3}[)]?[-\s.]?[0-9]{3}[-\s.]?[0-9]{4,6}$/im) !==
|
11
|
-
null,
|
12
|
-
isValidPassword: (value) => value.match(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/) !== null,
|
13
|
-
isValidUrl: (value) => value.match(/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/) !== null,
|
14
|
-
isValidHex: (value) => value.match(/^#([a-f0-9]{6}|[a-f0-9]{3})$/i) !== null,
|
15
|
-
isValidHexColor: (value) => value.match(/^#([a-f0-9]{6}|[a-f0-9]{3})$/i) !== null,
|
16
|
-
isValidHexAlpha: (value) => value.match(/^#([a-f0-9]{8}|[a-f0-9]{4})$/i) !== null,
|
17
|
-
isValidDate: (value) => value.match(/^\d{4}-\d{2}-\d{2}$/) !== null,
|
18
|
-
isValidTime: (value) => value.match(/^\d{2}:\d{2}(:\d{2})?$/) !== null,
|
19
|
-
isNullish: (value) => isNull(value) || isUndefined(value),
|
20
|
-
isUndefined: (value) => isUndefined(value),
|
21
|
-
isDefined: (value) => !isUndefined(value) && !isNull(value) && !isEmpty(value),
|
22
|
-
isDate: (value) => isDate(value),
|
23
|
-
isEmpty: (value) => isEmpty(value),
|
24
|
-
isInteger: (value) => isInteger(value),
|
25
|
-
isFloat: (value) => isNumber(value) && !isInteger(value),
|
26
|
-
isArrayLike: (value) => isArrayLike(value),
|
27
|
-
isArrayLikeObject: (value) => isArrayLikeObject(value),
|
28
|
-
isFunction: (value) => isFunction(value),
|
29
|
-
isLength: (value) => isLength(value),
|
30
|
-
isMap: (value) => isMap(value),
|
31
|
-
isSet: (value) => isSet(value),
|
32
|
-
isRegExp: (value) => isRegExp(value),
|
33
|
-
isSymbol: (value) => isSymbol(value),
|
34
|
-
isObjectLike: (value) => isObjectLike(value),
|
35
|
-
isPlainObject: (value) => isPlainObject(value),
|
36
|
-
isSafeInteger: (value) => isSafeInteger(value),
|
37
|
-
isTypedArray: (value) => isTypedArray(value),
|
38
|
-
isEqual: (value, other) => isEqual(value, other),
|
39
|
-
isMatch: (object, source) => isMatch(object, source),
|
40
|
-
has: (object, path) => has(object, path),
|
41
|
-
get: (object, path, defaultValue) => get(object, path, defaultValue),
|
42
|
-
};
|
@@ -1,17 +0,0 @@
|
|
1
|
-
import { type Databases, type Models } from "node-appwrite";
|
2
|
-
import { type Attribute, type CollectionCreate } from "appwrite-utils";
|
3
|
-
export declare const attributesSame: (databaseAttribute: Attribute, configAttribute: Attribute) => boolean;
|
4
|
-
export declare const createOrUpdateAttribute: (db: Databases, dbId: string, collection: Models.Collection, attribute: Attribute, options?: {
|
5
|
-
updateEnabled?: boolean;
|
6
|
-
useQueue?: boolean;
|
7
|
-
verbose?: boolean;
|
8
|
-
}) => Promise<void>;
|
9
|
-
export declare const createUpdateCollectionAttributes: (db: Databases, dbId: string, collection: Models.Collection, collectionConfig: CollectionCreate, options?: {
|
10
|
-
updateEnabled?: boolean;
|
11
|
-
useQueue?: boolean;
|
12
|
-
verbose?: boolean;
|
13
|
-
}) => Promise<void>;
|
14
|
-
export declare const deleteObsoleteAttributes: (db: Databases, dbId: string, collection: Models.Collection, collectionConfig: CollectionCreate, options?: {
|
15
|
-
useQueue?: boolean;
|
16
|
-
verbose?: boolean;
|
17
|
-
}) => Promise<void>;
|
@@ -1,272 +0,0 @@
|
|
1
|
-
import {} from "node-appwrite";
|
2
|
-
import { parseAttribute, } from "appwrite-utils";
|
3
|
-
import { nameToIdMapping, enqueueOperation } from "./operationQueue.js";
|
4
|
-
import { delay, tryAwaitWithRetry } from "../utils/helperFunctions.js";
|
5
|
-
import chalk from "chalk";
|
6
|
-
import pLimit from "p-limit";
|
7
|
-
// Concurrency limits for different operations
|
8
|
-
const attributeLimit = pLimit(3); // Low limit for attribute operations
|
9
|
-
const queryLimit = pLimit(25); // Higher limit for read operations
|
10
|
-
export const attributesSame = (databaseAttribute, configAttribute) => {
|
11
|
-
const attributesToCheck = [
|
12
|
-
"key",
|
13
|
-
"type",
|
14
|
-
"array",
|
15
|
-
"encrypted",
|
16
|
-
"required",
|
17
|
-
"size",
|
18
|
-
"min",
|
19
|
-
"max",
|
20
|
-
"xdefault",
|
21
|
-
"elements",
|
22
|
-
"relationType",
|
23
|
-
"twoWay",
|
24
|
-
"twoWayKey",
|
25
|
-
"onDelete",
|
26
|
-
"relatedCollection",
|
27
|
-
];
|
28
|
-
return attributesToCheck.every((attr) => {
|
29
|
-
// Special handling for min/max values
|
30
|
-
if (attr === "min" || attr === "max") {
|
31
|
-
const dbValue = databaseAttribute[attr];
|
32
|
-
const configValue = configAttribute[attr];
|
33
|
-
// Use type-specific default values when comparing
|
34
|
-
if (databaseAttribute.type === "integer") {
|
35
|
-
if (attr === "min") {
|
36
|
-
return (dbValue ?? -2147483647) === (configValue ?? -2147483647);
|
37
|
-
}
|
38
|
-
else { // attr === "max"
|
39
|
-
return (dbValue ?? 2147483647) === (configValue ?? 2147483647);
|
40
|
-
}
|
41
|
-
}
|
42
|
-
if (databaseAttribute.type === "double" || databaseAttribute.type === "float") {
|
43
|
-
if (attr === "min") {
|
44
|
-
return (dbValue ?? -2147483647) === (configValue ?? -2147483647);
|
45
|
-
}
|
46
|
-
else { // attr === "max"
|
47
|
-
return (dbValue ?? 2147483647) === (configValue ?? 2147483647);
|
48
|
-
}
|
49
|
-
}
|
50
|
-
}
|
51
|
-
// Check if both objects have the attribute
|
52
|
-
const dbHasAttr = attr in databaseAttribute;
|
53
|
-
const configHasAttr = attr in configAttribute;
|
54
|
-
// If both have the attribute, compare values
|
55
|
-
if (dbHasAttr && configHasAttr) {
|
56
|
-
const dbValue = databaseAttribute[attr];
|
57
|
-
const configValue = configAttribute[attr];
|
58
|
-
// Consider undefined and null as equivalent
|
59
|
-
if ((dbValue === undefined || dbValue === null) &&
|
60
|
-
(configValue === undefined || configValue === null)) {
|
61
|
-
return true;
|
62
|
-
}
|
63
|
-
return dbValue === configValue;
|
64
|
-
}
|
65
|
-
// If neither has the attribute, consider it the same
|
66
|
-
if (!dbHasAttr && !configHasAttr) {
|
67
|
-
return true;
|
68
|
-
}
|
69
|
-
// If one has the attribute and the other doesn't, check if it's undefined or null
|
70
|
-
if (dbHasAttr && !configHasAttr) {
|
71
|
-
const dbValue = databaseAttribute[attr];
|
72
|
-
return dbValue === undefined || dbValue === null;
|
73
|
-
}
|
74
|
-
if (!dbHasAttr && configHasAttr) {
|
75
|
-
const configValue = configAttribute[attr];
|
76
|
-
return configValue === undefined || configValue === null;
|
77
|
-
}
|
78
|
-
// If we reach here, the attributes are different
|
79
|
-
return false;
|
80
|
-
});
|
81
|
-
};
|
82
|
-
export const createOrUpdateAttribute = async (db, dbId, collection, attribute, options = {}) => {
|
83
|
-
const { updateEnabled = true, useQueue = true, verbose = false } = options;
|
84
|
-
let action = "create";
|
85
|
-
let foundAttribute;
|
86
|
-
let finalAttribute = attribute;
|
87
|
-
try {
|
88
|
-
const collectionAttr = collection.attributes.find((attr) => attr.key === attribute.key);
|
89
|
-
foundAttribute = parseAttribute(collectionAttr);
|
90
|
-
if (verbose) {
|
91
|
-
console.log(`Found attribute: ${JSON.stringify(foundAttribute)}`);
|
92
|
-
}
|
93
|
-
}
|
94
|
-
catch (error) {
|
95
|
-
foundAttribute = undefined;
|
96
|
-
}
|
97
|
-
if (foundAttribute &&
|
98
|
-
attributesSame(foundAttribute, attribute) &&
|
99
|
-
updateEnabled) {
|
100
|
-
if (verbose) {
|
101
|
-
console.log(chalk.green(`✓ Attribute ${attribute.key} is up to date`));
|
102
|
-
}
|
103
|
-
return;
|
104
|
-
}
|
105
|
-
if (foundAttribute) {
|
106
|
-
action = "update";
|
107
|
-
if (verbose) {
|
108
|
-
console.log(chalk.yellow(`⚠ Updating attribute ${attribute.key}`));
|
109
|
-
}
|
110
|
-
}
|
111
|
-
else {
|
112
|
-
if (verbose) {
|
113
|
-
console.log(chalk.blue(`+ Creating attribute ${attribute.key}`));
|
114
|
-
}
|
115
|
-
}
|
116
|
-
// Handle relationship attributes with nameToIdMapping
|
117
|
-
if (attribute.type === "relationship" && attribute.relatedCollection) {
|
118
|
-
const relatedCollectionId = nameToIdMapping.get(attribute.relatedCollection);
|
119
|
-
if (relatedCollectionId) {
|
120
|
-
finalAttribute = {
|
121
|
-
...attribute,
|
122
|
-
relatedCollection: relatedCollectionId,
|
123
|
-
};
|
124
|
-
}
|
125
|
-
}
|
126
|
-
// Handle BigInt values for integer, double and float types
|
127
|
-
if (attribute.type === "integer" || attribute.type === "double" || attribute.type === "float") {
|
128
|
-
if (typeof finalAttribute.min === "bigint") {
|
129
|
-
finalAttribute.min = Number(finalAttribute.min);
|
130
|
-
}
|
131
|
-
if (typeof finalAttribute.max === "bigint") {
|
132
|
-
finalAttribute.max = Number(finalAttribute.max);
|
133
|
-
}
|
134
|
-
}
|
135
|
-
const queuedOperation = {
|
136
|
-
type: "attribute",
|
137
|
-
collectionId: collection.$id,
|
138
|
-
attribute: finalAttribute,
|
139
|
-
collection,
|
140
|
-
};
|
141
|
-
const executeOperation = async () => {
|
142
|
-
await attributeLimit(async () => {
|
143
|
-
if (action === "update" && foundAttribute) {
|
144
|
-
// Delete existing attribute first
|
145
|
-
await tryAwaitWithRetry(async () => {
|
146
|
-
await db.deleteAttribute(dbId, collection.$id, attribute.key);
|
147
|
-
});
|
148
|
-
await delay(250);
|
149
|
-
}
|
150
|
-
// Create attribute based on type
|
151
|
-
switch (finalAttribute.type) {
|
152
|
-
case "string":
|
153
|
-
await tryAwaitWithRetry(async () => {
|
154
|
-
await db.createStringAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.size, finalAttribute.required, finalAttribute.xdefault, finalAttribute.array, finalAttribute.encrypted);
|
155
|
-
});
|
156
|
-
break;
|
157
|
-
case "integer":
|
158
|
-
await tryAwaitWithRetry(async () => {
|
159
|
-
await db.createIntegerAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required, finalAttribute.min, finalAttribute.max, finalAttribute.xdefault, finalAttribute.array);
|
160
|
-
});
|
161
|
-
break;
|
162
|
-
case "double":
|
163
|
-
case "float": // Backward compatibility
|
164
|
-
await tryAwaitWithRetry(async () => {
|
165
|
-
await db.createFloatAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required, finalAttribute.min, finalAttribute.max, finalAttribute.xdefault, finalAttribute.array);
|
166
|
-
});
|
167
|
-
break;
|
168
|
-
case "boolean":
|
169
|
-
await tryAwaitWithRetry(async () => {
|
170
|
-
await db.createBooleanAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required, finalAttribute.xdefault, finalAttribute.array);
|
171
|
-
});
|
172
|
-
break;
|
173
|
-
case "datetime":
|
174
|
-
await tryAwaitWithRetry(async () => {
|
175
|
-
await db.createDatetimeAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required, finalAttribute.xdefault, finalAttribute.array);
|
176
|
-
});
|
177
|
-
break;
|
178
|
-
case "email":
|
179
|
-
await tryAwaitWithRetry(async () => {
|
180
|
-
await db.createEmailAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required, finalAttribute.xdefault, finalAttribute.array);
|
181
|
-
});
|
182
|
-
break;
|
183
|
-
case "ip":
|
184
|
-
await tryAwaitWithRetry(async () => {
|
185
|
-
await db.createIpAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required, finalAttribute.xdefault, finalAttribute.array);
|
186
|
-
});
|
187
|
-
break;
|
188
|
-
case "url":
|
189
|
-
await tryAwaitWithRetry(async () => {
|
190
|
-
await db.createUrlAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.required, finalAttribute.xdefault, finalAttribute.array);
|
191
|
-
});
|
192
|
-
break;
|
193
|
-
case "enum":
|
194
|
-
await tryAwaitWithRetry(async () => {
|
195
|
-
await db.createEnumAttribute(dbId, collection.$id, finalAttribute.key, finalAttribute.elements, finalAttribute.required, finalAttribute.xdefault, finalAttribute.array);
|
196
|
-
});
|
197
|
-
break;
|
198
|
-
case "relationship":
|
199
|
-
await tryAwaitWithRetry(async () => {
|
200
|
-
await db.createRelationshipAttribute(dbId, collection.$id, finalAttribute.relatedCollection, finalAttribute.relationType, finalAttribute.twoWay, finalAttribute.key, finalAttribute.twoWayKey, finalAttribute.onDelete);
|
201
|
-
});
|
202
|
-
break;
|
203
|
-
default:
|
204
|
-
throw new Error(`Unknown attribute type: ${finalAttribute.type}`);
|
205
|
-
}
|
206
|
-
});
|
207
|
-
};
|
208
|
-
if (useQueue) {
|
209
|
-
enqueueOperation(queuedOperation);
|
210
|
-
}
|
211
|
-
else {
|
212
|
-
await executeOperation();
|
213
|
-
}
|
214
|
-
};
|
215
|
-
export const createUpdateCollectionAttributes = async (db, dbId, collection, collectionConfig, options = {}) => {
|
216
|
-
if (!collectionConfig.attributes)
|
217
|
-
return;
|
218
|
-
const { verbose = false } = options;
|
219
|
-
if (verbose) {
|
220
|
-
console.log(chalk.blue(`Processing ${collectionConfig.attributes.length} attributes for collection ${collection.name}`));
|
221
|
-
}
|
222
|
-
for (const attribute of collectionConfig.attributes) {
|
223
|
-
try {
|
224
|
-
await createOrUpdateAttribute(db, dbId, collection, attribute, options);
|
225
|
-
if (verbose) {
|
226
|
-
console.log(chalk.green(`✓ Processed attribute ${attribute.key}`));
|
227
|
-
}
|
228
|
-
// Add delay between attribute operations to prevent rate limiting
|
229
|
-
await delay(250);
|
230
|
-
}
|
231
|
-
catch (error) {
|
232
|
-
console.error(chalk.red(`❌ Failed to process attribute ${attribute.key}:`), error);
|
233
|
-
throw error;
|
234
|
-
}
|
235
|
-
}
|
236
|
-
};
|
237
|
-
export const deleteObsoleteAttributes = async (db, dbId, collection, collectionConfig, options = {}) => {
|
238
|
-
const { useQueue = true, verbose = false } = options;
|
239
|
-
const configAttributes = collectionConfig.attributes || [];
|
240
|
-
const configAttributeKeys = new Set(configAttributes.map(attr => attr.key));
|
241
|
-
// Find attributes that exist in the database but not in the config
|
242
|
-
const obsoleteAttributes = collection.attributes.filter((attr) => !configAttributeKeys.has(attr.key));
|
243
|
-
if (obsoleteAttributes.length === 0) {
|
244
|
-
return;
|
245
|
-
}
|
246
|
-
if (verbose) {
|
247
|
-
console.log(chalk.yellow(`🗑️ Removing ${obsoleteAttributes.length} obsolete attributes from collection ${collection.name}`));
|
248
|
-
}
|
249
|
-
for (const attr of obsoleteAttributes) {
|
250
|
-
const queuedOperation = {
|
251
|
-
type: "attribute",
|
252
|
-
collectionId: collection.$id,
|
253
|
-
attribute: { key: attr.key, type: "delete" },
|
254
|
-
collection,
|
255
|
-
};
|
256
|
-
const executeOperation = async () => {
|
257
|
-
await attributeLimit(() => tryAwaitWithRetry(async () => {
|
258
|
-
await db.deleteAttribute(dbId, collection.$id, attr.key);
|
259
|
-
}));
|
260
|
-
};
|
261
|
-
if (useQueue) {
|
262
|
-
enqueueOperation(queuedOperation);
|
263
|
-
}
|
264
|
-
else {
|
265
|
-
await executeOperation();
|
266
|
-
await delay(250);
|
267
|
-
}
|
268
|
-
if (verbose) {
|
269
|
-
console.log(chalk.gray(`🗑️ Deleted obsolete attribute ${attr.key}`));
|
270
|
-
}
|
271
|
-
}
|
272
|
-
};
|