@carbonorm/carbonnode 3.10.0 → 4.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 (33) hide show
  1. package/dist/api/C6Constants.d.ts +20 -0
  2. package/dist/api/orm/builders/ConditionBuilder.d.ts +8 -0
  3. package/dist/api/orm/builders/JoinBuilder.d.ts +9 -0
  4. package/dist/api/orm/queries/PostQueryBuilder.d.ts +1 -1
  5. package/dist/api/restOrm.d.ts +4 -4
  6. package/dist/api/types/ormInterfaces.d.ts +32 -12
  7. package/dist/api/utils/cacheManager.d.ts +7 -8
  8. package/dist/index.cjs.js +566 -142
  9. package/dist/index.cjs.js.map +1 -1
  10. package/dist/index.esm.js +562 -141
  11. package/dist/index.esm.js.map +1 -1
  12. package/package.json +1 -1
  13. package/src/__tests__/cacheManager.test.ts +67 -0
  14. package/src/__tests__/expressServer.e2e.test.ts +104 -2
  15. package/src/__tests__/fixtures/c6.fixture.ts +5 -0
  16. package/src/__tests__/httpExecutorSingular.e2e.test.ts +35 -4
  17. package/src/__tests__/sakila-db/C6.js +1 -1
  18. package/src/__tests__/sakila-db/C6.ts +1 -1
  19. package/src/__tests__/sqlBuilders.complex.test.ts +85 -0
  20. package/src/__tests__/sqlBuilders.test.ts +28 -0
  21. package/src/api/C6Constants.ts +12 -2
  22. package/src/api/axiosInstance.ts +29 -0
  23. package/src/api/executors/HttpExecutor.ts +73 -97
  24. package/src/api/handlers/ExpressHandler.ts +30 -7
  25. package/src/api/orm/builders/ConditionBuilder.ts +227 -0
  26. package/src/api/orm/builders/JoinBuilder.ts +150 -1
  27. package/src/api/orm/queries/PostQueryBuilder.ts +4 -2
  28. package/src/api/orm/queries/SelectQueryBuilder.ts +5 -0
  29. package/src/api/orm/queries/UpdateQueryBuilder.ts +3 -1
  30. package/src/api/types/ormInterfaces.ts +32 -18
  31. package/src/api/utils/cacheManager.ts +75 -34
  32. package/src/api/utils/testHelpers.ts +5 -3
  33. package/src/variables/isNode.ts +1 -8
@@ -0,0 +1,67 @@
1
+ import type { AxiosPromise } from "axios";
2
+ import { describe, it, expect, beforeEach } from "vitest";
3
+ import {
4
+ apiRequestCache,
5
+ checkCache,
6
+ clearCache,
7
+ setCache,
8
+ } from "../api/utils/cacheManager";
9
+ import { checkAllRequestsComplete } from "../api/utils/testHelpers";
10
+
11
+ describe("cacheManager with map storage", () => {
12
+ const requestData = { id: 1 } as const;
13
+
14
+ beforeEach(() => {
15
+ clearCache({ ignoreWarning: true });
16
+ });
17
+
18
+ it("stores and returns cached requests", () => {
19
+ const mockRequest = Promise.resolve({ data: { rest: [] } }) as AxiosPromise;
20
+
21
+ setCache("GET", "table", requestData, {
22
+ requestArgumentsSerialized: "serialized",
23
+ request: mockRequest,
24
+ });
25
+
26
+ const cached = checkCache("GET", "table", { ...requestData });
27
+ expect(cached).toBe(mockRequest);
28
+ });
29
+
30
+ it("clears cache entries", () => {
31
+ const mockRequest = Promise.resolve({ data: { rest: [] } }) as AxiosPromise;
32
+
33
+ setCache("GET", "table", requestData, {
34
+ requestArgumentsSerialized: "serialized",
35
+ request: mockRequest,
36
+ });
37
+
38
+ clearCache({ ignoreWarning: true });
39
+
40
+ expect(apiRequestCache.size).toBe(0);
41
+ expect(checkCache("GET", "table", requestData)).toBe(false);
42
+ });
43
+
44
+ it("reports pending and completed requests via helper", () => {
45
+ const originalDocument = (global as any).document;
46
+ (global as any).document = {};
47
+
48
+ setCache("GET", "table", requestData, {
49
+ requestArgumentsSerialized: "pending",
50
+ request: Promise.resolve({ data: null }) as AxiosPromise,
51
+ });
52
+
53
+ expect(checkAllRequestsComplete()).toEqual(["pending"]);
54
+
55
+ setCache("GET", "table", requestData, {
56
+ requestArgumentsSerialized: "pending",
57
+ request: Promise.resolve({ data: null }) as AxiosPromise,
58
+ response: {} as any,
59
+ final: true,
60
+ });
61
+
62
+ expect(checkAllRequestsComplete()).toBe(true);
63
+
64
+ (global as any).document = originalDocument;
65
+ });
66
+ });
67
+
@@ -2,7 +2,7 @@ import mysql from "mysql2/promise";
2
2
  import axios from "axios";
3
3
  import { AddressInfo } from "net";
4
4
  import {describe, it, expect, beforeAll, afterAll} from "vitest";
5
- import {Actor, C6, GLOBAL_REST_PARAMETERS} from "./sakila-db/C6.js";
5
+ import {Actor, C6, Film_Actor, GLOBAL_REST_PARAMETERS} from "./sakila-db/C6.js";
6
6
  import {C6C} from "../api/C6Constants";
7
7
  import createTestServer from "./fixtures/createTestServer";
8
8
 
@@ -23,7 +23,12 @@ beforeAll(async () => {
23
23
  const {port} = server.address() as AddressInfo;
24
24
 
25
25
  GLOBAL_REST_PARAMETERS.restURL = `http://127.0.0.1:${port}/rest/`;
26
- GLOBAL_REST_PARAMETERS.axios = axios;
26
+ const axiosClient = axios.create();
27
+ axiosClient.interceptors.response.use(
28
+ response => response,
29
+ error => Promise.reject(new Error(error?.message ?? 'Request failed')),
30
+ );
31
+ GLOBAL_REST_PARAMETERS.axios = axiosClient;
27
32
  GLOBAL_REST_PARAMETERS.verbose = false;
28
33
  // ensure HTTP executor is used
29
34
  // @ts-ignore
@@ -93,6 +98,79 @@ describe("ExpressHandler e2e", () => {
93
98
  expect(data?.rest.length).toBe(0);
94
99
  });
95
100
 
101
+ it("stringifies plain object values in PUT updates", async () => {
102
+ const first_name = `Json${Date.now()}`;
103
+ const last_name = `User${Date.now()}`;
104
+
105
+ await Actor.Post({
106
+ first_name,
107
+ last_name,
108
+ } as any);
109
+
110
+ const payload = { greeting: "hello", flags: [1, true] };
111
+
112
+ let data = await Actor.Get({
113
+ [C6C.WHERE]: { [Actor.FIRST_NAME]: first_name, [Actor.LAST_NAME]: last_name },
114
+ [C6C.PAGINATION]: { [C6C.LIMIT]: 1 },
115
+ } as any);
116
+
117
+ const actorId = data?.rest?.[0]?.actor_id;
118
+ expect(actorId).toBeTruthy();
119
+
120
+ await Actor.Put({
121
+ [C6C.WHERE]: { [Actor.ACTOR_ID]: actorId },
122
+ [C6C.UPDATE]: { first_name: payload },
123
+ } as any);
124
+
125
+ data = await Actor.Get({
126
+ [C6C.WHERE]: { [Actor.ACTOR_ID]: actorId },
127
+ } as any);
128
+
129
+ expect(data?.rest?.[0]?.first_name).toBe(JSON.stringify(payload));
130
+ });
131
+
132
+ it("rejects operator-like objects in PUT updates", async () => {
133
+ const first_name = `Invalid${Date.now()}`;
134
+ const last_name = `User${Date.now()}`;
135
+
136
+ await Actor.Post({
137
+ first_name,
138
+ last_name,
139
+ } as any);
140
+
141
+ const data = await Actor.Get({
142
+ [C6C.WHERE]: { [Actor.FIRST_NAME]: first_name, [Actor.LAST_NAME]: last_name },
143
+ [C6C.PAGINATION]: { [C6C.LIMIT]: 1 },
144
+ } as any);
145
+
146
+ const actorId = data?.rest?.[0]?.actor_id;
147
+ expect(actorId).toBeTruthy();
148
+
149
+ const operatorLike = { [C6C.GREATER_THAN]: "oops" } as any;
150
+
151
+ const prevRestUrl = GLOBAL_REST_PARAMETERS.restURL;
152
+ const prevAxios = GLOBAL_REST_PARAMETERS.axios;
153
+ GLOBAL_REST_PARAMETERS.mysqlPool = pool as any;
154
+ delete (GLOBAL_REST_PARAMETERS as any).restURL;
155
+ delete (GLOBAL_REST_PARAMETERS as any).axios;
156
+
157
+ try {
158
+ await Actor.Put({
159
+ [C6C.WHERE]: { [Actor.ACTOR_ID]: actorId },
160
+ [C6C.UPDATE]: { first_name: operatorLike },
161
+ } as any);
162
+ throw new Error('Expected PUT to reject for operator-like payload.');
163
+ } catch (error: any) {
164
+ const message = String(error?.message ?? error);
165
+ expect(message).toMatch(/operand/i);
166
+ } finally {
167
+ GLOBAL_REST_PARAMETERS.restURL = prevRestUrl;
168
+ GLOBAL_REST_PARAMETERS.axios = prevAxios;
169
+ // @ts-ignore
170
+ delete GLOBAL_REST_PARAMETERS.mysqlPool;
171
+ }
172
+ });
173
+
96
174
  it("respects METHOD=GET override on POST", async () => {
97
175
  const {restURL} = GLOBAL_REST_PARAMETERS;
98
176
  const table = Actor.TABLE_NAME;
@@ -121,4 +199,28 @@ describe("ExpressHandler e2e", () => {
121
199
  expect(response.status).toBe(200);
122
200
  expect(response.data?.success).toBeTruthy();
123
201
  });
202
+
203
+ it("allows composite keys when a URL primary is present", async () => {
204
+ const {restURL} = GLOBAL_REST_PARAMETERS;
205
+ const table = Film_Actor.TABLE_NAME;
206
+
207
+ const seed = await Film_Actor.Get({
208
+ [C6C.PAGINATION]: { [C6C.LIMIT]: 1 },
209
+ } as any);
210
+
211
+ const filmActor = seed.rest[0];
212
+ expect(filmActor).toBeTruthy();
213
+
214
+ const response = await axios.post(`${restURL}${table}/${filmActor.actor_id}?METHOD=GET`, {
215
+ [C6C.WHERE]: {
216
+ [Film_Actor.ACTOR_ID]: filmActor.actor_id,
217
+ [Film_Actor.FILM_ID]: filmActor.film_id,
218
+ },
219
+ });
220
+
221
+ expect(response.status).toBe(200);
222
+ expect(response.data?.rest).toHaveLength(1);
223
+ expect(response.data?.rest?.[0]?.actor_id).toBe(filmActor.actor_id);
224
+ expect(response.data?.rest?.[0]?.film_id).toBe(filmActor.film_id);
225
+ });
124
226
  });
@@ -43,6 +43,7 @@ export function buildTestConfig() {
43
43
  'actor.first_name': 'first_name',
44
44
  'actor.last_name': 'last_name',
45
45
  'actor.binarycol': 'binarycol',
46
+ 'actor.json_data': 'json_data',
46
47
  } as const;
47
48
 
48
49
  const filmActorCols = {
@@ -62,6 +63,7 @@ export function buildTestConfig() {
62
63
 
63
64
  // Special-case: mark binary column as BINARY to test conversion
64
65
  C6.TABLES.actor.TYPE_VALIDATION['binarycol'].MYSQL_TYPE = 'BINARY(16)';
66
+ C6.TABLES.actor.TYPE_VALIDATION['json_data'].MYSQL_TYPE = 'JSON';
65
67
 
66
68
  const baseConfig: iRest<any, any, any> = {
67
69
  C6,
@@ -135,6 +137,7 @@ export function buildParcelConfig() {
135
137
  'property_units.unit_id': 'unit_id',
136
138
  'property_units.location': 'location',
137
139
  'property_units.parcel_id': 'parcel_id',
140
+ 'property_units.county_id': 'county_id',
138
141
  } as const;
139
142
 
140
143
  const parcelSalesCols = {
@@ -146,6 +149,8 @@ export function buildParcelConfig() {
146
149
 
147
150
  const parcelBuildingCols = {
148
151
  'parcel_building_details.parcel_id': 'parcel_id',
152
+ 'parcel_building_details.year_built': 'year_built',
153
+ 'parcel_building_details.gla': 'gla',
149
154
  } as const;
150
155
 
151
156
  const C6 = {
@@ -23,7 +23,12 @@ beforeAll(async () => {
23
23
  const { port } = server.address() as AddressInfo;
24
24
 
25
25
  GLOBAL_REST_PARAMETERS.restURL = `http://127.0.0.1:${port}/rest/`;
26
- GLOBAL_REST_PARAMETERS.axios = axios;
26
+ const axiosClient = axios.create();
27
+ axiosClient.interceptors.response.use(
28
+ response => response,
29
+ error => Promise.reject(new Error(error?.message ?? "Request failed")),
30
+ );
31
+ GLOBAL_REST_PARAMETERS.axios = axiosClient;
27
32
  GLOBAL_REST_PARAMETERS.verbose = false;
28
33
  // ensure HTTP executor is used
29
34
  // @ts-ignore
@@ -41,14 +46,14 @@ describe("HttpExecutor singular e2e", () => {
41
46
  const last_name = `User${Date.now()}`;
42
47
 
43
48
  // POST
44
- const postRes = await Actor.Post({ first_name, last_name } as any);
45
- expect(postRes.affected).toBe(1);
49
+ await Actor.Post({ first_name, last_name } as any);
46
50
 
47
51
  // Fetch inserted id using complex query
48
52
  let data = await Actor.Get({
49
53
  [C6C.WHERE]: { [Actor.FIRST_NAME]: first_name, [Actor.LAST_NAME]: last_name },
50
54
  [C6C.PAGINATION]: { [C6C.LIMIT]: 1 },
51
- } as any);
55
+ });
56
+
52
57
  expect(data.rest).toHaveLength(1);
53
58
  const testId = data.rest[0].actor_id;
54
59
 
@@ -89,4 +94,30 @@ describe("HttpExecutor singular e2e", () => {
89
94
  expect(Array.isArray(data.rest)).toBe(true);
90
95
  expect(data.rest.length).toBe(0);
91
96
  });
97
+
98
+ it("exposes next when pagination continues", async () => {
99
+ const data = await Actor.Get({
100
+ [C6C.PAGINATION]: { [C6C.LIMIT]: 2 },
101
+ } as any);
102
+
103
+ expect(Array.isArray(data.rest)).toBe(true);
104
+ expect(data.rest).toHaveLength(2);
105
+ expect(typeof data.next).toBe("function");
106
+
107
+ const nextPage = await data.next?.();
108
+ expect(nextPage?.rest).toBeDefined();
109
+ expect(Array.isArray(nextPage?.rest)).toBe(true);
110
+ expect(nextPage?.rest?.length).toBeGreaterThan(0);
111
+ });
112
+
113
+ it("exposes limit 1 does not expose next", async () => {
114
+ const data = await Actor.Get({
115
+ [C6C.PAGINATION]: { [C6C.LIMIT]: 1 },
116
+ } as any);
117
+
118
+ expect(Array.isArray(data.rest)).toBe(true);
119
+ expect(data.rest).toHaveLength(1);
120
+ expect(typeof data.next).toBe("undefined");
121
+
122
+ });
92
123
  });
@@ -1342,7 +1342,7 @@ export const TABLES = {
1342
1342
  };
1343
1343
  export const C6 = {
1344
1344
  ...C6Constants,
1345
- C6VERSION: '3.10.0',
1345
+ C6VERSION: '4.0.0',
1346
1346
  IMPORT: async (tableName) => {
1347
1347
  tableName = tableName.toLowerCase();
1348
1348
  // if tableName is not a key in the TABLES object then throw an error
@@ -2009,7 +2009,7 @@ export type RestTableInterfaces = iActor
2009
2009
 
2010
2010
  export const C6 : iC6Object<RestTableInterfaces> = {
2011
2011
  ...C6Constants,
2012
- C6VERSION: '3.10.0',
2012
+ C6VERSION: '4.0.0',
2013
2013
  IMPORT: async (tableName: string) : Promise<iDynamicApiImport> => {
2014
2014
 
2015
2015
  tableName = tableName.toLowerCase();
@@ -9,6 +9,7 @@ const Property_Units = {
9
9
  UNIT_ID: 'property_units.unit_id',
10
10
  LOCATION: 'property_units.location',
11
11
  PARCEL_ID: 'property_units.parcel_id',
12
+ COUNTY_ID: 'property_units.county_id',
12
13
  } as const;
13
14
 
14
15
  const Parcel_Sales = {
@@ -19,6 +20,13 @@ const Parcel_Sales = {
19
20
  SALE_DATE: 'parcel_sales.sale_date',
20
21
  } as const;
21
22
 
23
+ const Parcel_Building_Details = {
24
+ TABLE_NAME: 'parcel_building_details',
25
+ PARCEL_ID: 'parcel_building_details.parcel_id',
26
+ YEAR_BUILT: 'parcel_building_details.year_built',
27
+ GLA: 'parcel_building_details.gla',
28
+ } as const;
29
+
22
30
  /**
23
31
  * Complex SELECT coverage focused on WHERE operators, JOIN chains, ORDER, and pagination.
24
32
  */
@@ -479,4 +487,81 @@ describe('SQL Builders - Complex SELECTs', () => {
479
487
  expect(sql).toContain('WHERE ( property_units.unit_id IN (SELECT parcel_sales.parcel_id');
480
488
  expect(params).toContain(5000);
481
489
  });
490
+
491
+ it('serializes spatial filtering with FORCE INDEX and correlated EXISTS subqueries', () => {
492
+ const config = buildParcelConfig();
493
+ const polygon = 'POLYGON((39.5185659 -105.0142915, 39.5401859 -105.0142915, 39.5401859 -104.9862115, 39.5185659 -104.9862115, 39.5185659 -105.0142915))';
494
+ const point = [C6C.ST_GEOMFROMTEXT, ['POINT(39.5293759 -105.0002515)', 4326]];
495
+ const unitId = Buffer.from('11F0615D24861BE1ADD40AFFCF6A1F27', 'hex');
496
+ const countyId = Buffer.from('11F012CFF561A29DBB0E0AFFF25F1747', 'hex');
497
+
498
+ const qb = new SelectQueryBuilder(config as any, {
499
+ [C6C.SELECT]: ['property_units.*'],
500
+ [C6C.INDEX_HINTS]: {
501
+ [C6C.FORCE_INDEX]: ['idx_county_id', 'idx_property_units_location'],
502
+ },
503
+ [C6C.WHERE]: {
504
+ [Property_Units.UNIT_ID]: [C6C.NOT_EQUAL, unitId],
505
+ [Property_Units.COUNTY_ID]: countyId,
506
+ [C6C.MBRCONTAINS]: [
507
+ [C6C.ST_GEOMFROMTEXT, [polygon, 4326]],
508
+ Property_Units.LOCATION,
509
+ ],
510
+ [C6C.LESS_THAN_OR_EQUAL_TO]: [
511
+ [C6C.ST_DISTANCE_SPHERE, Property_Units.LOCATION, point],
512
+ 1200,
513
+ ],
514
+ [C6C.EXISTS]: [
515
+ [
516
+ Property_Units.PARCEL_ID,
517
+ {
518
+ [C6C.SUBSELECT]: {
519
+ [C6C.SELECT]: [Parcel_Building_Details.PARCEL_ID],
520
+ [C6C.FROM]: Parcel_Building_Details.TABLE_NAME,
521
+ [C6C.WHERE]: {
522
+ [Parcel_Building_Details.YEAR_BUILT]: [C6C.BETWEEN, [1988, 2008]],
523
+ [Parcel_Building_Details.GLA]: [C6C.BETWEEN, [1876.5, 3127.5]],
524
+ },
525
+ },
526
+ },
527
+ ],
528
+ [
529
+ Property_Units.PARCEL_ID,
530
+ {
531
+ [C6C.SUBSELECT]: {
532
+ [C6C.SELECT]: [Parcel_Sales.PARCEL_ID],
533
+ [C6C.FROM]: Parcel_Sales.TABLE_NAME,
534
+ [C6C.WHERE]: {
535
+ [Parcel_Sales.SALE_DATE]: [C6C.BETWEEN, ['2023-01-01', '2024-06-30']],
536
+ [Parcel_Sales.SALE_PRICE]: [C6C.NOT_EQUAL, 0],
537
+ },
538
+ },
539
+ },
540
+ ],
541
+ ],
542
+ },
543
+ [C6C.PAGINATION]: {
544
+ [C6C.LIMIT]: 100,
545
+ [C6C.ORDER]: {
546
+ [C6C.ST_DISTANCE_SPHERE]: [Property_Units.LOCATION, point],
547
+ },
548
+ },
549
+ } as any, false);
550
+
551
+ const { sql, params } = qb.build(Property_Units.TABLE_NAME);
552
+
553
+ expect(sql).toContain('FORCE INDEX (`idx_county_id`, `idx_property_units_location`)');
554
+ expect(sql).toMatch(/MBRCONTAINS\(ST_GEOMFROMTEXT\('POLYGON\(\(39\.5185659 -105\.0142915, 39\.5401859 -105\.0142915, 39\.5401859 -104\.9862115, 39\.5185659 -104\.9862115, 39\.5185659 -105\.0142915\)\)', 4326\), property_units\.location\)/);
555
+ expect(sql).toMatch(/ST_DISTANCE_SPHERE\(property_units\.location, ST_GEOMFROMTEXT\('POINT\(39\.5293759 -105\.0002515\)', 4326\)\) <= \?/);
556
+ expect(sql).toMatch(/\(parcel_building_details\.parcel_id\) = property_units\.parcel_id/);
557
+ expect(sql).toMatch(/\(parcel_sales\.parcel_id\) = property_units\.parcel_id/);
558
+ expect(sql).toMatch(/ORDER BY ST[_]Distance[_]Sphere\(property_units\.location, ST_GEOMFROMTEXT\('POINT\(39\.5293759 -105\.0002515\)', 4326\)\)/i);
559
+
560
+ expect(params).toHaveLength(10);
561
+ expect(params[0]).toEqual(unitId);
562
+ expect(params[1]).toEqual(countyId);
563
+ expect(params).toContain(1200);
564
+ expect(params.slice(3, 7)).toEqual([1988, 2008, 1876.5, 3127.5]);
565
+ expect(params.slice(7)).toEqual(['2023-01-01', '2024-06-30', 0]);
566
+ });
482
567
  });
@@ -61,6 +61,34 @@ describe('SQL Builders', () => {
61
61
  expect(params).toEqual(['BOB', 'SMITH']);
62
62
  });
63
63
 
64
+ it('stringifies plain object inserts for JSON columns', () => {
65
+ const config = buildTestConfig();
66
+ const payload = { hello: 'world', nested: { ok: true } };
67
+ const qb = new PostQueryBuilder(config as any, {
68
+ [C6C.INSERT]: {
69
+ 'actor.json_data': payload,
70
+ },
71
+ } as any, false);
72
+
73
+ const { sql, params } = qb.build('actor');
74
+
75
+ expect(sql).toContain('`json_data`');
76
+ expect(params).toEqual([JSON.stringify(payload)]);
77
+ });
78
+
79
+ it('throws on operator-shaped insert payloads', () => {
80
+ const config = buildTestConfig();
81
+ const qb = new PostQueryBuilder(config as any, {
82
+ [C6C.INSERT]: {
83
+ 'actor.first_name': {
84
+ [C6C.GREATER_THAN]: 'ALICE',
85
+ },
86
+ },
87
+ } as any, false);
88
+
89
+ expect(() => qb.build('actor')).toThrowError(/requires two operands/);
90
+ });
91
+
64
92
  it('builds UPDATE with WHERE and pagination', () => {
65
93
  const config = buildTestConfig();
66
94
  const qb = new UpdateQueryBuilder(config as any, {
@@ -1,6 +1,4 @@
1
-
2
1
  export const C6Constants = {
3
-
4
2
  // try to 1=1 match the Rest abstract class
5
3
  ADDDATE: 'ADDDATE',
6
4
  ADDTIME: 'ADDTIME',
@@ -34,11 +32,13 @@ export const C6Constants = {
34
32
  DESC: 'DESC',
35
33
  DISTINCT: 'DISTINCT',
36
34
 
35
+ EXISTS: 'EXISTS',
37
36
  EXTRACT: 'EXTRACT',
38
37
  EQUAL: '=',
39
38
  EQUAL_NULL_SAFE: '<=>',
40
39
 
41
40
  FALSE: 'FALSE',
41
+ FORCE: 'FORCE',
42
42
  FULL_OUTER: 'FULL_OUTER',
43
43
  FROM_DAYS: 'FROM_DAYS',
44
44
  FROM_UNIXTIME: 'FROM_UNIXTIME',
@@ -57,6 +57,7 @@ export const C6Constants = {
57
57
  HOUR_MINUTE: 'HOUR_MINUTE',
58
58
 
59
59
  IN: 'IN',
60
+ INDEX: 'INDEX',
60
61
  IS: 'IS',
61
62
  IS_NOT: 'IS_NOT',
62
63
  INNER: 'INNER',
@@ -77,6 +78,7 @@ export const C6Constants = {
77
78
  MAKEDATE: 'MAKEDATE',
78
79
  MAKETIME: 'MAKETIME',
79
80
  MATCH_AGAINST: 'MATCH_AGAINST',
81
+ MBRCONTAINS: 'MBRContains',
80
82
  MONTHNAME: 'MONTHNAME',
81
83
  MICROSECOND: 'MICROSECOND',
82
84
  MINUTE: 'MINUTE',
@@ -95,8 +97,16 @@ export const C6Constants = {
95
97
  ORDER: 'ORDER',
96
98
  OR: 'OR',
97
99
 
100
+ INDEX_HINTS: 'INDEX_HINTS',
101
+
102
+ FORCE_INDEX: 'FORCE INDEX',
103
+ USE_INDEX: 'USE INDEX',
104
+ IGNORE_INDEX: 'IGNORE INDEX',
105
+
98
106
  PAGE: 'PAGE',
99
107
  PAGINATION: 'PAGINATION',
108
+ POLYGON: 'POLYGON',
109
+ POINT: 'POINT',
100
110
  RIGHT_OUTER: 'RIGHT_OUTER',
101
111
 
102
112
  SECOND: 'SECOND',
@@ -151,7 +151,36 @@ axiosInstance.interceptors.request.use((config) => {
151
151
  }
152
152
  }
153
153
 
154
+ (config as any).__carbonStart = performance.now();
155
+
154
156
  return config;
155
157
  });
156
158
 
159
+
160
+ axiosInstance.interceptors.response.use(
161
+ (response) => {
162
+ const end = performance.now();
163
+ const start = (response.config as any).__carbonStart;
164
+ (response as any).__carbonTiming = {
165
+ start,
166
+ end,
167
+ duration: end - start,
168
+ };
169
+ return response;
170
+ },
171
+ (error) => {
172
+ if (error.config) {
173
+ const end = performance.now();
174
+ const start = (error.config as any).__carbonStart;
175
+ (error as any).__carbonTiming = {
176
+ start,
177
+ end,
178
+ duration: end - start,
179
+ };
180
+ }
181
+ throw error;
182
+ }
183
+ );
184
+
185
+
157
186
  export default axiosInstance