@promakeai/orm 1.3.1 → 1.4.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.
@@ -18,6 +18,8 @@ export interface RestAdapterConfig {
18
18
  getToken?: () => string | null;
19
19
  /** Custom headers to include in all requests */
20
20
  headers?: Record<string, string>;
21
+ /** Dynamic headers getter - called on every request. Merged after static headers. */
22
+ getHeaders?: () => Record<string, string>;
21
23
  /** Schema definition for translation support */
22
24
  schema?: SchemaDefinition;
23
25
  /** Default language for translations */
@@ -33,6 +35,7 @@ export declare class RestAdapter implements IDataAdapter {
33
35
  private token?;
34
36
  private getTokenFn?;
35
37
  private customHeaders;
38
+ private getHeadersFn?;
36
39
  private fetchFn;
37
40
  private timeout;
38
41
  schema?: SchemaDefinition;
@@ -43,6 +46,8 @@ export declare class RestAdapter implements IDataAdapter {
43
46
  private request;
44
47
  private buildQueryParams;
45
48
  private urlWithParams;
49
+ private deserializeRecord;
50
+ private deserializeRecords;
46
51
  setSchema(schema: SchemaDefinition): void;
47
52
  connect(): Promise<void>;
48
53
  private mapFieldType;
package/dist/index.js CHANGED
@@ -293,6 +293,52 @@ class ORM {
293
293
  this.schema = schema;
294
294
  }
295
295
  }
296
+ // src/utils/deserializer.ts
297
+ function deserializeValue(value, fieldType) {
298
+ if (value === null || value === undefined)
299
+ return value;
300
+ if (fieldType === "bool") {
301
+ return value === 1 || value === true;
302
+ }
303
+ if (isJsonType(fieldType) && typeof value === "string") {
304
+ try {
305
+ return JSON.parse(value);
306
+ } catch {
307
+ return value;
308
+ }
309
+ }
310
+ return value;
311
+ }
312
+ function deserializeRow(row, fields) {
313
+ const result = { ...row };
314
+ for (const [fieldName, field] of Object.entries(fields)) {
315
+ if (fieldName in result) {
316
+ result[fieldName] = deserializeValue(result[fieldName], field.type);
317
+ }
318
+ }
319
+ return result;
320
+ }
321
+ function serializeValue(value, fieldType) {
322
+ if (value === null || value === undefined)
323
+ return value;
324
+ if (fieldType === "bool") {
325
+ return value ? 1 : 0;
326
+ }
327
+ if (isJsonType(fieldType) && typeof value !== "string") {
328
+ return JSON.stringify(value);
329
+ }
330
+ return value;
331
+ }
332
+ function serializeRow(data, fields) {
333
+ const result = { ...data };
334
+ for (const [fieldName, field] of Object.entries(fields)) {
335
+ if (fieldName in result) {
336
+ result[fieldName] = serializeValue(result[fieldName], field.type);
337
+ }
338
+ }
339
+ return result;
340
+ }
341
+
296
342
  // src/schema/schemaHelpers.ts
297
343
  function getTranslatableFields(table) {
298
344
  return Object.entries(table.fields).filter(([_, field]) => field.translatable).map(([name]) => name);
@@ -575,6 +621,7 @@ class RestAdapter {
575
621
  token;
576
622
  getTokenFn;
577
623
  customHeaders;
624
+ getHeadersFn;
578
625
  fetchFn;
579
626
  timeout;
580
627
  schema;
@@ -585,6 +632,7 @@ class RestAdapter {
585
632
  this.token = config.token;
586
633
  this.getTokenFn = config.getToken;
587
634
  this.customHeaders = config.headers ?? {};
635
+ this.getHeadersFn = config.getHeaders;
588
636
  const gf = globalThis;
589
637
  this.fetchFn = config.fetch ?? gf.fetch.bind(gf);
590
638
  this.timeout = config.timeout ?? 30000;
@@ -600,6 +648,9 @@ class RestAdapter {
600
648
  Accept: "application/json",
601
649
  ...this.customHeaders
602
650
  };
651
+ if (this.getHeadersFn) {
652
+ Object.assign(headers, this.getHeadersFn());
653
+ }
603
654
  const token = this.getTokenFn ? this.getTokenFn() : this.token;
604
655
  if (token) {
605
656
  headers["Authorization"] = `Bearer ${token}`;
@@ -653,6 +704,24 @@ class RestAdapter {
653
704
  const qs = params.toString();
654
705
  return qs ? `${base}?${qs}` : base;
655
706
  }
707
+ deserializeRecord(table, record) {
708
+ if (record == null)
709
+ return null;
710
+ const tableSchema = this.schema?.tables[table];
711
+ if (!tableSchema) {
712
+ return record;
713
+ }
714
+ return deserializeRow(record, tableSchema.fields);
715
+ }
716
+ deserializeRecords(table, records) {
717
+ if (!records?.length)
718
+ return records ?? [];
719
+ const tableSchema = this.schema?.tables[table];
720
+ if (!tableSchema) {
721
+ return records;
722
+ }
723
+ return records.map((record) => deserializeRow(record, tableSchema.fields));
724
+ }
656
725
  setSchema(schema) {
657
726
  this.schema = schema;
658
727
  }
@@ -719,7 +788,7 @@ class RestAdapter {
719
788
  const params = this.buildQueryParams(options);
720
789
  const url = this.urlWithParams(this.buildUrl(`/${table}/list`), params);
721
790
  const res = await this.request(url);
722
- return res.data;
791
+ return this.deserializeRecords(table, res.data);
723
792
  }
724
793
  async get(table, id, options) {
725
794
  const params = new URLSearchParams;
@@ -730,7 +799,7 @@ class RestAdapter {
730
799
  const url = this.urlWithParams(this.buildUrl(`/${table}/${id}`), params);
731
800
  try {
732
801
  const res = await this.request(url);
733
- return res.data ?? null;
802
+ return this.deserializeRecord(table, res.data);
734
803
  } catch (err) {
735
804
  if (err instanceof Error && err.message.includes("404")) {
736
805
  return null;
@@ -777,7 +846,7 @@ class RestAdapter {
777
846
  if (res.id !== undefined && !("id" in result)) {
778
847
  result.id = res.id;
779
848
  }
780
- return result;
849
+ return this.deserializeRecord(table, result);
781
850
  }
782
851
  async update(table, id, data, options) {
783
852
  const url = this.buildUrl(`/${table}/${id}`);
@@ -789,7 +858,7 @@ class RestAdapter {
789
858
  method: "PUT",
790
859
  body: JSON.stringify(body)
791
860
  });
792
- return res.data;
861
+ return this.deserializeRecord(table, res.data);
793
862
  }
794
863
  async delete(table, id) {
795
864
  const url = this.buildUrl(`/${table}/${id}`);
@@ -849,7 +918,7 @@ class RestAdapter {
849
918
  if (res.id !== undefined && !("id" in result)) {
850
919
  result.id = res.id;
851
920
  }
852
- return result;
921
+ return this.deserializeRecord(table, result);
853
922
  }
854
923
  async upsertTranslation(table, id, lang, data) {
855
924
  const url = this.buildUrl(`/${table}/${id}`);
@@ -865,7 +934,7 @@ class RestAdapter {
865
934
  params.set("where", JSON.stringify({ [fkName]: id }));
866
935
  const url = this.urlWithParams(this.buildUrl(`/${translationTable}/list`), params);
867
936
  const res = await this.request(url);
868
- return res.data;
937
+ return this.deserializeRecords(table, res.data);
869
938
  }
870
939
  async raw(query, params) {
871
940
  const url = this.buildUrl("/exec");
@@ -1319,51 +1388,6 @@ function convertJSONField(jsonField) {
1319
1388
  ref
1320
1389
  };
1321
1390
  }
1322
- // src/utils/deserializer.ts
1323
- function deserializeValue(value, fieldType) {
1324
- if (value === null || value === undefined)
1325
- return value;
1326
- if (fieldType === "bool") {
1327
- return value === 1 || value === true;
1328
- }
1329
- if (isJsonType(fieldType) && typeof value === "string") {
1330
- try {
1331
- return JSON.parse(value);
1332
- } catch {
1333
- return value;
1334
- }
1335
- }
1336
- return value;
1337
- }
1338
- function deserializeRow(row, fields) {
1339
- const result = { ...row };
1340
- for (const [fieldName, field] of Object.entries(fields)) {
1341
- if (fieldName in result) {
1342
- result[fieldName] = deserializeValue(result[fieldName], field.type);
1343
- }
1344
- }
1345
- return result;
1346
- }
1347
- function serializeValue(value, fieldType) {
1348
- if (value === null || value === undefined)
1349
- return value;
1350
- if (fieldType === "bool") {
1351
- return value ? 1 : 0;
1352
- }
1353
- if (isJsonType(fieldType) && typeof value !== "string") {
1354
- return JSON.stringify(value);
1355
- }
1356
- return value;
1357
- }
1358
- function serializeRow(data, fields) {
1359
- const result = { ...data };
1360
- for (const [fieldName, field] of Object.entries(fields)) {
1361
- if (fieldName in result) {
1362
- result[fieldName] = serializeValue(result[fieldName], field.type);
1363
- }
1364
- }
1365
- return result;
1366
- }
1367
1391
  export {
1368
1392
  validateTable,
1369
1393
  validateSchema,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promakeai/orm",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "description": "Database-agnostic ORM core - works in browser and Node.js",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -7,6 +7,7 @@
7
7
 
8
8
  import type { IDataAdapter } from "./IDataAdapter";
9
9
  import type { QueryOptions, PaginatedResult, SchemaDefinition } from "../types";
10
+ import { deserializeRow } from "../utils/deserializer";
10
11
  import { toTranslationTableName, toTranslationFKName } from "../schema";
11
12
 
12
13
  type FetchFn = (input: string, init?: any) => Promise<any>;
@@ -22,6 +23,8 @@ export interface RestAdapterConfig {
22
23
  getToken?: () => string | null;
23
24
  /** Custom headers to include in all requests */
24
25
  headers?: Record<string, string>;
26
+ /** Dynamic headers getter - called on every request. Merged after static headers. */
27
+ getHeaders?: () => Record<string, string>;
25
28
  /** Schema definition for translation support */
26
29
  schema?: SchemaDefinition;
27
30
  /** Default language for translations */
@@ -38,6 +41,7 @@ export class RestAdapter implements IDataAdapter {
38
41
  private token?: string;
39
42
  private getTokenFn?: () => string | null;
40
43
  private customHeaders: Record<string, string>;
44
+ private getHeadersFn?: () => Record<string, string>;
41
45
  private fetchFn: FetchFn;
42
46
  private timeout: number;
43
47
 
@@ -50,6 +54,7 @@ export class RestAdapter implements IDataAdapter {
50
54
  this.token = config.token;
51
55
  this.getTokenFn = config.getToken;
52
56
  this.customHeaders = config.headers ?? {};
57
+ this.getHeadersFn = config.getHeaders;
53
58
  const gf = globalThis as any;
54
59
  this.fetchFn = config.fetch ?? gf.fetch.bind(gf);
55
60
  this.timeout = config.timeout ?? 30000;
@@ -69,6 +74,9 @@ export class RestAdapter implements IDataAdapter {
69
74
  Accept: "application/json",
70
75
  ...this.customHeaders,
71
76
  };
77
+ if (this.getHeadersFn) {
78
+ Object.assign(headers, this.getHeadersFn());
79
+ }
72
80
  const token = this.getTokenFn ? this.getTokenFn() : this.token;
73
81
  if (token) {
74
82
  headers["Authorization"] = `Bearer ${token}`;
@@ -134,6 +142,30 @@ export class RestAdapter implements IDataAdapter {
134
142
  return qs ? `${base}?${qs}` : base;
135
143
  }
136
144
 
145
+ private deserializeRecord<T>(table: string, record: T | null | undefined): T | null {
146
+ if (record == null) return null;
147
+
148
+ const tableSchema = this.schema?.tables[table];
149
+ if (!tableSchema) {
150
+ return record;
151
+ }
152
+
153
+ return deserializeRow<T>(record as Record<string, unknown>, tableSchema.fields);
154
+ }
155
+
156
+ private deserializeRecords<T>(table: string, records: T[] | undefined): T[] {
157
+ if (!records?.length) return records ?? [];
158
+
159
+ const tableSchema = this.schema?.tables[table];
160
+ if (!tableSchema) {
161
+ return records;
162
+ }
163
+
164
+ return records.map((record) =>
165
+ deserializeRow<T>(record as Record<string, unknown>, tableSchema.fields)
166
+ );
167
+ }
168
+
137
169
  // ==================== Lifecycle ====================
138
170
 
139
171
  setSchema(schema: SchemaDefinition): void {
@@ -208,7 +240,7 @@ export class RestAdapter implements IDataAdapter {
208
240
  const params = this.buildQueryParams(options);
209
241
  const url = this.urlWithParams(this.buildUrl(`/${table}/list`), params);
210
242
  const res = await this.request<{ data: T[] }>(url);
211
- return res.data;
243
+ return this.deserializeRecords(table, res.data);
212
244
  }
213
245
 
214
246
  async get<T = unknown>(
@@ -223,7 +255,7 @@ export class RestAdapter implements IDataAdapter {
223
255
 
224
256
  try {
225
257
  const res = await this.request<{ data: T }>(url);
226
- return res.data ?? null;
258
+ return this.deserializeRecord(table, res.data);
227
259
  } catch (err) {
228
260
  if (err instanceof Error && err.message.includes("404")) {
229
261
  return null;
@@ -288,7 +320,7 @@ export class RestAdapter implements IDataAdapter {
288
320
  if (res.id !== undefined && !("id" in result)) {
289
321
  result.id = res.id;
290
322
  }
291
- return result as T;
323
+ return this.deserializeRecord(table, result as T) as T;
292
324
  }
293
325
 
294
326
  async update<T = unknown>(
@@ -306,7 +338,7 @@ export class RestAdapter implements IDataAdapter {
306
338
  method: "PUT",
307
339
  body: JSON.stringify(body),
308
340
  });
309
- return res.data;
341
+ return this.deserializeRecord(table, res.data) as T;
310
342
  }
311
343
 
312
344
  async delete(table: string, id: string | number): Promise<boolean> {
@@ -392,7 +424,7 @@ export class RestAdapter implements IDataAdapter {
392
424
  if (res.id !== undefined && !("id" in result)) {
393
425
  result.id = res.id;
394
426
  }
395
- return result as T;
427
+ return this.deserializeRecord(table, result as T) as T;
396
428
  }
397
429
 
398
430
  async upsertTranslation(
@@ -421,7 +453,7 @@ export class RestAdapter implements IDataAdapter {
421
453
  params
422
454
  );
423
455
  const res = await this.request<{ data: T[] }>(url);
424
- return res.data;
456
+ return this.deserializeRecords(table, res.data);
425
457
  }
426
458
 
427
459
  // ==================== Raw Query Methods ====================