@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.
- package/dist/cli/commands/migrateCommands.d.ts +6 -0
- package/dist/cli/commands/migrateCommands.js +118 -0
- package/dist/interactiveCLI.js +6 -0
- package/dist/main.js +56 -0
- package/dist/migrations/migrateStrings.d.ts +9 -0
- package/dist/migrations/migrateStrings.js +724 -0
- package/dist/migrations/migrateStringsTypes.d.ts +195 -0
- package/dist/migrations/migrateStringsTypes.js +117 -0
- package/package.json +1 -1
- package/src/cli/commands/migrateCommands.ts +157 -0
- package/src/interactiveCLI.ts +6 -0
- package/src/main.ts +69 -0
- package/src/migrations/migrateStrings.ts +1064 -0
- package/src/migrations/migrateStringsTypes.ts +158 -0
|
@@ -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
|
+
}
|