@servicenow/sdk-build-plugins 3.0.2 → 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 (359) hide show
  1. package/dist/acl-plugin.d.ts +19 -0
  2. package/dist/acl-plugin.js +210 -0
  3. package/dist/acl-plugin.js.map +1 -0
  4. package/dist/application-menu-plugin.d.ts +18 -0
  5. package/dist/application-menu-plugin.js +104 -0
  6. package/dist/application-menu-plugin.js.map +1 -0
  7. package/dist/arrow-function-plugin.d.ts +16 -0
  8. package/dist/arrow-function-plugin.js +107 -0
  9. package/dist/arrow-function-plugin.js.map +1 -0
  10. package/dist/atf/step-configs.d.ts +37 -0
  11. package/dist/atf/step-configs.js +2236 -0
  12. package/dist/atf/step-configs.js.map +1 -0
  13. package/dist/atf/test-plugin.d.ts +4 -0
  14. package/dist/atf/test-plugin.js +591 -0
  15. package/dist/atf/test-plugin.js.map +1 -0
  16. package/dist/basic-syntax-plugin.d.ts +2 -0
  17. package/dist/basic-syntax-plugin.js +636 -0
  18. package/dist/basic-syntax-plugin.js.map +1 -0
  19. package/dist/business-rule-plugin.d.ts +3 -0
  20. package/dist/business-rule-plugin.js +205 -0
  21. package/dist/business-rule-plugin.js.map +1 -0
  22. package/dist/call-expression-plugin.d.ts +2 -0
  23. package/dist/call-expression-plugin.js +175 -0
  24. package/dist/call-expression-plugin.js.map +1 -0
  25. package/dist/client-script-plugin.d.ts +29 -0
  26. package/dist/client-script-plugin.js +157 -0
  27. package/dist/client-script-plugin.js.map +1 -0
  28. package/dist/column/column-helper.d.ts +12 -0
  29. package/dist/column/column-helper.js +84 -0
  30. package/dist/column/column-helper.js.map +1 -0
  31. package/dist/column/column-to-record.d.ts +3 -0
  32. package/dist/column/column-to-record.js +105 -0
  33. package/dist/column/column-to-record.js.map +1 -0
  34. package/dist/column-plugin.d.ts +8 -0
  35. package/dist/column-plugin.js +453 -0
  36. package/dist/column-plugin.js.map +1 -0
  37. package/dist/cross-scope-privilege-plugin.d.ts +15 -0
  38. package/dist/cross-scope-privilege-plugin.js +82 -0
  39. package/dist/cross-scope-privilege-plugin.js.map +1 -0
  40. package/dist/html-import-plugin.d.ts +2 -0
  41. package/dist/html-import-plugin.js +72 -0
  42. package/dist/html-import-plugin.js.map +1 -0
  43. package/dist/index.d.ts +37 -21
  44. package/dist/index.js +40 -45
  45. package/dist/index.js.map +1 -1
  46. package/dist/json-plugin.d.ts +15 -0
  47. package/dist/json-plugin.js +104 -0
  48. package/dist/json-plugin.js.map +1 -0
  49. package/dist/list-plugin.d.ts +2 -0
  50. package/dist/list-plugin.js +191 -0
  51. package/dist/list-plugin.js.map +1 -0
  52. package/dist/now-config-plugin.d.ts +4 -0
  53. package/dist/now-config-plugin.js +154 -0
  54. package/dist/now-config-plugin.js.map +1 -0
  55. package/dist/now-id-plugin.d.ts +11 -0
  56. package/dist/now-id-plugin.js +71 -0
  57. package/dist/now-id-plugin.js.map +1 -0
  58. package/dist/now-include-plugin.d.ts +16 -0
  59. package/dist/now-include-plugin.js +118 -0
  60. package/dist/now-include-plugin.js.map +1 -0
  61. package/dist/now-ref-plugin.d.ts +2 -0
  62. package/dist/now-ref-plugin.js +43 -0
  63. package/dist/now-ref-plugin.js.map +1 -0
  64. package/dist/now-unresolved-plugin.d.ts +2 -0
  65. package/dist/now-unresolved-plugin.js +32 -0
  66. package/dist/now-unresolved-plugin.js.map +1 -0
  67. package/dist/package-json-plugin.d.ts +2 -0
  68. package/dist/package-json-plugin.js +70 -0
  69. package/dist/package-json-plugin.js.map +1 -0
  70. package/dist/property-plugin.d.ts +2 -0
  71. package/dist/property-plugin.js +105 -0
  72. package/dist/property-plugin.js.map +1 -0
  73. package/dist/record-plugin.d.ts +43 -0
  74. package/dist/record-plugin.js +147 -0
  75. package/dist/record-plugin.js.map +1 -0
  76. package/dist/repack/index.d.ts +1 -0
  77. package/dist/repack/index.js +10 -2
  78. package/dist/repack/index.js.map +1 -1
  79. package/dist/repack/lint/Rules.d.ts +1 -1
  80. package/dist/repack/lint/Rules.js +7 -3
  81. package/dist/repack/lint/Rules.js.map +1 -1
  82. package/dist/repack/lint/index.js +1 -1
  83. package/dist/rest-api-plugin.d.ts +2 -0
  84. package/dist/rest-api-plugin.js +469 -0
  85. package/dist/rest-api-plugin.js.map +1 -0
  86. package/dist/role-plugin.d.ts +2 -0
  87. package/dist/role-plugin.js +117 -0
  88. package/dist/role-plugin.js.map +1 -0
  89. package/dist/script-action-plugin.d.ts +2 -0
  90. package/dist/script-action-plugin.js +62 -0
  91. package/dist/script-action-plugin.js.map +1 -0
  92. package/dist/script-include-plugin.d.ts +2 -0
  93. package/dist/script-include-plugin.js +102 -0
  94. package/dist/script-include-plugin.js.map +1 -0
  95. package/dist/server-module-plugin/index.d.ts +14 -0
  96. package/dist/server-module-plugin/index.js +378 -0
  97. package/dist/server-module-plugin/index.js.map +1 -0
  98. package/dist/server-module-plugin/sbom-builder.d.ts +45 -0
  99. package/dist/server-module-plugin/sbom-builder.js +179 -0
  100. package/dist/server-module-plugin/sbom-builder.js.map +1 -0
  101. package/dist/service-portal/angular-provider-plugin.d.ts +2 -0
  102. package/dist/service-portal/angular-provider-plugin.js +78 -0
  103. package/dist/service-portal/angular-provider-plugin.js.map +1 -0
  104. package/dist/service-portal/dependency-plugin.d.ts +2 -0
  105. package/dist/service-portal/dependency-plugin.js +235 -0
  106. package/dist/service-portal/dependency-plugin.js.map +1 -0
  107. package/dist/service-portal/widget-plugin.d.ts +6 -0
  108. package/dist/service-portal/widget-plugin.js +230 -0
  109. package/dist/service-portal/widget-plugin.js.map +1 -0
  110. package/dist/static-content-plugin.d.ts +2 -0
  111. package/dist/static-content-plugin.js +272 -0
  112. package/dist/static-content-plugin.js.map +1 -0
  113. package/dist/table-plugin.d.ts +3 -0
  114. package/dist/table-plugin.js +1324 -0
  115. package/dist/table-plugin.js.map +1 -0
  116. package/dist/ui-action-plugin.d.ts +2 -0
  117. package/dist/ui-action-plugin.js +291 -0
  118. package/dist/ui-action-plugin.js.map +1 -0
  119. package/dist/ui-page-plugin.d.ts +2 -0
  120. package/dist/ui-page-plugin.js +165 -0
  121. package/dist/ui-page-plugin.js.map +1 -0
  122. package/dist/user-preference-plugin.d.ts +15 -0
  123. package/dist/user-preference-plugin.js +63 -0
  124. package/dist/user-preference-plugin.js.map +1 -0
  125. package/dist/utils.d.ts +17 -0
  126. package/dist/utils.js +72 -0
  127. package/dist/utils.js.map +1 -0
  128. package/dist/view-plugin.d.ts +2 -0
  129. package/dist/view-plugin.js +45 -0
  130. package/dist/view-plugin.js.map +1 -0
  131. package/package.json +17 -13
  132. package/src/acl-plugin.ts +256 -0
  133. package/src/application-menu-plugin.ts +109 -0
  134. package/src/arrow-function-plugin.ts +127 -0
  135. package/src/atf/step-configs.ts +2284 -0
  136. package/src/atf/test-plugin.ts +728 -0
  137. package/src/basic-syntax-plugin.ts +729 -0
  138. package/src/business-rule-plugin.ts +266 -0
  139. package/src/call-expression-plugin.ts +202 -0
  140. package/src/client-script-plugin.ts +170 -0
  141. package/src/column/column-helper.ts +119 -0
  142. package/src/column/column-to-record.ts +131 -0
  143. package/src/column-plugin.ts +506 -0
  144. package/src/cross-scope-privilege-plugin.ts +82 -0
  145. package/src/html-import-plugin.ts +79 -0
  146. package/src/index.ts +39 -21
  147. package/src/json-plugin.ts +128 -0
  148. package/src/list-plugin.ts +222 -0
  149. package/src/now-config-plugin.ts +194 -0
  150. package/src/now-id-plugin.ts +78 -0
  151. package/src/now-include-plugin.ts +140 -0
  152. package/src/now-ref-plugin.ts +48 -0
  153. package/src/now-unresolved-plugin.ts +30 -0
  154. package/src/package-json-plugin.ts +87 -0
  155. package/src/property-plugin.ts +118 -0
  156. package/src/record-plugin.ts +171 -0
  157. package/src/repack/index.ts +10 -1
  158. package/src/repack/lint/Rules.ts +5 -4
  159. package/src/repack/lint/index.ts +1 -1
  160. package/src/rest-api-plugin.ts +651 -0
  161. package/src/role-plugin.ts +128 -0
  162. package/src/script-action-plugin.ts +63 -0
  163. package/src/script-include-plugin.ts +110 -0
  164. package/src/server-module-plugin/index.ts +470 -0
  165. package/src/server-module-plugin/sbom-builder.ts +183 -0
  166. package/src/service-portal/angular-provider-plugin.ts +83 -0
  167. package/src/service-portal/dependency-plugin.ts +284 -0
  168. package/src/service-portal/widget-plugin.ts +263 -0
  169. package/src/static-content-plugin.ts +254 -0
  170. package/src/table-plugin.ts +1698 -0
  171. package/src/ui-action-plugin.ts +324 -0
  172. package/src/ui-page-plugin.ts +168 -0
  173. package/src/user-preference-plugin.ts +62 -0
  174. package/src/utils.ts +69 -0
  175. package/src/view-plugin.ts +46 -0
  176. package/dist/AttachmentPlugin.d.ts +0 -254
  177. package/dist/AttachmentPlugin.js +0 -220
  178. package/dist/AttachmentPlugin.js.map +0 -1
  179. package/dist/BusinessRulePlugin.d.ts +0 -30
  180. package/dist/BusinessRulePlugin.js +0 -149
  181. package/dist/BusinessRulePlugin.js.map +0 -1
  182. package/dist/CrossScopePrivilegePlugin.d.ts +0 -35
  183. package/dist/CrossScopePrivilegePlugin.js +0 -80
  184. package/dist/CrossScopePrivilegePlugin.js.map +0 -1
  185. package/dist/DefaultPlugin.d.ts +0 -86
  186. package/dist/DefaultPlugin.js +0 -226
  187. package/dist/DefaultPlugin.js.map +0 -1
  188. package/dist/HtmlTemplatePlugin.d.ts +0 -21
  189. package/dist/HtmlTemplatePlugin.js +0 -29
  190. package/dist/HtmlTemplatePlugin.js.map +0 -1
  191. package/dist/IdPlugin.d.ts +0 -28
  192. package/dist/IdPlugin.js +0 -68
  193. package/dist/IdPlugin.js.map +0 -1
  194. package/dist/IncludePlugin.d.ts +0 -34
  195. package/dist/IncludePlugin.js +0 -155
  196. package/dist/IncludePlugin.js.map +0 -1
  197. package/dist/JsonPlugin.d.ts +0 -28
  198. package/dist/JsonPlugin.js +0 -69
  199. package/dist/JsonPlugin.js.map +0 -1
  200. package/dist/ListPlugin.d.ts +0 -93
  201. package/dist/ListPlugin.js +0 -456
  202. package/dist/ListPlugin.js.map +0 -1
  203. package/dist/NowConfigPlugin.d.ts +0 -45
  204. package/dist/NowConfigPlugin.js +0 -64
  205. package/dist/NowConfigPlugin.js.map +0 -1
  206. package/dist/PackageJsonPlugin.d.ts +0 -34
  207. package/dist/PackageJsonPlugin.js +0 -63
  208. package/dist/PackageJsonPlugin.js.map +0 -1
  209. package/dist/PropertyPlugin.d.ts +0 -51
  210. package/dist/PropertyPlugin.js +0 -181
  211. package/dist/PropertyPlugin.js.map +0 -1
  212. package/dist/ScriptTemplatePlugin.d.ts +0 -13
  213. package/dist/ScriptTemplatePlugin.js +0 -55
  214. package/dist/ScriptTemplatePlugin.js.map +0 -1
  215. package/dist/ServerModulePlugin.d.ts +0 -75
  216. package/dist/ServerModulePlugin.js +0 -368
  217. package/dist/ServerModulePlugin.js.map +0 -1
  218. package/dist/UserPreferencePlugin.d.ts +0 -19
  219. package/dist/UserPreferencePlugin.js +0 -32
  220. package/dist/UserPreferencePlugin.js.map +0 -1
  221. package/dist/aclAndRole/AclPlugin.d.ts +0 -82
  222. package/dist/aclAndRole/AclPlugin.js +0 -262
  223. package/dist/aclAndRole/AclPlugin.js.map +0 -1
  224. package/dist/aclAndRole/RolePlugin.d.ts +0 -66
  225. package/dist/aclAndRole/RolePlugin.js +0 -179
  226. package/dist/aclAndRole/RolePlugin.js.map +0 -1
  227. package/dist/aclAndRole/Util.d.ts +0 -3
  228. package/dist/aclAndRole/Util.js +0 -90
  229. package/dist/aclAndRole/Util.js.map +0 -1
  230. package/dist/app/ApplicationMenuPlugin.d.ts +0 -34
  231. package/dist/app/ApplicationMenuPlugin.js +0 -112
  232. package/dist/app/ApplicationMenuPlugin.js.map +0 -1
  233. package/dist/db/ColumnPlugins.d.ts +0 -750
  234. package/dist/db/ColumnPlugins.js +0 -114
  235. package/dist/db/ColumnPlugins.js.map +0 -1
  236. package/dist/db/DBUtils.d.ts +0 -2
  237. package/dist/db/DBUtils.js +0 -27
  238. package/dist/db/DBUtils.js.map +0 -1
  239. package/dist/db/DocumentationPlugin.d.ts +0 -67
  240. package/dist/db/DocumentationPlugin.js +0 -258
  241. package/dist/db/DocumentationPlugin.js.map +0 -1
  242. package/dist/db/LicensingPlugin.d.ts +0 -60
  243. package/dist/db/LicensingPlugin.js +0 -117
  244. package/dist/db/LicensingPlugin.js.map +0 -1
  245. package/dist/db/RecordPlugin.d.ts +0 -133
  246. package/dist/db/RecordPlugin.js +0 -337
  247. package/dist/db/RecordPlugin.js.map +0 -1
  248. package/dist/db/TablePlugin.d.ts +0 -231
  249. package/dist/db/TablePlugin.js +0 -1628
  250. package/dist/db/TablePlugin.js.map +0 -1
  251. package/dist/db/index.d.ts +0 -6
  252. package/dist/db/index.js +0 -32
  253. package/dist/db/index.js.map +0 -1
  254. package/dist/scriptedRESTAPI/RESTDeserializationUtils.d.ts +0 -10
  255. package/dist/scriptedRESTAPI/RESTDeserializationUtils.js +0 -373
  256. package/dist/scriptedRESTAPI/RESTDeserializationUtils.js.map +0 -1
  257. package/dist/scriptedRESTAPI/RESTSerializationUtils.d.ts +0 -13
  258. package/dist/scriptedRESTAPI/RESTSerializationUtils.js +0 -177
  259. package/dist/scriptedRESTAPI/RESTSerializationUtils.js.map +0 -1
  260. package/dist/scriptedRESTAPI/RestApiPlugin.d.ts +0 -81
  261. package/dist/scriptedRESTAPI/RestApiPlugin.js +0 -345
  262. package/dist/scriptedRESTAPI/RestApiPlugin.js.map +0 -1
  263. package/dist/scriptedRESTAPI/RestSchemaUtils.d.ts +0 -190
  264. package/dist/scriptedRESTAPI/RestSchemaUtils.js +0 -53
  265. package/dist/scriptedRESTAPI/RestSchemaUtils.js.map +0 -1
  266. package/dist/scriptedRESTAPI/RestUtils.d.ts +0 -69
  267. package/dist/scriptedRESTAPI/RestUtils.js +0 -497
  268. package/dist/scriptedRESTAPI/RestUtils.js.map +0 -1
  269. package/dist/scripts/ClientScriptPlugin.d.ts +0 -64
  270. package/dist/scripts/ClientScriptPlugin.js +0 -170
  271. package/dist/scripts/ClientScriptPlugin.js.map +0 -1
  272. package/dist/scripts/scriptUtils.d.ts +0 -1
  273. package/dist/scripts/scriptUtils.js +0 -9
  274. package/dist/scripts/scriptUtils.js.map +0 -1
  275. package/dist/uxf/ExperiencePlugin.d.ts +0 -45
  276. package/dist/uxf/ExperiencePlugin.js +0 -61
  277. package/dist/uxf/ExperiencePlugin.js.map +0 -1
  278. package/dist/uxf/RoutesPlugin.d.ts +0 -29
  279. package/dist/uxf/RoutesPlugin.js +0 -181
  280. package/dist/uxf/RoutesPlugin.js.map +0 -1
  281. package/dist/uxf/UxfFormulaParser/cleanUxValue.d.ts +0 -4
  282. package/dist/uxf/UxfFormulaParser/cleanUxValue.js +0 -65
  283. package/dist/uxf/UxfFormulaParser/cleanUxValue.js.map +0 -1
  284. package/dist/uxf/UxfFormulaParser/grammerParser/api.d.ts +0 -189
  285. package/dist/uxf/UxfFormulaParser/grammerParser/api.js +0 -158
  286. package/dist/uxf/UxfFormulaParser/grammerParser/api.js.map +0 -1
  287. package/dist/uxf/UxfFormulaParser/grammerParser/clientTransformMap.d.ts +0 -13
  288. package/dist/uxf/UxfFormulaParser/grammerParser/clientTransformMap.js +0 -604
  289. package/dist/uxf/UxfFormulaParser/grammerParser/clientTransformMap.js.map +0 -1
  290. package/dist/uxf/UxfFormulaParser/grammerParser/grammarParser.d.ts +0 -12
  291. package/dist/uxf/UxfFormulaParser/grammerParser/grammarParser.js +0 -551
  292. package/dist/uxf/UxfFormulaParser/grammerParser/grammarParser.js.map +0 -1
  293. package/dist/uxf/UxfFormulaParser/grammerParser/spanHelpers.d.ts +0 -31
  294. package/dist/uxf/UxfFormulaParser/grammerParser/spanHelpers.js +0 -64
  295. package/dist/uxf/UxfFormulaParser/grammerParser/spanHelpers.js.map +0 -1
  296. package/dist/uxf/UxfFormulaParser/index.d.ts +0 -3
  297. package/dist/uxf/UxfFormulaParser/index.js +0 -11
  298. package/dist/uxf/UxfFormulaParser/index.js.map +0 -1
  299. package/dist/uxf/UxfFormulaParser/parser.d.ts +0 -8
  300. package/dist/uxf/UxfFormulaParser/parser.js +0 -87
  301. package/dist/uxf/UxfFormulaParser/parser.js.map +0 -1
  302. package/dist/uxf/UxfFormulaParser/utils/getErrorMsg.d.ts +0 -8
  303. package/dist/uxf/UxfFormulaParser/utils/getErrorMsg.js +0 -17
  304. package/dist/uxf/UxfFormulaParser/utils/getErrorMsg.js.map +0 -1
  305. package/dist/uxf/constants.d.ts +0 -2
  306. package/dist/uxf/constants.js +0 -8
  307. package/dist/uxf/constants.js.map +0 -1
  308. package/dist/uxf/index.d.ts +0 -2
  309. package/dist/uxf/index.js +0 -11
  310. package/dist/uxf/index.js.map +0 -1
  311. package/dist/uxf/tectonicIdGenerator.d.ts +0 -12
  312. package/dist/uxf/tectonicIdGenerator.js +0 -79
  313. package/dist/uxf/tectonicIdGenerator.js.map +0 -1
  314. package/src/AttachmentPlugin.ts +0 -268
  315. package/src/BusinessRulePlugin.ts +0 -238
  316. package/src/CrossScopePrivilegePlugin.ts +0 -115
  317. package/src/DefaultPlugin.ts +0 -288
  318. package/src/HtmlTemplatePlugin.ts +0 -31
  319. package/src/IdPlugin.ts +0 -74
  320. package/src/IncludePlugin.ts +0 -206
  321. package/src/JsonPlugin.ts +0 -76
  322. package/src/ListPlugin.ts +0 -570
  323. package/src/NowConfigPlugin.ts +0 -69
  324. package/src/PackageJsonPlugin.ts +0 -68
  325. package/src/PropertyPlugin.ts +0 -237
  326. package/src/ScriptTemplatePlugin.ts +0 -53
  327. package/src/ServerModulePlugin.ts +0 -480
  328. package/src/UserPreferencePlugin.ts +0 -45
  329. package/src/aclAndRole/AclPlugin.ts +0 -420
  330. package/src/aclAndRole/RolePlugin.ts +0 -254
  331. package/src/aclAndRole/Util.ts +0 -113
  332. package/src/app/ApplicationMenuPlugin.ts +0 -165
  333. package/src/db/ColumnPlugins.ts +0 -114
  334. package/src/db/DBUtils.ts +0 -36
  335. package/src/db/DocumentationPlugin.ts +0 -316
  336. package/src/db/LicensingPlugin.ts +0 -138
  337. package/src/db/RecordPlugin.ts +0 -459
  338. package/src/db/TablePlugin.ts +0 -2070
  339. package/src/db/index.ts +0 -6
  340. package/src/scriptedRESTAPI/RESTDeserializationUtils.ts +0 -419
  341. package/src/scriptedRESTAPI/RESTSerializationUtils.ts +0 -228
  342. package/src/scriptedRESTAPI/RestApiPlugin.ts +0 -469
  343. package/src/scriptedRESTAPI/RestSchemaUtils.ts +0 -72
  344. package/src/scriptedRESTAPI/RestUtils.ts +0 -569
  345. package/src/scripts/ClientScriptPlugin.ts +0 -257
  346. package/src/scripts/scriptUtils.ts +0 -5
  347. package/src/uxf/ExperiencePlugin.ts +0 -67
  348. package/src/uxf/RoutesPlugin.ts +0 -211
  349. package/src/uxf/UxfFormulaParser/cleanUxValue.ts +0 -73
  350. package/src/uxf/UxfFormulaParser/grammerParser/api.js +0 -166
  351. package/src/uxf/UxfFormulaParser/grammerParser/clientTransformMap.js +0 -606
  352. package/src/uxf/UxfFormulaParser/grammerParser/grammarParser.js +0 -551
  353. package/src/uxf/UxfFormulaParser/grammerParser/spanHelpers.js +0 -65
  354. package/src/uxf/UxfFormulaParser/index.ts +0 -4
  355. package/src/uxf/UxfFormulaParser/parser.ts +0 -64
  356. package/src/uxf/UxfFormulaParser/utils/getErrorMsg.ts +0 -13
  357. package/src/uxf/constants.ts +0 -4
  358. package/src/uxf/index.ts +0 -2
  359. package/src/uxf/tectonicIdGenerator.ts +0 -78
@@ -0,0 +1,1698 @@
1
+ import {
2
+ type Compiler,
3
+ ts,
4
+ CallExpressionShape,
5
+ type OutputFile,
6
+ Plugin,
7
+ unloadBuilder,
8
+ isSNScope,
9
+ type Record,
10
+ type RecordId,
11
+ type Shape,
12
+ VariableStatementShape,
13
+ IdentifierShape,
14
+ type ObjectShape,
15
+ } from '@servicenow/sdk-build-core'
16
+ import { XMLParser } from 'fast-xml-parser'
17
+ import { z } from 'zod'
18
+ import { columnToCallExpression, generatePlural, isDefaultDocumentation } from './column-plugin'
19
+ import { create } from 'xmlbuilder2'
20
+ import type { XMLBuilder } from 'xmlbuilder2/lib/interfaces'
21
+ import { addFieldsToColumn } from './column/column-helper'
22
+ import { generateDeprecatedDiagnostics } from './utils'
23
+ import isEqual from 'lodash/isEqual'
24
+
25
+ type GlobalRecord<T extends string | number | symbol, U> = globalThis.Record<T, U>
26
+
27
+ // Bootstrap XML schema
28
+ const BooleanFromString = z
29
+ .string()
30
+ .transform((str) => str === 'true')
31
+ .or(z.boolean())
32
+ .optional()
33
+
34
+ const ChoiceElementSchema = z.object({
35
+ '@_label': z.string().optional(),
36
+ '@_value': z.string().optional(),
37
+ '@_sequence': z.coerce.number().optional(),
38
+ '@_dependent_value': z.coerce.number().or(z.string()).optional(),
39
+ '@_hint': z.string().optional(),
40
+ '@_inactive': BooleanFromString.optional(),
41
+ '@_inactive_on_update': BooleanFromString.optional(),
42
+ '@_language': z.string().optional(),
43
+ })
44
+
45
+ const IndexElementSchema = z.object({
46
+ '@_name': z.string(),
47
+ })
48
+
49
+ const IndexSchema = z.object({
50
+ '@_name': z.string(),
51
+ '@_unique': BooleanFromString.optional(),
52
+ element: z.array(IndexElementSchema).or(IndexElementSchema),
53
+ })
54
+
55
+ const ColumnSchema = z
56
+ .object({
57
+ '@_name': z.string(),
58
+ '@_type': z.string().optional(),
59
+ '@_internal_type': z.string().optional(),
60
+ '@_default': z.string().optional(),
61
+ '@_default_value': z.string().optional(),
62
+ '@_max_length': z.coerce.number().or(z.string()).optional(),
63
+ '@_mandatory': BooleanFromString.optional(),
64
+ '@_read_only': BooleanFromString.optional(),
65
+ '@_reference_table': z.string().optional(),
66
+ '@_reference_qual': z.string().optional(),
67
+ '@_label': z.string().optional(),
68
+ '@_choice': z.coerce.number().optional(),
69
+ '@_active': BooleanFromString.optional(),
70
+ '@_display': BooleanFromString.optional(),
71
+ '@_use_dependent_field': BooleanFromString.optional(),
72
+ '@_use_dynamic_default': BooleanFromString.optional(),
73
+ '@_reference': z.string().optional(),
74
+ '@_virtual': BooleanFromString.optional(),
75
+ '@_calculation': z.string().optional(),
76
+ '@_choice_field': z.string().optional(),
77
+ '@_function_definition': z.string().optional(),
78
+ '@_reference_floats': BooleanFromString.optional(),
79
+ '@_reference_key': z.string().optional(),
80
+ '@_dynamic_creation_script': z.string().optional(),
81
+ '@_dynamic_creation': BooleanFromString.optional(),
82
+ '@_plural': z.string().optional(),
83
+ '@_hint': z.string().optional(),
84
+ '@_help': z.string().optional(),
85
+ '@_widget': z.string().optional(),
86
+ '@_table_reference': BooleanFromString.optional(),
87
+ '@_element_reference': BooleanFromString.optional(),
88
+ '@_unique': BooleanFromString.optional(),
89
+ '@_spell_check': BooleanFromString.optional(),
90
+ '@_xml_view': BooleanFromString.optional(),
91
+ '@_array': BooleanFromString.optional(),
92
+ '@_text_index': BooleanFromString.optional(),
93
+ '@_primary': BooleanFromString.optional(),
94
+ '@_attributes': z.string().optional(),
95
+ '@_audit': BooleanFromString.optional(),
96
+ choice: z
97
+ .object({
98
+ element: z.array(ChoiceElementSchema).or(ChoiceElementSchema).optional(),
99
+ })
100
+ .optional(),
101
+ })
102
+ .catchall(z.any())
103
+
104
+ const TableSchema = z
105
+ .object({
106
+ '@_name': z.string(),
107
+ '@_extends': z.string().optional(),
108
+ '@_label': z.string().optional(),
109
+ '@_plural': z.string().optional(),
110
+ '@_audit': BooleanFromString.optional(),
111
+ '@_read_only': BooleanFromString.optional(),
112
+ '@_text_index': BooleanFromString.optional(),
113
+ '@_client_scripts_access': BooleanFromString.optional().default(true),
114
+ '@_ws_access': BooleanFromString.optional().default(true),
115
+ '@_alter_access': BooleanFromString.optional().default(true),
116
+ '@_create_access': BooleanFromString.optional().default(true),
117
+ '@_delete_access': BooleanFromString.optional().default(false),
118
+ '@_update_access': BooleanFromString.optional().default(true),
119
+ '@_actions_access': BooleanFromString.optional().default(true),
120
+ '@_read_access': BooleanFromString.optional().default(true),
121
+ '@_is_extendable': BooleanFromString.optional().default(false),
122
+ '@_scriptable_table': BooleanFromString.optional().default(false),
123
+ '@_attributes': z.string().optional(),
124
+ element: z.array(ColumnSchema).or(ColumnSchema).optional(),
125
+ index: z.array(IndexSchema).or(IndexSchema).optional(),
126
+ })
127
+ .catchall(z.any())
128
+
129
+ const BootstrapDatabaseSchema = z.object({
130
+ element: TableSchema,
131
+ })
132
+
133
+ // Parsed table definitions
134
+ type ChoiceDefinition = {
135
+ label: string | undefined
136
+ value: string
137
+ sequence: number | undefined
138
+ dependentValue: string | number | undefined
139
+ hint: string | undefined
140
+ inactive: boolean | undefined
141
+ inactiveOnUpdate: boolean | undefined
142
+ language: string | undefined
143
+ }
144
+
145
+ type ColumnDefinition = {
146
+ name: string
147
+ type: string | undefined
148
+ defaultValue: string | undefined
149
+ maxLength: number | string | undefined
150
+ isMandatory: boolean | undefined
151
+ isReadOnly: boolean | undefined
152
+ referenceTable: string | undefined
153
+ referenceQual: string | undefined
154
+ columnLabel: string | undefined
155
+ choices: ChoiceDefinition[] | undefined
156
+ isActive: boolean | undefined
157
+ display: boolean | undefined
158
+ useDependentField: boolean | undefined
159
+ useDynamicDefault: boolean | undefined
160
+ reference: string | undefined
161
+ isVirtual: boolean | undefined
162
+ calculation: string | undefined
163
+ choiceField: string | undefined
164
+ functionDefinition: string | undefined
165
+ referenceFloats: boolean | undefined
166
+ referenceKey: string | undefined
167
+ dynamicCreationScript: string | undefined
168
+ dynamicCreation: boolean | undefined
169
+ plural: string | undefined
170
+ hint: string | undefined
171
+ help: string | undefined
172
+ widget: string | undefined
173
+ tableReference: boolean | undefined
174
+ elementReference: boolean | undefined
175
+ unique: boolean | undefined
176
+ spellCheck: boolean | undefined
177
+ xmlView: boolean | undefined
178
+ isArray: boolean | undefined
179
+ textIndex: boolean | undefined
180
+ isPrimary: boolean | undefined
181
+ attributes: string | undefined
182
+ referenceCascadeRule: string | undefined
183
+ choiceValue: number | undefined
184
+ choiceTable: string | undefined
185
+ dependent: string | undefined
186
+ audit: boolean | undefined
187
+ }
188
+
189
+ type IndexDefinition = {
190
+ name: string
191
+ unique: boolean | undefined
192
+ columns: string[]
193
+ }
194
+
195
+ type TableDefinition = {
196
+ name: string
197
+ label: string | undefined
198
+ plural: string | undefined
199
+ extends: string | undefined
200
+ columns: ColumnDefinition[]
201
+ indexes: IndexDefinition[]
202
+ audit: boolean | undefined
203
+ readOnly: boolean | undefined
204
+ textIndex: boolean | undefined
205
+ clientScriptsAccess: boolean | undefined
206
+ wsAccess: boolean | undefined
207
+ alterAccess: boolean | undefined
208
+ createAccess: boolean | undefined
209
+ deleteAccess: boolean | undefined
210
+ updateAccess: boolean | undefined
211
+ callerAccess: number | undefined
212
+ actionsAccess: boolean | undefined
213
+ readAccess: boolean | undefined
214
+ isExtendable: boolean | undefined
215
+ scriptableTable: boolean | undefined
216
+ attributes: string | undefined
217
+ display: string | undefined
218
+ }
219
+
220
+ // sys_db_object & related record properties
221
+ type SysDbObjectProperties = {
222
+ name: string
223
+ label: string | undefined
224
+ super_class: string | undefined
225
+ is_extendable: boolean | undefined
226
+ scriptable_table: boolean | undefined
227
+ client_scripts_access: boolean | undefined
228
+ ws_access: boolean | undefined
229
+ alter_access: boolean | undefined
230
+ create_access: boolean | undefined
231
+ delete_access: boolean | undefined
232
+ update_access: boolean | undefined
233
+ caller_access: number | undefined
234
+ actions_access: boolean | undefined
235
+ read_access: boolean | undefined
236
+ }
237
+
238
+ type SysDictionaryProperties = {
239
+ name: string
240
+ internal_type: string
241
+ element: string | undefined
242
+ default_value: string | undefined
243
+ audit: boolean | undefined
244
+ max_length: number | string | undefined
245
+ mandatory: boolean | undefined
246
+ read_only: boolean | undefined
247
+ reference_table: string | undefined
248
+ reference_qual: string | undefined
249
+ column_label: string | undefined
250
+ active: boolean | undefined
251
+ display: boolean | undefined
252
+ use_dependent_field: boolean | undefined
253
+ use_dynamic_default: boolean | undefined
254
+ reference: string | undefined
255
+ virtual: boolean | undefined
256
+ default: string | undefined
257
+ calculation: string | undefined
258
+ choice_field: string | undefined
259
+ function_definition: string | undefined
260
+ reference_floats: boolean | undefined
261
+ reference_key: string | undefined
262
+ dynamic_creation_script: string | undefined
263
+ dynamic_creation: boolean | undefined
264
+ plural: string | undefined
265
+ hint: string | undefined
266
+ help: string | undefined
267
+ widget: string | undefined
268
+ table_reference: boolean | undefined
269
+ element_reference: boolean | undefined
270
+ unique: boolean | undefined
271
+ spell_check: boolean | undefined
272
+ xml_view: boolean | undefined
273
+ array: boolean | undefined
274
+ text_index: boolean | undefined
275
+ primary: boolean | undefined
276
+ attributes: string | undefined
277
+ reference_cascade_rule: string | undefined
278
+ choice: number | undefined
279
+ choice_table: string | undefined
280
+ dependent: string | undefined
281
+ function_field: boolean | undefined
282
+ }
283
+
284
+ type SysChoiceProperties = {
285
+ name: string
286
+ element: string
287
+ label: string
288
+ value: string
289
+ sequence: number | undefined
290
+ dependent_value: string | number | undefined
291
+ hint: string | undefined
292
+ inactive: boolean | undefined
293
+ inactive_on_update: boolean | undefined
294
+ language: string | undefined
295
+ }
296
+
297
+ type SysIndexProperties = {
298
+ index_name: string
299
+ col_name_string: string
300
+ unique_index: boolean | undefined
301
+ logical_table_name: string
302
+ }
303
+
304
+ type SysDocumentationProperties = {
305
+ name: string
306
+ element: string | undefined
307
+ label: string | undefined
308
+ plural: string | undefined
309
+ language: string
310
+ hint: string | undefined
311
+ help: string | undefined
312
+ }
313
+
314
+ /**
315
+ * The access levels are mapped to the following values on the platform
316
+ *
317
+ * - `none`: 0
318
+ * - `tracking`: 1
319
+ * - `restricted`: 2
320
+ *
321
+ */
322
+ const callerAccessLevels = ['none', 'tracking', 'restricted'] as const
323
+
324
+ const tableNameRegex = /^[a-z_][a-z0-9_]*[a-z0-9]$/
325
+ const columnNameRegex = /^[a-z_][a-z0-9_]*$/
326
+
327
+ const tableAliases = {
328
+ readOnly: ['read_only'],
329
+ textIndex: ['text_index'],
330
+ allowWebServiceAccess: ['allow_web_service_access'],
331
+ allowNewFields: ['allow_new_fields'],
332
+ allowUiActions: ['allow_ui_actions'],
333
+ allowClientScripts: ['allow_client_scripts'],
334
+ licensingConfig: ['licensing_config'],
335
+ liveFeed: ['live_feed'],
336
+ accessibleFrom: ['accessible_from'],
337
+ callerAccess: ['caller_access'],
338
+ scriptableTable: ['scriptable_table'],
339
+ autoNumber: ['auto_number'],
340
+ }
341
+
342
+ const documentationAliases = {
343
+ urlTarget: ['url_target'],
344
+ }
345
+
346
+ const autoNumberAliases = {
347
+ numberOfDigits: ['number_of_digits'],
348
+ }
349
+
350
+ const licensingAliases = {
351
+ licenseModel: ['license_model'],
352
+ ownerCondition: ['owner_condition'],
353
+ licenseCondition: ['license_condition'],
354
+ isFulfillment: ['is_fulfillment'],
355
+ opDelete: ['op_delete'],
356
+ opUpdate: ['op_update'],
357
+ opInsert: ['op_insert'],
358
+ licenseRoles: ['license_roles'],
359
+ }
360
+
361
+ export const TablePlugin = Plugin.create({
362
+ name: 'TablePlugin',
363
+ files: [
364
+ {
365
+ matcher: /\.xml$/,
366
+ toRecord(file, { factory }) {
367
+ const xml = new XMLParser({
368
+ ignoreAttributes: false,
369
+ alwaysCreateTextNode: true,
370
+ htmlEntities: true,
371
+ }).parse(file.content).database
372
+
373
+ if (!xml) {
374
+ return { success: false }
375
+ }
376
+
377
+ const tableDef = parseTableBootstrapXml(xml)
378
+ if (!tableDef) {
379
+ return { success: false }
380
+ }
381
+
382
+ const recordDefs = tableDefToRecordProperties(tableDef)
383
+ const [sysDbRecord, ...relatedRecords] = (
384
+ [
385
+ ['sysDbObject', 'sys_db_object'],
386
+ ['sysDictionary', 'sys_dictionary'],
387
+ ['sysChoice', 'sys_choice'],
388
+ ['sysIndex', 'sys_index'],
389
+ ['sysDocumentation', 'sys_documentation'],
390
+ ] as const
391
+ ).flatMap(([key, table]) => {
392
+ return [recordDefs[key]].flat().map((rec) =>
393
+ factory.createRecord({
394
+ source: file,
395
+ table,
396
+ properties: filterUndefinedProperties(rec),
397
+ })
398
+ )
399
+ })
400
+
401
+ return {
402
+ success: true,
403
+ value: sysDbRecord!.with(...relatedRecords),
404
+ }
405
+ },
406
+ },
407
+ ],
408
+ records: {
409
+ sys_db_object: {
410
+ coalesce: ['name'],
411
+ relationships: {
412
+ sys_dictionary: {
413
+ via: { name: 'name' },
414
+ descendant: true,
415
+ relationships: {
416
+ sys_documentation: {
417
+ descendant: true,
418
+ via: {
419
+ name: 'name',
420
+ element: 'element',
421
+ },
422
+ },
423
+ sys_choice: {
424
+ descendant: true,
425
+ via: {
426
+ name: 'name',
427
+ element: 'element',
428
+ },
429
+ },
430
+ sys_choice_set: {
431
+ descendant: true,
432
+ via: {
433
+ name: 'name',
434
+ element: 'element',
435
+ },
436
+ },
437
+ },
438
+ },
439
+ ua_table_licensing_config: {
440
+ descendant: true,
441
+ via: { name: 'name' },
442
+ },
443
+ sys_documentation: {
444
+ descendant: true,
445
+ via: { name: 'name' },
446
+ },
447
+ sys_number: {
448
+ descendant: true,
449
+ via: { category: 'name' },
450
+ },
451
+ sys_index: {
452
+ descendant: true,
453
+ via: { logical_table_name: 'name' },
454
+ },
455
+ sys_db_object: {
456
+ descendant: false,
457
+ via: 'super_class',
458
+ },
459
+ },
460
+ toShape(record, { descendants }) {
461
+ const schema = {}
462
+ let displayColumn: string | undefined
463
+ const columns = descendants.query('sys_dictionary')
464
+ let collectionRecord: Record
465
+ const choices = descendants.query('sys_choice')
466
+ const documentation = descendants.query('sys_documentation')
467
+ for (const column of columns) {
468
+ if (column.get('internal_type').getValue() === 'collection') {
469
+ // 'collection' sys_dictionary record only has table properties
470
+ collectionRecord = column
471
+ continue
472
+ }
473
+ const columnName = column.get('element').asString().getValue()
474
+ schema[columnName] = columnToCallExpression(column, {
475
+ choices: choices.filter((choice) => choice.get('element').asString().getValue() === columnName),
476
+ documentation: documentation.filter(
477
+ (d) => d.get('element').ifString()?.getValue() === columnName
478
+ ),
479
+ })
480
+ if (column.get('display').ifDefined()?.toBoolean().getValue()) {
481
+ displayColumn = columnName
482
+ }
483
+ }
484
+
485
+ const tableDocumentation = documentation.filter((d) => !d.get('element').getValue())
486
+ const licensing = descendants.query('ua_table_licensing_config')
487
+ const autoNumber = descendants.query('sys_number')
488
+ const indexes = descendants.query('sys_index')
489
+ const originalSource = record.getOriginalSource()
490
+
491
+ // Avoid replacing call expressions with variable statements
492
+ const writeAsCallExpression =
493
+ ts.Node.isNode(originalSource) && originalSource.isKind(ts.SyntaxKind.CallExpression)
494
+ const callExpression = new CallExpressionShape({
495
+ source: record,
496
+ callee: 'Table',
497
+ args: [
498
+ record
499
+ .transform(({ $ }) => ({
500
+ accessibleFrom: $.from('access')
501
+ .map((access) => (access.ifString()?.getValue() === '' ? undefined : access))
502
+ .def('public'),
503
+ actions: $.from('read_access', 'update_access', 'delete_access', 'create_access')
504
+ .map((readAccess, updateAccess, deleteAccess, createAccess) => {
505
+ // TODO Add framework solution so these don't come in as strings
506
+ const isTrueString = (value: string | undefined): boolean => value === 'true'
507
+ const actions: ('read' | 'update' | 'delete' | 'create')[] = []
508
+ if (
509
+ readAccess.ifBoolean()?.getValue() ||
510
+ isTrueString(readAccess.ifString()?.getValue())
511
+ ) {
512
+ actions.push('read')
513
+ }
514
+ if (
515
+ updateAccess.ifBoolean()?.getValue() ||
516
+ isTrueString(updateAccess.ifString()?.getValue())
517
+ ) {
518
+ actions.push('update')
519
+ }
520
+ if (
521
+ deleteAccess.ifBoolean()?.getValue() ||
522
+ isTrueString(deleteAccess.ifString()?.getValue())
523
+ ) {
524
+ actions.push('delete')
525
+ }
526
+ if (
527
+ createAccess.ifBoolean()?.getValue() ||
528
+ isTrueString(createAccess.ifString()?.getValue())
529
+ ) {
530
+ actions.push('create')
531
+ }
532
+ return actions.length ? actions : undefined
533
+ })
534
+ .def(['read']),
535
+ allowClientScripts: $.from('client_scripts_access').toBoolean().def(false),
536
+ allowNewFields: $.from('alter_access').toBoolean().def(false),
537
+ allowUiActions: $.from('actions_access').toBoolean().def(false),
538
+ allowWebServiceAccess: $.from('ws_access').toBoolean().def(false),
539
+ attributes: $.val(
540
+ (() => {
541
+ const attributes = collectionRecord?.get('attributes')
542
+ if (!attributes?.isString()) {
543
+ return undefined
544
+ }
545
+ const result = {}
546
+ attributes
547
+ .toString()
548
+ .getValue()
549
+ .split(',')
550
+ .forEach((attr) => {
551
+ if (attr === '') {
552
+ return
553
+ }
554
+ const [key, value] = attr.split('=').map((s) => s.trim())
555
+ if (value === 'true') {
556
+ result[key!] = true
557
+ } else if (value === 'false') {
558
+ result[key!] = false
559
+ } else {
560
+ result[key!] = value
561
+ }
562
+ })
563
+ return result
564
+ })()
565
+ ).def({ edgeEncryptionEnabled: true }),
566
+ audit: $.val(collectionRecord?.get('audit')).toBoolean().def(false),
567
+ autoNumber: $.val(
568
+ autoNumber[0]
569
+ ?.transform(({ $ }) => ({
570
+ number: $.toNumber().def(1000),
571
+ numberOfDigits: $.from('maximum_digits').toNumber().def(7),
572
+ prefix: $.def('PRE'),
573
+ }))
574
+ .withAliasedKeys(autoNumberAliases)
575
+ ),
576
+ callerAccess: $.from('caller_access')
577
+ .map((callerAccess) => {
578
+ if (callerAccess.isNumber()) {
579
+ return callerAccessLevels[callerAccess.getValue()]
580
+ }
581
+ if (callerAccess.isString()) {
582
+ const callerAccessString = callerAccess.getValue()
583
+ if (!isNaN(Number(callerAccessString))) {
584
+ return callerAccessLevels[Number(callerAccessString)]
585
+ }
586
+ }
587
+ return 'none'
588
+ })
589
+ .def('none'),
590
+ display: displayColumn ? $.val(displayColumn) : undefined,
591
+ extends: $.from('super_class').def(''),
592
+ extensible: $.from('is_extendable').toBoolean().def(false),
593
+ index: $.val(
594
+ indexes.map((idx) => {
595
+ return idx.transform(({ $ }) => ({
596
+ // TODO Find actual home of 'index_name' instead of using a fake property here
597
+ name: $.from('index_name'),
598
+ unique: $.from('unique_index').map(
599
+ (uniqueIndex) => uniqueIndex.ifBoolean() ?? false
600
+ ),
601
+ element: $.from('col_name_string').map((colNamesString) => {
602
+ const colNames = colNamesString.asString().getValue().split(',')
603
+ return colNames.length > 1 ? colNames : colNames[0]
604
+ }),
605
+ }))
606
+ })
607
+ ).def([]),
608
+ label: $.map(
609
+ (label) =>
610
+ label.ifString() ??
611
+ (tableDocumentation.length && !isDefaultDocumentation('', tableDocumentation)
612
+ ? tableDocumentation.map((doc) =>
613
+ doc
614
+ .transform(({ $ }) => ({
615
+ label: $.def(''),
616
+ help: $.def(''),
617
+ hint: $.def(''),
618
+ language: $.def(''),
619
+ plural: $.def(''),
620
+ url: $.def(''),
621
+ urlTarget: $.from('url_target').def(''),
622
+ }))
623
+ .withAliasedKeys(documentationAliases)
624
+ )
625
+ : undefined)
626
+ ),
627
+ licensingConfig: licensing.length
628
+ ? $.val(
629
+ licensing[0]!
630
+ .transform(({ $ }) => ({
631
+ licenseModel: $.from('license_model').def('none'),
632
+ ownerCondition: $.from('owner_condition').def(''),
633
+ licenseCondition: $.from('license_condition').def(''),
634
+ isFulfillment: $.from('is_fulfillment').toBoolean().def(false),
635
+ opDelete: $.from('op_delete').toBoolean().def(true),
636
+ opUpdate: $.from('op_update').toBoolean().def(true),
637
+ opInsert: $.from('op_insert').toBoolean().def(true),
638
+ licenseRoles: $.from('license_roles')
639
+ .map((v) => {
640
+ return v.isString() && !v.isEmpty()
641
+ ? v
642
+ .asString()
643
+ .getValue()
644
+ .split(',')
645
+ .map((role) => role.trim())
646
+ : []
647
+ })
648
+ .def([]),
649
+ }))
650
+ .withAliasedKeys(licensingAliases)
651
+ ).def({
652
+ licenseModel: 'none',
653
+ ownerCondition: '',
654
+ licenseCondition: '',
655
+ isFulfillment: false,
656
+ opDelete: true,
657
+ opUpdate: true,
658
+ opInsert: true,
659
+ licenseRoles: [],
660
+ })
661
+ : undefined,
662
+ liveFeed: $.from('live_feed_enabled').toBoolean().def(false),
663
+ name: $,
664
+ readOnly: $.val(collectionRecord?.get('read_only')).toBoolean().def(false),
665
+ schema: $.val(schema),
666
+ scriptableTable: $.from('scriptable_table').toBoolean().def(false),
667
+ textIndex: $.val(collectionRecord?.get('text_index')).toBoolean().def(false),
668
+ }))
669
+ .withAliasedKeys(tableAliases),
670
+ ],
671
+ })
672
+ return {
673
+ success: true,
674
+ value: writeAsCallExpression
675
+ ? callExpression
676
+ : new VariableStatementShape({
677
+ source: record,
678
+ isExported: true,
679
+ variableName: new IdentifierShape({
680
+ source: record,
681
+ name: record.get('name').asString().getValue(),
682
+ }),
683
+ initializer: callExpression,
684
+ }),
685
+ }
686
+ },
687
+ toFile(record, { descendants, config }) {
688
+ if (config.tableOutputFormat !== 'bootstrap' || record.isDeleted()) {
689
+ // Defer to record plugin
690
+ return { success: false }
691
+ }
692
+
693
+ const tableName = record.get('name').asString().getValue()
694
+ const columns = descendants.query('sys_dictionary')
695
+ const choices = descendants.query('sys_choice')
696
+ const indexes = descendants.query('sys_index')
697
+ const documentation = descendants.query('sys_documentation')
698
+ const licensing = descendants.query('ua_table_licensing_config')
699
+ const autoNumber = descendants.query('sys_number')
700
+ let collectionRecord: Record | undefined
701
+ let displayColumn: string | undefined
702
+ const elements: XMLBuilder[] = []
703
+ for (const column of columns) {
704
+ const displayValue = column.get('display').ifBoolean()?.getValue()
705
+ if (displayValue) {
706
+ displayColumn = column.get('element').asString().getValue()
707
+ }
708
+ if (column.get('internal_type').asString().getValue() === 'collection') {
709
+ // collection element has only table properties
710
+ collectionRecord = column
711
+ continue
712
+ }
713
+
714
+ const defaultValue = column.get('default_value')
715
+ const maxLength = column.get('max_length')
716
+ const dependentOnField = column.get('dependent_on_field').ifString()
717
+ const columnAttributes: [string, string | undefined][] = [
718
+ ['name', column.get('element').asString().getValue()],
719
+ ['type', column.get('internal_type').asString().getValue()],
720
+ ['active', column.get('active').ifBoolean()?.getValue().toString()],
721
+ ['array', column.get('array').ifBoolean()?.getValue().toString()],
722
+ ['audit', column.get('audit').ifBoolean()?.getValue().toString()],
723
+ [
724
+ 'default_value',
725
+ defaultValue.ifString()?.getValue() ??
726
+ defaultValue.ifNumber()?.toString().getValue() ??
727
+ defaultValue.ifBoolean()?.toString().getValue() ??
728
+ defaultValue
729
+ .ifArray()
730
+ ?.getElements()
731
+ .map((v) => v.asString().getValue())
732
+ .join(', '),
733
+ ],
734
+ ['unique', column.get('unique').ifBoolean()?.getValue().toString()],
735
+ ['label', column.get('column_label').ifString()?.getValue()],
736
+ ['max_length', maxLength.ifNumber()?.getValue().toString() ?? maxLength.ifString()?.getValue()],
737
+ ['mandatory', column.get('mandatory').ifBoolean()?.getValue().toString()],
738
+ ['read_only', column.get('read_only').ifBoolean()?.getValue().toString()],
739
+ ['reference_cascade_rule', column.get('reference_cascade_rule').ifString()?.getValue()],
740
+ ['calculation', column.get('calculation').ifString()?.getValue()],
741
+ ['choice', column.get('choice').ifNumber()?.getValue().toString()],
742
+ ['choice_table', column.get('choice_table').ifString()?.getValue()],
743
+ ['choice_field', column.get('choice_field').ifString()?.getValue()],
744
+ ['display', displayValue?.toString()],
745
+ ['function_field', column.get('function_field').ifBoolean()?.getValue().toString()],
746
+ ['attributes', column.get('attributes').ifString()?.getValue()],
747
+ ['inactive', column.get('inactive').ifBoolean()?.getValue().toString()],
748
+ ['dynamic_default', column.get('dynamic_default').ifString()?.getValue()],
749
+ ['spell_check', column.get('spell_check').ifBoolean()?.getValue().toString()],
750
+ ['xml_view', column.get('xml_view').ifBoolean()?.getValue().toString()],
751
+ ['table_reference', column.get('table_reference').ifBoolean()?.getValue().toString()],
752
+ ['text_index', column?.get('text_index').ifBoolean()?.getValue().toString()],
753
+ ['element_reference', column.get('element_reference').ifBoolean()?.getValue().toString()],
754
+ ['primary', column.get('primary').ifBoolean()?.getValue().toString()],
755
+ ['dependent', column.get('dependent').ifString()?.getValue().toString()],
756
+ ['dependent_on_field', dependentOnField?.getValue()],
757
+ [
758
+ 'use_dependent_field',
759
+ dependentOnField ? (!!dependentOnField.getValue()).toString() : undefined,
760
+ ],
761
+ ['use_dynamic_default', column.get('use_dynamic_default').ifBoolean()?.getValue().toString()],
762
+ ['reference', column.get('reference').ifString()?.getValue()],
763
+ ['function_definition', column.get('function_definition').ifString()?.getValue()],
764
+ ['reference_floats', column.get('reference_floats').ifBoolean()?.getValue().toString()],
765
+ ['reference_key', column.get('reference_key').ifString()?.getValue()],
766
+ ['dynamic_creation_script', column.get('dynamic_creation_script').ifString()?.getValue()],
767
+ ['dynamic_creation', column.get('dynamic_creation').ifBoolean()?.getValue().toString()],
768
+ ['plural', column.get('plural').ifString()?.getValue()],
769
+ ['hint', column.get('hint').ifString()?.getValue()],
770
+ ['help', column.get('help').ifString()?.getValue()],
771
+ ['virtual', column.get('virtual').ifBoolean()?.getValue().toString()],
772
+ ['widget', column.get('widget').ifString()?.getValue()],
773
+ ['reference_qual', column.get('reference_qual').ifString()?.getValue()],
774
+ ['reference_qual_condition', column.get('reference_qual_condition').ifString()?.getValue()],
775
+ ]
776
+
777
+ const choiceElements = choices
778
+ .filter(
779
+ (choice) =>
780
+ choice.get('element').asString().getValue() ===
781
+ column.get('element').asString().getValue()
782
+ )
783
+ .map((choice) =>
784
+ createElement(
785
+ 'element',
786
+ filterUndefinedAttributes([
787
+ ['value', choice.get('value').ifString()?.getValue()],
788
+ ['label', choice.get('label').ifString()?.getValue()],
789
+ [
790
+ 'dependent_value',
791
+ choice.get('dependent_value').ifNumber()?.getValue().toString() ??
792
+ choice.get('dependent_value').ifString()?.asString().getValue(),
793
+ ],
794
+ ['inactive', choice.get('inactive').ifBoolean()?.getValue().toString()],
795
+ [
796
+ 'inactive_on_update',
797
+ choice.get('inactive_on_update').ifBoolean()?.getValue().toString(),
798
+ ],
799
+ ['sequence', choice.get('sequence').ifNumber()?.getValue().toString()],
800
+ ])
801
+ )
802
+ )
803
+
804
+ elements.push(
805
+ createElement(
806
+ 'element',
807
+ filterUndefinedAttributes(columnAttributes),
808
+ choiceElements.length ? [createElement('choice', [], choiceElements)] : []
809
+ )
810
+ )
811
+ }
812
+
813
+ const indexElements = indexes.map((index) => {
814
+ const indexAttributes: [string, string | undefined][] = [
815
+ ['name', index.get('index_name').ifString()?.getValue()],
816
+ ['unique', index.get('unique_index').ifBoolean()?.getValue().toString()],
817
+ ]
818
+
819
+ const indexColumns = index
820
+ .get('col_name_string')
821
+ .asString()
822
+ .getValue()
823
+ .split(',')
824
+ .map((col) => createElement('element', [['name', col.trim()]]))
825
+
826
+ return createElement('index', filterUndefinedAttributes(indexAttributes), indexColumns)
827
+ })
828
+
829
+ const tableAttributes: [string, string | undefined][] = [
830
+ ['name', tableName],
831
+ ['type', 'collection'],
832
+ ['label', record.get('label').ifString()?.getValue()],
833
+ ['extends', record.get('super_class').ifRecordId()?.getPrimaryKey()],
834
+ ['is_extendable', record.get('is_extendable').ifBoolean()?.getValue().toString()],
835
+ ['text_index', collectionRecord?.get('text_index').ifBoolean()?.getValue().toString()],
836
+ ['read_only', collectionRecord?.get('read_only').ifBoolean()?.getValue().toString()],
837
+ ['audit', collectionRecord?.get('audit').ifBoolean()?.getValue().toString()],
838
+ ['display', displayColumn],
839
+ ['access', record.get('access').ifString()?.getValue()],
840
+ ['caller_access', record.get('caller_access').ifNumber()?.getValue().toString()],
841
+ ['ws_access', record.get('ws_access').ifBoolean()?.getValue().toString()],
842
+ ['read_access', record.get('read_access').ifBoolean()?.getValue().toString()],
843
+ ['alter_access', record.get('alter_access').ifBoolean()?.getValue().toString()],
844
+ ['create_access', record.get('create_access').ifBoolean()?.getValue().toString()],
845
+ ['update_access', record.get('update_access').ifBoolean()?.getValue().toString()],
846
+ ['delete_access', record.get('delete_access').ifBoolean()?.getValue().toString()],
847
+ ['actions_access', record.get('actions_access').ifBoolean()?.getValue().toString()],
848
+ ['client_scripts_access', record.get('client_scripts_access').ifBoolean()?.getValue().toString()],
849
+ ['scriptable_table', record.get('scriptable_table').ifBoolean()?.getValue().toString()],
850
+ ['attributes', collectionRecord?.get('attributes').ifString()?.getValue()],
851
+ ]
852
+
853
+ const tableXml = createElement(
854
+ 'database',
855
+ [],
856
+ [
857
+ createElement('element', filterUndefinedAttributes(tableAttributes), [
858
+ ...elements,
859
+ ...indexElements,
860
+ ]),
861
+ ]
862
+ ).end({ prettyPrint: true })
863
+
864
+ const documentationFiles = generateRecordXml(
865
+ documentation.filter(
866
+ (doc) => !isDefaultDocumentation(doc.get('element')?.toString().getValue(), [doc])
867
+ ),
868
+ config,
869
+ (doc) =>
870
+ `sys_documentation_${tableName}${doc.get('element').isDefined() ? `_${doc.get('element').asString().getValue()}` : ''}_${doc.get('language').isDefined() ? doc.get('language').asString().getValue() : 'en'}.xml`
871
+ )
872
+
873
+ const licensingFiles = generateRecordXml(
874
+ licensing.filter((licensing) => !isDefaultLicenseConfig(tableName, licensing)),
875
+ config,
876
+ (record) => `ua_table_licensing_config_${record.getId().getValue()}.xml`
877
+ )
878
+
879
+ const autoNumberFiles = generateRecordXml(
880
+ autoNumber,
881
+ config,
882
+ (record) => `sys_number_${record.getId().getValue()}.xml`
883
+ )
884
+
885
+ return {
886
+ success: true,
887
+ value: [
888
+ {
889
+ name: `${tableName}.xml`,
890
+ category: 'dictionary',
891
+ content: tableXml,
892
+ },
893
+ ...documentationFiles,
894
+ ...licensingFiles,
895
+ ...autoNumberFiles,
896
+ ],
897
+ }
898
+ },
899
+ },
900
+ sys_dictionary: {
901
+ coalesce: ['name', 'element'],
902
+ },
903
+ sys_documentation: {
904
+ coalesce: ['name', 'element', 'language'],
905
+ },
906
+ ua_table_licensing_config: {
907
+ coalesce: ['name'],
908
+ },
909
+ sys_index: {
910
+ coalesce: ['logical_table_name', 'col_name_string'],
911
+ },
912
+ sys_number: {
913
+ coalesce: ['category', 'prefix'],
914
+ },
915
+ },
916
+ shapes: [
917
+ {
918
+ shape: CallExpressionShape,
919
+ fileTypes: ['fluent'],
920
+ async toRecord(callExpression, { config, factory, transform, diagnostics, compiler }) {
921
+ if (callExpression.getCallee() !== 'Table') {
922
+ return { success: false }
923
+ }
924
+
925
+ const statement = callExpression
926
+ .getOriginalNode()
927
+ .getFirstAncestorByKind(ts.SyntaxKind.VariableStatement)
928
+ const table = callExpression.getArgument(0).asObject().withAliasedKeys(tableAliases)
929
+ generateDeprecatedDiagnostics(table, diagnostics)
930
+ const relatedRecords: Record[] = []
931
+ const tableName = table.get('name').asString()
932
+ if (!tableName.getValue().match(tableNameRegex)) {
933
+ diagnostics.error(
934
+ table.get('name'),
935
+ 'Table name must only contain lowercase letters, numbers, and underscores and end with a letter or number'
936
+ )
937
+ }
938
+
939
+ let ignoreColumnNameCheck = false
940
+ const scopeName = config.scope
941
+ const scopeRegex = new RegExp(`^${scopeName}_`)
942
+ const tableNameMatch = tableName.getValue().match(scopeRegex)
943
+ if (!tableNameMatch && !isSNScope(scopeName) && scopeName !== 'global') {
944
+ const nameNode = tableName.getOriginalNode()
945
+ if (nameNode && !nameNode.isKind(ts.SyntaxKind.AsExpression)) {
946
+ // 'sn' and 'now' scoped apps ignore this validation
947
+ diagnostics.error(
948
+ table.get('name'),
949
+ `'name' property should start with scope prefix '${scopeName}_'`
950
+ )
951
+ } else {
952
+ ignoreColumnNameCheck = true
953
+ }
954
+ }
955
+
956
+ const declaration = callExpression.getOriginalNode().getParent()
957
+ const exportedByName =
958
+ declaration?.isKind(ts.SyntaxKind.VariableDeclaration) &&
959
+ declaration.isNamedExport() &&
960
+ declaration.getName() === tableName.getValue()
961
+ if (!exportedByName) {
962
+ diagnostics.error(
963
+ callExpression,
964
+ `Table definition should be exported as a named export with the name '${tableName.getValue()}'`
965
+ )
966
+ }
967
+
968
+ // sys_dictionary
969
+ const schema = table.get('schema').asObject()
970
+ for (const [name, column] of schema.entries()) {
971
+ if (!name.match(columnNameRegex)) {
972
+ diagnostics.error(
973
+ column.getOriginalNode().getParentIfKind(ts.SyntaxKind.PropertyAssignment) ?? column,
974
+ 'Column name must only contain lowercase letters, numbers, and underscores'
975
+ )
976
+ }
977
+ if (
978
+ !ignoreColumnNameCheck &&
979
+ !tableNameMatch &&
980
+ !isSNScope(scopeName) &&
981
+ scopeName !== 'global' &&
982
+ !name.match(scopeRegex)
983
+ ) {
984
+ // 'sn' and 'now' scoped apps ignore this validation
985
+ diagnostics.error(
986
+ column.getOriginalNode().getParentIfKind(ts.SyntaxKind.PropertyAssignment) ?? column,
987
+ `Column name should be prefixed with scope '${scopeName}_' if table name does not contain prefix`
988
+ )
989
+ }
990
+ const display = table.get('display').ifString()?.getValue() === name
991
+ const result = await transform.toRecord(
992
+ addFieldsToColumn(
993
+ { name, table: tableName.getValue(), display },
994
+ column.as(CallExpressionShape)
995
+ )
996
+ )
997
+ if (!result.success) {
998
+ diagnostics.error(column, 'Invalid column in table schema')
999
+ return { success: false }
1000
+ }
1001
+ relatedRecords.push(result.value)
1002
+ }
1003
+
1004
+ // sys_index
1005
+ if (table.get('index').isArray()) {
1006
+ for (const index of table.get('index').asArray().getElements()) {
1007
+ const indexObject = index.asObject().getValue()
1008
+ const colNames = Array.isArray(indexObject['element'])
1009
+ ? indexObject['element'].join(',')
1010
+ : (indexObject['element'] as string)
1011
+
1012
+ relatedRecords.push(
1013
+ factory.createRecord({
1014
+ source: statement ?? callExpression,
1015
+ table: 'sys_index',
1016
+ properties: {
1017
+ index_name: indexObject['name'],
1018
+ col_name_string: colNames,
1019
+ unique_index: indexObject['unique'] === true,
1020
+ logical_table_name: table.get('name').asString().getValue(),
1021
+ },
1022
+ })
1023
+ )
1024
+ }
1025
+ }
1026
+
1027
+ // ua_table_licensing_config
1028
+ if (table.get('licensingConfig').isObject()) {
1029
+ const licenseObj = table.get('licensingConfig').asObject().withAliasedKeys(licensingAliases)
1030
+ generateDeprecatedDiagnostics(licenseObj, diagnostics)
1031
+ const licenseRecord = factory.createRecord({
1032
+ source: statement ?? callExpression,
1033
+ table: 'ua_table_licensing_config',
1034
+ properties: licenseObj.transform(({ $ }) => ({
1035
+ name: $.val(table.get('name')),
1036
+ license_model: $.from('licenseModel').def('none'),
1037
+ owner_condition: $.from('ownerCondition'),
1038
+ license_condition: $.from('licenseCondition'),
1039
+ is_fulfillment: $.from('isFulfillment').def(false),
1040
+ op_delete: $.from('opDelete').def(true),
1041
+ op_update: $.from('opUpdate').def(true),
1042
+ license_roles: $.from('licenseRoles')
1043
+ .map((licenseRoles) => {
1044
+ return (
1045
+ licenseRoles
1046
+ .ifArray()
1047
+ ?.getElements()
1048
+ .map((e) => e.asString().getValue())
1049
+ .join(',') ?? []
1050
+ )
1051
+ })
1052
+ .def([]),
1053
+ op_insert: $.from('opInsert').def(true),
1054
+ })),
1055
+ })
1056
+ relatedRecords.push(licenseRecord)
1057
+ } else {
1058
+ relatedRecords.push(
1059
+ factory.createRecord({
1060
+ source: statement ?? callExpression,
1061
+ table: 'ua_table_licensing_config',
1062
+ properties: {
1063
+ name: tableName,
1064
+ license_model: 'none',
1065
+ is_fulfillment: false,
1066
+ op_delete: true,
1067
+ op_insert: true,
1068
+ op_update: true,
1069
+ },
1070
+ })
1071
+ )
1072
+ }
1073
+
1074
+ // sys_documentation
1075
+ if (table.get('label').isArray()) {
1076
+ const documentation = table.get('label').asArray().getElements()
1077
+ const languages = new Set<string>()
1078
+ for (const doc of documentation) {
1079
+ const docObject = doc.asObject().withAliasedKeys(documentationAliases)
1080
+ generateDeprecatedDiagnostics(docObject, diagnostics)
1081
+ const language = docObject.get('language').ifString()?.getValue()
1082
+ if (language) {
1083
+ if (language.length !== 2) {
1084
+ diagnostics.error(
1085
+ doc.getOriginalNode(),
1086
+ `'language' must be a 2 character identifier (Ex. 'en', 'es')`
1087
+ )
1088
+ }
1089
+ if (languages.has(language)) {
1090
+ diagnostics.error(doc.getOriginalNode(), `'language' must be unique per table/column`)
1091
+ }
1092
+ languages.add(language)
1093
+ }
1094
+ relatedRecords.push(
1095
+ factory.createRecord({
1096
+ source: statement ?? callExpression,
1097
+ table: 'sys_documentation',
1098
+ properties: docObject.transform(({ $ }) => ({
1099
+ name: $.val(table.get('name').asString().getValue()),
1100
+ element: $.val(undefined),
1101
+ label: $,
1102
+ help: $,
1103
+ hint: $,
1104
+ language: $,
1105
+ plural: $,
1106
+ url: $,
1107
+ url_target: $.from('urlTarget'),
1108
+ })),
1109
+ })
1110
+ )
1111
+ }
1112
+ } else {
1113
+ relatedRecords.push(
1114
+ factory.createRecord({
1115
+ source: statement ?? callExpression,
1116
+ table: 'sys_documentation',
1117
+ properties: {
1118
+ name: tableName,
1119
+ element: undefined,
1120
+ language: 'en',
1121
+ label: table.get('label').ifString() ?? tableName,
1122
+ plural: generatePlural(
1123
+ table.get('label')?.ifString()?.getValue() ?? tableName.getValue()
1124
+ ),
1125
+ },
1126
+ })
1127
+ )
1128
+ }
1129
+
1130
+ // sys-number
1131
+ let numberRef: RecordId | undefined
1132
+ if (table.get('autoNumber').isObject()) {
1133
+ const autoNumber = table.get('autoNumber').asObject().withAliasedKeys(autoNumberAliases)
1134
+ const sysNumberRecord = factory.createRecord({
1135
+ source: statement ?? callExpression,
1136
+ table: 'sys_number',
1137
+ properties: autoNumber.transform(({ $ }) => ({
1138
+ category: $.val(table.get('name').getValue()),
1139
+ maximum_digits: $.from('numberOfDigits').toNumber().def(7),
1140
+ number: $.def(1000),
1141
+ prefix: $.from('prefix').def('PRE'),
1142
+ })),
1143
+ })
1144
+ numberRef = sysNumberRecord.getId()
1145
+ relatedRecords.push(sysNumberRecord)
1146
+ }
1147
+
1148
+ // sys_dictionary (collection)
1149
+ relatedRecords.push(
1150
+ factory.createRecord({
1151
+ source: statement ?? callExpression,
1152
+ table: 'sys_dictionary',
1153
+ properties: table.transform(({ $ }) => ({
1154
+ name: $,
1155
+ element: $.val(undefined),
1156
+ internal_type: $.def('collection'),
1157
+ attributes: $.map((attributes) => {
1158
+ if (!attributes.isObject()) {
1159
+ return undefined
1160
+ }
1161
+ const attributesObj = attributes.asObject().getValue()
1162
+ return Object.entries(attributesObj)
1163
+ .map(([key, value]) => `${key}=${value}`)
1164
+ .join(',')
1165
+ }).def(''),
1166
+ audit: $.def(false),
1167
+ read_only: $.from('readOnly').def(false),
1168
+ text_index: $.from('textIndex').def(false),
1169
+ })),
1170
+ })
1171
+ )
1172
+
1173
+ const hasAction = (actionName: string, actions: Shape): boolean | undefined => {
1174
+ return actions
1175
+ .ifArray()
1176
+ ?.getElements()
1177
+ .some((action) => action.isString() && action.getValue() === actionName)
1178
+ }
1179
+
1180
+ // sys_db_object
1181
+ const tableRecord = factory.createRecord({
1182
+ source: statement ?? callExpression,
1183
+ table: 'sys_db_object',
1184
+ properties: table.transform(({ $ }) => ({
1185
+ client_scripts_access: $.from('allowClientScripts').toBoolean().def(false),
1186
+ alter_access: $.from('allowNewFields').toBoolean().def(false),
1187
+ actions_access: $.from('allowUiActions').toBoolean().def(false),
1188
+ ws_access: $.from('allowWebServiceAccess').toBoolean().def(false),
1189
+ number_ref: $.val(numberRef),
1190
+ access: $.from('accessibleFrom').def('public'),
1191
+ caller_access: $.from('callerAccess').map((callerAccess) =>
1192
+ callerAccessLevels.indexOf(
1193
+ (callerAccess.ifString()?.getValue() ?? 'none') as 'none' | 'tracking' | 'restricted'
1194
+ )
1195
+ ),
1196
+ super_class: $.from('extends').map(
1197
+ (v) =>
1198
+ v.ifString()?.pipe((parent) =>
1199
+ factory.createReference({
1200
+ source: parent,
1201
+ table: 'sys_db_object',
1202
+ keys: { name: parent.getValue() },
1203
+ })
1204
+ ) ?? v.ifDefined()?.toRecordId()
1205
+ ),
1206
+ read_access: $.from('actions')
1207
+ .map((actions) => hasAction('read', actions))
1208
+ .def(true),
1209
+ update_access: $.from('actions')
1210
+ .map((actions) => hasAction('update', actions))
1211
+ .def(false),
1212
+ delete_access: $.from('actions')
1213
+ .map((actions) => hasAction('delete', actions))
1214
+ .def(false),
1215
+ create_access: $.from('actions')
1216
+ .map((actions) => hasAction('create', actions))
1217
+ .def(false),
1218
+ is_extendable: $.from('extensible').toBoolean().def(false),
1219
+ label: $.map((label) => (label.isString() ? label.asString() : undefined)),
1220
+ live_feed_enabled: $.from('liveFeed').toBoolean().def(false),
1221
+ name: $,
1222
+ scriptable_table: $.from('scriptableTable').toBoolean().def(false),
1223
+ })),
1224
+ })
1225
+
1226
+ if (exportedByName) {
1227
+ addTableToGlobalGeneratedFile(table, callExpression.getOriginalFilePath(), compiler)
1228
+ }
1229
+ return {
1230
+ success: true,
1231
+ value: tableRecord.with(...relatedRecords),
1232
+ }
1233
+ },
1234
+ },
1235
+ ],
1236
+ nodes: [
1237
+ {
1238
+ node: 'VariableStatement',
1239
+ fileTypes: ['fluent'],
1240
+ entryPoint: true,
1241
+ },
1242
+ {
1243
+ node: 'ExportAssignment',
1244
+ fileTypes: ['fluent'],
1245
+ entryPoint: true,
1246
+ toShape(node, { diagnostics }) {
1247
+ const expression = node.getExpression().asKind(ts.SyntaxKind.CallExpression)?.getExpression()
1248
+ if (expression && expression.getText() === 'Table') {
1249
+ diagnostics.error(expression, 'Export assignments are not supported for Table definitions')
1250
+ }
1251
+ return { success: false }
1252
+ },
1253
+ },
1254
+ ],
1255
+ })
1256
+
1257
+ function parseTableBootstrapXml(xml: unknown): TableDefinition | null {
1258
+ const parsedXml = BootstrapDatabaseSchema.safeParse(xml)
1259
+ if (!parsedXml.success) {
1260
+ return null
1261
+ }
1262
+
1263
+ const table = parsedXml.data.element
1264
+ const columns: ColumnDefinition[] = []
1265
+ if (table.element) {
1266
+ const mapColumnToDefinition = (column: z.infer<typeof ColumnSchema>): ColumnDefinition => ({
1267
+ name: column['@_name'],
1268
+ type: column['@_internal_type'] ?? column['@_type'],
1269
+ defaultValue: column['@_default'] ?? column['@_default_value'],
1270
+ audit: column['@_audit'],
1271
+ maxLength: column['@_max_length'],
1272
+ isMandatory: column['@_mandatory'],
1273
+ isReadOnly: column['@_read_only'],
1274
+ referenceTable: column['@_reference_table'],
1275
+ referenceQual: column['@_reference_qual'],
1276
+ columnLabel: column['@_label'],
1277
+ choices: column.choice?.element
1278
+ ? Array.isArray(column.choice.element)
1279
+ ? column.choice.element.map((choice) => ({
1280
+ label: choice['@_label'],
1281
+ value: choice['@_value'] ?? '',
1282
+ sequence: choice['@_sequence'],
1283
+ dependentValue: choice['@_dependent_value'],
1284
+ hint: choice['@_hint'],
1285
+ inactive: choice['@_inactive'],
1286
+ inactiveOnUpdate: choice['@_inactive_on_update'],
1287
+ language: choice['@_language'],
1288
+ }))
1289
+ : [
1290
+ {
1291
+ label: column.choice.element['@_label'],
1292
+ value: column.choice.element['@_value'] ?? '',
1293
+ sequence: column.choice.element['@_sequence'],
1294
+ dependentValue: column.choice.element['@_dependent_value'],
1295
+ hint: column.choice.element['@_hint'],
1296
+ inactive: column.choice.element['@_inactive'],
1297
+ inactiveOnUpdate: column.choice.element['@_inactive_on_update'],
1298
+ language: column.choice.element['@_language'],
1299
+ },
1300
+ ]
1301
+ : undefined,
1302
+ isActive: column['@_active'],
1303
+ display: column['@_display'],
1304
+ useDependentField: column['@_use_dependent_field'],
1305
+ useDynamicDefault: column['@_use_dynamic_default'],
1306
+ reference: column['@_reference'],
1307
+ isVirtual: column['@_virtual'],
1308
+ calculation: column['@_calculation'],
1309
+ choiceField: column['@_choice_field'],
1310
+ functionDefinition: column['@_function_definition'],
1311
+ referenceFloats: column['@_reference_floats'],
1312
+ referenceKey: column['@_reference_key'],
1313
+ dynamicCreationScript: column['@_dynamic_creation_script'],
1314
+ dynamicCreation: column['@_dynamic_creation'],
1315
+ plural: column['@_plural'],
1316
+ hint: column['@_hint'],
1317
+ help: column['@_help'],
1318
+ widget: column['@_widget'],
1319
+ tableReference: column['@_table_reference'],
1320
+ elementReference: column['@_element_reference'],
1321
+ unique: column['@_unique'],
1322
+ spellCheck: column['@_spell_check'],
1323
+ xmlView: column['@_xml_view'],
1324
+ isArray: column['@_array'],
1325
+ textIndex: column['@_text_index'],
1326
+ isPrimary: column['@_primary'],
1327
+ attributes: column['@_attributes'],
1328
+ referenceCascadeRule: column['@_reference_cascade_rule'],
1329
+ choiceValue: column['@_choice'],
1330
+ choiceTable: column['@_choice_table'],
1331
+ dependent: column['@_dependent'],
1332
+ })
1333
+
1334
+ if (Array.isArray(table.element)) {
1335
+ columns.push(...table.element.map(mapColumnToDefinition))
1336
+ } else {
1337
+ columns.push(mapColumnToDefinition(table.element))
1338
+ }
1339
+ }
1340
+
1341
+ const indexes: IndexDefinition[] = []
1342
+ if (table.index) {
1343
+ const mapIndexToDefinition = (index: z.infer<typeof IndexSchema>): IndexDefinition => ({
1344
+ name: index['@_name'],
1345
+ unique: index['@_unique'],
1346
+ columns: index.element
1347
+ ? Array.isArray(index.element)
1348
+ ? index.element.map((element) => element['@_name'])
1349
+ : [index.element['@_name']]
1350
+ : [],
1351
+ })
1352
+
1353
+ if (Array.isArray(table.index)) {
1354
+ indexes.push(...table.index.map(mapIndexToDefinition))
1355
+ } else {
1356
+ indexes.push(mapIndexToDefinition(table.index))
1357
+ }
1358
+ }
1359
+
1360
+ return {
1361
+ name: table['@_name'],
1362
+ label: table['@_label'],
1363
+ plural: table['@_plural'],
1364
+ extends: table['@_extends'],
1365
+ columns,
1366
+ indexes,
1367
+ audit: table['@_audit'],
1368
+ readOnly: table['@_read_only'],
1369
+ textIndex: table['@_text_index'],
1370
+ clientScriptsAccess: table['@_client_scripts_access'],
1371
+ wsAccess: table['@_ws_access'],
1372
+ display: table['@_display'],
1373
+ alterAccess: table['@_alter_access'],
1374
+ createAccess: table['@_create_access'],
1375
+ deleteAccess: table['@_delete_access'],
1376
+ updateAccess: table['@_update_access'],
1377
+ callerAccess: table['@_caller_access'],
1378
+ actionsAccess: table['@_actions_access'],
1379
+ readAccess: table['@_read_access'],
1380
+ isExtendable: table['@_is_extendable'],
1381
+ scriptableTable: table['@_scriptable_table'],
1382
+ attributes: table['@_attributes'],
1383
+ }
1384
+ }
1385
+
1386
+ function tableDefToRecordProperties(tableDef: TableDefinition): {
1387
+ sysDbObject: SysDbObjectProperties
1388
+ sysDictionary: SysDictionaryProperties[]
1389
+ sysChoice: SysChoiceProperties[]
1390
+ sysIndex: SysIndexProperties[]
1391
+ sysDocumentation: SysDocumentationProperties[]
1392
+ } {
1393
+ const sysDbObject: SysDbObjectProperties = {
1394
+ name: tableDef.name,
1395
+ label: tableDef.label,
1396
+ super_class: tableDef.extends,
1397
+ is_extendable: tableDef.isExtendable,
1398
+ scriptable_table: tableDef.scriptableTable,
1399
+ client_scripts_access: tableDef.clientScriptsAccess,
1400
+ ws_access: tableDef.wsAccess,
1401
+ alter_access: tableDef.alterAccess,
1402
+ create_access: tableDef.createAccess,
1403
+ delete_access: tableDef.deleteAccess,
1404
+ update_access: tableDef.updateAccess,
1405
+ caller_access: tableDef.callerAccess,
1406
+ actions_access: tableDef.actionsAccess,
1407
+ read_access: tableDef.readAccess,
1408
+ }
1409
+
1410
+ const sysDictionary: SysDictionaryProperties[] = []
1411
+ const sysChoice: SysChoiceProperties[] = []
1412
+ const sysDocumentation: SysDocumentationProperties[] = []
1413
+
1414
+ // table documentation
1415
+ if (tableDef.label && tableDef.plural) {
1416
+ sysDocumentation.push({
1417
+ name: tableDef.name,
1418
+ element: undefined,
1419
+ label: tableDef.label,
1420
+ plural: tableDef.plural,
1421
+ language: 'en',
1422
+ hint: undefined,
1423
+ help: undefined,
1424
+ })
1425
+ }
1426
+
1427
+ for (const column of tableDef.columns) {
1428
+ sysDictionary.push({
1429
+ name: tableDef.name,
1430
+ element: column.name,
1431
+ internal_type: column.type || 'string',
1432
+ default_value: column.defaultValue,
1433
+ max_length: column.maxLength,
1434
+ mandatory: column.isMandatory,
1435
+ read_only: column.isReadOnly,
1436
+ reference_table: column.referenceTable,
1437
+ reference_qual: column.referenceQual,
1438
+ column_label: column.columnLabel,
1439
+ active: column.isActive,
1440
+ display: tableDef.display === column.name || column.display,
1441
+ use_dependent_field: column.useDependentField,
1442
+ use_dynamic_default: column.useDynamicDefault,
1443
+ reference: column.reference,
1444
+ virtual: column.isVirtual,
1445
+ default: column.defaultValue,
1446
+ calculation: column.calculation,
1447
+ choice_field: column.choiceField,
1448
+ function_definition: column.functionDefinition,
1449
+ reference_floats: column.referenceFloats,
1450
+ reference_key: column.referenceKey,
1451
+ dynamic_creation_script: column.dynamicCreationScript,
1452
+ dynamic_creation: column.dynamicCreation,
1453
+ plural: column.plural,
1454
+ hint: column.hint,
1455
+ help: column.help,
1456
+ widget: column.widget,
1457
+ table_reference: column.tableReference,
1458
+ element_reference: column.elementReference,
1459
+ unique: column.unique,
1460
+ spell_check: column.spellCheck,
1461
+ xml_view: column.xmlView,
1462
+ array: column.isArray,
1463
+ text_index: column.textIndex,
1464
+ primary: column.isPrimary,
1465
+ attributes: column.attributes,
1466
+ reference_cascade_rule: column.referenceCascadeRule,
1467
+ choice: column.choiceValue,
1468
+ choice_table: column.choiceTable,
1469
+ dependent: column.dependent,
1470
+ function_field: column.functionDefinition ? true : undefined,
1471
+ audit: column.audit,
1472
+ })
1473
+
1474
+ if (column.choices?.length) {
1475
+ for (const choice of column.choices) {
1476
+ sysChoice.push({
1477
+ name: tableDef.name,
1478
+ element: column.name,
1479
+ label: choice.label || choice.value,
1480
+ value: choice.value,
1481
+ sequence: choice.sequence,
1482
+ dependent_value: choice.dependentValue,
1483
+ hint: choice.hint,
1484
+ inactive: choice.inactive,
1485
+ inactive_on_update: choice.inactiveOnUpdate,
1486
+ language: choice.language,
1487
+ })
1488
+ }
1489
+ }
1490
+
1491
+ // Add column documentation
1492
+ sysDocumentation.push({
1493
+ name: tableDef.name,
1494
+ element: column.name,
1495
+ label: column.columnLabel || column.name,
1496
+ plural: column.plural || generatePlural(column.columnLabel || column.name),
1497
+ language: 'en',
1498
+ hint: column.hint,
1499
+ help: column.help,
1500
+ })
1501
+ }
1502
+
1503
+ // sys_dictionary collection record
1504
+ sysDictionary.push({
1505
+ name: tableDef.name,
1506
+ internal_type: 'collection',
1507
+ // table properties
1508
+ attributes: tableDef.attributes,
1509
+ audit: tableDef.audit,
1510
+ text_index: tableDef.textIndex,
1511
+ read_only: tableDef.readOnly,
1512
+ // column properties left undefined
1513
+ element: undefined,
1514
+ default_value: undefined,
1515
+ max_length: undefined,
1516
+ mandatory: undefined,
1517
+ reference_table: undefined,
1518
+ reference_qual: undefined,
1519
+ column_label: undefined,
1520
+ active: undefined,
1521
+ display: undefined,
1522
+ use_dependent_field: undefined,
1523
+ use_dynamic_default: undefined,
1524
+ reference: undefined,
1525
+ virtual: undefined,
1526
+ default: undefined,
1527
+ calculation: undefined,
1528
+ choice_field: undefined,
1529
+ function_definition: undefined,
1530
+ reference_floats: undefined,
1531
+ reference_key: undefined,
1532
+ dynamic_creation_script: undefined,
1533
+ dynamic_creation: undefined,
1534
+ plural: undefined,
1535
+ hint: undefined,
1536
+ help: undefined,
1537
+ widget: undefined,
1538
+ table_reference: undefined,
1539
+ element_reference: undefined,
1540
+ unique: undefined,
1541
+ spell_check: undefined,
1542
+ xml_view: undefined,
1543
+ array: undefined,
1544
+ primary: undefined,
1545
+ reference_cascade_rule: undefined,
1546
+ choice: undefined,
1547
+ choice_table: undefined,
1548
+ dependent: undefined,
1549
+ function_field: undefined,
1550
+ })
1551
+
1552
+ const sysIndex: SysIndexProperties[] = []
1553
+ for (const index of tableDef.indexes) {
1554
+ sysIndex.push({
1555
+ index_name: index.name,
1556
+ col_name_string: index.columns.join(','),
1557
+ unique_index: index.unique,
1558
+ logical_table_name: tableDef.name,
1559
+ })
1560
+ }
1561
+
1562
+ return {
1563
+ sysDbObject,
1564
+ sysDictionary,
1565
+ sysChoice,
1566
+ sysIndex,
1567
+ sysDocumentation,
1568
+ }
1569
+ }
1570
+
1571
+ function addTableToGlobalGeneratedFile(tableArg: ObjectShape, sourceFilePath: string, compiler: Compiler): void {
1572
+ const { interfaces, properties, imports, namedImports } = {
1573
+ interfaces: [] as ts.OptionalKind<ts.InterfaceDeclarationStructure>[],
1574
+ properties: [] as ts.OptionalKind<ts.PropertySignatureStructure>[],
1575
+ imports: {} as GlobalRecord<string, ts.OptionalKind<ts.ImportDeclarationStructure>>,
1576
+ namedImports: [] as Array<
1577
+ ts.OptionalKind<ts.ImportSpecifierStructure> & {
1578
+ existingImport: ts.ImportDeclaration
1579
+ }
1580
+ >,
1581
+ }
1582
+ const generatedTableFile: ts.SourceFile | undefined = compiler.getGeneratedTableFile()
1583
+ const tableName = tableArg.get('name').asString().getValue()
1584
+ if (!(tableName.trim().length > 0 && tableName.match(tableNameRegex))) {
1585
+ return
1586
+ }
1587
+ const extendsTable = tableArg.get('extends').ifString()?.getValue()
1588
+ if (!compiler.interfaceExistsInGlobalDeclaration(tableName)) {
1589
+ interfaces.push({
1590
+ name: tableName,
1591
+ extends: [`Helper<typeof ${tableName}>`],
1592
+ })
1593
+ }
1594
+ if (!compiler.propertyExistsInGlobalDeclaration(tableName)) {
1595
+ const interfaceType = extendsTable
1596
+ ? `Table<TableSchemas.${tableName}, "${extendsTable}">`
1597
+ : `Table<TableSchemas.${tableName}>`
1598
+
1599
+ properties.push({
1600
+ name: tableName,
1601
+ type: interfaceType,
1602
+ })
1603
+ }
1604
+ if (generatedTableFile) {
1605
+ const resolvedModuleSpecifier = generatedTableFile.getRelativePathAsModuleSpecifierTo(sourceFilePath)
1606
+ if (resolvedModuleSpecifier) {
1607
+ const importExists = compiler.importExistsInGlobalDeclaration(resolvedModuleSpecifier)
1608
+ if (!importExists) {
1609
+ if (imports[sourceFilePath]) {
1610
+ const existingImport = imports[sourceFilePath]
1611
+ if (Array.isArray(existingImport.namedImports)) {
1612
+ ;(existingImport.namedImports as string[]).push(tableName)
1613
+ }
1614
+ } else {
1615
+ imports[sourceFilePath] = {
1616
+ namedImports: [tableName],
1617
+ moduleSpecifier: resolvedModuleSpecifier,
1618
+ }
1619
+ }
1620
+ } else {
1621
+ const namedImportExists = importExists?.getNamedImports().some((value) => value.getName() === tableName)
1622
+ if (!namedImportExists) {
1623
+ namedImports.push({
1624
+ existingImport: importExists,
1625
+ name: tableName,
1626
+ })
1627
+ }
1628
+ }
1629
+ }
1630
+ }
1631
+ if (interfaces.length > 0 || properties.length > 0 || Object.keys(imports).length > 0 || namedImports.length > 0) {
1632
+ compiler.addTableInterfacesToGlobalDeclaration({
1633
+ interfaces,
1634
+ properties,
1635
+ imports,
1636
+ namedImports,
1637
+ })
1638
+ }
1639
+ }
1640
+
1641
+ function filterUndefinedProperties<T extends object>(obj: T): T {
1642
+ return Object.fromEntries(Object.entries(obj).filter(([_, value]) => value !== undefined)) as T
1643
+ }
1644
+
1645
+ function filterUndefinedAttributes<T>(attributes: [string, T | undefined][]): [string, T][] {
1646
+ return attributes.filter((pair): pair is [string, T] => pair[1] !== undefined)
1647
+ }
1648
+
1649
+ function createElement(name: string, attributes: [string, string][] = [], children: XMLBuilder[] = []): XMLBuilder {
1650
+ const element = create().ele(name)
1651
+ for (const [key, value] of attributes) {
1652
+ const sanitizedValue = value.replaceAll('\n', '&#10;').replaceAll('\r', '&#13;')
1653
+ element.att(key, sanitizedValue)
1654
+ }
1655
+ for (const child of children) {
1656
+ element.import(child)
1657
+ }
1658
+ return element
1659
+ }
1660
+
1661
+ function generateRecordXml(
1662
+ records: Record[],
1663
+ config: { scope: string; scopeId: string },
1664
+ getFileName: (record: Record) => string
1665
+ ): OutputFile[] {
1666
+ return records.map((record) => {
1667
+ const recordBuilder = unloadBuilder({ scope: config.scope, scopeId: config.scopeId })
1668
+ const builder = recordBuilder.record(record)
1669
+ record
1670
+ .entries()
1671
+ .sort(([a], [b]) => a.localeCompare(b))
1672
+ .forEach(([prop, shape]) => builder.field(prop, shape))
1673
+ return {
1674
+ name: getFileName(record),
1675
+ category: record.getInstallCategory(),
1676
+ content: recordBuilder.end(),
1677
+ }
1678
+ })
1679
+ }
1680
+
1681
+ export function isDefaultLicenseConfig(tableName: string, licenseRecord: Record): boolean {
1682
+ const license = licenseRecord!.asObject().getValue() as globalThis.Record<string, unknown>
1683
+ const defaultRecord: globalThis.Record<string, unknown> = {
1684
+ name: tableName,
1685
+ license_model: 'none',
1686
+ is_fulfillment: false,
1687
+ op_delete: true,
1688
+ op_insert: true,
1689
+ op_update: true,
1690
+ }
1691
+ const optionalFields = ['owner_condition', 'license_condition', 'license_roles'] as const
1692
+ optionalFields.forEach((field) => {
1693
+ if (field in license) {
1694
+ defaultRecord[field] = ''
1695
+ }
1696
+ })
1697
+ return isEqual(defaultRecord, license)
1698
+ }