@njdamstra/appwrite-utils-cli 1.11.7 → 1.11.8

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.
@@ -250,6 +250,31 @@ export async function executeMigrationPlan(adapter, options) {
250
250
  skipped += entries.length;
251
251
  continue;
252
252
  }
253
+ // Temporarily set ALL required attributes in this collection to optional.
254
+ // Appwrite rejects partial updateRow calls if a required attribute is missing
255
+ // from the payload — even if the field exists in the document. This affects
256
+ // every data-copy step during migration.
257
+ const schemaRes = await tryAwaitWithRetry(() => adapter.getTable({ databaseId: first.databaseId, tableId: first.collectionId }));
258
+ const allAttrs = schemaRes?.data?.attributes || schemaRes?.data?.columns || [];
259
+ const originallyRequired = allAttrs
260
+ .filter((a) => a.required === true && a.status === "available")
261
+ .map((a) => a.key);
262
+ if (originallyRequired.length > 0) {
263
+ MessageFormatter.info(` Temporarily setting ${originallyRequired.length} required attribute(s) to optional...`, { prefix: "Execute" });
264
+ for (const key of originallyRequired) {
265
+ try {
266
+ await tryAwaitWithRetry(() => adapter.updateAttribute({
267
+ databaseId: first.databaseId,
268
+ tableId: first.collectionId,
269
+ key,
270
+ required: false,
271
+ }));
272
+ }
273
+ catch {
274
+ // Non-fatal — attribute might not support updating required
275
+ }
276
+ }
277
+ }
253
278
  // Migrate each attribute in this collection
254
279
  for (const entry of entries) {
255
280
  const cpEntry = getOrCreateCheckpointEntry(checkpoint, entry);
@@ -275,25 +300,23 @@ export async function executeMigrationPlan(adapter, options) {
275
300
  MessageFormatter.error(` ${entry.attributeKey}: FAILED — ${cpEntry.error}`, undefined, { prefix: "Execute" });
276
301
  }
277
302
  }
278
- // Restore required flags after all attributes in this collection are done.
279
- // This must happen AFTER all migrations to avoid partial-update validation
280
- // errors (Appwrite rejects updateRow if a required attribute is missing
281
- // from the payload, even for partial updates).
282
- const completedRequired = entries.filter((e) => {
283
- const cp = findCheckpointEntry(checkpoint, e);
284
- return cp?.phase === "completed" && e.isRequired;
285
- });
286
- for (const entry of completedRequired) {
287
- try {
288
- await tryAwaitWithRetry(() => adapter.updateAttribute({
289
- databaseId: entry.databaseId,
290
- tableId: entry.collectionId,
291
- key: entry.attributeKey,
292
- required: true,
293
- }));
294
- }
295
- catch {
296
- MessageFormatter.info(` Warning: could not set ${entry.attributeKey} back to required`, { prefix: "Execute" });
303
+ // Restore required flags for ALL originally-required attributes.
304
+ // This covers both migrated attributes (recreated as optional) and
305
+ // non-migrated attributes (temporarily set to optional above).
306
+ if (originallyRequired.length > 0) {
307
+ MessageFormatter.info(` Restoring ${originallyRequired.length} required attribute(s)...`, { prefix: "Execute" });
308
+ for (const key of originallyRequired) {
309
+ try {
310
+ await tryAwaitWithRetry(() => adapter.updateAttribute({
311
+ databaseId: first.databaseId,
312
+ tableId: first.collectionId,
313
+ key,
314
+ required: true,
315
+ }));
316
+ }
317
+ catch {
318
+ MessageFormatter.info(` Warning: could not restore required flag for ${key}`, { prefix: "Execute" });
319
+ }
297
320
  }
298
321
  }
299
322
  // After collection completes, offer to update local YAML
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@njdamstra/appwrite-utils-cli",
3
3
  "description": "Appwrite Utility Functions to help with database management, data conversion, data import, migrations, and much more. Meant to be used as a CLI tool, I do not recommend installing this in frontend environments.",
4
- "version": "1.11.7",
4
+ "version": "1.11.8",
5
5
  "main": "dist/main.js",
6
6
  "type": "module",
7
7
  "repository": {
@@ -338,6 +338,40 @@ export async function executeMigrationPlan(
338
338
  continue;
339
339
  }
340
340
 
341
+ // Temporarily set ALL required attributes in this collection to optional.
342
+ // Appwrite rejects partial updateRow calls if a required attribute is missing
343
+ // from the payload — even if the field exists in the document. This affects
344
+ // every data-copy step during migration.
345
+ const schemaRes = await tryAwaitWithRetry(() =>
346
+ adapter.getTable({ databaseId: first.databaseId, tableId: first.collectionId })
347
+ );
348
+ const allAttrs: any[] =
349
+ schemaRes?.data?.attributes || schemaRes?.data?.columns || [];
350
+ const originallyRequired = allAttrs
351
+ .filter((a: any) => a.required === true && a.status === "available")
352
+ .map((a: any) => a.key as string);
353
+
354
+ if (originallyRequired.length > 0) {
355
+ MessageFormatter.info(
356
+ ` Temporarily setting ${originallyRequired.length} required attribute(s) to optional...`,
357
+ { prefix: "Execute" }
358
+ );
359
+ for (const key of originallyRequired) {
360
+ try {
361
+ await tryAwaitWithRetry(() =>
362
+ adapter.updateAttribute({
363
+ databaseId: first.databaseId,
364
+ tableId: first.collectionId,
365
+ key,
366
+ required: false,
367
+ } as any)
368
+ );
369
+ } catch {
370
+ // Non-fatal — attribute might not support updating required
371
+ }
372
+ }
373
+ }
374
+
341
375
  // Migrate each attribute in this collection
342
376
  for (const entry of entries) {
343
377
  const cpEntry = getOrCreateCheckpointEntry(checkpoint, entry);
@@ -382,29 +416,30 @@ export async function executeMigrationPlan(
382
416
  }
383
417
  }
384
418
 
385
- // Restore required flags after all attributes in this collection are done.
386
- // This must happen AFTER all migrations to avoid partial-update validation
387
- // errors (Appwrite rejects updateRow if a required attribute is missing
388
- // from the payload, even for partial updates).
389
- const completedRequired = entries.filter((e) => {
390
- const cp = findCheckpointEntry(checkpoint, e);
391
- return cp?.phase === "completed" && e.isRequired;
392
- });
393
- for (const entry of completedRequired) {
394
- try {
395
- await tryAwaitWithRetry(() =>
396
- adapter.updateAttribute({
397
- databaseId: entry.databaseId,
398
- tableId: entry.collectionId,
399
- key: entry.attributeKey,
400
- required: true,
401
- } as any)
402
- );
403
- } catch {
404
- MessageFormatter.info(
405
- ` Warning: could not set ${entry.attributeKey} back to required`,
406
- { prefix: "Execute" }
407
- );
419
+ // Restore required flags for ALL originally-required attributes.
420
+ // This covers both migrated attributes (recreated as optional) and
421
+ // non-migrated attributes (temporarily set to optional above).
422
+ if (originallyRequired.length > 0) {
423
+ MessageFormatter.info(
424
+ ` Restoring ${originallyRequired.length} required attribute(s)...`,
425
+ { prefix: "Execute" }
426
+ );
427
+ for (const key of originallyRequired) {
428
+ try {
429
+ await tryAwaitWithRetry(() =>
430
+ adapter.updateAttribute({
431
+ databaseId: first.databaseId,
432
+ tableId: first.collectionId,
433
+ key,
434
+ required: true,
435
+ } as any)
436
+ );
437
+ } catch {
438
+ MessageFormatter.info(
439
+ ` Warning: could not restore required flag for ${key}`,
440
+ { prefix: "Execute" }
441
+ );
442
+ }
408
443
  }
409
444
  }
410
445