@servicenow/sdk-build-plugins 2.0.1

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 (166) hide show
  1. package/dist/AttachmentPlugin.d.ts +253 -0
  2. package/dist/AttachmentPlugin.js +216 -0
  3. package/dist/AttachmentPlugin.js.map +1 -0
  4. package/dist/BusinessRulePlugin.d.ts +56 -0
  5. package/dist/BusinessRulePlugin.js +171 -0
  6. package/dist/BusinessRulePlugin.js.map +1 -0
  7. package/dist/CrossScopePrivilegePlugin.d.ts +22 -0
  8. package/dist/CrossScopePrivilegePlugin.js +42 -0
  9. package/dist/CrossScopePrivilegePlugin.js.map +1 -0
  10. package/dist/DefaultPlugin.d.ts +71 -0
  11. package/dist/DefaultPlugin.js +238 -0
  12. package/dist/DefaultPlugin.js.map +1 -0
  13. package/dist/IdPlugin.d.ts +17 -0
  14. package/dist/IdPlugin.js +45 -0
  15. package/dist/IdPlugin.js.map +1 -0
  16. package/dist/ListPlugin.d.ts +91 -0
  17. package/dist/ListPlugin.js +398 -0
  18. package/dist/ListPlugin.js.map +1 -0
  19. package/dist/PropertyPlugin.d.ts +122 -0
  20. package/dist/PropertyPlugin.js +165 -0
  21. package/dist/PropertyPlugin.js.map +1 -0
  22. package/dist/ScriptTemplatePlugin.d.ts +31 -0
  23. package/dist/ScriptTemplatePlugin.js +208 -0
  24. package/dist/ScriptTemplatePlugin.js.map +1 -0
  25. package/dist/UserPreferencePlugin.d.ts +16 -0
  26. package/dist/UserPreferencePlugin.js +30 -0
  27. package/dist/UserPreferencePlugin.js.map +1 -0
  28. package/dist/aclAndRole/AclPlugin.d.ts +117 -0
  29. package/dist/aclAndRole/AclPlugin.js +285 -0
  30. package/dist/aclAndRole/AclPlugin.js.map +1 -0
  31. package/dist/aclAndRole/RolePlugin.d.ts +58 -0
  32. package/dist/aclAndRole/RolePlugin.js +152 -0
  33. package/dist/aclAndRole/RolePlugin.js.map +1 -0
  34. package/dist/aclAndRole/Util.d.ts +3 -0
  35. package/dist/aclAndRole/Util.js +106 -0
  36. package/dist/aclAndRole/Util.js.map +1 -0
  37. package/dist/app/ApplicationMenuPlugin.d.ts +32 -0
  38. package/dist/app/ApplicationMenuPlugin.js +106 -0
  39. package/dist/app/ApplicationMenuPlugin.js.map +1 -0
  40. package/dist/atf/ATFComposer.d.ts +492 -0
  41. package/dist/atf/ATFComposer.js +2717 -0
  42. package/dist/atf/ATFComposer.js.map +1 -0
  43. package/dist/atf/TestPlugin.d.ts +31 -0
  44. package/dist/atf/TestPlugin.js +95 -0
  45. package/dist/atf/TestPlugin.js.map +1 -0
  46. package/dist/atf/index.d.ts +1 -0
  47. package/dist/atf/index.js +9 -0
  48. package/dist/atf/index.js.map +1 -0
  49. package/dist/db/ColumnPlugins.d.ts +278 -0
  50. package/dist/db/ColumnPlugins.js +112 -0
  51. package/dist/db/ColumnPlugins.js.map +1 -0
  52. package/dist/db/RecordPlugin.d.ts +208 -0
  53. package/dist/db/RecordPlugin.js +287 -0
  54. package/dist/db/RecordPlugin.js.map +1 -0
  55. package/dist/db/TablePlugin.d.ts +742 -0
  56. package/dist/db/TablePlugin.js +1249 -0
  57. package/dist/db/TablePlugin.js.map +1 -0
  58. package/dist/db/index.d.ts +3 -0
  59. package/dist/db/index.js +27 -0
  60. package/dist/db/index.js.map +1 -0
  61. package/dist/index.d.ts +16 -0
  62. package/dist/index.js +51 -0
  63. package/dist/index.js.map +1 -0
  64. package/dist/scriptedRESTAPI/RESTDeserializationUtils.d.ts +12 -0
  65. package/dist/scriptedRESTAPI/RESTDeserializationUtils.js +371 -0
  66. package/dist/scriptedRESTAPI/RESTDeserializationUtils.js.map +1 -0
  67. package/dist/scriptedRESTAPI/RESTSerializationUtils.d.ts +15 -0
  68. package/dist/scriptedRESTAPI/RESTSerializationUtils.js +177 -0
  69. package/dist/scriptedRESTAPI/RESTSerializationUtils.js.map +1 -0
  70. package/dist/scriptedRESTAPI/RestApiPlugin.d.ts +144 -0
  71. package/dist/scriptedRESTAPI/RestApiPlugin.js +318 -0
  72. package/dist/scriptedRESTAPI/RestApiPlugin.js.map +1 -0
  73. package/dist/scriptedRESTAPI/RestSchemaUtils.d.ts +190 -0
  74. package/dist/scriptedRESTAPI/RestSchemaUtils.js +53 -0
  75. package/dist/scriptedRESTAPI/RestSchemaUtils.js.map +1 -0
  76. package/dist/scriptedRESTAPI/RestUtils.d.ts +75 -0
  77. package/dist/scriptedRESTAPI/RestUtils.js +469 -0
  78. package/dist/scriptedRESTAPI/RestUtils.js.map +1 -0
  79. package/dist/scripts/ClientScriptPlugin.d.ts +43 -0
  80. package/dist/scripts/ClientScriptPlugin.js +190 -0
  81. package/dist/scripts/ClientScriptPlugin.js.map +1 -0
  82. package/dist/scripts/scriptUtils.d.ts +15 -0
  83. package/dist/scripts/scriptUtils.js +83 -0
  84. package/dist/scripts/scriptUtils.js.map +1 -0
  85. package/dist/uxf/ExperiencePlugin.d.ts +22 -0
  86. package/dist/uxf/ExperiencePlugin.js +55 -0
  87. package/dist/uxf/ExperiencePlugin.js.map +1 -0
  88. package/dist/uxf/RoutesPlugin.d.ts +22 -0
  89. package/dist/uxf/RoutesPlugin.js +176 -0
  90. package/dist/uxf/RoutesPlugin.js.map +1 -0
  91. package/dist/uxf/UxfFormulaParser/cleanUxValue.d.ts +4 -0
  92. package/dist/uxf/UxfFormulaParser/cleanUxValue.js +65 -0
  93. package/dist/uxf/UxfFormulaParser/cleanUxValue.js.map +1 -0
  94. package/dist/uxf/UxfFormulaParser/grammerParser/api.d.ts +189 -0
  95. package/dist/uxf/UxfFormulaParser/grammerParser/api.js +158 -0
  96. package/dist/uxf/UxfFormulaParser/grammerParser/api.js.map +1 -0
  97. package/dist/uxf/UxfFormulaParser/grammerParser/clientTransformMap.d.ts +13 -0
  98. package/dist/uxf/UxfFormulaParser/grammerParser/clientTransformMap.js +604 -0
  99. package/dist/uxf/UxfFormulaParser/grammerParser/clientTransformMap.js.map +1 -0
  100. package/dist/uxf/UxfFormulaParser/grammerParser/grammarParser.d.ts +12 -0
  101. package/dist/uxf/UxfFormulaParser/grammerParser/grammarParser.js +551 -0
  102. package/dist/uxf/UxfFormulaParser/grammerParser/grammarParser.js.map +1 -0
  103. package/dist/uxf/UxfFormulaParser/grammerParser/spanHelpers.d.ts +31 -0
  104. package/dist/uxf/UxfFormulaParser/grammerParser/spanHelpers.js +64 -0
  105. package/dist/uxf/UxfFormulaParser/grammerParser/spanHelpers.js.map +1 -0
  106. package/dist/uxf/UxfFormulaParser/index.d.ts +3 -0
  107. package/dist/uxf/UxfFormulaParser/index.js +11 -0
  108. package/dist/uxf/UxfFormulaParser/index.js.map +1 -0
  109. package/dist/uxf/UxfFormulaParser/parser.d.ts +8 -0
  110. package/dist/uxf/UxfFormulaParser/parser.js +87 -0
  111. package/dist/uxf/UxfFormulaParser/parser.js.map +1 -0
  112. package/dist/uxf/UxfFormulaParser/utils/getErrorMsg.d.ts +8 -0
  113. package/dist/uxf/UxfFormulaParser/utils/getErrorMsg.js +17 -0
  114. package/dist/uxf/UxfFormulaParser/utils/getErrorMsg.js.map +1 -0
  115. package/dist/uxf/constants.d.ts +2 -0
  116. package/dist/uxf/constants.js +8 -0
  117. package/dist/uxf/constants.js.map +1 -0
  118. package/dist/uxf/index.d.ts +2 -0
  119. package/dist/uxf/index.js +11 -0
  120. package/dist/uxf/index.js.map +1 -0
  121. package/dist/uxf/tectonicIdGenerator.d.ts +12 -0
  122. package/dist/uxf/tectonicIdGenerator.js +102 -0
  123. package/dist/uxf/tectonicIdGenerator.js.map +1 -0
  124. package/license +9 -0
  125. package/package.json +42 -0
  126. package/src/AttachmentPlugin.ts +262 -0
  127. package/src/BusinessRulePlugin.ts +251 -0
  128. package/src/CrossScopePrivilegePlugin.ts +54 -0
  129. package/src/DefaultPlugin.ts +272 -0
  130. package/src/IdPlugin.ts +47 -0
  131. package/src/ListPlugin.ts +497 -0
  132. package/src/PropertyPlugin.ts +218 -0
  133. package/src/ScriptTemplatePlugin.ts +223 -0
  134. package/src/UserPreferencePlugin.ts +36 -0
  135. package/src/aclAndRole/AclPlugin.ts +410 -0
  136. package/src/aclAndRole/RolePlugin.ts +225 -0
  137. package/src/aclAndRole/Util.ts +104 -0
  138. package/src/app/ApplicationMenuPlugin.ts +158 -0
  139. package/src/atf/ATFComposer.ts +3356 -0
  140. package/src/atf/TestPlugin.ts +119 -0
  141. package/src/atf/index.ts +1 -0
  142. package/src/db/ColumnPlugins.ts +117 -0
  143. package/src/db/RecordPlugin.ts +391 -0
  144. package/src/db/TablePlugin.ts +1581 -0
  145. package/src/db/index.ts +3 -0
  146. package/src/index.ts +16 -0
  147. package/src/scriptedRESTAPI/RESTDeserializationUtils.ts +410 -0
  148. package/src/scriptedRESTAPI/RESTSerializationUtils.ts +227 -0
  149. package/src/scriptedRESTAPI/RestApiPlugin.ts +438 -0
  150. package/src/scriptedRESTAPI/RestSchemaUtils.ts +72 -0
  151. package/src/scriptedRESTAPI/RestUtils.ts +507 -0
  152. package/src/scripts/ClientScriptPlugin.ts +251 -0
  153. package/src/scripts/scriptUtils.ts +81 -0
  154. package/src/uxf/ExperiencePlugin.ts +64 -0
  155. package/src/uxf/RoutesPlugin.ts +215 -0
  156. package/src/uxf/UxfFormulaParser/cleanUxValue.ts +73 -0
  157. package/src/uxf/UxfFormulaParser/grammerParser/api.js +166 -0
  158. package/src/uxf/UxfFormulaParser/grammerParser/clientTransformMap.js +606 -0
  159. package/src/uxf/UxfFormulaParser/grammerParser/grammarParser.js +551 -0
  160. package/src/uxf/UxfFormulaParser/grammerParser/spanHelpers.js +65 -0
  161. package/src/uxf/UxfFormulaParser/index.ts +4 -0
  162. package/src/uxf/UxfFormulaParser/parser.ts +64 -0
  163. package/src/uxf/UxfFormulaParser/utils/getErrorMsg.ts +13 -0
  164. package/src/uxf/constants.ts +4 -0
  165. package/src/uxf/index.ts +2 -0
  166. package/src/uxf/tectonicIdGenerator.ts +81 -0
@@ -0,0 +1,1581 @@
1
+ import {
2
+ ChoiceConfig,
3
+ Table,
4
+ Record as NowRecord,
5
+ TableName,
6
+ choiceDropdown,
7
+ DateColumn,
8
+ BasicDateTimeColumn,
9
+ GenericColumn,
10
+ BooleanColumn,
11
+ ChoiceColumn,
12
+ IntegerDateColumn,
13
+ ScheduleDateTimeColumn,
14
+ ConditionsColumn,
15
+ DateTimeColumn,
16
+ CalendarDateTimeColumn,
17
+ DueDateColumn,
18
+ OtherDateColumn,
19
+ DecimalColumn,
20
+ DocumentIdColumn,
21
+ DomainIdColumn,
22
+ DomainPathColumn,
23
+ FieldNameColumn,
24
+ IntegerColumn,
25
+ RadioColumn,
26
+ ReferenceColumn,
27
+ ScriptColumn,
28
+ SystemClassNameColumn,
29
+ TableNameColumn,
30
+ TranslatedFieldColumn,
31
+ TranslatedTextColumn,
32
+ UserRolesColumn,
33
+ VersionColumn,
34
+ StringColumn,
35
+ } from '@servicenow/sdk-core/runtime/db'
36
+ import {
37
+ Context,
38
+ EntityData,
39
+ LinkedDocument,
40
+ Plugin,
41
+ extractCallExpression,
42
+ getCallExpressionName,
43
+ getOrCreateEntitySourceFile,
44
+ getOrCreatePropertyAssignment,
45
+ linkDocument,
46
+ removeNode,
47
+ stringify,
48
+ transformFunctionArguments,
49
+ writeCustomProperty,
50
+ XmlData,
51
+ FluentDiagnostic,
52
+ PartialElements,
53
+ generateCallExpressionExport,
54
+ generateCallExpression,
55
+ createPropertyIdentifier,
56
+ XMLJsonBuilder,
57
+ XMLJsonElement,
58
+ ObjectData,
59
+ } from '@servicenow/sdk-build-core'
60
+ import { XMLBuilder } from 'fast-xml-parser'
61
+ import { isArray, isEmpty, isObject, isString, parseInt } from 'lodash'
62
+ import { z } from 'zod'
63
+ import { RecordPlugin, TextBooleanSchema, TextNumberSchema, TextStringSchema } from './RecordPlugin'
64
+ import { ScriptInfo, buildScriptImport, scriptInfo } from '../scripts/scriptUtils'
65
+ import { SyntaxKind, Node, ts, ObjectLiteralExpression, CallExpression, Structure, SourceFile } from 'ts-morph'
66
+ import { Diagnostic } from '@servicenow/sdk-project'
67
+ import { formatScript } from '../ScriptTemplatePlugin'
68
+ import * as os from 'os'
69
+ import * as path from 'path'
70
+
71
+ /**
72
+ * The access levels are mapped to the following values on the platform
73
+ *
74
+ * - `none`: 0
75
+ * - `tracking`: 1
76
+ * - `restricted`: 2
77
+ *
78
+ */
79
+ export const callerAccess = ['none', 'tracking', 'restricted'] as const
80
+
81
+ // Basic info needed for table references
82
+ export const TableSchemaFull = z.object({
83
+ /** lowercase, one word name. Numbers, letters and underscores only */
84
+ name: z.string(),
85
+ /** Object literal of column names with <type>Column() calls as values.
86
+ * Ex. { column_name_1: StringColumn({...args}), column_name_2: IntegerColumn({...args}), }
87
+ * Import column definitions from `@servicenow/sdk-core/db`
88
+ */
89
+ schema: z.record(z.record(z.any())).default({}),
90
+ /** Table as a string, this table will inherit schema from extends table.
91
+ * Escape with `` (import from `@servicenow/sdk-core/global`)
92
+ * if table definition not present locally */
93
+ extends: z.string().optional(),
94
+ /** Will match name if not provided, cannot exceed 80 characters */
95
+ label: z.string().max(80, 'Cannot exceed 80 characters').optional(),
96
+ /** Must match column name */
97
+ display: z.string().optional(),
98
+
99
+ // Controls
100
+ extensible: z.boolean().optional().default(false),
101
+ live_feed: z.boolean().optional().default(false),
102
+
103
+ /** sys_number to create auto prefixing. Specify as Object literal */
104
+ auto_number: z
105
+ .object({
106
+ prefix: z.string().default('pre').optional(),
107
+ number: z.number().default(1000).optional(),
108
+ number_of_digits: z.number().default(7).optional(),
109
+ })
110
+
111
+ .optional(),
112
+
113
+ // Application Access
114
+ accessible_from: z
115
+ .union([z.literal('package_private'), z.literal('public')])
116
+ .optional()
117
+ .default('public'),
118
+ caller_access: z.enum(callerAccess).optional(),
119
+ /** Array of read, update, create, delete actions (['read', 'update',...])*/
120
+ actions: z
121
+ .array(z.union([z.literal('read'), z.literal('update'), z.literal('delete'), z.literal('create')]))
122
+ .optional(),
123
+
124
+ allow_web_service_access: z.boolean().optional().default(false),
125
+ allow_new_fields: z.boolean().optional().default(false),
126
+ allow_ui_actions: z.boolean().optional().default(false),
127
+ allow_client_scripts: z.boolean().optional().default(false),
128
+
129
+ // From sys_dictionary
130
+ audit: z.boolean().optional().default(false),
131
+ read_only: z.boolean().optional().default(false),
132
+ text_index: z.boolean().optional().default(false),
133
+
134
+ /** Object literal of key value pairs corresponding to attributes */
135
+ attributes: z.record(z.union([z.string(), z.number(), z.boolean()])).optional(),
136
+
137
+ /** Array of objects [{ { name: <columnName>, element: ...,}...}] */
138
+ index: z
139
+ .array(
140
+ z.object({
141
+ name: z.string(),
142
+ unique: z.boolean(),
143
+ element: z.string(),
144
+ })
145
+ )
146
+ .optional(),
147
+ })
148
+
149
+ const componentTables = ['sys_dictionary', 'sys_choice', 'sys_documentation']
150
+
151
+ type IndexType = {
152
+ name: string
153
+ unique: boolean
154
+ element: string
155
+ }
156
+
157
+ // Everything that goes into a table
158
+
159
+ // Everything which goes into a element with type='string/int/etc' in bootstrap.xml
160
+ type ColumnSchema = z.infer<typeof ColumnSchema>
161
+ export const ColumnSchema = z.object({
162
+ entityKind: z.string().optional(),
163
+ column_type: z.string().optional(),
164
+ audit: z.boolean().optional().default(false),
165
+ label: z.string().optional(),
166
+ maxLength: z.number().optional().default(40),
167
+ mandatory: z.boolean().optional().default(false),
168
+ read_only: z.boolean().optional().default(false),
169
+ default: z.any().optional(),
170
+ attributes: z.record(z.string(), z.union([z.string(), z.boolean(), z.number()])).optional(),
171
+ referenceTable: z.string().optional(),
172
+ dropdown: z.enum(choiceDropdown).default('none').optional(),
173
+ choices: z.record(z.any()).or(z.array(z.any())).optional(),
174
+ choice_elements: z.record(z.any()).or(z.array(z.any())).optional(),
175
+ display: z.boolean().default(false).optional(),
176
+ function_definition: z.string().optional(),
177
+ dynamic_value_definitions: z
178
+ .object({
179
+ type: z.union([
180
+ z.literal('dynamic_default'),
181
+ z.literal('choices_from_other_table'),
182
+ z.literal('calculated_value'),
183
+ z.literal('dependent_field'),
184
+ ]),
185
+ })
186
+ .and(z.any())
187
+ .optional(),
188
+ })
189
+
190
+ const BooleanFromString = z
191
+ .string()
192
+ .transform((str) => str === 'true')
193
+ .or(z.boolean())
194
+ .optional()
195
+
196
+ const IntFromString = z
197
+ .string()
198
+ .transform((str) => parseInt(str))
199
+ .or(z.number())
200
+ .optional()
201
+
202
+ // These fields transform from sys_db_object only, not bootstrap. (Except name, which is needed to match)
203
+ const DBObjectIncomingAttributes = z.object({
204
+ name: TextStringSchema.or(z.string()).optional(),
205
+
206
+ read_access: TextBooleanSchema.or(BooleanFromString).optional().default(true),
207
+ create_access: TextBooleanSchema.or(BooleanFromString).optional().default(true),
208
+ update_access: TextBooleanSchema.or(BooleanFromString).optional().default(true),
209
+ delete_access: TextBooleanSchema.or(BooleanFromString).optional().default(false),
210
+
211
+ ws_access: TextBooleanSchema.or(BooleanFromString).optional().default(true),
212
+ caller_access: TextNumberSchema.or(IntFromString)
213
+ .transform((val) => {
214
+ if (val) {
215
+ return callerAccess[val]
216
+ }
217
+ return
218
+ })
219
+ .optional(),
220
+ alter_access: TextBooleanSchema.or(BooleanFromString).optional().default(false),
221
+ client_scripts_access: TextBooleanSchema.or(BooleanFromString).optional().default(false),
222
+ actions_access: TextBooleanSchema.or(BooleanFromString).optional().default(false),
223
+ //configuration_access: TextBooleanSchema.or(BooleanFromString).optional().default(false),
224
+
225
+ is_extendable: TextBooleanSchema.or(BooleanFromString).optional().default(false),
226
+ // live_feed_enabled: TextBooleanSchema.or(BooleanFromString).optional().default(false),
227
+
228
+ access: TextStringSchema.or(z.string()).optional(),
229
+ })
230
+
231
+ // Attributes in the element='collection' tag of bootstrap xml
232
+ export const TableSchemaBootstrap = z.object({
233
+ name: z.string(),
234
+ extends: z.string().optional(),
235
+ label: z.string().max(80, 'Cannot exceed 80 characters').optional(),
236
+
237
+ // Controls
238
+ extensible: z.boolean().optional().default(false),
239
+ live_feed: z.boolean().optional().default(false),
240
+
241
+ auto_number: z
242
+ .object({
243
+ prefix: z.string().default('pre').optional(),
244
+ number: z.number().default(1000).optional(),
245
+ number_of_digits: z.number().default(7).optional(),
246
+ })
247
+ .optional(),
248
+
249
+ number_ref: z.any().optional(),
250
+
251
+ // Application Access
252
+ accessible_from: z
253
+ .union([z.literal('public'), z.literal('package_private')])
254
+ .optional()
255
+ .default('public'),
256
+ caller_access: z.enum(callerAccess).default('none').optional(),
257
+
258
+ actions: z
259
+ .array(z.union([z.literal('read'), z.literal('update'), z.literal('delete'), z.literal('create')]))
260
+ .default(['read'])
261
+ .optional(),
262
+
263
+ allow_web_service_access: z.boolean().optional().default(true),
264
+ allow_new_fields: z.boolean().optional().default(false),
265
+ allow_ui_actions: z.boolean().optional().default(false),
266
+ allow_client_scripts: z.boolean().optional().default(false),
267
+
268
+ // From sys_dictionary
269
+ audit: z.boolean().optional().default(false),
270
+ read_only: z.boolean().optional().default(false),
271
+ text_index: z.boolean().optional().default(false),
272
+
273
+ attributes: z
274
+ .record(z.union([z.string(), z.number(), z.boolean()]))
275
+ .default({})
276
+ .optional(),
277
+ })
278
+
279
+ const TableSchemaBootstrapIncoming = TableSchemaBootstrap.omit({ actions: true }).extend({
280
+ caller_access: z
281
+ .union([z.literal(0), z.literal(1), z.literal(2)])
282
+ .transform((val) => {
283
+ if (val) {
284
+ return callerAccess[val]
285
+ }
286
+ return
287
+ })
288
+ .optional(),
289
+ accessible_from: z.union([z.literal('public'), z.literal('package_private')]).optional(),
290
+ extends: z
291
+ .string()
292
+ .or(z.object({ name: z.string() }).transform((obj) => obj.name))
293
+ .optional(),
294
+ live_feed: z.boolean().optional().default(false),
295
+ index: z.any().optional(),
296
+ columns: z.any().optional(),
297
+ })
298
+
299
+ // Everything in side <database> in boostrap.xml
300
+ const BootstrapDatabaseElement = z.object({
301
+ element: z.record(z.any()),
302
+ })
303
+
304
+ const DBObjectIncoming = z.object({
305
+ '@_table': z.literal('sys_db_object'),
306
+ sys_db_object: z.record(z.any()),
307
+ })
308
+
309
+ export const Documentation = z.object({
310
+ sys_documentation: z.object({
311
+ element: TextStringSchema.optional(),
312
+ label: TextStringSchema.optional(),
313
+ language: TextStringSchema.optional(),
314
+ hint: TextStringSchema.optional(),
315
+ help: TextStringSchema.optional(),
316
+ name: TextStringSchema.optional(),
317
+ plural: TextStringSchema.optional(),
318
+ url: TextStringSchema.optional(),
319
+ url_target: TextStringSchema.optional(),
320
+ }),
321
+ })
322
+
323
+ // Attributes from element=table in bootstrap.xml
324
+ export const TableSchemaBootstrapAttributes = z
325
+ .object({
326
+ '@_name': z.string(),
327
+ '@_extends': z.string().optional(),
328
+ '@_label': z.string().optional(),
329
+ '@_max_length': z.coerce.number().optional(),
330
+
331
+ // From sys_dictionary
332
+ '@_audit': BooleanFromString,
333
+ '@_read_only': BooleanFromString,
334
+ '@_text_index': BooleanFromString,
335
+ '@_client_scripts_access': BooleanFromString,
336
+ '@_ws_access': BooleanFromString,
337
+ '@_alter_access': BooleanFromString,
338
+ '@_create_access': BooleanFromString,
339
+ '@_delete_access': BooleanFromString,
340
+ '@_update_access': BooleanFromString,
341
+ '@_query_access': BooleanFromString,
342
+ '@_actions_access': BooleanFromString,
343
+ '@_read_access': BooleanFromString,
344
+ '@_is_extendable': BooleanFromString,
345
+ '@_caller_access': IntFromString,
346
+
347
+ '@_attributes': z
348
+ .string()
349
+ .transform((values) => {
350
+ const ret = {}
351
+ if (values === '') {
352
+ return undefined
353
+ }
354
+ const entries = values.split(',')
355
+ entries.forEach((attr) => {
356
+ const s = attr.split('=')
357
+ if (s.length === 2 && s[0] && s[1]) {
358
+ try {
359
+ ret[s[0]] = JSON.parse(s[1])
360
+ } catch (err) {
361
+ ret[s[0]] = s[1]
362
+ }
363
+ }
364
+ })
365
+ return ret
366
+ })
367
+ .optional(),
368
+ })
369
+ .catchall(z.any())
370
+
371
+ const IndexSchemaBootstrapAttributes = z.object({
372
+ '@_name': z.string(),
373
+ '@_unique': BooleanFromString,
374
+ element: z.object({
375
+ '@_name': z.string(),
376
+ }),
377
+ })
378
+
379
+ export const ColumnSchemaBootstrapAttributes = TableSchemaBootstrapAttributes.omit({ '@_extends': true })
380
+ .extend({
381
+ '@_choice': z.coerce.number().optional(),
382
+ '@_active': BooleanFromString,
383
+ '@_mandatory': BooleanFromString,
384
+ '@_display': BooleanFromString,
385
+ '@_type': z.string().optional(),
386
+ '@_use_dependent_field': BooleanFromString,
387
+ '@_use_dynamic_default': BooleanFromString,
388
+ '@_reference': z.string().optional(),
389
+ '@_virtual': BooleanFromString,
390
+ '@_default': z.string().optional(),
391
+ '@_calculation': z.string().optional(),
392
+ '@_choice_field': z.string().optional(),
393
+ '@_function_definition': z.string().optional(),
394
+ choice: z.object({ element: z.array(z.any()).or(z.record(z.any())).optional() }).optional(),
395
+ })
396
+ .catchall(z.any())
397
+
398
+ const ChoiceSchema = z.object({
399
+ label: z.string().optional(),
400
+ value: z.string().optional(),
401
+ sequence: z.coerce.number().optional(),
402
+ dependent_value: z.coerce.number().optional(),
403
+ hint: z.string().optional(),
404
+ inactive: BooleanFromString.default(false).optional(),
405
+ inactive_on_update: BooleanFromString.default(false).optional(),
406
+ language: z.string().optional(),
407
+ })
408
+
409
+ const ChoiceSchemaIncoming = ChoiceSchema.omit({ value: true })
410
+
411
+ type ComposedTableData = {
412
+ data: {
413
+ bootstrapData: any
414
+ dbObjectData: any
415
+ }
416
+ }
417
+
418
+ function isSNScope(scopeName: string) {
419
+ return scopeName.startsWith('sn') || scopeName.startsWith('now')
420
+ }
421
+
422
+ export default Plugin({
423
+ name: 'Table',
424
+ ownedTables: {
425
+ sys_db_object: { diagnosticLevel: Diagnostic.Level.Warn },
426
+ },
427
+ extractors: {
428
+ entity: {
429
+ // TODO: Cast to `any` is temporary workaround for the circular nature of Table entities
430
+ CallExpression: (node, context) => {
431
+ const result = extractCallExpression(Table, 'table', node, context, (table) => table.name)
432
+ if (!result.handled || !(0 in result.data)) {
433
+ return result
434
+ }
435
+
436
+ const data = result.data[0]
437
+ const diagnostics = result.diagnostics
438
+ const scopeName = context.app.config.scope
439
+ const scopeRegex = new RegExp(`^${scopeName}_`)
440
+ const nameRegex = new RegExp(`^[a-z_][a-z0-9_]`)
441
+ const tableName = data.getProperty('name')
442
+ const scopeMatches = tableName.getValue().match(scopeRegex)
443
+
444
+ if (!scopeMatches) {
445
+ const nameNode = tableName.getNode().asKindOrThrow(SyntaxKind.StringLiteral)
446
+ if (nameNode.getParent().getKindName() !== 'AsExpression') {
447
+ diagnostics.push(
448
+ new FluentDiagnostic(
449
+ tableName.getNode(),
450
+ `'name' property must start with scope prefix '${scopeName}'`
451
+ )
452
+ )
453
+ }
454
+ }
455
+
456
+ const schema = data.getProperty('schema')
457
+ const schemaNode = schema.getNode().asKindOrThrow(SyntaxKind.ObjectLiteralExpression)
458
+ for (const columnName in schema.getValue()) {
459
+ const columnNode = schemaNode.getPropertyOrThrow(columnName)
460
+ if (!scopeMatches && !isSNScope(scopeName) && !columnName.match(scopeRegex)) {
461
+ diagnostics.push(
462
+ new FluentDiagnostic(
463
+ columnNode,
464
+ `Column name must be prefixed with scope '${scopeName}' if table name does not contain prefix`
465
+ )
466
+ )
467
+ }
468
+
469
+ if (!columnName.match(nameRegex)) {
470
+ diagnostics.push(
471
+ new FluentDiagnostic(
472
+ columnNode,
473
+ `Column name must only contain lowercase letters, numbers, and underscores`
474
+ )
475
+ )
476
+ }
477
+ }
478
+
479
+ const declaration = node.getParentIfKind(SyntaxKind.VariableDeclaration)
480
+ if (
481
+ !declaration ||
482
+ !declaration.isExported() ||
483
+ !declaration.isNamedExport() ||
484
+ declaration.getName() !== tableName.getValue()
485
+ ) {
486
+ diagnostics.push(
487
+ new FluentDiagnostic(
488
+ node,
489
+ `Table definition should be exported as a named export with the name '${tableName.getValue()}'`,
490
+ { level: Diagnostic.Level.Warn }
491
+ )
492
+ )
493
+ }
494
+
495
+ return { handled: true, data: [data], diagnostics }
496
+ },
497
+ },
498
+ raw: {
499
+ FunctionDeclaration(node, context) {
500
+ const info = scriptInfo(node, context, Table.name)
501
+ if (!info) {
502
+ return { handled: false }
503
+ }
504
+
505
+ return { handled: true, diagnostics: [], data: [info] }
506
+ },
507
+
508
+ FunctionExpression(node, context) {
509
+ const info = scriptInfo(node, context, Table.name)
510
+ if (!info) {
511
+ return { handled: false }
512
+ }
513
+
514
+ return { handled: true, diagnostics: [], data: [info] }
515
+ },
516
+ },
517
+ xml(xml, context) {
518
+ const bootstrapData = {}
519
+ let dbObjectData = {}
520
+ const bootstrapDatabaseXML = BootstrapDatabaseElement.safeParse(xml.data['database'])
521
+ if (bootstrapDatabaseXML.success) {
522
+ const data = TableSchemaBootstrapAttributes.parse(bootstrapDatabaseXML.data.element)
523
+ Object.entries(data).forEach(([key, value]) => {
524
+ if (key.startsWith('@_')) {
525
+ bootstrapData[key.substring(2)] = value
526
+ } else if (key === 'element') {
527
+ //
528
+ } else {
529
+ bootstrapData[key] = value
530
+ }
531
+ })
532
+ context.handledXmls[xml.filePath] = 'handled'
533
+ }
534
+
535
+ const db_object_inc = DBObjectIncoming.safeParse(xml.data['record_update'])
536
+ if (db_object_inc.success) {
537
+ dbObjectData = DBObjectIncomingAttributes.parse(db_object_inc.data.sys_db_object)
538
+ context.handledXmls[xml.filePath] = 'handled'
539
+ return new XmlData(
540
+ dbObjectData,
541
+ xml.filePath,
542
+ 'sys_db_object',
543
+ db_object_inc.data.sys_db_object['@_action']
544
+ )
545
+ } else {
546
+ const record_update = xml.data['record_update']
547
+ if (record_update) {
548
+ const table = Object.keys(record_update)[0]
549
+ if (!table || !componentTables.includes(table)) {
550
+ return
551
+ }
552
+
553
+ if (table === 'sys_documentation') {
554
+ const data = Documentation.parse(record_update[table])
555
+ return new XmlData(
556
+ {
557
+ table: table,
558
+ id: record_update[table][table]['sys_id']['#text'],
559
+ data: data.sys_documentation,
560
+ },
561
+ xml.filePath,
562
+ 'record',
563
+ record_update[table][table]['@_action']
564
+ )
565
+ }
566
+
567
+ context.handledXmls[xml.filePath] = 'ignored'
568
+ return
569
+ }
570
+ }
571
+
572
+ // Columns and Indexes
573
+ const columns: z.infer<typeof ColumnSchemaBootstrapAttributes>[] = []
574
+ const idxElements: IndexType[] = []
575
+ if (bootstrapDatabaseXML.success && bootstrapDatabaseXML.data.element['element']) {
576
+ const element = bootstrapDatabaseXML.data.element['element']
577
+ const elementsArray = isArray(element) ? element : [element]
578
+ columns.push(...z.array(ColumnSchemaBootstrapAttributes).parse(elementsArray))
579
+
580
+ columns.forEach((element) => {
581
+ if (element.choice && !isEmpty(element.choice)) {
582
+ element['choice_elements'] = {}
583
+ const choices = isArray(element.choice.element)
584
+ ? element.choice.element
585
+ : [element.choice.element]
586
+ choices.forEach((choice) => {
587
+ const obj = removeAttributeTag(choice)
588
+ const choiceData = ChoiceSchemaIncoming.parse(obj)
589
+ if (!choiceData.label) {
590
+ choiceData.label = obj.value
591
+ }
592
+ element['choice_elements'][obj.value] = choiceData
593
+ })
594
+ }
595
+
596
+ element = removeAttributeTag(element)
597
+ })
598
+ bootstrapData['columns'] = columns.reduce((map, obj) => ((map[obj['name']] = obj), map), {})
599
+
600
+ let elements = bootstrapDatabaseXML.data.element['index']
601
+ elements = isArray(elements) ? elements : [elements]
602
+ elements.forEach((element) => {
603
+ const idxData = IndexSchemaBootstrapAttributes.safeParse(element)
604
+ if (idxData.success) {
605
+ idxElements.push({
606
+ name: idxData.data['@_name'],
607
+ element: idxData.data.element['@_name'],
608
+ unique: idxData.data['@_unique'] ?? false,
609
+ })
610
+ }
611
+ })
612
+ bootstrapData['index'] = idxElements
613
+ }
614
+
615
+ return new XmlData(bootstrapData, xml.filePath, 'bootstrap')
616
+ },
617
+ },
618
+ composers: {
619
+ entity: {
620
+ table(entity, context) {
621
+ const node = entity.getNode()
622
+ const data = entity.getValue()
623
+ const table = TableSchemaFull.parse(data)
624
+
625
+ const composeData = compose(table)
626
+ composeData['columns'] = composeColumns(
627
+ table.schema as Record<string, ColumnSchema>,
628
+ context,
629
+ table.display
630
+ )
631
+
632
+ const entities: LinkedDocument[] = []
633
+ if (table.auto_number) {
634
+ const data = table.auto_number
635
+
636
+ const numberRecord = SysNumber(table.name, data.number_of_digits, data.number, data.prefix)
637
+
638
+ const composedNumber = RecordPlugin.composers.entity.record(
639
+ new EntityData(
640
+ 'record',
641
+ context.registerExplicitId(numberRecord.table, numberRecord.$id),
642
+ ObjectData.fromObjectValue(numberRecord, node),
643
+ node
644
+ ),
645
+ context
646
+ )
647
+
648
+ entities.push(composedNumber)
649
+
650
+ composeData['number_ref'] = composedNumber.guid
651
+ }
652
+
653
+ composeData['index'] = table.index
654
+
655
+ entities.push({
656
+ kind: 'bootstrap',
657
+ node,
658
+ guid: composeData.name,
659
+ data: { data: { bootstrapData: composeData, dbObjectData: composeData } },
660
+ })
661
+
662
+ return entities
663
+ },
664
+ },
665
+ xml: {
666
+ bootstrap(xml) {
667
+ return {
668
+ kind: xml.kind,
669
+ guid: xml.data['name'] as string,
670
+ data: { data: { bootstrapData: xml.data } },
671
+ }
672
+ },
673
+ sys_db_object(xml) {
674
+ return {
675
+ kind: 'bootstrap',
676
+ guid: xml.data['name'] as string,
677
+ data: { data: { dbObjectData: xml.data } },
678
+ action: xml.action,
679
+ }
680
+ },
681
+ },
682
+ },
683
+ generators: {
684
+ bootstrap(document, context) {
685
+ const parsedBoostrapData = TableSchemaBootstrapIncoming.safeParse(
686
+ (document.data as ComposedTableData).data.bootstrapData
687
+ )
688
+ if (!parsedBoostrapData.success) {
689
+ return undefined
690
+ }
691
+
692
+ return linkDocument(
693
+ document,
694
+ generateCallExpressionExportForDocument(
695
+ context,
696
+ {
697
+ sourceFile: getOrCreateEntitySourceFile(context, parsedBoostrapData.data.name),
698
+ moduleSpecifier: '@servicenow/sdk/core',
699
+ exportName: parsedBoostrapData.data.name,
700
+ },
701
+ Table,
702
+ {}
703
+ ).getInitializerIfKindOrThrow(SyntaxKind.CallExpression)
704
+ )
705
+ },
706
+
707
+ record(document, context) {
708
+ if (
709
+ document.data?.['table'] !== 'sys_documentation' &&
710
+ document.data?.['table'] !== 'ua_table_licensing_config'
711
+ ) {
712
+ return
713
+ }
714
+
715
+ return linkDocument(
716
+ document,
717
+ generateCallExpression(
718
+ context,
719
+ getOrCreateEntitySourceFile(context, `${document.data?.['data'].name}`),
720
+ '@servicenow/sdk/core',
721
+ NowRecord,
722
+ { $id: document.guid }
723
+ )
724
+ )
725
+ },
726
+ },
727
+ serializers: {
728
+ bootstrap(document) {
729
+ const composedData = (document.data as ComposedTableData).data
730
+ const { columns, index, attributes, ...bootstrapData } = composedData.bootstrapData
731
+
732
+ if (bootstrapData.live_feed) {
733
+ // live_feed needs to be a table sys_dictionary attribute so it can be synced to sys_db_object by a startup process
734
+ attributes['live_feed'] = true
735
+ }
736
+
737
+ const builder = new XMLJsonBuilder('1.0')
738
+ const databaseObj = builder.createRoot('database', undefined, undefined)
739
+ const composedAttributes = composeAttributes(attributes)
740
+ const collectionObj = databaseObj.addJsonObj(
741
+ 'element',
742
+ undefined,
743
+ { ...bootstrapData, attributes: composedAttributes }!
744
+ )
745
+ for (const column of Object.values(columns)) {
746
+ const { choice_elements, attributes, ...parsedColumn } = column as ColumnSchema
747
+ const columnAttributes = composeAttributes(attributes)
748
+ const elementObj = collectionObj.addJsonObj(
749
+ 'element',
750
+ undefined,
751
+ {
752
+ ...(parsedColumn as any),
753
+ attributes: columnAttributes,
754
+ }!
755
+ )
756
+
757
+ buildChoices(column as ColumnSchema, elementObj)
758
+ }
759
+
760
+ buildIndexes(index, collectionObj)
761
+
762
+ const bootstrapXmlJson = new XMLBuilder({
763
+ ignoreAttributes: false,
764
+ format: true,
765
+ suppressBooleanAttributes: false,
766
+ suppressEmptyNode: true,
767
+ }).build(builder.buildJsonObj())
768
+
769
+ return {
770
+ name: `${bootstrapData.name}.xml`,
771
+ directory: 'dictionary',
772
+ content: bootstrapXmlJson,
773
+ usedIds: [],
774
+ }
775
+ },
776
+ },
777
+ transformers: {
778
+ bootstrap: {
779
+ CallExpression(document) {
780
+ const expressionName = getCallExpressionName(document.node)
781
+ if (expressionName !== Table.name) {
782
+ return false
783
+ }
784
+ const [args] = document.node.getArguments()
785
+ if (!Node.isObjectLiteralExpression(args)) {
786
+ return false
787
+ }
788
+
789
+ if (document.action === 'DELETE') {
790
+ removeNode(document.node)
791
+ return true
792
+ }
793
+
794
+ if (!document.changedData) {
795
+ return true
796
+ }
797
+
798
+ const composedData = (document.changedData as ComposedTableData).data
799
+ const xmlData = (document.xmlData as ComposedTableData).data
800
+
801
+ let bootstrap_data = {}
802
+ let db_data = {}
803
+ if (!isEmpty(composedData.bootstrapData)) {
804
+ // sys_db_object or node without bootstrap.xml. Mark as handled and ignore
805
+ bootstrap_data = TableSchemaBootstrapIncoming.partial().parse(composedData.bootstrapData)
806
+ }
807
+ if (!isEmpty(composedData.dbObjectData)) {
808
+ db_data = DBObjectIncomingAttributes.partial().parse(composedData.dbObjectData)
809
+ }
810
+
811
+ const {
812
+ auto_number,
813
+ number_ref,
814
+ extends: ext,
815
+ index,
816
+ columns,
817
+ ...bootstrap_rest
818
+ } = mapNames(bootstrap_data)
819
+ const { create_access, delete_access, update_access, read_access, caller_access, ...db_rest } =
820
+ mapNames(db_data)
821
+ const rest = { ...bootstrap_rest, ...db_rest }
822
+
823
+ if (ext) {
824
+ rest['extends'] = ext
825
+ }
826
+
827
+ if (caller_access) {
828
+ rest['caller_access'] = callerAccess[caller_access]
829
+ }
830
+
831
+ if (!isEmpty(db_data)) {
832
+ const actions = getActionsArray({ create_access, delete_access, update_access, read_access })
833
+ if (actions.length > 0) {
834
+ writeCustomProperty(args, 'actions', '[]', stringify(actions))
835
+ } else {
836
+ const actionsProp = getOrCreatePropertyAssignment(args, 'actions', '[]')
837
+ actionsProp.remove()
838
+ }
839
+ }
840
+
841
+ if (!rest.attributes) {
842
+ delete rest.attributes
843
+ }
844
+
845
+ if (xmlData.bootstrapData && xmlData.bootstrapData.index) {
846
+ const indexProp = getOrCreatePropertyAssignment(args, 'index', '[]')
847
+ indexProp.setInitializer(stringify(xmlData.bootstrapData.index))
848
+ if (xmlData.bootstrapData.index.length === 0) {
849
+ indexProp.remove()
850
+ }
851
+ }
852
+
853
+ transformFunctionArguments(document.node, Table as any, rest)
854
+
855
+ // Handle columns
856
+ const incomingColumns = xmlData.bootstrapData ? xmlData.bootstrapData.columns : undefined
857
+
858
+ updateDisplayValue(document.node, columns, incomingColumns)
859
+ transformColumns(incomingColumns, args)
860
+ return true
861
+ },
862
+ },
863
+ },
864
+ postProcessors: {
865
+ entities(entities, context) {
866
+ entities
867
+ .filter((entity) => entity.getKind() === 'table')
868
+ .forEach((table) => {
869
+ const tableName = table.getProperty('name')!.getValue() as string
870
+ const originalSourceFilePath = path.normalize(table.getNode().getSourceFile().getFilePath())
871
+ context.compiler.addTableInterfaceToGlobalDeclaration(originalSourceFilePath, tableName)
872
+ })
873
+ },
874
+ },
875
+ })
876
+
877
+ function generateCallExpressionExportForDocument<const A extends unknown[]>(
878
+ context: Context,
879
+ info: {
880
+ sourceFile: SourceFile
881
+ moduleSpecifier: string
882
+ exportName: string
883
+ },
884
+ fn: (...args: A) => unknown,
885
+ ...args: PartialElements<A>
886
+ ) {
887
+ const { sourceFile, moduleSpecifier } = info
888
+ return generateCallExpressionExport(context, sourceFile, moduleSpecifier, info.exportName, fn, ...args)
889
+ }
890
+
891
+ function composeAttributes(attributes: Record<string, any> | undefined) {
892
+ if (!attributes) {
893
+ return ''
894
+ }
895
+
896
+ return Object.entries(attributes)
897
+ .map(([key, value]) => `${key}=${value}`)
898
+ .join(',')
899
+ }
900
+
901
+ function updateDisplayValue(node: CallExpression, columns: ColumnSchema, incomingColumns: ColumnSchema) {
902
+ const display = Object.values(columns ?? {}).filter((col: any) => col.display)
903
+ if (display) {
904
+ const displayName = Object.values(incomingColumns ?? {}).find((c) => c.display)
905
+ if (displayName) {
906
+ transformFunctionArguments(node, Table as any, { display: displayName.name })
907
+ }
908
+ }
909
+ }
910
+
911
+ function composeColumns(columns: Record<string, ColumnSchema>, context: Context, display?: string) {
912
+ const composedColumns = {}
913
+ for (const [columnName, columnData] of Object.entries(columns)) {
914
+ const column = ColumnSchema.parse(columnData)
915
+ const requiresDropdown = column.dynamic_value_definitions || column.choices
916
+ const element = {
917
+ name: columnName,
918
+ type: column.entityKind === 'generic' ? column.column_type : column.entityKind,
919
+ attributes: column.attributes ?? undefined,
920
+ reference: column.referenceTable,
921
+ audit: column.audit,
922
+ label: column.label ?? columnName,
923
+ max_length: column.maxLength,
924
+ mandatory: column.mandatory,
925
+ read_only: column.read_only,
926
+ choice: choiceDropdown.indexOf(column.dropdown ?? requiresDropdown ? 'dropdown_with_none' : 'none'),
927
+ display: display ? display === columnName : false,
928
+
929
+ // dynamic values
930
+ virtual: column.dynamic_value_definitions?.type === 'calculated_value' ? true : undefined,
931
+ calculation:
932
+ column.dynamic_value_definitions?.type === 'calculated_value'
933
+ ? getScriptInfo(column.dynamic_value_definitions!['calculated_value'], context)
934
+ : undefined,
935
+ choice_table:
936
+ column.dynamic_value_definitions?.type === 'choices_from_other_table'
937
+ ? column.dynamic_value_definitions.table
938
+ : undefined,
939
+ choice_field:
940
+ column.dynamic_value_definitions?.type === 'choices_from_other_table'
941
+ ? column.dynamic_value_definitions.field
942
+ : undefined,
943
+
944
+ use_dependent_field: column.dynamic_value_definitions?.type === 'dependent_field' || undefined,
945
+
946
+ dependent:
947
+ column.dynamic_value_definitions?.type === 'dependent_field'
948
+ ? column.dynamic_value_definitions!['column_name']
949
+ : undefined,
950
+
951
+ use_dynamic_default: column.dynamic_value_definitions?.type === 'dynamic_default' || undefined,
952
+
953
+ default_value:
954
+ column.dynamic_value_definitions?.type === 'dynamic_default'
955
+ ? getDynamicDefault(column.dynamic_value_definitions?.dynamic_default)
956
+ : undefined,
957
+
958
+ default: column.default,
959
+
960
+ function_definition: column.function_definition,
961
+ function_field: column.function_definition ? true : false,
962
+ }
963
+
964
+ if (column.choices) {
965
+ Object.keys(column.choices).forEach((key) => {
966
+ if (isObject(column.choices![key])) {
967
+ column.choices![key] = { value: key, ...column.choices![key] }
968
+ } else {
969
+ column.choices![key] = { value: key, label: column.choices![key] }
970
+ }
971
+ })
972
+ element['choice_elements'] = z.record(ChoiceSchema).parse(column.choices)
973
+ }
974
+
975
+ composedColumns[columnName] = element
976
+ }
977
+ return composedColumns
978
+ }
979
+
980
+ function compose(bootstrapData: z.infer<typeof TableSchemaBootstrap>) {
981
+ if (bootstrapData.live_feed) {
982
+ bootstrapData.attributes = {
983
+ // live_feed needs to be a table sys_dictionary attribute so it can be synced to sys_db_object by a startup process
984
+ ...bootstrapData.attributes,
985
+ live_feed: true,
986
+ }
987
+ }
988
+
989
+ const composedData = {
990
+ // Generic data
991
+ name: bootstrapData.name,
992
+ type: 'collection',
993
+ extends: bootstrapData.extends,
994
+ label: bootstrapData.label ?? bootstrapData.name,
995
+
996
+ // Controls
997
+ is_extendable: bootstrapData.extensible,
998
+ text_index: bootstrapData.text_index,
999
+ read_only: bootstrapData.read_only,
1000
+ audit: bootstrapData.audit,
1001
+
1002
+ access: bootstrapData.accessible_from,
1003
+ caller_access: callerAccess.indexOf(bootstrapData.caller_access ?? 'none'),
1004
+
1005
+ read_access: bootstrapData.actions?.includes('read'),
1006
+ update_access: bootstrapData.actions?.includes('update'),
1007
+ create_access: bootstrapData.actions?.includes('create'),
1008
+ delete_access: bootstrapData.actions?.includes('delete'),
1009
+
1010
+ ws_access: bootstrapData.allow_web_service_access,
1011
+ alter_access: bootstrapData.allow_new_fields,
1012
+ actions_access: bootstrapData.allow_ui_actions,
1013
+ client_scripts_access: bootstrapData.allow_client_scripts,
1014
+
1015
+ attributes: bootstrapData.attributes,
1016
+ }
1017
+
1018
+ return composedData
1019
+ }
1020
+
1021
+ // API properties that are different in the bootstrap/sys_db_object file
1022
+ const property_name_mapping = {
1023
+ client_scripts_access: 'allow_client_scripts',
1024
+ alter_access: 'allow_new_fields',
1025
+ actions_access: 'allow_ui_actions',
1026
+ ws_access: 'allow_web_service_access',
1027
+ is_extendable: 'extensible',
1028
+ access: 'accessible_from',
1029
+ live_feed_enabled: 'live_feed',
1030
+ }
1031
+
1032
+ function mapNames(schema: any) {
1033
+ Object.keys(schema).forEach((key) => {
1034
+ if (property_name_mapping[key]) {
1035
+ schema[property_name_mapping[key]] = schema[key]
1036
+ delete schema[key]
1037
+ }
1038
+ })
1039
+ return schema
1040
+ }
1041
+
1042
+ let schemaPosition = 4
1043
+
1044
+ function transformColumns(incoming_schema: ColumnSchema, args: ObjectLiteralExpression) {
1045
+ const schemaProperty = getOrCreatePropertyAssignment(args, 'schema', '{}')
1046
+ const schemaLiteral = schemaProperty.getChildrenOfKind(ts.SyntaxKind.ObjectLiteralExpression)[0]
1047
+ if (!schemaLiteral) {
1048
+ return
1049
+ }
1050
+
1051
+ const keys = Object.keys(incoming_schema ?? {})
1052
+ if (keys.length === 0) {
1053
+ return
1054
+ }
1055
+
1056
+ //TODO:: need to come up with a way to format script more accurately
1057
+ schemaPosition = schemaLiteral.getSourceFile().getLineAndColumnAtPos(schemaLiteral.getStart()).column
1058
+ const assignments: ts.PropertyAssignment[] = []
1059
+ keys.forEach((key) => {
1060
+ const data = incoming_schema[key]
1061
+ const callExpFromType = getCallExpressionFromType(data.type)
1062
+ const transformedColumn = transformColumnData(data, callExpFromType)
1063
+ const callExp = generateColumnExpression(callExpFromType, {
1064
+ ...transformedColumn,
1065
+ choices: data.choice_elements,
1066
+ })
1067
+ assignments.push(ts.factory.createPropertyAssignment(createPropertyIdentifier(data.name), callExp))
1068
+ })
1069
+
1070
+ schemaLiteral.transform((traversal) => {
1071
+ if (ts.isObjectLiteralExpression(traversal.currentNode)) {
1072
+ return traversal.factory.updateObjectLiteralExpression(traversal.currentNode, assignments)
1073
+ }
1074
+ return traversal.currentNode
1075
+ })
1076
+ }
1077
+
1078
+ type DB_ACTIONS = 'read' | 'delete' | 'create' | 'update'
1079
+
1080
+ function getActionsArray(data) {
1081
+ const actions: DB_ACTIONS[] = []
1082
+ if (data.read_access) {
1083
+ actions.push('read')
1084
+ }
1085
+ if (data.update_access) {
1086
+ actions.push('update')
1087
+ }
1088
+ if (data.delete_access) {
1089
+ actions.push('delete')
1090
+ }
1091
+ if (data.create_access) {
1092
+ actions.push('create')
1093
+ }
1094
+
1095
+ return actions
1096
+ }
1097
+
1098
+ function getDynamicDefault(val: string) {
1099
+ return val.startsWith('javascript:') ? val : `javascript:${dynamic_value_mapping[val]}`
1100
+ }
1101
+
1102
+ function buildIndexes(indexes: IndexType[] = [], collection: XMLJsonElement) {
1103
+ indexes.forEach((index) => {
1104
+ const indexElement = collection.addJsonObj('index', undefined, { name: index.name, unique: index.unique })
1105
+ indexElement.addJsonObj('element', undefined, { name: index.element })
1106
+ })
1107
+ }
1108
+
1109
+ function buildChoices(column: ColumnSchema, element: XMLJsonElement) {
1110
+ if (!column.choice_elements) {
1111
+ return
1112
+ }
1113
+
1114
+ const choices = element.addJsonObj('choice', undefined, undefined)
1115
+ if (Array.isArray(column.choices) && column.choices.length > 0) {
1116
+ // Formatted - choices: [1, 2, 3, 4]
1117
+ for (const choice in column.choices) {
1118
+ choices.addJsonObj('element', undefined, {
1119
+ value: column.choices[choice],
1120
+ label: column.choices[choice],
1121
+ })
1122
+ }
1123
+ } else {
1124
+ // Formatted - choices: { 1: { label: 'label' }, 2: { lable: label 2}, ...}
1125
+ Object.entries(column.choice_elements).forEach((value) => {
1126
+ if (isObject(value[1])) {
1127
+ choices.addJsonObj('element', undefined, {
1128
+ value: value[0],
1129
+ ...(value[1] as ChoiceConfig),
1130
+ })
1131
+ } else {
1132
+ choices.addJsonObj('element', undefined, {
1133
+ value: value[1],
1134
+ label: value[0],
1135
+ })
1136
+ }
1137
+ })
1138
+ }
1139
+ }
1140
+
1141
+ export function transformColumnData(col: any, callExpFromType: any) {
1142
+ const {
1143
+ type: internal_type,
1144
+ choice,
1145
+ name,
1146
+ virtual,
1147
+ display,
1148
+ choice_table,
1149
+ choice_field,
1150
+ use_dependent_field,
1151
+ dependent,
1152
+ calculation,
1153
+ use_dynamic_default,
1154
+ default_value,
1155
+ choice_elements,
1156
+ table,
1157
+ default: def,
1158
+ max_length,
1159
+ reference,
1160
+ function_field,
1161
+ ...column
1162
+ } = col
1163
+
1164
+ if (max_length) {
1165
+ column.maxLength = max_length
1166
+ }
1167
+
1168
+ if (internal_type && (callExpFromType.entityKind === 'generic' || callExpFromType.name === 'GenericColumn')) {
1169
+ column.column_type = internal_type
1170
+ }
1171
+
1172
+ if (choice !== undefined) {
1173
+ column.dropdown = choiceDropdown[choice]
1174
+ }
1175
+
1176
+ if (reference) {
1177
+ column.referenceTable = reference
1178
+ }
1179
+
1180
+ // Handle dynamic values
1181
+ if (calculation) {
1182
+ column.dynamic_value_definitions = {
1183
+ type: 'calculated_value',
1184
+ calculated_value: calculation,
1185
+ }
1186
+ } else if (choice_table || choice_field) {
1187
+ column.dynamic_value_definitions = {
1188
+ type: 'choices_from_other_table',
1189
+ table: choice_table,
1190
+ field: choice_field,
1191
+ }
1192
+ } else if (dependent) {
1193
+ column.dynamic_value_definitions = {
1194
+ type: 'dependent_field',
1195
+ column_name: dependent,
1196
+ }
1197
+ } else if (def) {
1198
+ column.default = def
1199
+ } else if (default_value) {
1200
+ column.dynamic_value_definitions = {
1201
+ type: 'dynamic_default',
1202
+ dynamic_default: def,
1203
+ }
1204
+ }
1205
+
1206
+ if (column.label && column.label === name) {
1207
+ delete column.label
1208
+ }
1209
+
1210
+ if (isEmpty(column.attributes)) {
1211
+ delete column.attributes
1212
+ } else if (isDefaultEdgeEncryptionAttribute(column.attributes)) {
1213
+ delete column.attributes
1214
+ }
1215
+
1216
+ return column
1217
+ }
1218
+
1219
+ // The instance will sometimes add edge_encryption_enabled=true, this is a default so don't write it if it is the only one
1220
+ function isDefaultEdgeEncryptionAttribute(attributes: any) {
1221
+ return (
1222
+ attributes &&
1223
+ Object.entries(attributes).length === 1 &&
1224
+ attributes.edge_encryption_enabled !== undefined &&
1225
+ attributes.edge_encryption_enabled
1226
+ )
1227
+ }
1228
+
1229
+ function getScriptInfo(scriptInfo: ScriptInfo | string, context: Context) {
1230
+ if (isString(scriptInfo)) {
1231
+ return scriptInfo
1232
+ }
1233
+ const { filePath, functionName, isDefault } = scriptInfo
1234
+ const script = buildScriptImport(
1235
+ filePath,
1236
+ functionName,
1237
+ isDefault,
1238
+ context,
1239
+ (funcName: string) => `;${funcName}(current)`
1240
+ )
1241
+ return script.replaceAll('\n', '')
1242
+ }
1243
+
1244
+ function removeAttributeTag(obj: any) {
1245
+ Object.entries(obj).forEach(([key, value]) => {
1246
+ if (key.startsWith('@_')) {
1247
+ obj[key.substring(2)] = value
1248
+ delete obj[key]
1249
+ }
1250
+ })
1251
+ return obj
1252
+ }
1253
+
1254
+ export function SysNumber(
1255
+ category: TableName,
1256
+ maximum_digits: number | undefined,
1257
+ number?: number | undefined,
1258
+ prefix?: string | undefined
1259
+ ) {
1260
+ return NowRecord({
1261
+ table: 'sys_number',
1262
+ $id: `${category as string}_${prefix}`,
1263
+ data: {
1264
+ category,
1265
+ maximum_digits: maximum_digits ?? 1000,
1266
+ number: number ?? 7,
1267
+ prefix: prefix ?? 'PRE',
1268
+ },
1269
+ })
1270
+ }
1271
+
1272
+ // TODO should this reference sys_filter_option_dynamic instead?
1273
+ export const dynamic_value_mapping = {
1274
+ 'Current Name': `current.name`,
1275
+ 'Next ECC Sequence Number': `GlideCounter.next('ecc:sequence');`,
1276
+ 'Get Module View': `var viewDefault='';if(current.device_type=='mobile') viewDefault='Mobile';else if(!current.application.nil() && current.application.device_type=='mobile' && current.device_type.nil()) viewDefault='Mobile';viewDefault`,
1277
+ 'Get Label Display Value': `gs.getDisplayValueFor(current.table, current.table_key, '')`,
1278
+ 'Get CMDB Item Category': `gs.include("CMDBItem");var item = new CMDBItem(current);item.setCategory();`,
1279
+ 'Parent Record Matcher Table': `if (typeof parent != "undefined") parent.matcher_table`,
1280
+ 'Next Cache Flush Number': `GlideCounter.next('sys_cache_flush');`,
1281
+ 'Get Next Number': `global.getNextObjNumber()`,
1282
+ 'Next Workflow Log Order Number': `GlideCounter.next('wf_log:order');`,
1283
+ Subject: 'email.subject',
1284
+ 'Get Mobile Module View': `var viewDefault='';if(typeof parent != 'undefined')if(parent.device_type=='mobile') viewDefault='Mobile';viewDefault`,
1285
+ 'Parent Record Model Table': `if (typeof parent != "undefined") parent.model_table`,
1286
+ 'Get Label Table Display Value': `gs.getDisplayValueFor(current.table, current.table_key, 'sys_class_name')`,
1287
+ 'Parent Record Source Table': `if (typeof parent != "undefined") parent.source_table`,
1288
+ 'Get Next Padded Number': `global.getNextObjNumberPadded();`,
1289
+ 'My First Name': `gs.getUser().firstName`,
1290
+ 'My Last Name': `gs.getUser().lastName`,
1291
+ 'My Job Title': `gs.getUser().title`,
1292
+ }
1293
+
1294
+ export function generateColumnExpression<Type extends (...args: any) => any>(
1295
+ expression: Type,
1296
+ args: Parameters<Type>[0],
1297
+ argsSetIfDefined?: Undefined<Parameters<Type>[0]>
1298
+ ): ts.CallExpression {
1299
+ const setArgs = { ...args }
1300
+ if (argsSetIfDefined) {
1301
+ Object.keys(argsSetIfDefined).forEach((key) => {
1302
+ if (argsSetIfDefined[key] !== undefined) {
1303
+ setArgs[key] = argsSetIfDefined[key]
1304
+ }
1305
+ })
1306
+ }
1307
+
1308
+ return ts.factory.createCallExpression(ts.factory.createIdentifier(expression.name), undefined, [
1309
+ createObjectExpression(setArgs),
1310
+ ])
1311
+ }
1312
+
1313
+ export function createObjectExpression(firstArg: object): ts.Expression {
1314
+ return ts.factory.createObjectLiteralExpression(
1315
+ Object.entries(firstArg)
1316
+ .filter(([_key, value]) => value !== undefined)
1317
+ .map(([key, value]) => {
1318
+ if (key === 'referenceTable') {
1319
+ return ts.factory.createPropertyAssignment(
1320
+ ts.factory.createIdentifier('referenceTable'),
1321
+ ts.factory.createStringLiteral(value)
1322
+ )
1323
+ } else if (key === 'calculated_value') {
1324
+ const calVal = `${os.EOL}${formatScript(value, schemaPosition)}`
1325
+ return ts.factory.createPropertyAssignment(
1326
+ ts.factory.createIdentifier(key),
1327
+ ts.factory.createTaggedTemplateExpression(
1328
+ ts.factory.createIdentifier('script'),
1329
+ undefined,
1330
+ ts.factory.createNoSubstitutionTemplateLiteral(calVal, calVal)
1331
+ )
1332
+ )
1333
+ }
1334
+ return ts.factory.createPropertyAssignment(
1335
+ createPropertyIdentifier(key),
1336
+ factoryCreateValue(value, ts.factory)
1337
+ )
1338
+ })
1339
+ )
1340
+ }
1341
+
1342
+ export function factoryCreateValue(value: unknown, factory: ts.NodeFactory) {
1343
+ if (typeof value === 'string') {
1344
+ return factory.createStringLiteral(value)
1345
+ }
1346
+
1347
+ if (typeof value === 'number') {
1348
+ if (value >= 0) {
1349
+ return factory.createNumericLiteral(value)
1350
+ } else {
1351
+ return factory.createPrefixUnaryExpression(
1352
+ ts.SyntaxKind.MinusToken,
1353
+ factory.createNumericLiteral(Math.abs(value))
1354
+ )
1355
+ }
1356
+ }
1357
+
1358
+ if (typeof value === 'boolean') {
1359
+ return value ? factory.createTrue() : factory.createFalse()
1360
+ }
1361
+
1362
+ if (typeof value === 'object') {
1363
+ if (value === null) {
1364
+ return factory.createNull()
1365
+ }
1366
+ if (isArray(value)) {
1367
+ const newvals: ts.Expression[] = []
1368
+
1369
+ Object.values(value).forEach((v) => {
1370
+ newvals.push(factoryCreateValue(v, factory))
1371
+ })
1372
+
1373
+ return factory.createArrayLiteralExpression(newvals)
1374
+ } else if (Structure.isImportSpecifier(value)) {
1375
+ return factory.createIdentifier(value['name'])
1376
+ } else {
1377
+ return createObjectExpression(value)
1378
+ }
1379
+ }
1380
+
1381
+ return factory.createNull()
1382
+
1383
+ // throw new Error(`Unsupported value type: ${typeof value}`)
1384
+ }
1385
+
1386
+ type Undefined<T> = {
1387
+ [P in keyof T]: T[P] | undefined
1388
+ }
1389
+ export function getCallExpressionFromType(internal_type: string) {
1390
+ switch (internal_type) {
1391
+ case 'boolean':
1392
+ return BooleanColumn
1393
+ case 'choice':
1394
+ return ChoiceColumn
1395
+ case 'conditions':
1396
+ return ConditionsColumn
1397
+ case 'date':
1398
+ return OtherDateColumn
1399
+ case 'calendar_date_time':
1400
+ return CalendarDateTimeColumn
1401
+ case 'datetime':
1402
+ return BasicDateTimeColumn
1403
+ case 'due_date':
1404
+ return DueDateColumn
1405
+ case 'glide_date':
1406
+ return DateColumn
1407
+ case 'glide_date_time':
1408
+ return DateTimeColumn
1409
+ case 'integer_date':
1410
+ return IntegerDateColumn
1411
+ case 'schedule_date_time':
1412
+ return ScheduleDateTimeColumn
1413
+ case 'decimal':
1414
+ return DecimalColumn
1415
+ case 'document_id':
1416
+ return DocumentIdColumn
1417
+ case 'domain_id':
1418
+ return DomainIdColumn
1419
+ case 'domain_path':
1420
+ return DomainPathColumn
1421
+ case 'field_name':
1422
+ return FieldNameColumn
1423
+ case 'integer':
1424
+ return IntegerColumn
1425
+ case 'radio':
1426
+ return RadioColumn
1427
+ case 'reference':
1428
+ return ReferenceColumn
1429
+ case 'script':
1430
+ return ScriptColumn
1431
+ case 'sys_class_name':
1432
+ return SystemClassNameColumn
1433
+ case 'table_name':
1434
+ return TableNameColumn
1435
+ case 'translated_field':
1436
+ return TranslatedFieldColumn
1437
+ case 'translated_text':
1438
+ return TranslatedTextColumn
1439
+ case 'user_roles':
1440
+ return UserRolesColumn
1441
+ case 'version':
1442
+ return VersionColumn
1443
+ case 'string':
1444
+ return StringColumn
1445
+ case 'int':
1446
+ case 'string_types':
1447
+ case 'approval_rules':
1448
+ case 'audio':
1449
+ case 'auto_increment':
1450
+ case 'auto_number':
1451
+ case 'bootstrap_color':
1452
+ case 'breakdown_element':
1453
+ case 'catalog_preview':
1454
+ case 'char':
1455
+ case 'collection':
1456
+ case 'color':
1457
+ case 'color_display':
1458
+ case 'composite_field':
1459
+ case 'composite_name':
1460
+ case 'compressed':
1461
+ case 'condition_string':
1462
+ case 'counter':
1463
+ case 'css':
1464
+ case 'currency':
1465
+ case 'currency2':
1466
+ case 'data_array':
1467
+ case 'data_object':
1468
+ case 'data_structure':
1469
+ case 'days_of_week':
1470
+ case 'day_of_week':
1471
+ case 'documentation_field':
1472
+ case 'dynamic_json':
1473
+ case 'email':
1474
+ case 'email_script':
1475
+ case 'expression':
1476
+ case 'external_names':
1477
+ case 'field_list':
1478
+ case 'file_attachment':
1479
+ case 'float':
1480
+ case 'formula':
1481
+ case 'geo_point':
1482
+ case 'glide_action_list':
1483
+ case 'glide_duration':
1484
+ case 'glide_list':
1485
+ case 'glide_precise_time':
1486
+ case 'glide_time':
1487
+ case 'glide_utc_time':
1488
+ case 'glide_var':
1489
+ case 'glyphicon':
1490
+ case 'graphql_schema':
1491
+ case 'GUID':
1492
+ case 'html':
1493
+ case 'html_script':
1494
+ case 'html_template':
1495
+ case 'icon':
1496
+ case 'image':
1497
+ case 'index_name':
1498
+ case 'insert_timestamp':
1499
+ case 'integer_time':
1500
+ case 'internal_type':
1501
+ case 'ip_addr':
1502
+ case 'ip_address':
1503
+ case 'journal':
1504
+ case 'journal_input':
1505
+ case 'journal_list':
1506
+ case 'json':
1507
+ case 'json_translations':
1508
+ case 'language':
1509
+ case 'long':
1510
+ case 'longint':
1511
+ case 'mask_code':
1512
+ case 'metric_absolute':
1513
+ case 'metric_counter':
1514
+ case 'metric_derive':
1515
+ case 'metric_gauge':
1516
+ case 'mid_config':
1517
+ case 'month_of_year':
1518
+ case 'multi_small':
1519
+ case 'multi_two_lines':
1520
+ case 'name_values':
1521
+ case 'nds_icon':
1522
+ case 'nl_task_int1':
1523
+ case 'order_index':
1524
+ case 'password':
1525
+ case 'password2':
1526
+ case 'percent_complete':
1527
+ case 'phone_number':
1528
+ case 'phone_number_e164':
1529
+ case 'ph_number':
1530
+ case 'price':
1531
+ case 'properties':
1532
+ case 'records':
1533
+ case 'reference_name':
1534
+ case 'related_tags':
1535
+ case 'reminder_field_name':
1536
+ case 'repeat_count':
1537
+ case 'repeat_type':
1538
+ case 'replication_payload':
1539
+ case 'schedule_interval_count':
1540
+ case 'script_client':
1541
+ case 'script_plain':
1542
+ case 'script_server':
1543
+ case 'short_field_name':
1544
+ case 'short_table_name':
1545
+ case 'simple_name_values':
1546
+ case 'slushbucket':
1547
+ case 'snapshot_template_value':
1548
+ case 'source_id':
1549
+ case 'source_name':
1550
+ case 'source_table':
1551
+ case 'string_boolean':
1552
+ case 'string_full_utf8':
1553
+ case 'structure':
1554
+ case 'sysevent_name':
1555
+ case 'sysrule_field_name':
1556
+ case 'sys_class_path':
1557
+ case 'template_value':
1558
+ case 'time':
1559
+ case 'timer':
1560
+ case 'translated':
1561
+ case 'translated_html':
1562
+ case 'tree_code':
1563
+ case 'tree_path':
1564
+ case 'url':
1565
+ case 'user_image':
1566
+ case 'user_input':
1567
+ case 'variables':
1568
+ case 'variable_conditions':
1569
+ case 'variable_template_value':
1570
+ case 'video':
1571
+ case 'week_of_month':
1572
+ case 'wide_text':
1573
+ case 'wiki_text':
1574
+ case 'wms_job':
1575
+ case 'workflow':
1576
+ case 'workflow_conditions':
1577
+ case 'xml':
1578
+ default:
1579
+ return GenericColumn
1580
+ }
1581
+ }