@njdamstra/appwrite-utils-cli 1.10.1 → 1.11.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.
@@ -0,0 +1,158 @@
1
+ import { z } from "zod";
2
+
3
+ // ── Target types for string attribute migration ──
4
+
5
+ export const MigrationTargetType = z.enum([
6
+ "varchar",
7
+ "text",
8
+ "mediumtext",
9
+ "longtext",
10
+ ]);
11
+ export type MigrationTargetType = z.infer<typeof MigrationTargetType>;
12
+
13
+ export const MigrationAction = z.enum(["migrate", "skip"]);
14
+ export type MigrationAction = z.infer<typeof MigrationAction>;
15
+
16
+ // ── Plan entry: one per attribute to migrate ──
17
+
18
+ export const MigrationPlanEntrySchema = z.object({
19
+ databaseId: z.string(),
20
+ databaseName: z.string(),
21
+ collectionId: z.string(),
22
+ collectionName: z.string(),
23
+ attributeKey: z.string(),
24
+ currentType: z.string().default("string"),
25
+ currentSize: z.number(),
26
+ isRequired: z.boolean().default(false),
27
+ isArray: z.boolean().default(false),
28
+ isEncrypted: z.boolean().default(false),
29
+ hasDefault: z.boolean().default(false),
30
+ defaultValue: z.any().optional(),
31
+ suggestedType: MigrationTargetType,
32
+ targetType: MigrationTargetType,
33
+ targetSize: z.number().optional(), // varchar only
34
+ action: MigrationAction,
35
+ skipReason: z.string().optional(),
36
+ indexesAffected: z.array(z.string()).default([]),
37
+ });
38
+ export type MigrationPlanEntry = z.infer<typeof MigrationPlanEntrySchema>;
39
+
40
+ // ── Plan: full migration plan (user-editable YAML) ──
41
+
42
+ export const MigrationPlanSchema = z.object({
43
+ version: z.number().default(1),
44
+ generatedAt: z.string(),
45
+ appwriteEndpoint: z.string().optional(),
46
+ appwriteProject: z.string().optional(),
47
+ summary: z.object({
48
+ totalStringAttributes: z.number(),
49
+ toMigrate: z.number(),
50
+ toSkip: z.number(),
51
+ databaseCount: z.number(),
52
+ collectionCount: z.number(),
53
+ }),
54
+ entries: z.array(MigrationPlanEntrySchema),
55
+ });
56
+ export type MigrationPlan = z.infer<typeof MigrationPlanSchema>;
57
+
58
+ // ── Checkpoint: tracks progress during execution ──
59
+
60
+ export const CheckpointPhase = z.enum([
61
+ "pending",
62
+ "backup_created",
63
+ "data_copied_to_backup",
64
+ "data_verified_backup",
65
+ "original_deleted",
66
+ "new_attr_created",
67
+ "data_copied_back",
68
+ "data_verified_final",
69
+ "backup_deleted",
70
+ "completed",
71
+ "failed",
72
+ ]);
73
+ export type CheckpointPhase = z.infer<typeof CheckpointPhase>;
74
+
75
+ export const CheckpointEntrySchema = z.object({
76
+ databaseId: z.string(),
77
+ collectionId: z.string(),
78
+ attributeKey: z.string(),
79
+ backupKey: z.string(),
80
+ phase: CheckpointPhase,
81
+ targetType: MigrationTargetType,
82
+ targetSize: z.number().optional(),
83
+ error: z.string().optional(),
84
+ // Store index definitions for recreation after attribute delete
85
+ storedIndexes: z
86
+ .array(
87
+ z.object({
88
+ key: z.string(),
89
+ type: z.string(),
90
+ attributes: z.array(z.string()),
91
+ orders: z.array(z.string()).optional(),
92
+ })
93
+ )
94
+ .default([]),
95
+ });
96
+ export type CheckpointEntry = z.infer<typeof CheckpointEntrySchema>;
97
+
98
+ export const MigrationCheckpointSchema = z.object({
99
+ planFile: z.string(),
100
+ startedAt: z.string(),
101
+ lastUpdatedAt: z.string(),
102
+ entries: z.array(CheckpointEntrySchema),
103
+ });
104
+ export type MigrationCheckpoint = z.infer<typeof MigrationCheckpointSchema>;
105
+
106
+ // ── Options ──
107
+
108
+ export interface AnalyzeOptions {
109
+ outputPath?: string;
110
+ verbose?: boolean;
111
+ }
112
+
113
+ export interface ExecuteOptions {
114
+ planPath: string;
115
+ keepBackups?: boolean;
116
+ dryRun?: boolean;
117
+ batchSize?: number;
118
+ batchDelayMs?: number;
119
+ checkpointPath?: string;
120
+ }
121
+
122
+ // ── Helper: suggest target type from size + index presence ──
123
+
124
+ export function suggestTargetType(
125
+ size: number,
126
+ hasIndex: boolean
127
+ ): MigrationTargetType {
128
+ // varchar supports up to 16,383 bytes and allows full indexing
129
+ if (size <= 768) return "varchar";
130
+ if (hasIndex) return "varchar"; // indexes require varchar
131
+ if (size <= 16_383) return "text";
132
+ if (size <= 4_000_000) return "mediumtext";
133
+ return "longtext";
134
+ }
135
+
136
+ // ── Helper: generate backup key with length limits ──
137
+
138
+ const MAX_KEY_LENGTH = 36;
139
+ const BACKUP_PREFIX = "_mig_";
140
+
141
+ export function generateBackupKey(originalKey: string): string {
142
+ const candidate = `${BACKUP_PREFIX}${originalKey}`;
143
+ if (candidate.length <= MAX_KEY_LENGTH) {
144
+ return candidate;
145
+ }
146
+ // Truncate + 4-char hash for uniqueness
147
+ const hash = simpleHash(originalKey);
148
+ const maxOrigLen = MAX_KEY_LENGTH - BACKUP_PREFIX.length - 1 - 4; // _mig_ + _ + hash4
149
+ return `_m_${originalKey.slice(0, maxOrigLen)}_${hash}`;
150
+ }
151
+
152
+ function simpleHash(str: string): string {
153
+ let h = 0;
154
+ for (let i = 0; i < str.length; i++) {
155
+ h = ((h << 5) - h + str.charCodeAt(i)) | 0;
156
+ }
157
+ return Math.abs(h).toString(36).slice(0, 4).padStart(4, "0");
158
+ }