@carbonorm/carbonnode 4.0.0 → 5.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 (158) hide show
  1. package/README.md +246 -507
  2. package/dist/api/executors/SqlExecutor.d.ts +6 -0
  3. package/dist/api/handlers/ExpressHandler.d.ts +2 -1
  4. package/dist/api/orm/builders/ConditionBuilder.d.ts +2 -0
  5. package/dist/api/types/ormInterfaces.d.ts +12 -0
  6. package/dist/api/utils/sqlAllowList.d.ts +2 -0
  7. package/dist/index.cjs.js +279 -20
  8. package/dist/index.cjs.js.map +1 -1
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.esm.js +278 -21
  11. package/dist/index.esm.js.map +1 -1
  12. package/package.json +1 -1
  13. package/scripts/assets/handlebars/C6.test.ts.handlebars +578 -32
  14. package/scripts/generateRestBindings.cjs +5 -5
  15. package/scripts/generateRestBindings.ts +5 -5
  16. package/src/__tests__/fixtures/createTestServer.ts +11 -3
  17. package/src/__tests__/fixtures/sqlResponses/actor.get.json +13 -0
  18. package/src/__tests__/fixtures/sqlResponses/sqlAllowList.blocked.json +3 -0
  19. package/src/__tests__/fixtures/sqlResponses/sqlAllowList.json +3 -0
  20. package/src/__tests__/sakila-db/C6.js +1 -1
  21. package/src/__tests__/sakila-db/C6.mysql.cnf +6 -0
  22. package/src/__tests__/sakila-db/C6.mysqldump.json +1 -0
  23. package/src/__tests__/sakila-db/C6.mysqldump.sql +720 -0
  24. package/src/__tests__/sakila-db/C6.sqlAllowList.json +94 -0
  25. package/src/__tests__/sakila-db/C6.test.ts +578 -32
  26. package/src/__tests__/sakila-db/C6.ts +1 -1
  27. package/src/__tests__/sakila-db/sqlResponses/C6.actor.delete.json +10 -0
  28. package/src/__tests__/sakila-db/sqlResponses/C6.actor.delete.lookup.json +9 -0
  29. package/src/__tests__/sakila-db/sqlResponses/C6.actor.get.json +14 -0
  30. package/src/__tests__/sakila-db/sqlResponses/C6.actor.join.json +15 -0
  31. package/src/__tests__/sakila-db/sqlResponses/C6.actor.post.json +12 -0
  32. package/src/__tests__/sakila-db/sqlResponses/C6.actor.post.latest.json +14 -0
  33. package/src/__tests__/sakila-db/sqlResponses/C6.actor.put.json +11 -0
  34. package/src/__tests__/sakila-db/sqlResponses/C6.actor.put.lookup.json +16 -0
  35. package/src/__tests__/sakila-db/sqlResponses/C6.actor.seed.json +14 -0
  36. package/src/__tests__/sakila-db/sqlResponses/C6.address.delete.json +10 -0
  37. package/src/__tests__/sakila-db/sqlResponses/C6.address.delete.lookup.json +9 -0
  38. package/src/__tests__/sakila-db/sqlResponses/C6.address.fk.current.json +358 -0
  39. package/src/__tests__/sakila-db/sqlResponses/C6.address.fk.referenced.json +158 -0
  40. package/src/__tests__/sakila-db/sqlResponses/C6.address.get.json +22 -0
  41. package/src/__tests__/sakila-db/sqlResponses/C6.address.join.json +24 -0
  42. package/src/__tests__/sakila-db/sqlResponses/C6.address.post.json +16 -0
  43. package/src/__tests__/sakila-db/sqlResponses/C6.address.post.latest.json +22 -0
  44. package/src/__tests__/sakila-db/sqlResponses/C6.address.put.json +11 -0
  45. package/src/__tests__/sakila-db/sqlResponses/C6.address.put.lookup.json +24 -0
  46. package/src/__tests__/sakila-db/sqlResponses/C6.address.seed.json +22 -0
  47. package/src/__tests__/sakila-db/sqlResponses/C6.category.delete.json +10 -0
  48. package/src/__tests__/sakila-db/sqlResponses/C6.category.delete.lookup.json +9 -0
  49. package/src/__tests__/sakila-db/sqlResponses/C6.category.get.json +13 -0
  50. package/src/__tests__/sakila-db/sqlResponses/C6.category.join.json +14 -0
  51. package/src/__tests__/sakila-db/sqlResponses/C6.category.post.json +11 -0
  52. package/src/__tests__/sakila-db/sqlResponses/C6.category.post.latest.json +13 -0
  53. package/src/__tests__/sakila-db/sqlResponses/C6.category.put.json +11 -0
  54. package/src/__tests__/sakila-db/sqlResponses/C6.category.put.lookup.json +15 -0
  55. package/src/__tests__/sakila-db/sqlResponses/C6.category.seed.json +13 -0
  56. package/src/__tests__/sakila-db/sqlResponses/C6.city.delete.json +10 -0
  57. package/src/__tests__/sakila-db/sqlResponses/C6.city.delete.lookup.json +9 -0
  58. package/src/__tests__/sakila-db/sqlResponses/C6.city.fk.current.json +158 -0
  59. package/src/__tests__/sakila-db/sqlResponses/C6.city.fk.referenced.json +133 -0
  60. package/src/__tests__/sakila-db/sqlResponses/C6.city.get.json +14 -0
  61. package/src/__tests__/sakila-db/sqlResponses/C6.city.join.json +15 -0
  62. package/src/__tests__/sakila-db/sqlResponses/C6.city.post.json +12 -0
  63. package/src/__tests__/sakila-db/sqlResponses/C6.city.post.latest.json +14 -0
  64. package/src/__tests__/sakila-db/sqlResponses/C6.city.put.json +11 -0
  65. package/src/__tests__/sakila-db/sqlResponses/C6.city.put.lookup.json +16 -0
  66. package/src/__tests__/sakila-db/sqlResponses/C6.city.seed.json +14 -0
  67. package/src/__tests__/sakila-db/sqlResponses/C6.country.delete.json +10 -0
  68. package/src/__tests__/sakila-db/sqlResponses/C6.country.delete.lookup.json +9 -0
  69. package/src/__tests__/sakila-db/sqlResponses/C6.country.get.json +13 -0
  70. package/src/__tests__/sakila-db/sqlResponses/C6.country.join.json +15 -0
  71. package/src/__tests__/sakila-db/sqlResponses/C6.country.post.json +11 -0
  72. package/src/__tests__/sakila-db/sqlResponses/C6.country.post.latest.json +13 -0
  73. package/src/__tests__/sakila-db/sqlResponses/C6.country.put.json +11 -0
  74. package/src/__tests__/sakila-db/sqlResponses/C6.country.put.lookup.json +15 -0
  75. package/src/__tests__/sakila-db/sqlResponses/C6.country.seed.json +13 -0
  76. package/src/__tests__/sakila-db/sqlResponses/C6.customer.delete.json +10 -0
  77. package/src/__tests__/sakila-db/sqlResponses/C6.customer.delete.lookup.json +9 -0
  78. package/src/__tests__/sakila-db/sqlResponses/C6.customer.fk.current.json +283 -0
  79. package/src/__tests__/sakila-db/sqlResponses/C6.customer.fk.referenced.json +358 -0
  80. package/src/__tests__/sakila-db/sqlResponses/C6.customer.get.json +19 -0
  81. package/src/__tests__/sakila-db/sqlResponses/C6.customer.join.json +29 -0
  82. package/src/__tests__/sakila-db/sqlResponses/C6.customer.post.json +17 -0
  83. package/src/__tests__/sakila-db/sqlResponses/C6.customer.post.latest.json +19 -0
  84. package/src/__tests__/sakila-db/sqlResponses/C6.customer.put.json +11 -0
  85. package/src/__tests__/sakila-db/sqlResponses/C6.customer.put.lookup.json +21 -0
  86. package/src/__tests__/sakila-db/sqlResponses/C6.customer.seed.json +19 -0
  87. package/src/__tests__/sakila-db/sqlResponses/C6.film.delete.json +10 -0
  88. package/src/__tests__/sakila-db/sqlResponses/C6.film.delete.lookup.json +9 -0
  89. package/src/__tests__/sakila-db/sqlResponses/C6.film.fk.current.json +383 -0
  90. package/src/__tests__/sakila-db/sqlResponses/C6.film.fk.referenced.json +38 -0
  91. package/src/__tests__/sakila-db/sqlResponses/C6.film.get.json +23 -0
  92. package/src/__tests__/sakila-db/sqlResponses/C6.film.join.json +24 -0
  93. package/src/__tests__/sakila-db/sqlResponses/C6.film.post.json +20 -0
  94. package/src/__tests__/sakila-db/sqlResponses/C6.film.post.latest.json +23 -0
  95. package/src/__tests__/sakila-db/sqlResponses/C6.film.put.json +11 -0
  96. package/src/__tests__/sakila-db/sqlResponses/C6.film.put.lookup.json +25 -0
  97. package/src/__tests__/sakila-db/sqlResponses/C6.film.seed.json +23 -0
  98. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.delete.json +10 -0
  99. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.delete.lookup.json +9 -0
  100. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.fk.current.json +158 -0
  101. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.fk.referenced.json +20 -0
  102. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.get.json +14 -0
  103. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.join.json +25 -0
  104. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.post.json +12 -0
  105. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.post.latest.json +14 -0
  106. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.put.json +11 -0
  107. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.put.lookup.json +16 -0
  108. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.seed.json +14 -0
  109. package/src/__tests__/sakila-db/sqlResponses/C6.language.delete.json +10 -0
  110. package/src/__tests__/sakila-db/sqlResponses/C6.language.delete.lookup.json +9 -0
  111. package/src/__tests__/sakila-db/sqlResponses/C6.language.get.json +13 -0
  112. package/src/__tests__/sakila-db/sqlResponses/C6.language.join.json +24 -0
  113. package/src/__tests__/sakila-db/sqlResponses/C6.language.post.json +11 -0
  114. package/src/__tests__/sakila-db/sqlResponses/C6.language.post.latest.json +13 -0
  115. package/src/__tests__/sakila-db/sqlResponses/C6.language.put.json +11 -0
  116. package/src/__tests__/sakila-db/sqlResponses/C6.language.put.lookup.json +15 -0
  117. package/src/__tests__/sakila-db/sqlResponses/C6.language.seed.json +13 -0
  118. package/src/__tests__/sakila-db/sqlResponses/C6.payment.delete.json +10 -0
  119. package/src/__tests__/sakila-db/sqlResponses/C6.payment.delete.lookup.json +9 -0
  120. package/src/__tests__/sakila-db/sqlResponses/C6.payment.fk.current.json +233 -0
  121. package/src/__tests__/sakila-db/sqlResponses/C6.payment.fk.referenced.json +233 -0
  122. package/src/__tests__/sakila-db/sqlResponses/C6.payment.get.json +17 -0
  123. package/src/__tests__/sakila-db/sqlResponses/C6.payment.join.json +24 -0
  124. package/src/__tests__/sakila-db/sqlResponses/C6.payment.post.json +15 -0
  125. package/src/__tests__/sakila-db/sqlResponses/C6.payment.post.latest.json +17 -0
  126. package/src/__tests__/sakila-db/sqlResponses/C6.payment.put.json +11 -0
  127. package/src/__tests__/sakila-db/sqlResponses/C6.payment.put.lookup.json +19 -0
  128. package/src/__tests__/sakila-db/sqlResponses/C6.payment.seed.json +17 -0
  129. package/src/__tests__/sakila-db/sqlResponses/C6.rental.delete.json +10 -0
  130. package/src/__tests__/sakila-db/sqlResponses/C6.rental.delete.lookup.json +9 -0
  131. package/src/__tests__/sakila-db/sqlResponses/C6.rental.fk.current.json +233 -0
  132. package/src/__tests__/sakila-db/sqlResponses/C6.rental.fk.referenced.json +34 -0
  133. package/src/__tests__/sakila-db/sqlResponses/C6.rental.get.json +17 -0
  134. package/src/__tests__/sakila-db/sqlResponses/C6.rental.join.json +24 -0
  135. package/src/__tests__/sakila-db/sqlResponses/C6.rental.post.json +15 -0
  136. package/src/__tests__/sakila-db/sqlResponses/C6.rental.post.latest.json +17 -0
  137. package/src/__tests__/sakila-db/sqlResponses/C6.rental.put.json +11 -0
  138. package/src/__tests__/sakila-db/sqlResponses/C6.rental.put.lookup.json +19 -0
  139. package/src/__tests__/sakila-db/sqlResponses/C6.rental.seed.json +17 -0
  140. package/src/__tests__/sakila-db/sqlResponses/C6.staff.fk.current.json +34 -0
  141. package/src/__tests__/sakila-db/sqlResponses/C6.staff.fk.referenced.json +20 -0
  142. package/src/__tests__/sakila-db/sqlResponses/C6.staff.get.json +21 -0
  143. package/src/__tests__/sakila-db/sqlResponses/C6.staff.join.json +31 -0
  144. package/src/__tests__/sakila-db/sqlResponses/C6.staff.seed.json +21 -0
  145. package/src/__tests__/sakila-db/sqlResponses/C6.store.fk.current.json +20 -0
  146. package/src/__tests__/sakila-db/sqlResponses/C6.store.fk.referenced.json +34 -0
  147. package/src/__tests__/sakila-db/sqlResponses/C6.store.get.json +14 -0
  148. package/src/__tests__/sakila-db/sqlResponses/C6.store.join.json +24 -0
  149. package/src/__tests__/sakila-db/sqlResponses/C6.store.seed.json +14 -0
  150. package/src/__tests__/sakila.generated.test.ts +31 -0
  151. package/src/__tests__/sqlAllowList.test.ts +135 -0
  152. package/src/__tests__/sqlBuilders.test.ts +17 -0
  153. package/src/api/executors/SqlExecutor.ts +156 -0
  154. package/src/api/handlers/ExpressHandler.ts +10 -1
  155. package/src/api/orm/builders/ConditionBuilder.ts +27 -7
  156. package/src/api/types/ormInterfaces.ts +15 -0
  157. package/src/api/utils/sqlAllowList.ts +54 -0
  158. package/src/index.ts +1 -0
package/README.md CHANGED
@@ -7,12 +7,28 @@
7
7
  ![Star](https://img.shields.io/github/stars/carbonorm/carbonnode?style=social)
8
8
  [![Github Actions Test and Publish to NPM](https://github.com/CarbonORM/CarbonNode/actions/workflows/npm-publish-on-bump.yml/badge.svg)](https://github.com/CarbonORM/CarbonNode/actions/workflows/npm-publish-on-bump.yml)
9
9
 
10
- # CarbonNode (Alpha Release)
10
+ # CarbonNode
11
+
12
+ CarbonNode is a part of the CarbonORM series. It is a NodeJS MySQL ORM that can run independently in the backend or paired with
13
+ CarbonReact for 1=1 syntax. Note the CarbonNode + CarbonReact experience is unmatched in interoperability.
14
+
15
+ # Purpose
16
+
17
+ CarbonNode is designed to generate RESTful API bindings for a MySQL database. The generated code provides a simple and
18
+ consistent interface for performing CRUD operations on the database tables. The goal is to reduce the amount of boilerplate
19
+ code needed to interact with the database and to provide a more efficient and reliable way to work with MySQL data in a NodeJS
20
+ environment. The major goals:
21
+ - Allow a 1-1 interoperability when querying data from the frontend to the backend.
22
+ - Language based Objects/Arrays for representing and modifying queries to eliminate string manipulation operations.
23
+ - Explicit column references to allow for easier refactoring and code completion in IDEs.
24
+ - Selecting a dead column will result in a compile time error instead of a runtime error.
25
+ - TypeScript types generated for each table in the database.
26
+ - Lifecycle hooks for each CRUD operation to allow for custom logic to be executed before and after the operation.
27
+ - Validation of data types and formats before executing CRUD operations to ensure data integrity.
28
+
29
+ It's easier to scale your middleware than your database.
30
+ CarbonNode aims to capture issues before they reach your database.
11
31
 
12
- CarbonNode is a part of the CarbonORM series. It is a NodeJS MySQL ORM that is designed to work with CarbonPHP. This langauge
13
- will implement the same ORM as CarbonPHP, but will be written in Typescript. Currently only C6 enabled requests can be sent
14
- using the bindings. Receiving API requests and handling it appropriately is not yet implemented. This is scheduled for
15
- early 2024. This repository is in the early stages of development an any support is greatly appreciated.
16
32
 
17
33
  ## Alpha Release
18
34
 
@@ -31,422 +47,173 @@ npm install @carbonorm/carbonnode
31
47
 
32
48
  ## Generate Models
33
49
 
34
- The command below will generate the models for the database. The models will be generated in the output directory. We do
35
- recommend you keep this folder separate from other work. It is also best to track the output directory in your version
36
- control system. All arguments are optional. If you do not provide them the defaults will be used. The example arguments
37
- below are the defaults.
50
+ The generator produces a single `C6.ts` file containing all tables, types, and REST bindings. Keep this file in version
51
+ control and share it between server and client. All arguments are optional; the example below shows the defaults.
38
52
 
39
53
  ```bash
40
- npx generateRestBindings --user root --pass password --host 127.0.0.1 --port 3306 --dbname carbonPHP --prefix carbon_ --output /src/api/rest
54
+ npx generateRestBindings --user root --pass password --host 127.0.0.1 --port 3306 --dbname carbonPHP --prefix carbon_ --output ./shared/rest/C6.ts
41
55
  ```
42
56
 
43
- You can view the [code generated](https://github.com/CarbonORM/CarbonORM.dev/blob/www/src/api/rest/Users.tsx) by
44
- [this command](https://github.com/CarbonORM/CarbonNode/blob/main/scripts/generateRestBindings.ts) in
45
- [this repository](git@github.com:CarbonORM/CarbonNode.git). We use [Handlebars templates](https://mustache.github.io/)
46
- to generate the code.
57
+ The generated file exports `C6`, `GLOBAL_REST_PARAMETERS`, `TABLES`, `ORM`, and per-table bindings (e.g. `Users`):
47
58
 
48
- ### Generated Tests
59
+ ```typescript
60
+ import { C6, GLOBAL_REST_PARAMETERS, Users } from "./shared/rest/C6";
61
+ ```
49
62
 
50
- Tests are generated for each table in the database. The tests are generated in the same directory as the models.
51
- Our Jest tests are not designed to run immediately. You will need to edit the tests manually to change *xdescribe* with just
52
- *describe*. Once a test does not have xdescribe it will no longer be updated with new generation changes.
63
+ You can view the generator source in
64
+ [CarbonNode](https://github.com/CarbonORM/CarbonNode/blob/main/scripts/generateRestBindings.ts). We use
65
+ [Handlebars templates](https://mustache.github.io/) to generate the code.
53
66
 
54
- Note - I prefer to keep tests nested in my IDE project viewer. See the documentation for
55
- [IntelliJ](https://www.jetbrains.com/help/idea/file-nesting-dialog.html) or
56
- [VSCode](https://code.visualstudio.com/updates/v1_67#_explorer-file-nesting).
67
+ ### Runtime Setup
57
68
 
58
- ### Templates
69
+ CarbonNode executes SQL directly when `GLOBAL_REST_PARAMETERS.mysqlPool` is provided. If no pool is set, it will use
70
+ the HTTP executor (useful for frontends or non-Node runtimes).
59
71
 
60
- Three templates are used to generate the models. The output will be multiple files; two files for each table in the
61
- database consisting of your GET PUT POST and DELETE methods and a Jest test file, a C6.tsx file which contains all
62
- table information and TypeScript types, and finally a websocket file which contains references to methods that are
63
- generate. Here are the templates used to generate the code:
72
+ ```typescript
73
+ import mysql from "mysql2/promise";
74
+ import { GLOBAL_REST_PARAMETERS } from "./shared/rest/C6";
75
+
76
+ GLOBAL_REST_PARAMETERS.mysqlPool = mysql.createPool({
77
+ host: "127.0.0.1",
78
+ user: "root",
79
+ password: "password",
80
+ database: "carbonPHP",
81
+ });
64
82
 
65
- 1) [C6.ts.handlebars](https://github.com/CarbonORM/CarbonNode/blob/main/scripts/assets/handlebars/C6.ts.handlebars)
66
- 2) [Table.ts.handlebars](https://github.com/CarbonORM/CarbonNode/blob/main/scripts/assets/handlebars/Table.ts.handlebars)
67
- 3) [Websocket.ts.handlebars](https://github.com/CarbonORM/CarbonNode/blob/main/scripts/assets/handlebars/C6RestApi.ts.handlebars)
83
+ // Optional HTTP path:
84
+ // GLOBAL_REST_PARAMETERS.axios = axiosInstance;
85
+ // GLOBAL_REST_PARAMETERS.restURL = "/rest/";
68
86
 
69
- #### Generation Example
87
+ // Optional websocket broadcast on writes:
88
+ // GLOBAL_REST_PARAMETERS.websocketBroadcast = (payload) => wsServer.broadcast(JSON.stringify(payload));
89
+ ```
70
90
 
71
- 0) **npx generateRestBindings** is executed.
72
- 1) **The MySQL dump tool** outputs a strcture for every table.
73
- ```sql
74
- --
75
- -- Table structure for table `carbon_users`
76
- --
77
-
78
- DROP TABLE IF EXISTS `carbon_users`;
79
- /*!40101 SET @saved_cs_client = @@character_set_client */;
80
- /*!50503 SET character_set_client = utf8mb4 */;
81
- CREATE TABLE `carbon_users` (
82
- `user_username` varchar(100) NOT NULL,
83
- `user_password` varchar(225) NOT NULL,
84
- `user_id` binary(16) NOT NULL,
85
- `user_type` varchar(20) NOT NULL DEFAULT 'Athlete',
86
- `user_sport` varchar(20) DEFAULT 'GOLF',
87
- `user_session_id` varchar(225) DEFAULT NULL,
88
- `user_facebook_id` varchar(225) DEFAULT NULL,
89
- `user_first_name` varchar(25) NOT NULL,
90
- `user_last_name` varchar(25) NOT NULL,
91
- `user_profile_pic` varchar(225) DEFAULT NULL,
92
- `user_profile_uri` varchar(225) DEFAULT NULL,
93
- `user_cover_photo` varchar(225) DEFAULT NULL,
94
- `user_birthday` varchar(9) DEFAULT NULL,
95
- `user_gender` varchar(25) DEFAULT NULL,
96
- `user_about_me` varchar(225) DEFAULT NULL,
97
- `user_rank` int DEFAULT '0',
98
- `user_email` varchar(50) NOT NULL,
99
- `user_email_code` varchar(225) DEFAULT NULL,
100
- `user_email_confirmed` tinyint DEFAULT '0' COMMENT 'need to change to enums, but no support in rest yet',
101
- `user_generated_string` varchar(200) DEFAULT NULL,
102
- `user_membership` int DEFAULT '0',
103
- `user_deactivated` tinyint DEFAULT '0',
104
- `user_last_login` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
105
- `user_ip` varchar(20) NOT NULL,
106
- `user_education_history` varchar(200) DEFAULT NULL,
107
- `user_location` varchar(20) DEFAULT NULL,
108
- `user_creation_date` datetime DEFAULT CURRENT_TIMESTAMP,
109
- PRIMARY KEY (`user_id`),
110
- UNIQUE KEY `carbon_users_user_username_uindex` (`user_username`),
111
- UNIQUE KEY `user_user_profile_uri_uindex` (`user_profile_uri`),
112
- UNIQUE KEY `carbon_users_user_facebook_id_uindex` (`user_facebook_id`),
113
- CONSTRAINT `user_entity_entity_pk_fk` FOREIGN KEY (`user_id`) REFERENCES `carbon_carbons` (`entity_pk`) ON DELETE CASCADE ON UPDATE CASCADE
114
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
115
- /*!40101 SET character_set_client = @saved_cs_client */;
91
+ ### Request Flow
92
+
93
+ ```mermaid
94
+ flowchart LR
95
+ Client["App code\nActor.Get(...)" ] --> RestRequest["restOrm + restRequest"]
96
+ RestRequest -->|"Node + mysqlPool"| SqlExec["SqlExecutor"] --> MySQL[("MySQL")]
97
+ RestRequest -->|"No pool"| HttpExec["HttpExecutor"] --> RestApi["/rest/:table"]
98
+ RestApi --> Express["ExpressHandler"] --> SqlExec
116
99
  ```
117
- 3) **Profit**
118
- - C6 will produce 1-1 constants.
100
+
101
+ ### SQL Allowlist
102
+
103
+ To restrict which SQL statements can run in production, set `GLOBAL_REST_PARAMETERS.sqlAllowListPath` to a JSON file
104
+ containing allowed SQL strings. When the path is set, `SqlExecutor` normalizes whitespace and validates each query against
105
+ the allowlist. If the file is missing, an error is thrown; if the SQL is not listed, execution is blocked.
106
+
119
107
  ```typescript
120
- export interface iUsers {
121
- 'user_username'?: string;
122
- 'user_password'?: string;
123
- 'user_id'?: string;
124
- 'user_type'?: string;
125
- 'user_sport'?: string | null;
126
- 'user_session_id'?: string | null;
127
- 'user_facebook_id'?: string | null;
128
- 'user_first_name'?: string;
129
- 'user_last_name'?: string;
130
- 'user_profile_pic'?: string | null;
131
- 'user_profile_uri'?: string | null;
132
- 'user_cover_photo'?: string | null;
133
- 'user_birthday'?: string | null;
134
- 'user_gender'?: string | null;
135
- 'user_about_me'?: string | null;
136
- 'user_rank'?: number | null;
137
- 'user_email'?: string;
138
- 'user_email_code'?: string | null;
139
- 'user_email_confirmed'?: number | null;
140
- 'user_generated_string'?: string | null;
141
- 'user_membership'?: number | null;
142
- 'user_deactivated'?: number | null;
143
- 'user_last_login'?: string;
144
- 'user_ip'?: string;
145
- 'user_education_history'?: string | null;
146
- 'user_location'?: string | null;
147
- 'user_creation_date'?: string | null;
148
- }
108
+ GLOBAL_REST_PARAMETERS.sqlAllowListPath = "/path/to/sqlAllowList.json";
109
+ ```
149
110
 
150
- ### Derived table joins
111
+ Allowlist format:
151
112
 
152
- The C6 query builder now supports joining derived tables (subselects) so you can project
153
- single-row lookups and reuse their fields elsewhere in the query. Wrap the derived table
154
- definition with `derivedTable(...)` to register it, then reference it inside the JOIN tree:
113
+ ```json
114
+ [
115
+ "SELECT * FROM `actor` LIMIT 1"
116
+ ]
117
+ ```
155
118
 
156
- ```ts
157
- import { C6C } from "@carbonorm/carbonnode";
158
- import { derivedTable, F } from "@carbonorm/carbonnode/api/orm/queryHelpers";
119
+ Generated tests in `src/__tests__/sakila-db/C6.test.ts` write response fixtures into `src/__tests__/sakila-db/sqlResponses/`
120
+ and compile `src/__tests__/sakila-db/C6.sqlAllowList.json` after the suite finishes. Pass that file path to enable
121
+ validation.
159
122
 
160
- const puTarget = derivedTable({
161
- [C6C.SUBSELECT]: {
162
- [C6C.SELECT]: [Property_Units.LOCATION],
163
- [C6C.FROM]: Property_Units.TABLE_NAME,
164
- [C6C.WHERE]: { [Property_Units.UNIT_ID]: [C6C.EQUAL, unitIdParam] },
165
- [C6C.LIMIT]: 1,
166
- },
167
- [C6C.AS]: 'pu_target',
168
- });
123
+ When using the REST handler directly, forward the path as well:
169
124
 
170
- const query = {
171
- [C6C.SELECT]: [
172
- Property_Units.UNIT_ID,
173
- Property_Units.LOCATION,
174
- F(Property_Units.LOCATION, 'pu_target'),
175
- ],
176
- [C6C.JOIN]: {
177
- [C6C.INNER]: {
178
- 'parcel_sales ps': { 'ps.parcel_id': [C6C.EQUAL, Property_Units.PARCEL_ID] },
179
- [puTarget as any]: {},
180
- },
181
- },
182
- [C6C.PAGINATION]: {
183
- [C6C.ORDER]: {
184
- [C6C.ST_DISTANCE_SPHERE]: [
185
- Property_Units.LOCATION,
186
- F(Property_Units.LOCATION, 'pu_target'),
187
- ],
188
- },
189
- },
190
- };
125
+ ```typescript
126
+ app.all("/rest/:table", ExpressHandler({ C6, mysqlPool, sqlAllowListPath }));
191
127
  ```
192
128
 
193
- Parameters from the subselect are hoisted ahead of the outer query’s bindings and the alias
194
- (`pu_target` in the example above) is available to `F(...)`, WHERE, ORDER BY, and other
195
- expressions.
196
-
197
- interface iDefineUsers {
198
- 'USER_USERNAME': string;
199
- 'USER_PASSWORD': string;
200
- 'USER_ID': string;
201
- 'USER_TYPE': string;
202
- 'USER_SPORT': string;
203
- 'USER_SESSION_ID': string;
204
- 'USER_FACEBOOK_ID': string;
205
- 'USER_FIRST_NAME': string;
206
- 'USER_LAST_NAME': string;
207
- 'USER_PROFILE_PIC': string;
208
- 'USER_PROFILE_URI': string;
209
- 'USER_COVER_PHOTO': string;
210
- 'USER_BIRTHDAY': string;
211
- 'USER_GENDER': string;
212
- 'USER_ABOUT_ME': string;
213
- 'USER_RANK': string;
214
- 'USER_EMAIL': string;
215
- 'USER_EMAIL_CODE': string;
216
- 'USER_EMAIL_CONFIRMED': string;
217
- 'USER_GENERATED_STRING': string;
218
- 'USER_MEMBERSHIP': string;
219
- 'USER_DEACTIVATED': string;
220
- 'USER_LAST_LOGIN': string;
221
- 'USER_IP': string;
222
- 'USER_EDUCATION_HISTORY': string;
223
- 'USER_LOCATION': string;
224
- 'USER_CREATION_DATE': string;
129
+ ### Generated Tests
130
+
131
+ The generator also writes `C6.test.ts` alongside `C6.ts`. Tests use Vitest and the generated bindings. Keep or delete the
132
+ file depending on your workflow.
133
+
134
+ The generator also writes `C6.MySqlDump.json`, `C6.mysqldump.sql`, and `C6.mysql.cnf` into the same output directory for
135
+ debugging and inspection.
136
+
137
+ ### Templates
138
+
139
+ Two templates are used to generate the output:
140
+
141
+ 1) [C6.ts.handlebars](https://github.com/CarbonORM/CarbonNode/blob/main/scripts/assets/handlebars/C6.ts.handlebars)
142
+ 2) [C6.test.ts.handlebars](https://github.com/CarbonORM/CarbonNode/blob/main/scripts/assets/handlebars/C6.test.ts.handlebars)
143
+
144
+ #### Generation Example
145
+
146
+ 0) **npx generateRestBindings** is executed.
147
+ 1) **The MySQL dump tool** outputs a structure for every table.
148
+
149
+ ```mysql
150
+ CREATE TABLE actor (
151
+ actor_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
152
+ first_name VARCHAR(45) NOT NULL,
153
+ last_name VARCHAR(45) NOT NULL,
154
+ last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
155
+ PRIMARY KEY (actor_id),
156
+ KEY idx_actor_last_name (last_name)
157
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
158
+ ```
159
+
160
+ 2) **The generator** parses the table structure and creates an internal representation.
161
+ ```typescript
162
+ export interface iActor {
163
+ 'actor_id'?: number;
164
+ 'first_name'?: string;
165
+ 'last_name'?: string;
166
+ 'last_update'?: Date | number | string;
225
167
  }
226
168
 
227
- export const users: iC6RestfulModel<RestTableNames> & iDefineUsers = {
228
- TABLE_NAME: 'carbon_users',
229
- USER_USERNAME: 'carbon_users.user_username',
230
- USER_PASSWORD: 'carbon_users.user_password',
231
- USER_ID: 'carbon_users.user_id',
232
- USER_TYPE: 'carbon_users.user_type',
233
- USER_SPORT: 'carbon_users.user_sport',
234
- USER_SESSION_ID: 'carbon_users.user_session_id',
235
- USER_FACEBOOK_ID: 'carbon_users.user_facebook_id',
236
- USER_FIRST_NAME: 'carbon_users.user_first_name',
237
- USER_LAST_NAME: 'carbon_users.user_last_name',
238
- USER_PROFILE_PIC: 'carbon_users.user_profile_pic',
239
- USER_PROFILE_URI: 'carbon_users.user_profile_uri',
240
- USER_COVER_PHOTO: 'carbon_users.user_cover_photo',
241
- USER_BIRTHDAY: 'carbon_users.user_birthday',
242
- USER_GENDER: 'carbon_users.user_gender',
243
- USER_ABOUT_ME: 'carbon_users.user_about_me',
244
- USER_RANK: 'carbon_users.user_rank',
245
- USER_EMAIL: 'carbon_users.user_email',
246
- USER_EMAIL_CODE: 'carbon_users.user_email_code',
247
- USER_EMAIL_CONFIRMED: 'carbon_users.user_email_confirmed',
248
- USER_GENERATED_STRING: 'carbon_users.user_generated_string',
249
- USER_MEMBERSHIP: 'carbon_users.user_membership',
250
- USER_DEACTIVATED: 'carbon_users.user_deactivated',
251
- USER_LAST_LOGIN: 'carbon_users.user_last_login',
252
- USER_IP: 'carbon_users.user_ip',
253
- USER_EDUCATION_HISTORY: 'carbon_users.user_education_history',
254
- USER_LOCATION: 'carbon_users.user_location',
255
- USER_CREATION_DATE: 'carbon_users.user_creation_date',
169
+ export type ActorPrimaryKeys =
170
+ 'actor_id'
171
+ ;
172
+
173
+ const actor:
174
+ C6RestfulModel<
175
+ 'actor',
176
+ iActor,
177
+ ActorPrimaryKeys
178
+ > = {
179
+ TABLE_NAME: 'actor',
180
+ ACTOR_ID: 'actor.actor_id',
181
+ FIRST_NAME: 'actor.first_name',
182
+ LAST_NAME: 'actor.last_name',
183
+ LAST_UPDATE: 'actor.last_update',
256
184
  PRIMARY: [
257
- 'carbon_users.user_id',
185
+ 'actor.actor_id',
258
186
  ],
259
187
  PRIMARY_SHORT: [
260
- 'user_id',
188
+ 'actor_id',
261
189
  ],
262
190
  COLUMNS: {
263
- 'carbon_users.user_username': 'user_username',
264
- 'carbon_users.user_password': 'user_password',
265
- 'carbon_users.user_id': 'user_id',
266
- 'carbon_users.user_type': 'user_type',
267
- 'carbon_users.user_sport': 'user_sport',
268
- 'carbon_users.user_session_id': 'user_session_id',
269
- 'carbon_users.user_facebook_id': 'user_facebook_id',
270
- 'carbon_users.user_first_name': 'user_first_name',
271
- 'carbon_users.user_last_name': 'user_last_name',
272
- 'carbon_users.user_profile_pic': 'user_profile_pic',
273
- 'carbon_users.user_profile_uri': 'user_profile_uri',
274
- 'carbon_users.user_cover_photo': 'user_cover_photo',
275
- 'carbon_users.user_birthday': 'user_birthday',
276
- 'carbon_users.user_gender': 'user_gender',
277
- 'carbon_users.user_about_me': 'user_about_me',
278
- 'carbon_users.user_rank': 'user_rank',
279
- 'carbon_users.user_email': 'user_email',
280
- 'carbon_users.user_email_code': 'user_email_code',
281
- 'carbon_users.user_email_confirmed': 'user_email_confirmed',
282
- 'carbon_users.user_generated_string': 'user_generated_string',
283
- 'carbon_users.user_membership': 'user_membership',
284
- 'carbon_users.user_deactivated': 'user_deactivated',
285
- 'carbon_users.user_last_login': 'user_last_login',
286
- 'carbon_users.user_ip': 'user_ip',
287
- 'carbon_users.user_education_history': 'user_education_history',
288
- 'carbon_users.user_location': 'user_location',
289
- 'carbon_users.user_creation_date': 'user_creation_date',
191
+ 'actor.actor_id': 'actor_id',
192
+ 'actor.first_name': 'first_name',
193
+ 'actor.last_name': 'last_name',
194
+ 'actor.last_update': 'last_update',
290
195
  },
291
196
  TYPE_VALIDATION: {
292
- 'carbon_users.user_username': {
293
- MYSQL_TYPE: 'varchar',
294
- MAX_LENGTH: '100',
295
- AUTO_INCREMENT: false,
296
- SKIP_COLUMN_IN_POST: false
297
- },
298
- 'carbon_users.user_password': {
299
- MYSQL_TYPE: 'varchar',
300
- MAX_LENGTH: '225',
301
- AUTO_INCREMENT: false,
302
- SKIP_COLUMN_IN_POST: false
303
- },
304
- 'carbon_users.user_id': {
305
- MYSQL_TYPE: 'binary',
306
- MAX_LENGTH: '16',
307
- AUTO_INCREMENT: false,
308
- SKIP_COLUMN_IN_POST: false
309
- },
310
- 'carbon_users.user_type': {
311
- MYSQL_TYPE: 'varchar',
312
- MAX_LENGTH: '20',
313
- AUTO_INCREMENT: false,
314
- SKIP_COLUMN_IN_POST: false
315
- },
316
- 'carbon_users.user_sport': {
317
- MYSQL_TYPE: 'varchar',
318
- MAX_LENGTH: '20',
319
- AUTO_INCREMENT: false,
320
- SKIP_COLUMN_IN_POST: false
321
- },
322
- 'carbon_users.user_session_id': {
323
- MYSQL_TYPE: 'varchar',
324
- MAX_LENGTH: '225',
325
- AUTO_INCREMENT: false,
326
- SKIP_COLUMN_IN_POST: false
327
- },
328
- 'carbon_users.user_facebook_id': {
329
- MYSQL_TYPE: 'varchar',
330
- MAX_LENGTH: '225',
331
- AUTO_INCREMENT: false,
332
- SKIP_COLUMN_IN_POST: false
333
- },
334
- 'carbon_users.user_first_name': {
335
- MYSQL_TYPE: 'varchar',
336
- MAX_LENGTH: '25',
337
- AUTO_INCREMENT: false,
338
- SKIP_COLUMN_IN_POST: false
339
- },
340
- 'carbon_users.user_last_name': {
341
- MYSQL_TYPE: 'varchar',
342
- MAX_LENGTH: '25',
343
- AUTO_INCREMENT: false,
344
- SKIP_COLUMN_IN_POST: false
345
- },
346
- 'carbon_users.user_profile_pic': {
347
- MYSQL_TYPE: 'varchar',
348
- MAX_LENGTH: '225',
349
- AUTO_INCREMENT: false,
350
- SKIP_COLUMN_IN_POST: false
351
- },
352
- 'carbon_users.user_profile_uri': {
353
- MYSQL_TYPE: 'varchar',
354
- MAX_LENGTH: '225',
355
- AUTO_INCREMENT: false,
356
- SKIP_COLUMN_IN_POST: false
357
- },
358
- 'carbon_users.user_cover_photo': {
359
- MYSQL_TYPE: 'varchar',
360
- MAX_LENGTH: '225',
361
- AUTO_INCREMENT: false,
362
- SKIP_COLUMN_IN_POST: false
363
- },
364
- 'carbon_users.user_birthday': {
365
- MYSQL_TYPE: 'varchar',
366
- MAX_LENGTH: '9',
367
- AUTO_INCREMENT: false,
368
- SKIP_COLUMN_IN_POST: false
369
- },
370
- 'carbon_users.user_gender': {
371
- MYSQL_TYPE: 'varchar',
372
- MAX_LENGTH: '25',
373
- AUTO_INCREMENT: false,
374
- SKIP_COLUMN_IN_POST: false
375
- },
376
- 'carbon_users.user_about_me': {
377
- MYSQL_TYPE: 'varchar',
378
- MAX_LENGTH: '225',
379
- AUTO_INCREMENT: false,
380
- SKIP_COLUMN_IN_POST: false
381
- },
382
- 'carbon_users.user_rank': {
383
- MYSQL_TYPE: 'int',
384
- MAX_LENGTH: '',
385
- AUTO_INCREMENT: false,
386
- SKIP_COLUMN_IN_POST: false
387
- },
388
- 'carbon_users.user_email': {
389
- MYSQL_TYPE: 'varchar',
390
- MAX_LENGTH: '50',
391
- AUTO_INCREMENT: false,
392
- SKIP_COLUMN_IN_POST: false
393
- },
394
- 'carbon_users.user_email_code': {
395
- MYSQL_TYPE: 'varchar',
396
- MAX_LENGTH: '225',
397
- AUTO_INCREMENT: false,
398
- SKIP_COLUMN_IN_POST: false
399
- },
400
- 'carbon_users.user_email_confirmed': {
401
- MYSQL_TYPE: 'tinyint',
402
- MAX_LENGTH: '',
403
- AUTO_INCREMENT: false,
404
- SKIP_COLUMN_IN_POST: false
405
- },
406
- 'carbon_users.user_generated_string': {
407
- MYSQL_TYPE: 'varchar',
408
- MAX_LENGTH: '200',
409
- AUTO_INCREMENT: false,
410
- SKIP_COLUMN_IN_POST: false
411
- },
412
- 'carbon_users.user_membership': {
413
- MYSQL_TYPE: 'int',
414
- MAX_LENGTH: '',
415
- AUTO_INCREMENT: false,
416
- SKIP_COLUMN_IN_POST: false
417
- },
418
- 'carbon_users.user_deactivated': {
419
- MYSQL_TYPE: 'tinyint',
420
- MAX_LENGTH: '',
421
- AUTO_INCREMENT: false,
422
- SKIP_COLUMN_IN_POST: false
423
- },
424
- 'carbon_users.user_last_login': {
425
- MYSQL_TYPE: 'datetime',
197
+ 'actor.actor_id': {
198
+ MYSQL_TYPE: 'smallint',
426
199
  MAX_LENGTH: '',
427
- AUTO_INCREMENT: false,
200
+ AUTO_INCREMENT: true,
428
201
  SKIP_COLUMN_IN_POST: false
429
202
  },
430
- 'carbon_users.user_ip': {
203
+ 'actor.first_name': {
431
204
  MYSQL_TYPE: 'varchar',
432
- MAX_LENGTH: '20',
205
+ MAX_LENGTH: '45',
433
206
  AUTO_INCREMENT: false,
434
207
  SKIP_COLUMN_IN_POST: false
435
208
  },
436
- 'carbon_users.user_education_history': {
209
+ 'actor.last_name': {
437
210
  MYSQL_TYPE: 'varchar',
438
- MAX_LENGTH: '200',
211
+ MAX_LENGTH: '45',
439
212
  AUTO_INCREMENT: false,
440
213
  SKIP_COLUMN_IN_POST: false
441
214
  },
442
- 'carbon_users.user_location': {
443
- MYSQL_TYPE: 'varchar',
444
- MAX_LENGTH: '20',
445
- AUTO_INCREMENT: false,
446
- SKIP_COLUMN_IN_POST: false
447
- },
448
- 'carbon_users.user_creation_date': {
449
- MYSQL_TYPE: 'datetime',
215
+ 'actor.last_update': {
216
+ MYSQL_TYPE: 'timestamp',
450
217
  MAX_LENGTH: '',
451
218
  AUTO_INCREMENT: false,
452
219
  SKIP_COLUMN_IN_POST: false
@@ -454,160 +221,133 @@ export const users: iC6RestfulModel<RestTableNames> & iDefineUsers = {
454
221
  },
455
222
  REGEX_VALIDATION: {
456
223
  },
224
+ LIFECYCLE_HOOKS: {
225
+ GET: {beforeProcessing:{}, beforeExecution:{}, afterExecution:{}, afterCommit:{}},
226
+ PUT: {beforeProcessing:{}, beforeExecution:{}, afterExecution:{}, afterCommit:{}},
227
+ POST: {beforeProcessing:{}, beforeExecution:{}, afterExecution:{}, afterCommit:{}},
228
+ DELETE: {beforeProcessing:{}, beforeExecution:{}, afterExecution:{}, afterCommit:{}},
229
+ },
457
230
  TABLE_REFERENCES: {
458
- 'user_id': [{
459
- TABLE: 'carbon_carbons',
460
- COLUMN: 'entity_pk',
461
- CONSTRAINT: 'user_entity_entity_pk_fk',
462
- },],
231
+
463
232
  },
464
233
  TABLE_REFERENCED_BY: {
465
-
234
+ 'actor_id': [{
235
+ TABLE: 'film_actor',
236
+ COLUMN: 'actor_id',
237
+ CONSTRAINT: 'fk_film_actor_actor',
238
+ },],
466
239
  }
467
240
  }
241
+
242
+ export const Actor = {
243
+ ...actor,
244
+ ...restOrm<
245
+ OrmGenerics<any, 'actor', iActor, ActorPrimaryKeys>
246
+ >(() => ({
247
+ ...GLOBAL_REST_PARAMETERS,
248
+ restModel: actor
249
+ }))
250
+ }
468
251
  ```
469
- - A File named the pascal case formated table name will be created with bindings to query the middleware (backend langague) -> MySQL.
252
+
253
+ 3) **Profit**
254
+ You import from the frontend or backend using the same syntax:
255
+
470
256
  ```typescript
471
- import {AxiosResponse} from "axios";
472
- import {
473
- iPostC6RestResponse,
474
- restRequest,
475
- GET,
476
- POST,
477
- PUT,
478
- DELETE,
479
- iDeleteC6RestResponse,
480
- iGetC6RestResponse,
481
- iPutC6RestResponse,
482
- removeInvalidKeys,
483
- iAPI,
484
- Modify
485
- } from "@carbonorm/carbonnode";
486
- import {deleteRestfulObjectArrays, updateRestfulObjectArrays} from "@carbonorm/carbonreact";
487
- import {C6, iUsers, users, RestTableNames} from "./C6";
488
-
489
- type GetCustomAndRequiredFields = {}
490
-
491
- type GetRequestTableOverrides = {}
492
-
493
- // required parameters, optional parameters, parameter type overrides, response, and table names
494
- const Get = restRequest<GetCustomAndRequiredFields, iUsers, GetRequestTableOverrides, iGetC6RestResponse<iUsers>, RestTableNames>({
495
- C6: C6,
496
- tableName: users.TABLE_NAME,
497
- requestMethod: GET,
498
- queryCallback: (request) => {
499
- request.success ??= 'Successfully received users!'
500
- request.error ??= 'An unknown issue occurred creating the users!'
501
- return request
257
+ import { Actor, C6 } from "./shared/rest/C6";
258
+
259
+ // GET
260
+ const actors = await Actor.Get({
261
+ [C6.SELECT]: [
262
+ Actor.ACTOR_ID,
263
+ Actor.FIRST_NAME,
264
+ Actor.LAST_NAME,
265
+ ],
266
+ [C6.WHERE]: {
267
+ [Actor.LAST_NAME]: { like: "%PITT%" },
502
268
  },
503
- responseCallback: (response, _request) => {
504
- const responseData = response?.data?.rest;
505
- updateRestfulObjectArrays<iUsers>(Array.isArray(responseData) ? responseData : [responseData], "users", C6.users.PRIMARY_SHORT as (keyof iUsers)[])
506
- }
269
+ [C6.PAGINATION]: { [C6.LIMIT]: 10 },
507
270
  });
508
271
 
509
- type PutCustomAndRequiredFields = {}
510
-
511
- type PutRequestTableOverrides = {}
272
+ // POST
273
+ await Actor.Post({
274
+ [Actor.FIRST_NAME]: "Brad",
275
+ [Actor.LAST_NAME]: "Pitt",
276
+ });
512
277
 
513
- export function putStateUsers(response : AxiosResponse<iPutC6RestResponse<iUsers>>, request : iAPI<Modify<iUsers, PutRequestTableOverrides>> & PutCustomAndRequiredFields) {
514
- updateRestfulObjectArrays<iUsers>([
515
- removeInvalidKeys<iUsers>({
516
- ...request,
517
- ...response?.data?.rest,
518
- }, C6.TABLES)
519
- ], "users", users.PRIMARY_SHORT as (keyof iUsers)[])
520
- }
278
+ // PUT (singular)
279
+ await Actor.Put({
280
+ [Actor.ACTOR_ID]: 42,
281
+ [Actor.LAST_NAME]: "Updated",
282
+ });
521
283
 
522
- const Put = restRequest<PutCustomAndRequiredFields, iUsers, PutRequestTableOverrides, iPutC6RestResponse<iUsers>, RestTableNames>({
523
- C6: C6,
524
- tableName: users.TABLE_NAME,
525
- requestMethod: PUT,
526
- queryCallback: (request) => {
527
- request.success ??= 'Successfully updated users data!'
528
- request.error ??= 'An unknown issue occurred updating the users data!'
529
- return request
530
- },
531
- responseCallback: putStateUsers
284
+ // DELETE (singular)
285
+ await Actor.Delete({
286
+ [Actor.ACTOR_ID]: 42,
532
287
  });
288
+ ```
533
289
 
534
- type PostCustomAndRequiredFields = {}
290
+ Example response payloads (HTTP executor):
535
291
 
536
- type PostRequestTableOverrides = {}
292
+ GET
537
293
 
538
- export function postStateUsers(response : AxiosResponse<iPostC6RestResponse<iUsers>>, request : iAPI<Modify<iUsers, PostRequestTableOverrides>> & PostCustomAndRequiredFields, id: string | number | boolean) {
539
- if ('number' === typeof id || 'string' === typeof id) {
540
- if (1 !== users.PRIMARY_SHORT.length) {
541
- console.error("C6 received unexpected result's given the primary key length");
542
- } else {
543
- request[users.PRIMARY_SHORT[0]] = id
544
- }
545
- }
546
- updateRestfulObjectArrays<iUsers>(
547
- undefined !== request.dataInsertMultipleRows
548
- ? request.dataInsertMultipleRows.map((request, index) => {
549
- return removeInvalidKeys<iUsers>({
550
- ...request,
551
- ...(index === 0 ? response?.data?.rest : {}),
552
- }, C6.TABLES)
553
- })
554
- : [
555
- removeInvalidKeys<iUsers>({
556
- ...request,
557
- ...response?.data?.rest,
558
- }, C6.TABLES)
559
- ],
560
- "users",
561
- users.PRIMARY_SHORT as (keyof iUsers)[]
562
- )
294
+ ```json
295
+ {
296
+ "success": true,
297
+ "rest": [
298
+ { "actor_id": 1, "first_name": "PENELOPE", "last_name": "GUINESS" }
299
+ ],
300
+ "next": "Function"
563
301
  }
302
+ ```
564
303
 
565
- const Post = restRequest<PostCustomAndRequiredFields, iUsers, PostRequestTableOverrides, iPostC6RestResponse<iUsers>, RestTableNames>({
566
- C6: C6,
567
- tableName: users.TABLE_NAME,
568
- requestMethod: POST,
569
- queryCallback: (request) => {
570
- request.success ??= 'Successfully created the users data!'
571
- request.error ??= 'An unknown issue occurred creating the users data!'
572
- return request
573
- },
574
- responseCallback: postStateUsers
575
- });
304
+ POST
576
305
 
577
- type DeleteCustomAndRequiredFields = {}
306
+ ```json
307
+ {
308
+ "success": true,
309
+ "created": 201,
310
+ "rest": { "actor_id": 201, "first_name": "Brad", "last_name": "Pitt" }
311
+ }
312
+ ```
578
313
 
579
- type DeleteRequestTableOverrides = {}
314
+ PUT
580
315
 
581
- export function deleteStateUsers(_response : AxiosResponse<iDeleteC6RestResponse<iUsers>>, request : iAPI<Modify<iUsers, DeleteRequestTableOverrides>> & DeleteCustomAndRequiredFields) {
582
- deleteRestfulObjectArrays<iUsers>([
583
- request
584
- ], "users", users.PRIMARY_SHORT as (keyof iUsers)[])
316
+ ```json
317
+ {
318
+ "success": true,
319
+ "updated": true,
320
+ "rest": { "actor_id": 42, "last_name": "Updated" }
585
321
  }
322
+ ```
586
323
 
587
- const Delete = restRequest<DeleteCustomAndRequiredFields, iUsers, DeleteRequestTableOverrides, iDeleteC6RestResponse<iUsers>, RestTableNames>({
588
- C6: C6,
589
- tableName: users.TABLE_NAME,
590
- requestMethod: DELETE,
591
- queryCallback: (request) => {
592
- request.success ??= 'Successfully removed the users data!'
593
- request.error ??= 'An unknown issue occurred removing the users data!'
594
- return request
595
- },
596
- responseCallback: deleteStateUsers
597
- });
324
+ DELETE
598
325
 
599
- const Users = {
600
- // Export all GET, POST, PUT, DELETE functions for each table
601
- Get,
602
- Post,
603
- Put,
604
- Delete,
326
+ ```json
327
+ {
328
+ "success": true,
329
+ "deleted": true,
330
+ "rest": { "actor_id": 42 }
605
331
  }
606
-
607
- export default Users;
608
332
  ```
609
333
 
334
+ SQL executor responses omit `success` and include `sql` for GETs plus `affected` for writes. Express responses from `ExpressHandler` add `success: true`.
335
+
336
+ SQL executor example (GET):
610
337
 
338
+ ```json
339
+ {
340
+ "rest": [
341
+ { "actor_id": 1, "first_name": "PENELOPE", "last_name": "GUINESS" }
342
+ ],
343
+ "sql": {
344
+ "sql": "SELECT * FROM `actor` LIMIT 10",
345
+ "values": []
346
+ }
347
+ }
348
+ ```
349
+
350
+ Our CarbonReact extends this solution for automatic state and pagination management.
611
351
 
612
352
 
613
353
  # Git Hooks
@@ -628,4 +368,3 @@ This will configure Git to use the hooks in the `.githooks` directory. The hooks
628
368
  # Support and Issues
629
369
 
630
370
  Any issues found should be reported on [GitHub](https://github.com/CarbonORM/CarbonNode/issues).
631
-