melaka 0.0.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/CONTRIBUTING.md +347 -0
- package/LICENSE +21 -0
- package/README.md +57 -0
- package/docs/AI_PROVIDERS.md +343 -0
- package/docs/ARCHITECTURE.md +512 -0
- package/docs/CLI.md +438 -0
- package/docs/CONFIGURATION.md +453 -0
- package/docs/INTEGRATION.md +477 -0
- package/docs/ROADMAP.md +248 -0
- package/package.json +46 -0
- package/packages/ai/README.md +43 -0
- package/packages/ai/package.json +42 -0
- package/packages/ai/src/facade.ts +120 -0
- package/packages/ai/src/index.ts +34 -0
- package/packages/ai/src/prompt.ts +117 -0
- package/packages/ai/src/providers/gemini.ts +185 -0
- package/packages/ai/src/providers/index.ts +9 -0
- package/packages/ai/src/types.ts +134 -0
- package/packages/ai/tsconfig.json +19 -0
- package/packages/cli/README.md +70 -0
- package/packages/cli/package.json +44 -0
- package/packages/cli/src/cli.ts +30 -0
- package/packages/cli/src/commands/deploy.ts +115 -0
- package/packages/cli/src/commands/index.ts +9 -0
- package/packages/cli/src/commands/init.ts +107 -0
- package/packages/cli/src/commands/status.ts +73 -0
- package/packages/cli/src/commands/translate.ts +92 -0
- package/packages/cli/src/commands/validate.ts +69 -0
- package/packages/cli/tsconfig.json +19 -0
- package/packages/core/README.md +46 -0
- package/packages/core/package.json +50 -0
- package/packages/core/src/config.ts +241 -0
- package/packages/core/src/index.ts +111 -0
- package/packages/core/src/schema-generator.ts +263 -0
- package/packages/core/src/schemas.ts +126 -0
- package/packages/core/src/types.ts +481 -0
- package/packages/core/src/utils.ts +343 -0
- package/packages/core/tsconfig.json +19 -0
- package/packages/firestore/README.md +60 -0
- package/packages/firestore/package.json +48 -0
- package/packages/firestore/src/generator.ts +270 -0
- package/packages/firestore/src/i18n.ts +262 -0
- package/packages/firestore/src/index.ts +54 -0
- package/packages/firestore/src/processor.ts +245 -0
- package/packages/firestore/src/queue.ts +202 -0
- package/packages/firestore/src/task-handler.ts +164 -0
- package/packages/firestore/tsconfig.json +19 -0
- package/pnpm-workspace.yaml +2 -0
- package/turbo.json +31 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Melaka Core - Dynamic Schema Generation
|
|
3
|
+
*
|
|
4
|
+
* Generates Zod schemas dynamically based on document content.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import type { SchemaType, FieldMapping, SeparatedContent } from './types';
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Schema Generation
|
|
12
|
+
// ============================================================================
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create a Zod schema for a single field based on its schema type.
|
|
16
|
+
*
|
|
17
|
+
* @param schemaType - The detected or configured schema type
|
|
18
|
+
* @param required - Whether the field is required
|
|
19
|
+
* @returns Zod schema for the field
|
|
20
|
+
*/
|
|
21
|
+
export function createFieldSchema(
|
|
22
|
+
schemaType: SchemaType,
|
|
23
|
+
required = true
|
|
24
|
+
): z.ZodTypeAny {
|
|
25
|
+
let schema: z.ZodTypeAny;
|
|
26
|
+
|
|
27
|
+
switch (schemaType) {
|
|
28
|
+
case 'string':
|
|
29
|
+
schema = z.string();
|
|
30
|
+
break;
|
|
31
|
+
case 'string[]':
|
|
32
|
+
schema = z.array(z.string());
|
|
33
|
+
break;
|
|
34
|
+
case 'number':
|
|
35
|
+
schema = z.number();
|
|
36
|
+
break;
|
|
37
|
+
case 'number[]':
|
|
38
|
+
schema = z.array(z.number());
|
|
39
|
+
break;
|
|
40
|
+
case 'boolean':
|
|
41
|
+
schema = z.boolean();
|
|
42
|
+
break;
|
|
43
|
+
case 'object':
|
|
44
|
+
schema = z.record(z.unknown());
|
|
45
|
+
break;
|
|
46
|
+
case 'object[]':
|
|
47
|
+
schema = z.array(z.record(z.unknown()));
|
|
48
|
+
break;
|
|
49
|
+
case 'object|null':
|
|
50
|
+
schema = z.record(z.unknown()).nullable();
|
|
51
|
+
break;
|
|
52
|
+
case 'DocumentReference':
|
|
53
|
+
// DocumentReferences are validated as objects with specific shape
|
|
54
|
+
schema = z.object({
|
|
55
|
+
path: z.string(),
|
|
56
|
+
id: z.string(),
|
|
57
|
+
}).passthrough();
|
|
58
|
+
break;
|
|
59
|
+
case 'DocumentReference[]':
|
|
60
|
+
schema = z.array(
|
|
61
|
+
z.object({
|
|
62
|
+
path: z.string(),
|
|
63
|
+
id: z.string(),
|
|
64
|
+
}).passthrough()
|
|
65
|
+
);
|
|
66
|
+
break;
|
|
67
|
+
default:
|
|
68
|
+
schema = z.unknown();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return required ? schema : schema.optional();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Create a Zod schema for translatable content.
|
|
76
|
+
*
|
|
77
|
+
* This schema is used to validate AI translation output.
|
|
78
|
+
*
|
|
79
|
+
* @param separatedContent - Content that has been separated into translatable/non-translatable
|
|
80
|
+
* @returns Zod schema matching the translatable content structure
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* const { translatable, detectedTypes } = separateContent(doc);
|
|
85
|
+
* const schema = createTranslationSchema({ translatable, detectedTypes });
|
|
86
|
+
*
|
|
87
|
+
* // Use with AI translation
|
|
88
|
+
* const result = await ai.generate({
|
|
89
|
+
* output: { schema },
|
|
90
|
+
* // ...
|
|
91
|
+
* });
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export function createTranslationSchema(
|
|
95
|
+
separatedContent: Pick<SeparatedContent, 'translatable' | 'detectedTypes'>
|
|
96
|
+
): z.ZodObject<Record<string, z.ZodTypeAny>> {
|
|
97
|
+
const { translatable, detectedTypes } = separatedContent;
|
|
98
|
+
const schemaFields: Record<string, z.ZodTypeAny> = {};
|
|
99
|
+
|
|
100
|
+
for (const field of Object.keys(translatable)) {
|
|
101
|
+
const schemaType = detectedTypes[field] || 'string';
|
|
102
|
+
schemaFields[field] = createFieldSchema(schemaType, true);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return z.object(schemaFields);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Create a Zod schema from explicit field mappings.
|
|
110
|
+
*
|
|
111
|
+
* Used when the user provides `fieldMappings` in their config
|
|
112
|
+
* instead of relying on auto-detection.
|
|
113
|
+
*
|
|
114
|
+
* @param fieldMappings - Array of field mapping configurations
|
|
115
|
+
* @returns Zod schema matching the field mappings
|
|
116
|
+
*/
|
|
117
|
+
export function createSchemaFromMappings(
|
|
118
|
+
fieldMappings: FieldMapping[]
|
|
119
|
+
): z.ZodObject<Record<string, z.ZodTypeAny>> {
|
|
120
|
+
const schemaFields: Record<string, z.ZodTypeAny> = {};
|
|
121
|
+
|
|
122
|
+
for (const mapping of fieldMappings) {
|
|
123
|
+
const targetField = mapping.targetField || mapping.sourceField;
|
|
124
|
+
const schemaType = mapping.schemaType || 'string';
|
|
125
|
+
const required = mapping.required ?? true;
|
|
126
|
+
|
|
127
|
+
schemaFields[targetField] = createFieldSchema(schemaType, required);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return z.object(schemaFields);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Create a schema that only includes translatable fields from field mappings.
|
|
135
|
+
*
|
|
136
|
+
* Filters out non-translatable types (numbers, booleans, refs, etc.)
|
|
137
|
+
*
|
|
138
|
+
* @param fieldMappings - Array of field mapping configurations
|
|
139
|
+
* @returns Zod schema for translatable fields only
|
|
140
|
+
*/
|
|
141
|
+
export function createTranslatableSchemaFromMappings(
|
|
142
|
+
fieldMappings: FieldMapping[]
|
|
143
|
+
): z.ZodObject<Record<string, z.ZodTypeAny>> {
|
|
144
|
+
const translatableTypes: SchemaType[] = ['string', 'string[]'];
|
|
145
|
+
const schemaFields: Record<string, z.ZodTypeAny> = {};
|
|
146
|
+
|
|
147
|
+
for (const mapping of fieldMappings) {
|
|
148
|
+
const schemaType = mapping.schemaType || 'string';
|
|
149
|
+
|
|
150
|
+
// Skip non-translatable types
|
|
151
|
+
if (!translatableTypes.includes(schemaType)) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const targetField = mapping.targetField || mapping.sourceField;
|
|
156
|
+
const required = mapping.required ?? true;
|
|
157
|
+
|
|
158
|
+
schemaFields[targetField] = createFieldSchema(schemaType, required);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return z.object(schemaFields);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ============================================================================
|
|
165
|
+
// Schema Utilities
|
|
166
|
+
// ============================================================================
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Validate data against a schema and return typed result.
|
|
170
|
+
*
|
|
171
|
+
* @param schema - Zod schema to validate against
|
|
172
|
+
* @param data - Data to validate
|
|
173
|
+
* @returns Validated and typed data
|
|
174
|
+
* @throws Error if validation fails
|
|
175
|
+
*/
|
|
176
|
+
export function validateWithSchema<T>(
|
|
177
|
+
schema: z.ZodSchema<T>,
|
|
178
|
+
data: unknown
|
|
179
|
+
): T {
|
|
180
|
+
const result = schema.safeParse(data);
|
|
181
|
+
if (!result.success) {
|
|
182
|
+
const errors = result.error.errors
|
|
183
|
+
.map((e) => ` - ${e.path.join('.')}: ${e.message}`)
|
|
184
|
+
.join('\n');
|
|
185
|
+
throw new Error(`Schema validation failed:\n${errors}`);
|
|
186
|
+
}
|
|
187
|
+
return result.data;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Safely validate data against a schema.
|
|
192
|
+
*
|
|
193
|
+
* @param schema - Zod schema to validate against
|
|
194
|
+
* @param data - Data to validate
|
|
195
|
+
* @returns Result object with success flag and data or error
|
|
196
|
+
*/
|
|
197
|
+
export function safeValidateWithSchema<T>(
|
|
198
|
+
schema: z.ZodSchema<T>,
|
|
199
|
+
data: unknown
|
|
200
|
+
): { success: true; data: T } | { success: false; error: z.ZodError } {
|
|
201
|
+
const result = schema.safeParse(data);
|
|
202
|
+
if (result.success) {
|
|
203
|
+
return { success: true, data: result.data };
|
|
204
|
+
}
|
|
205
|
+
return { success: false, error: result.error };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get a JSON-serializable representation of a Zod schema.
|
|
210
|
+
*
|
|
211
|
+
* Useful for sending schema information to AI providers.
|
|
212
|
+
*
|
|
213
|
+
* @param schema - Zod schema to convert
|
|
214
|
+
* @returns JSON Schema representation
|
|
215
|
+
*/
|
|
216
|
+
export function schemaToJsonSchema(schema: z.ZodTypeAny): unknown {
|
|
217
|
+
// Use zod-to-json-schema if available, otherwise return basic info
|
|
218
|
+
// For now, return a simplified representation
|
|
219
|
+
const shape = (schema as z.ZodObject<Record<string, z.ZodTypeAny>>).shape;
|
|
220
|
+
|
|
221
|
+
if (!shape) {
|
|
222
|
+
return { type: 'unknown' };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const properties: Record<string, { type: string }> = {};
|
|
226
|
+
for (const [key, fieldSchema] of Object.entries(shape)) {
|
|
227
|
+
properties[key] = {
|
|
228
|
+
type: getJsonSchemaType(fieldSchema),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
type: 'object',
|
|
234
|
+
properties,
|
|
235
|
+
required: Object.keys(properties),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Get JSON Schema type string for a Zod schema.
|
|
241
|
+
*/
|
|
242
|
+
function getJsonSchemaType(schema: z.ZodTypeAny): string {
|
|
243
|
+
const typeName = schema._def.typeName;
|
|
244
|
+
|
|
245
|
+
switch (typeName) {
|
|
246
|
+
case 'ZodString':
|
|
247
|
+
return 'string';
|
|
248
|
+
case 'ZodNumber':
|
|
249
|
+
return 'number';
|
|
250
|
+
case 'ZodBoolean':
|
|
251
|
+
return 'boolean';
|
|
252
|
+
case 'ZodArray':
|
|
253
|
+
return 'array';
|
|
254
|
+
case 'ZodObject':
|
|
255
|
+
return 'object';
|
|
256
|
+
case 'ZodOptional':
|
|
257
|
+
return getJsonSchemaType((schema._def as { innerType: z.ZodTypeAny }).innerType);
|
|
258
|
+
case 'ZodNullable':
|
|
259
|
+
return getJsonSchemaType((schema._def as { innerType: z.ZodTypeAny }).innerType);
|
|
260
|
+
default:
|
|
261
|
+
return 'unknown';
|
|
262
|
+
}
|
|
263
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Melaka Core - Zod Schemas
|
|
3
|
+
*
|
|
4
|
+
* Validation schemas for configuration and runtime data.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// AI Configuration Schema
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
export const AIProviderSchema = z.enum(['gemini', 'openai', 'claude']);
|
|
14
|
+
|
|
15
|
+
export const AIConfigSchema = z.object({
|
|
16
|
+
provider: AIProviderSchema,
|
|
17
|
+
model: z.string().min(1, 'Model name is required'),
|
|
18
|
+
temperature: z.number().min(0).max(1).optional().default(0.3),
|
|
19
|
+
apiKeySecret: z.string().optional(),
|
|
20
|
+
apiKey: z.string().optional(),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Field Mapping Schema
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
export const SchemaTypeSchema = z.enum([
|
|
28
|
+
'string',
|
|
29
|
+
'string[]',
|
|
30
|
+
'number',
|
|
31
|
+
'number[]',
|
|
32
|
+
'boolean',
|
|
33
|
+
'object',
|
|
34
|
+
'object[]',
|
|
35
|
+
'object|null',
|
|
36
|
+
'DocumentReference',
|
|
37
|
+
'DocumentReference[]',
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
export const FieldMappingSchema = z.object({
|
|
41
|
+
sourceField: z.string().min(1),
|
|
42
|
+
targetField: z.string().optional(),
|
|
43
|
+
schemaType: SchemaTypeSchema.optional(),
|
|
44
|
+
required: z.boolean().optional(),
|
|
45
|
+
description: z.string().optional(),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// ============================================================================
|
|
49
|
+
// Collection Configuration Schema
|
|
50
|
+
// ============================================================================
|
|
51
|
+
|
|
52
|
+
export const CollectionConfigSchema = z.object({
|
|
53
|
+
path: z.string().min(1, 'Collection path is required'),
|
|
54
|
+
isCollectionGroup: z.boolean().optional().default(false),
|
|
55
|
+
fields: z.array(z.string()).optional(),
|
|
56
|
+
fieldMappings: z.array(FieldMappingSchema).optional(),
|
|
57
|
+
prompt: z.string().optional(),
|
|
58
|
+
glossary: z.record(z.string(), z.string()).optional(),
|
|
59
|
+
ai: AIConfigSchema.partial().optional(),
|
|
60
|
+
batchSize: z.number().positive().optional(),
|
|
61
|
+
maxConcurrency: z.number().positive().optional(),
|
|
62
|
+
forceUpdate: z.boolean().optional(),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// ============================================================================
|
|
66
|
+
// Defaults Configuration Schema
|
|
67
|
+
// ============================================================================
|
|
68
|
+
|
|
69
|
+
export const DefaultsConfigSchema = z.object({
|
|
70
|
+
batchSize: z.number().positive().optional().default(20),
|
|
71
|
+
maxConcurrency: z.number().positive().optional().default(10),
|
|
72
|
+
forceUpdate: z.boolean().optional().default(false),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// ============================================================================
|
|
76
|
+
// Root Configuration Schema
|
|
77
|
+
// ============================================================================
|
|
78
|
+
|
|
79
|
+
export const MelakaConfigSchema = z.object({
|
|
80
|
+
languages: z
|
|
81
|
+
.array(z.string().regex(/^[a-z]{2,3}(-[A-Z]{2,3})?$/, 'Invalid language code (use BCP 47 format, e.g., ms-MY)'))
|
|
82
|
+
.min(1, 'At least one language is required'),
|
|
83
|
+
ai: AIConfigSchema,
|
|
84
|
+
region: z.string().optional().default('us-central1'),
|
|
85
|
+
defaults: DefaultsConfigSchema.optional(),
|
|
86
|
+
glossary: z.record(z.string(), z.string()).optional(),
|
|
87
|
+
collections: z.array(CollectionConfigSchema).min(1, 'At least one collection is required'),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// ============================================================================
|
|
91
|
+
// Translation Task Payload Schema
|
|
92
|
+
// ============================================================================
|
|
93
|
+
|
|
94
|
+
export const TranslationTaskPayloadSchema = z.object({
|
|
95
|
+
collectionPath: z.string().min(1),
|
|
96
|
+
documentId: z.string().min(1),
|
|
97
|
+
targetLanguage: z.string().min(1),
|
|
98
|
+
config: CollectionConfigSchema,
|
|
99
|
+
batchId: z.string().min(1),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// ============================================================================
|
|
103
|
+
// Melaka Metadata Schema
|
|
104
|
+
// ============================================================================
|
|
105
|
+
|
|
106
|
+
export const TranslationStatusSchema = z.enum(['completed', 'failed', 'pending']);
|
|
107
|
+
|
|
108
|
+
export const MelakaMetadataSchema = z.object({
|
|
109
|
+
source_hash: z.string().min(1),
|
|
110
|
+
translated_at: z.any(), // Firestore Timestamp
|
|
111
|
+
model: z.string().min(1),
|
|
112
|
+
status: TranslationStatusSchema,
|
|
113
|
+
reviewed: z.boolean(),
|
|
114
|
+
error: z.string().optional(),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// ============================================================================
|
|
118
|
+
// Type Inference
|
|
119
|
+
// ============================================================================
|
|
120
|
+
|
|
121
|
+
export type AIConfigInput = z.input<typeof AIConfigSchema>;
|
|
122
|
+
export type AIConfigOutput = z.output<typeof AIConfigSchema>;
|
|
123
|
+
export type CollectionConfigInput = z.input<typeof CollectionConfigSchema>;
|
|
124
|
+
export type CollectionConfigOutput = z.output<typeof CollectionConfigSchema>;
|
|
125
|
+
export type MelakaConfigInput = z.input<typeof MelakaConfigSchema>;
|
|
126
|
+
export type MelakaConfigOutput = z.output<typeof MelakaConfigSchema>;
|