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.
Files changed (49) hide show
  1. package/CONTRIBUTING.md +347 -0
  2. package/LICENSE +21 -0
  3. package/README.md +57 -0
  4. package/docs/AI_PROVIDERS.md +343 -0
  5. package/docs/ARCHITECTURE.md +512 -0
  6. package/docs/CLI.md +438 -0
  7. package/docs/CONFIGURATION.md +453 -0
  8. package/docs/INTEGRATION.md +477 -0
  9. package/docs/ROADMAP.md +248 -0
  10. package/package.json +46 -0
  11. package/packages/ai/README.md +43 -0
  12. package/packages/ai/package.json +42 -0
  13. package/packages/ai/src/facade.ts +120 -0
  14. package/packages/ai/src/index.ts +34 -0
  15. package/packages/ai/src/prompt.ts +117 -0
  16. package/packages/ai/src/providers/gemini.ts +185 -0
  17. package/packages/ai/src/providers/index.ts +9 -0
  18. package/packages/ai/src/types.ts +134 -0
  19. package/packages/ai/tsconfig.json +19 -0
  20. package/packages/cli/README.md +70 -0
  21. package/packages/cli/package.json +44 -0
  22. package/packages/cli/src/cli.ts +30 -0
  23. package/packages/cli/src/commands/deploy.ts +115 -0
  24. package/packages/cli/src/commands/index.ts +9 -0
  25. package/packages/cli/src/commands/init.ts +107 -0
  26. package/packages/cli/src/commands/status.ts +73 -0
  27. package/packages/cli/src/commands/translate.ts +92 -0
  28. package/packages/cli/src/commands/validate.ts +69 -0
  29. package/packages/cli/tsconfig.json +19 -0
  30. package/packages/core/README.md +46 -0
  31. package/packages/core/package.json +50 -0
  32. package/packages/core/src/config.ts +241 -0
  33. package/packages/core/src/index.ts +111 -0
  34. package/packages/core/src/schema-generator.ts +263 -0
  35. package/packages/core/src/schemas.ts +126 -0
  36. package/packages/core/src/types.ts +481 -0
  37. package/packages/core/src/utils.ts +343 -0
  38. package/packages/core/tsconfig.json +19 -0
  39. package/packages/firestore/README.md +60 -0
  40. package/packages/firestore/package.json +48 -0
  41. package/packages/firestore/src/generator.ts +270 -0
  42. package/packages/firestore/src/i18n.ts +262 -0
  43. package/packages/firestore/src/index.ts +54 -0
  44. package/packages/firestore/src/processor.ts +245 -0
  45. package/packages/firestore/src/queue.ts +202 -0
  46. package/packages/firestore/src/task-handler.ts +164 -0
  47. package/packages/firestore/tsconfig.json +19 -0
  48. package/pnpm-workspace.yaml +2 -0
  49. 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>;