@promakeai/orm 1.0.3 → 1.0.5

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/README.md CHANGED
@@ -13,9 +13,9 @@ npm install @promakeai/orm
13
13
  ```typescript
14
14
  import { defineSchema, f } from '@promakeai/orm';
15
15
 
16
- const schema = defineSchema({
17
- name: 'myapp',
18
- languages: ['en', 'tr', 'de'],
16
+ const schema = defineSchema({
17
+ name: 'myapp',
18
+ languages: ['en', 'tr', 'de'],
19
19
  tables: {
20
20
  users: {
21
21
  id: f.id(),
@@ -47,21 +47,45 @@ const schema = defineSchema({
47
47
  }),
48
48
  },
49
49
  },
50
- });
51
- ```
52
-
53
- ## Field Types
54
-
55
- | Type | SQL | Description |
56
- |------|-----|-------------|
57
- | `f.id()` | INTEGER PRIMARY KEY AUTOINCREMENT | Auto-increment primary key |
58
- | `f.string()` | VARCHAR | Short text |
59
- | `f.text()` | TEXT | Long text |
60
- | `f.int()` | INTEGER | Integer |
61
- | `f.decimal()` | REAL/DECIMAL | Decimal number |
62
- | `f.bool()` | INTEGER (0/1) | Boolean |
63
- | `f.timestamp()` | TEXT (ISO) | ISO datetime string |
64
- | `f.json()` | TEXT | JSON serialized data |
50
+ });
51
+ ```
52
+
53
+ ## JSON Schema (AI-Friendly)
54
+
55
+ The JSON schema format is the runtime-friendly version and supports native
56
+ arrays and typed objects:
57
+
58
+ ```json
59
+ {
60
+ "name": "myapp",
61
+ "languages": ["en", "tr"],
62
+ "defaultLanguage": "en",
63
+ "tables": {
64
+ "products": {
65
+ "id": { "type": "id" },
66
+ "tags": { "type": ["string"] },
67
+ "metadata": { "type": { "color": "string", "weight": "number" } },
68
+ "variants": { "type": [{ "sku": "string", "price": "number" }] }
69
+ }
70
+ }
71
+ }
72
+ ```
73
+
74
+ ## Field Types
75
+
76
+ | Type | SQL | Description |
77
+ |------|-----|-------------|
78
+ | `f.id()` | INTEGER PRIMARY KEY AUTOINCREMENT | Auto-increment primary key |
79
+ | `f.string()` | VARCHAR | Short text |
80
+ | `f.text()` | TEXT | Long text |
81
+ | `f.int()` | INTEGER | Integer |
82
+ | `f.decimal()` | REAL/DECIMAL | Decimal number |
83
+ | `f.bool()` | INTEGER (0/1) | Boolean |
84
+ | `f.timestamp()` | TEXT (ISO) | ISO datetime string |
85
+ | `f.json()` | TEXT | JSON serialized data |
86
+
87
+ JSON schema type syntax supports `string[]`, `number[]`, `boolean[]`, `object`,
88
+ and `object[]` which are stored as JSON in SQL (TEXT) and typed in TS.
65
89
 
66
90
  ## Field Modifiers
67
91
 
@@ -148,10 +172,14 @@ buildWhereClause({ id: { $nin: [1, 2, 3] } }); // NOT IN (?, ?, ?)
148
172
  buildWhereClause({ name: { $like: '%john%' } }); // LIKE ?
149
173
  buildWhereClause({ name: { $notLike: '%test%' } }); // NOT LIKE ?
150
174
 
151
- // Range and null
152
- buildWhereClause({ price: { $between: [10, 100] } }); // BETWEEN ? AND ?
153
- buildWhereClause({ deletedAt: { $isNull: true } }); // IS NULL
154
- buildWhereClause({ email: { $isNull: false } }); // IS NOT NULL
175
+ // Range and null
176
+ buildWhereClause({ price: { $between: [10, 100] } }); // BETWEEN ? AND ?
177
+ buildWhereClause({ deletedAt: { $isNull: true } }); // IS NULL
178
+ buildWhereClause({ email: { $isNull: false } }); // IS NOT NULL
179
+
180
+ // JSON array contains
181
+ buildWhereClause({ tags: { $contains: "sale" } }); // json_each(...) = "sale"
182
+ buildWhereClause({ tags: { $containsAny: ["sale", "new"] } }); // json_each(...) IN (...)
155
183
 
156
184
  // Logical operators
157
185
  buildWhereClause({
@@ -262,9 +290,12 @@ const { sql, params } = buildTranslationQuery('products', schema, {
262
290
  // LIMIT 10
263
291
  ```
264
292
 
265
- ## Populate Resolver
266
-
267
- Batch-fetches references to prevent N+1 queries:
293
+ ## Populate Resolver
294
+
295
+ Batch-fetches references to prevent N+1 queries:
296
+
297
+ Populate options accept a space-separated string, string array, or nested object
298
+ for deeper population.
268
299
 
269
300
  ```typescript
270
301
  import { resolvePopulate } from '@promakeai/orm';
package/dist/index.js CHANGED
@@ -852,6 +852,7 @@ function buildTranslationQuery(options) {
852
852
  schema,
853
853
  lang,
854
854
  fallbackLang = schema.languages.default,
855
+ mainFallbackFields,
855
856
  where,
856
857
  orderBy,
857
858
  limit,
@@ -870,10 +871,15 @@ function buildTranslationQuery(options) {
870
871
  const mainAlias = "m";
871
872
  const transAlias = "t";
872
873
  const fallbackAlias = "fb";
874
+ const mainFallbackSet = mainFallbackFields !== undefined ? new Set(mainFallbackFields) : null;
873
875
  const selectFields = [];
874
876
  for (const [fieldName, field] of Object.entries(tableSchema.fields)) {
875
877
  if (field.translatable) {
876
- selectFields.push(`COALESCE(${transAlias}.${fieldName}, ${fallbackAlias}.${fieldName}) as ${fieldName}`);
878
+ if (mainFallbackSet && !mainFallbackSet.has(fieldName)) {
879
+ selectFields.push(`COALESCE(${transAlias}.${fieldName}, ${fallbackAlias}.${fieldName}) as ${fieldName}`);
880
+ } else {
881
+ selectFields.push(`COALESCE(${transAlias}.${fieldName}, ${fallbackAlias}.${fieldName}, ${mainAlias}.${fieldName}) as ${fieldName}`);
882
+ }
877
883
  } else {
878
884
  selectFields.push(`${mainAlias}.${fieldName}`);
879
885
  }
@@ -915,12 +921,13 @@ OFFSET ?`;
915
921
  }
916
922
  return { sql, params };
917
923
  }
918
- function buildTranslationQueryById(table, schema, id, lang, fallbackLang) {
924
+ function buildTranslationQueryById(table, schema, id, lang, fallbackLang, mainFallbackFields) {
919
925
  return buildTranslationQuery({
920
926
  table,
921
927
  schema,
922
928
  lang,
923
929
  fallbackLang,
930
+ mainFallbackFields,
924
931
  where: { id },
925
932
  limit: 1
926
933
  });
@@ -13,6 +13,11 @@ export interface TranslationQueryOptions {
13
13
  schema: SchemaDefinition;
14
14
  lang: string;
15
15
  fallbackLang?: string;
16
+ /**
17
+ * Main-table fields that can be safely used as cache fallback in COALESCE.
18
+ * If omitted, all translatable fields are assumed available in main table.
19
+ */
20
+ mainFallbackFields?: string[];
16
21
  where?: Record<string, unknown>;
17
22
  orderBy?: OrderByOption[];
18
23
  limit?: number;
@@ -32,7 +37,7 @@ export declare function buildTranslationQuery(options: TranslationQueryOptions):
32
37
  /**
33
38
  * Build query for single record by ID with translations
34
39
  */
35
- export declare function buildTranslationQueryById(table: string, schema: SchemaDefinition, id: number | string, lang: string, fallbackLang?: string): TranslationQueryResult;
40
+ export declare function buildTranslationQueryById(table: string, schema: SchemaDefinition, id: number | string, lang: string, fallbackLang?: string, mainFallbackFields?: string[]): TranslationQueryResult;
36
41
  /**
37
42
  * Build INSERT statement for translation
38
43
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promakeai/orm",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Database-agnostic ORM core - works in browser and Node.js",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -44,16 +44,21 @@ function buildAliasedWhereClause(
44
44
  /**
45
45
  * Options for translation queries
46
46
  */
47
- export interface TranslationQueryOptions {
48
- table: string;
49
- schema: SchemaDefinition;
50
- lang: string;
51
- fallbackLang?: string;
52
- where?: Record<string, unknown>;
53
- orderBy?: OrderByOption[];
54
- limit?: number;
55
- offset?: number;
56
- }
47
+ export interface TranslationQueryOptions {
48
+ table: string;
49
+ schema: SchemaDefinition;
50
+ lang: string;
51
+ fallbackLang?: string;
52
+ /**
53
+ * Main-table fields that can be safely used as cache fallback in COALESCE.
54
+ * If omitted, all translatable fields are assumed available in main table.
55
+ */
56
+ mainFallbackFields?: string[];
57
+ where?: Record<string, unknown>;
58
+ orderBy?: OrderByOption[];
59
+ limit?: number;
60
+ offset?: number;
61
+ }
57
62
 
58
63
  /**
59
64
  * Query result with SQL and parameters
@@ -73,12 +78,13 @@ export function buildTranslationQuery(
73
78
  table,
74
79
  schema,
75
80
  lang,
76
- fallbackLang = schema.languages.default,
77
- where,
78
- orderBy,
79
- limit,
80
- offset,
81
- } = options;
81
+ fallbackLang = schema.languages.default,
82
+ mainFallbackFields,
83
+ where,
84
+ orderBy,
85
+ limit,
86
+ offset,
87
+ } = options;
82
88
 
83
89
  const tableSchema = schema.tables[table];
84
90
  if (!tableSchema) {
@@ -94,22 +100,32 @@ export function buildTranslationQuery(
94
100
  const transTable = toTranslationTableName(table);
95
101
  const fkName = toTranslationFKName(table);
96
102
 
97
- const mainAlias = "m";
98
- const transAlias = "t";
99
- const fallbackAlias = "fb";
103
+ const mainAlias = "m";
104
+ const transAlias = "t";
105
+ const fallbackAlias = "fb";
106
+ const mainFallbackSet =
107
+ mainFallbackFields !== undefined
108
+ ? new Set(mainFallbackFields)
109
+ : null;
100
110
 
101
111
  // Build SELECT fields
102
112
  const selectFields: string[] = [];
103
113
 
104
- for (const [fieldName, field] of Object.entries(tableSchema.fields)) {
105
- if (field.translatable) {
106
- selectFields.push(
107
- `COALESCE(${transAlias}.${fieldName}, ${fallbackAlias}.${fieldName}) as ${fieldName}`
108
- );
109
- } else {
110
- selectFields.push(`${mainAlias}.${fieldName}`);
111
- }
112
- }
114
+ for (const [fieldName, field] of Object.entries(tableSchema.fields)) {
115
+ if (field.translatable) {
116
+ if (mainFallbackSet && !mainFallbackSet.has(fieldName)) {
117
+ selectFields.push(
118
+ `COALESCE(${transAlias}.${fieldName}, ${fallbackAlias}.${fieldName}) as ${fieldName}`
119
+ );
120
+ } else {
121
+ selectFields.push(
122
+ `COALESCE(${transAlias}.${fieldName}, ${fallbackAlias}.${fieldName}, ${mainAlias}.${fieldName}) as ${fieldName}`
123
+ );
124
+ }
125
+ } else {
126
+ selectFields.push(`${mainAlias}.${fieldName}`);
127
+ }
128
+ }
113
129
 
114
130
  let sql = `SELECT ${selectFields.join(", ")}
115
131
  FROM ${table} ${mainAlias}
@@ -154,22 +170,24 @@ LEFT JOIN ${transTable} ${fallbackAlias}
154
170
  /**
155
171
  * Build query for single record by ID with translations
156
172
  */
157
- export function buildTranslationQueryById(
158
- table: string,
159
- schema: SchemaDefinition,
160
- id: number | string,
161
- lang: string,
162
- fallbackLang?: string
163
- ): TranslationQueryResult {
164
- return buildTranslationQuery({
165
- table,
166
- schema,
167
- lang,
168
- fallbackLang,
169
- where: { id },
170
- limit: 1,
171
- });
172
- }
173
+ export function buildTranslationQueryById(
174
+ table: string,
175
+ schema: SchemaDefinition,
176
+ id: number | string,
177
+ lang: string,
178
+ fallbackLang?: string,
179
+ mainFallbackFields?: string[]
180
+ ): TranslationQueryResult {
181
+ return buildTranslationQuery({
182
+ table,
183
+ schema,
184
+ lang,
185
+ fallbackLang,
186
+ mainFallbackFields,
187
+ where: { id },
188
+ limit: 1,
189
+ });
190
+ }
173
191
 
174
192
  /**
175
193
  * Build simple query without translations