@carbonorm/carbonnode 3.7.17 → 3.7.19

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.
@@ -0,0 +1,82 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
+ import mysql from 'mysql2/promise';
3
+ import { PostQueryBuilder } from '../api/orm/queries/PostQueryBuilder';
4
+ import { UpdateQueryBuilder } from '../api/orm/queries/UpdateQueryBuilder';
5
+ import { C6C } from '../api/C6Constants';
6
+ import { buildBinaryTestConfig } from './fixtures/c6.fixture';
7
+
8
+ let pool: mysql.Pool;
9
+
10
+ describe('BINARY column hex string persistence', () => {
11
+ beforeAll(async () => {
12
+ pool = mysql.createPool({
13
+ host: '127.0.0.1',
14
+ user: 'root',
15
+ password: 'password',
16
+ database: 'sakila',
17
+ });
18
+
19
+ await pool.query('DROP TABLE IF EXISTS binary_test');
20
+ await pool.query(`
21
+ CREATE TABLE binary_test (
22
+ id INT NOT NULL AUTO_INCREMENT,
23
+ bin_col BINARY(16) DEFAULT NULL,
24
+ PRIMARY KEY (id)
25
+ )
26
+ `);
27
+ });
28
+
29
+ afterAll(async () => {
30
+ await pool.end();
31
+ });
32
+
33
+ it('inserts and updates hex strings as Buffer in BINARY(16) columns', async () => {
34
+ const config = buildBinaryTestConfig();
35
+
36
+ // ---------- INSERT ----------
37
+ const insertBuilder = new PostQueryBuilder(
38
+ config as any,
39
+ {
40
+ [C6C.INSERT]: {
41
+ 'binary_test.bin_col': '0123456789abcdef0123456789abcdef',
42
+ },
43
+ } as any,
44
+ false
45
+ );
46
+
47
+ // NOTE: allow union type so reassignment from different builders is safe
48
+ let sql: string;
49
+ let params: any[] | Record<string, any>;
50
+
51
+ ({ sql, params } = insertBuilder.build('binary_test'));
52
+ const [insertResult]: any = await pool.query(sql, params);
53
+ const id = insertResult.insertId as number;
54
+
55
+ let [rows]: any = await pool.query(
56
+ 'SELECT HEX(bin_col) AS bin FROM binary_test WHERE id = ?',
57
+ [id]
58
+ );
59
+ expect(rows[0].bin).toBe('0123456789ABCDEF0123456789ABCDEF');
60
+
61
+ // ---------- UPDATE ----------
62
+ const updateBuilder = new UpdateQueryBuilder(
63
+ config as any,
64
+ {
65
+ [C6C.UPDATE]: {
66
+ 'binary_test.bin_col': 'ffffffffffffffffffffffffffffffff',
67
+ },
68
+ WHERE: { 'binary_test.id': [C6C.EQUAL, id] },
69
+ } as any,
70
+ false
71
+ );
72
+
73
+ ({ sql, params } = updateBuilder.build('binary_test'));
74
+ await pool.query(sql, params);
75
+
76
+ [rows] = await pool.query(
77
+ 'SELECT HEX(bin_col) AS bin FROM binary_test WHERE id = ?',
78
+ [id]
79
+ );
80
+ expect(rows[0].bin).toBe('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF');
81
+ });
82
+ });
@@ -6,7 +6,7 @@ function tableModel<T extends Record<string, any>>(name: string, columns: Record
6
6
  const COLUMNS: any = {};
7
7
  Object.entries(columns).forEach(([fq, short]) => {
8
8
  COLUMNS[fq] = short;
9
- TYPE_VALIDATION[fq] = {
9
+ TYPE_VALIDATION[short as string] = {
10
10
  MYSQL_TYPE: 'VARCHAR(255)',
11
11
  MAX_LENGTH: '255',
12
12
  AUTO_INCREMENT: false,
@@ -61,7 +61,7 @@ export function buildTestConfig() {
61
61
  } as any;
62
62
 
63
63
  // Special-case: mark binary column as BINARY to test conversion
64
- C6.TABLES.actor.TYPE_VALIDATION['actor.binarycol'].MYSQL_TYPE = 'BINARY(16)';
64
+ C6.TABLES.actor.TYPE_VALIDATION['binarycol'].MYSQL_TYPE = 'BINARY(16)';
65
65
 
66
66
  const baseConfig: iRest<any, any, any> = {
67
67
  C6,
@@ -72,3 +72,30 @@ export function buildTestConfig() {
72
72
 
73
73
  return baseConfig;
74
74
  }
75
+
76
+ export function buildBinaryTestConfig() {
77
+ const binaryCols = {
78
+ 'binary_test.id': 'id',
79
+ 'binary_test.bin_col': 'bin_col',
80
+ } as const;
81
+
82
+ const C6 = {
83
+ C6VERSION: 'test',
84
+ TABLES: {
85
+ binary_test: tableModel<'binary_test' & any>('binary_test', binaryCols as any),
86
+ },
87
+ PREFIX: '',
88
+ ORM: {} as any,
89
+ } as any;
90
+
91
+ C6.TABLES.binary_test.TYPE_VALIDATION['bin_col'].MYSQL_TYPE = 'BINARY(16)';
92
+
93
+ const baseConfig: iRest<any, any, any> = {
94
+ C6,
95
+ restModel: C6.TABLES.binary_test,
96
+ requestMethod: 'POST',
97
+ verbose: false,
98
+ } as any;
99
+
100
+ return baseConfig;
101
+ }
@@ -63,6 +63,15 @@ describe("HttpExecutor singular e2e", () => {
63
63
  expect(data.rest).toHaveLength(1);
64
64
  expect(data.rest[0].first_name).toBe("Updated");
65
65
 
66
+ // PUT using fully qualified keys
67
+ await Actor.Put({
68
+ [Actor.ACTOR_ID]: testId,
69
+ [Actor.FIRST_NAME]: "UpdatedFQ",
70
+ } as any);
71
+ data = await Actor.Get({ actor_id: testId, cacheResults: false } as any);
72
+ expect(data.rest).toHaveLength(1);
73
+ expect(data.rest[0].first_name).toBe("UpdatedFQ");
74
+
66
75
  // DELETE singular
67
76
  await Actor.Delete({ actor_id: testId } as any);
68
77
  data = await Actor.Get({ actor_id: testId, cacheResults: false } as any);
@@ -175,6 +175,44 @@ const address = {
175
175
  },],
176
176
  }
177
177
  };
178
+ const binary_test = {
179
+ TABLE_NAME: 'binary_test',
180
+ ID: 'binary_test.id',
181
+ BIN_COL: 'binary_test.bin_col',
182
+ PRIMARY: [
183
+ 'binary_test.id',
184
+ ],
185
+ PRIMARY_SHORT: [
186
+ 'id',
187
+ ],
188
+ COLUMNS: {
189
+ 'binary_test.id': 'id',
190
+ 'binary_test.bin_col': 'bin_col',
191
+ },
192
+ TYPE_VALIDATION: {
193
+ 'binary_test.id': {
194
+ MYSQL_TYPE: 'int',
195
+ MAX_LENGTH: '',
196
+ AUTO_INCREMENT: true,
197
+ SKIP_COLUMN_IN_POST: false
198
+ },
199
+ 'binary_test.bin_col': {
200
+ MYSQL_TYPE: 'binary',
201
+ MAX_LENGTH: '16',
202
+ AUTO_INCREMENT: false,
203
+ SKIP_COLUMN_IN_POST: false
204
+ },
205
+ },
206
+ REGEX_VALIDATION: {},
207
+ LIFECYCLE_HOOKS: {
208
+ GET: { beforeProcessing: {}, beforeExecution: {}, afterExecution: {}, afterCommit: {} },
209
+ PUT: { beforeProcessing: {}, beforeExecution: {}, afterExecution: {}, afterCommit: {} },
210
+ POST: { beforeProcessing: {}, beforeExecution: {}, afterExecution: {}, afterCommit: {} },
211
+ DELETE: { beforeProcessing: {}, beforeExecution: {}, afterExecution: {}, afterCommit: {} },
212
+ },
213
+ TABLE_REFERENCES: {},
214
+ TABLE_REFERENCED_BY: {}
215
+ };
178
216
  const category = {
179
217
  TABLE_NAME: 'category',
180
218
  CATEGORY_ID: 'category.category_id',
@@ -1300,11 +1338,11 @@ const store = {
1300
1338
  }
1301
1339
  };
1302
1340
  export const TABLES = {
1303
- actor, address, category, city, country, customer, film, film_actor, film_category, film_text, inventory, language, payment, rental, staff, store,
1341
+ actor, address, binary_test, category, city, country, customer, film, film_actor, film_category, film_text, inventory, language, payment, rental, staff, store,
1304
1342
  };
1305
1343
  export const C6 = {
1306
1344
  ...C6Constants,
1307
- C6VERSION: '3.7.17',
1345
+ C6VERSION: '3.7.19',
1308
1346
  IMPORT: async (tableName) => {
1309
1347
  tableName = tableName.toLowerCase();
1310
1348
  // if tableName is not a key in the TABLES object then throw an error
@@ -1333,6 +1371,7 @@ export const C6 = {
1333
1371
  export const initialRestfulObjectsState = {
1334
1372
  actor: undefined,
1335
1373
  address: undefined,
1374
+ binary_test: undefined,
1336
1375
  category: undefined,
1337
1376
  city: undefined,
1338
1377
  country: undefined,
@@ -1351,6 +1390,7 @@ export const initialRestfulObjectsState = {
1351
1390
  export const COLUMNS = {
1352
1391
  'actor.actor_id': 'actor_id', 'actor.first_name': 'first_name', 'actor.last_name': 'last_name', 'actor.last_update': 'last_update',
1353
1392
  'address.address_id': 'address_id', 'address.address': 'address', 'address.address2': 'address2', 'address.district': 'district', 'address.city_id': 'city_id', 'address.postal_code': 'postal_code', 'address.phone': 'phone', 'address.location': 'location', 'address.last_update': 'last_update',
1393
+ 'binary_test.id': 'id', 'binary_test.bin_col': 'bin_col',
1354
1394
  'category.category_id': 'category_id', 'category.name': 'name', 'category.last_update': 'last_update',
1355
1395
  'city.city_id': 'city_id', 'city.city': 'city', 'city.country_id': 'country_id', 'city.last_update': 'last_update',
1356
1396
  'country.country_id': 'country_id', 'country.country': 'country', 'country.last_update': 'last_update',
@@ -1384,6 +1424,13 @@ export const Address = {
1384
1424
  restModel: address
1385
1425
  }))
1386
1426
  };
1427
+ export const Binary_Test = {
1428
+ ...binary_test,
1429
+ ...restOrm(() => ({
1430
+ ...GLOBAL_REST_PARAMETERS,
1431
+ restModel: binary_test
1432
+ }))
1433
+ };
1387
1434
  export const Category = {
1388
1435
  ...category,
1389
1436
  ...restOrm(() => ({
@@ -1483,5 +1530,5 @@ export const Store = {
1483
1530
  }))
1484
1531
  };
1485
1532
  C6.ORM = {
1486
- Actor, Address, Category, City, Country, Customer, Film, Film_Actor, Film_Category, Film_Text, Inventory, Language, Payment, Rental, Staff, Store,
1533
+ Actor, Address, Binary_Test, Category, City, Country, Customer, Film, Film_Actor, Film_Category, Film_Text, Inventory, Language, Payment, Rental, Staff, Store,
1487
1534
  };
@@ -59,5 +59,32 @@ describe('sakila-db generated C6 bindings', () => {
59
59
  await waitForRequests();
60
60
  });
61
61
  }
62
+
63
+ it('[actor] PUT fully qualified keys', async () => {
64
+ const Actor = C6.ORM.Actor;
65
+ const testId = 1;
66
+
67
+ let result = await Actor.Get({ [Actor.ACTOR_ID]: testId } as any);
68
+ let data = result?.data ?? result;
69
+ const originalLastName = data?.rest?.[0]?.last_name;
70
+
71
+ await Actor.Put({
72
+ [Actor.ACTOR_ID]: testId,
73
+ [Actor.LAST_NAME]: 'Updated',
74
+ } as any);
75
+
76
+ result = await Actor.Get({
77
+ [Actor.ACTOR_ID]: testId,
78
+ cacheResults: false,
79
+ } as any);
80
+ data = result?.data ?? result;
81
+ expect(data?.rest?.[0]?.last_name).toBe('Updated');
82
+
83
+ await Actor.Put({
84
+ [Actor.ACTOR_ID]: testId,
85
+ [Actor.LAST_NAME]: originalLastName,
86
+ } as any);
87
+ await waitForRequests();
88
+ });
62
89
  });
63
90
 
@@ -16,6 +16,7 @@ export const RestTablePrefix = '';
16
16
 
17
17
  export type RestTableNames = 'actor'
18
18
  | 'address'
19
+ | 'binary_test'
19
20
  | 'category'
20
21
  | 'city'
21
22
  | 'country'
@@ -33,6 +34,7 @@ export type RestTableNames = 'actor'
33
34
 
34
35
  export type RestShortTableNames = 'actor'
35
36
  | 'address'
37
+ | 'binary_test'
36
38
  | 'category'
37
39
  | 'city'
38
40
  | 'country'
@@ -295,6 +297,72 @@ const address:
295
297
  }
296
298
  }
297
299
 
300
+ /**
301
+ CREATE TABLE `binary_test` (
302
+ `id` int NOT NULL AUTO_INCREMENT,
303
+ `bin_col` binary(16) DEFAULT NULL,
304
+ PRIMARY KEY (`id`)
305
+ ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
306
+ **/
307
+
308
+ export interface iBinary_Test {
309
+ 'id'?: number;
310
+ 'bin_col'?: Buffer | string | null;
311
+ }
312
+
313
+ export type Binary_TestPrimaryKeys =
314
+ 'id'
315
+ ;
316
+
317
+ const binary_test:
318
+ C6RestfulModel<
319
+ 'binary_test',
320
+ iBinary_Test,
321
+ Binary_TestPrimaryKeys
322
+ > = {
323
+ TABLE_NAME: 'binary_test',
324
+ ID: 'binary_test.id',
325
+ BIN_COL: 'binary_test.bin_col',
326
+ PRIMARY: [
327
+ 'binary_test.id',
328
+ ],
329
+ PRIMARY_SHORT: [
330
+ 'id',
331
+ ],
332
+ COLUMNS: {
333
+ 'binary_test.id': 'id',
334
+ 'binary_test.bin_col': 'bin_col',
335
+ },
336
+ TYPE_VALIDATION: {
337
+ 'binary_test.id': {
338
+ MYSQL_TYPE: 'int',
339
+ MAX_LENGTH: '',
340
+ AUTO_INCREMENT: true,
341
+ SKIP_COLUMN_IN_POST: false
342
+ },
343
+ 'binary_test.bin_col': {
344
+ MYSQL_TYPE: 'binary',
345
+ MAX_LENGTH: '16',
346
+ AUTO_INCREMENT: false,
347
+ SKIP_COLUMN_IN_POST: false
348
+ },
349
+ },
350
+ REGEX_VALIDATION: {
351
+ },
352
+ LIFECYCLE_HOOKS: {
353
+ GET: {beforeProcessing:{}, beforeExecution:{}, afterExecution:{}, afterCommit:{}},
354
+ PUT: {beforeProcessing:{}, beforeExecution:{}, afterExecution:{}, afterCommit:{}},
355
+ POST: {beforeProcessing:{}, beforeExecution:{}, afterExecution:{}, afterCommit:{}},
356
+ DELETE: {beforeProcessing:{}, beforeExecution:{}, afterExecution:{}, afterCommit:{}},
357
+ },
358
+ TABLE_REFERENCES: {
359
+
360
+ },
361
+ TABLE_REFERENCED_BY: {
362
+
363
+ }
364
+ }
365
+
298
366
  /**
299
367
  CREATE TABLE `category` (
300
368
  `category_id` tinyint unsigned NOT NULL AUTO_INCREMENT,
@@ -1916,13 +1984,14 @@ const store:
1916
1984
  }
1917
1985
 
1918
1986
  export const TABLES = {
1919
- actor,address,category,city,country,customer,film,film_actor,film_category,film_text,inventory,language,payment,rental,staff,store,
1987
+ actor,address,binary_test,category,city,country,customer,film,film_actor,film_category,film_text,inventory,language,payment,rental,staff,store,
1920
1988
  } satisfies {
1921
1989
  [K in keyof RestTableInterfaces]: C6RestfulModel<K, RestTableInterfaces[K], keyof RestTableInterfaces[K] & string>;
1922
1990
  };
1923
1991
 
1924
1992
  export type RestTableInterfaces = iActor
1925
1993
  | iAddress
1994
+ | iBinary_Test
1926
1995
  | iCategory
1927
1996
  | iCity
1928
1997
  | iCountry
@@ -1940,7 +2009,7 @@ export type RestTableInterfaces = iActor
1940
2009
 
1941
2010
  export const C6 : iC6Object<RestTableInterfaces> = {
1942
2011
  ...C6Constants,
1943
- C6VERSION: '3.7.17',
2012
+ C6VERSION: '3.7.19',
1944
2013
  IMPORT: async (tableName: string) : Promise<iDynamicApiImport> => {
1945
2014
 
1946
2015
  tableName = tableName.toLowerCase();
@@ -1976,6 +2045,7 @@ export type tStatefulApiData<T> = T[] | undefined | null;
1976
2045
  export interface iRestfulObjectArrayTypes {
1977
2046
  actor: tStatefulApiData<iActor>,
1978
2047
  address: tStatefulApiData<iAddress>,
2048
+ binary_test: tStatefulApiData<iBinary_Test>,
1979
2049
  category: tStatefulApiData<iCategory>,
1980
2050
  city: tStatefulApiData<iCity>,
1981
2051
  country: tStatefulApiData<iCountry>,
@@ -1997,6 +2067,7 @@ export type tRestfulObjectArrayValues = iRestfulObjectArrayTypes[keyof iRestfulO
1997
2067
  export const initialRestfulObjectsState: iRestfulObjectArrayTypes = {
1998
2068
  actor: undefined,
1999
2069
  address: undefined,
2070
+ binary_test: undefined,
2000
2071
  category: undefined,
2001
2072
  city: undefined,
2002
2073
  country: undefined,
@@ -2016,6 +2087,7 @@ export const initialRestfulObjectsState: iRestfulObjectArrayTypes = {
2016
2087
  export const COLUMNS = {
2017
2088
  'actor.actor_id': 'actor_id','actor.first_name': 'first_name','actor.last_name': 'last_name','actor.last_update': 'last_update',
2018
2089
  'address.address_id': 'address_id','address.address': 'address','address.address2': 'address2','address.district': 'district','address.city_id': 'city_id','address.postal_code': 'postal_code','address.phone': 'phone','address.location': 'location','address.last_update': 'last_update',
2090
+ 'binary_test.id': 'id','binary_test.bin_col': 'bin_col',
2019
2091
  'category.category_id': 'category_id','category.name': 'name','category.last_update': 'last_update',
2020
2092
  'city.city_id': 'city_id','city.city': 'city','city.country_id': 'country_id','city.last_update': 'last_update',
2021
2093
  'country.country_id': 'country_id','country.country': 'country','country.last_update': 'last_update',
@@ -2059,6 +2131,16 @@ export const Address = {
2059
2131
  }))
2060
2132
  }
2061
2133
 
2134
+ export const Binary_Test = {
2135
+ ...binary_test,
2136
+ ...restOrm<
2137
+ OrmGenerics<any, 'binary_test', iBinary_Test, Binary_TestPrimaryKeys>
2138
+ >(() => ({
2139
+ ...GLOBAL_REST_PARAMETERS,
2140
+ restModel: binary_test
2141
+ }))
2142
+ }
2143
+
2062
2144
  export const Category = {
2063
2145
  ...category,
2064
2146
  ...restOrm<
@@ -2200,7 +2282,7 @@ export const Store = {
2200
2282
  }
2201
2283
 
2202
2284
  C6.ORM = {
2203
- Actor, Address, Category, City, Country, Customer, Film, Film_Actor, Film_Category, Film_Text, Inventory, Language, Payment, Rental, Staff, Store,
2285
+ Actor, Address, Binary_Test, Category, City, Country, Customer, Film, Film_Actor, Film_Category, Film_Text, Inventory, Language, Payment, Rental, Staff, Store,
2204
2286
  };
2205
2287
 
2206
2288