@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,3 @@
1
+ export * from './ColumnPlugins'
2
+ export { default as TablePlugin, generateColumnExpression } from './TablePlugin'
3
+ export { RecordPlugin } from './RecordPlugin'
package/src/index.ts ADDED
@@ -0,0 +1,16 @@
1
+ export * from './db'
2
+ export * from './uxf'
3
+ export * from './atf'
4
+ export { default as DefaultPlugin } from './DefaultPlugin'
5
+ export { default as ApplicationMenuPlugin } from './app/ApplicationMenuPlugin'
6
+ export { default as PropertyPlugin } from './PropertyPlugin'
7
+ export { default as RestApiPlugin } from './scriptedRESTAPI/RestApiPlugin'
8
+ export { default as UserPreferencePlugin } from './UserPreferencePlugin'
9
+ export { default as CrossScopePrivilegePlugin } from './CrossScopePrivilegePlugin'
10
+ export { default as ClientScriptPlugin } from './scripts/ClientScriptPlugin'
11
+ export { default as BusinessRulePlugin } from './BusinessRulePlugin'
12
+ export { default as ListPluginPlugin } from './ListPlugin'
13
+ export { default as RolePlugin } from './aclAndRole/RolePlugin'
14
+ export { default as AclPlugin } from './aclAndRole/AclPlugin'
15
+ export { default as ScriptTemplatePlugin, UNIQUE_SCRIPT_FILES } from './ScriptTemplatePlugin'
16
+ export { default as IdPlugin } from './IdPlugin'
@@ -0,0 +1,410 @@
1
+ import {
2
+ Document,
3
+ getOrCreatePropertyAssignment,
4
+ mergeDataIntoObjectLiteral,
5
+ stringify,
6
+ Context,
7
+ DocumentMap,
8
+ removeNode,
9
+ getCallExpressionName,
10
+ getNodeId,
11
+ ArrayIterator,
12
+ } from '@servicenow/sdk-build-core'
13
+ import { RouteSchema, RestApiSchema } from '@servicenow/sdk-core/runtime/rest'
14
+ import * as ts from 'ts-morph'
15
+ import * as z from 'zod'
16
+ import { SyntaxKind } from 'ts-morph'
17
+ import { processScript } from '../ScriptTemplatePlugin'
18
+ import {
19
+ RestDefinitionTransformer,
20
+ VersionWithOutIdSchema,
21
+ RouteAttrWithOutIdSchema,
22
+ RestRouteTransformer,
23
+ } from './RestSchemaUtils'
24
+ import { getArgs, potentialRecordCallRouteParams, isParamAssociated, createRecordCall } from './RestUtils'
25
+ import { Record } from '@servicenow/sdk-core/runtime/db'
26
+ import { isEqual } from 'lodash'
27
+
28
+ function transformId(
29
+ arg: ts.ObjectLiteralExpression,
30
+ argName: string,
31
+ argValue: string,
32
+ tableName: string,
33
+ context: Context
34
+ ) {
35
+ if (!arg.getProperty(argName)) {
36
+ const generatedKey = context.keys.getNextAvailableExplicitKey()
37
+ getOrCreatePropertyAssignment(arg, argName, stringify(generatedKey))
38
+ return context.keys.registerExplicitId(tableName, generatedKey, argValue)
39
+ }
40
+ return undefined
41
+ }
42
+
43
+ export function transformDefinition(document: Document, context: Context) {
44
+ if (document.action === 'DELETE') {
45
+ removeNode(document.node!)
46
+ return true
47
+ }
48
+ const changedData = document.changedData ? document.changedData!['data'] : {}
49
+ const { enforce_acl, ...rest } = RestDefinitionTransformer.parse(changedData)
50
+ const restArgs = getArgs(document)
51
+ cleanUpDefault(RestApiSchema, rest, restArgs)
52
+ transformId(restArgs, '$id', document.guid, 'sys_ws_definition', context)
53
+ transformEnforceAcl(restArgs, enforce_acl, context)
54
+ mergeDataIntoObjectLiteral(restArgs, rest)
55
+ return true
56
+ }
57
+
58
+ function transformEnforceAcl(args: ts.ObjectLiteralExpression, enforce_acl: string[] | undefined, context: Context) {
59
+ if (enforce_acl === undefined) {
60
+ return
61
+ }
62
+ if (enforce_acl.length === 0) {
63
+ args.getProperty('enforce_acl')?.remove()
64
+ args.addPropertyAssignment({ name: 'enforce_acl', initializer: '[]' })
65
+ return
66
+ }
67
+ const enforceAclArg = getOrCreatePropertyAssignment(args, 'enforce_acl', '[]')
68
+ const aclIdSet = new Set(enforce_acl)
69
+ const enforceAclIterator = new ArrayIterator(enforceAclArg)
70
+ while (enforceAclIterator.hasNext()) {
71
+ const aclElement = enforceAclIterator.next()
72
+ let aclSysId: string | undefined = undefined
73
+ if (aclElement.isKind(SyntaxKind.StringLiteral)) {
74
+ aclSysId = aclElement.getLiteralValue()
75
+ } else {
76
+ const varDec = (aclElement.getSymbol()?.getValueDeclaration() ??
77
+ aclElement.getSymbol()?.getAliasedSymbol()?.getValueDeclaration()) as ts.VariableDeclaration
78
+ if (varDec) {
79
+ const nodeID = getNodeId(
80
+ varDec
81
+ .getInitializerIfKind(SyntaxKind.CallExpression)
82
+ ?.getArguments()[0] as ts.ObjectLiteralExpression
83
+ )!
84
+ aclSysId = context.keys.explicit[nodeID]?.id
85
+ }
86
+ }
87
+
88
+ if (aclSysId && aclIdSet.has(aclSysId)) {
89
+ aclIdSet.delete(aclSysId)
90
+ } else {
91
+ enforceAclIterator.getExpression().removeElement(aclElement)
92
+ }
93
+ }
94
+
95
+ if (aclIdSet.size > 0) {
96
+ aclIdSet.forEach((id) => {
97
+ enforceAclIterator.getExpression().addElement(stringify(id))
98
+ })
99
+ }
100
+ }
101
+
102
+ export function transformRestAttrs(document: Document, context: Context) {
103
+ const restArgsNode = getArgs(document)
104
+ const changedData = document.changedData ? document.changedData!['data'] : {}
105
+ const schemaResult = VersionWithOutIdSchema.partial().safeParse(changedData)
106
+ if (!schemaResult.success) {
107
+ return false
108
+ }
109
+
110
+ const restAttrArrayNode = getOrCreatePropertyAssignment(restArgsNode, 'versions', '[]')
111
+ const cData = schemaResult.data
112
+ const versionArrayIterator = new ArrayIterator(restAttrArrayNode)
113
+ const expression = versionArrayIterator.getExpression()
114
+ let isHandled = false
115
+ while (versionArrayIterator.hasNext() && !isHandled) {
116
+ const element = versionArrayIterator.next()
117
+ const verId = getNodeId(element.asKindOrThrow(SyntaxKind.ObjectLiteralExpression))!
118
+ const verSysId = context.keys.explicit[verId]?.id
119
+ if (verSysId === document.guid) {
120
+ if (document.action === 'DELETE') {
121
+ expression.removeElement(element)
122
+ } else {
123
+ const eleObj = element.asKindOrThrow(SyntaxKind.ObjectLiteralExpression)
124
+ cleanUpDefault(VersionWithOutIdSchema, cData, eleObj)
125
+ mergeDataIntoObjectLiteral(eleObj, cData)
126
+ }
127
+ isHandled = true
128
+ }
129
+ }
130
+ if (!isHandled && document.action !== 'DELETE') {
131
+ cleanUpDefault(VersionWithOutIdSchema, cData)
132
+ const element = expression.addElement(stringify(cData)).asKindOrThrow(SyntaxKind.ObjectLiteralExpression)
133
+ transformId(element, '$id', document.guid, 'sys_ws_version', context)
134
+ return true
135
+ }
136
+ return isHandled
137
+ }
138
+
139
+ export function transformRouteAttrs(document: Document, context: Context, argName: string) {
140
+ const restArgsNode = getArgs(document)
141
+ const changedData = document.changedData ? document.changedData!['data'] : {}
142
+ const routeAttr = RouteAttrWithOutIdSchema.partial().safeParse(changedData)
143
+ if (!routeAttr.success) {
144
+ return false
145
+ }
146
+ const routeAttrData = routeAttr.data
147
+ const routesArrayNode = getOrCreatePropertyAssignment(restArgsNode, 'routes', '[]')
148
+ const routeArrayIterator = new ArrayIterator(routesArrayNode)
149
+ while (routeArrayIterator.hasNext()) {
150
+ const element = routeArrayIterator.next()
151
+ const routeAttrsNode = getOrCreatePropertyAssignment(
152
+ element.asKindOrThrow(SyntaxKind.ObjectLiteralExpression),
153
+ argName,
154
+ '[]'
155
+ )
156
+ const routeAttrIterator = new ArrayIterator(routeAttrsNode)
157
+ const arrayExpression = routeArrayIterator.getExpression()
158
+ while (routeAttrIterator.hasNext()) {
159
+ const attrElement = routeAttrIterator.next()
160
+ const attrId = getNodeId(attrElement.asKindOrThrow(SyntaxKind.ObjectLiteralExpression))
161
+ const attrSysId = context.keys.explicit[attrId!]?.id
162
+ if (attrSysId === document.guid) {
163
+ if (document.action === 'DELETE') {
164
+ arrayExpression.removeElement(element)
165
+ } else {
166
+ const attrEleObj = attrElement.asKindOrThrow(SyntaxKind.ObjectLiteralExpression)
167
+ cleanUpDefault(RouteAttrWithOutIdSchema, routeAttrData, attrEleObj)
168
+ mergeDataIntoObjectLiteral(attrEleObj, routeAttrData)
169
+ }
170
+ }
171
+ }
172
+ }
173
+ return true
174
+ }
175
+
176
+ const getDocument = (documentId: string, kind: string, docMap: DocumentMap) => {
177
+ if (!docMap[kind] || !docMap[kind]![documentId]) {
178
+ throw new Error(`Unable to find ${kind} with sysId: ${documentId}`)
179
+ }
180
+ return docMap[kind]![documentId]!
181
+ }
182
+
183
+ function getDefaults<Schema extends z.AnyZodObject>(schema: Schema) {
184
+ return Object.fromEntries(
185
+ Object.entries(schema.shape).map(([key, value]) => {
186
+ if (value instanceof z.ZodDefault) {
187
+ return [key, value._def.defaultValue()]
188
+ }
189
+ return [key, undefined]
190
+ })
191
+ )
192
+ }
193
+
194
+ export function cleanUpDefault<Schema extends z.AnyZodObject>(
195
+ schema: Schema,
196
+ targetObj: any,
197
+ node?: ts.ObjectLiteralExpression
198
+ ) {
199
+ const defaults = getDefaults(schema)
200
+ Object.entries(targetObj).forEach(([key, value]) => {
201
+ const removeDef = node ? !node.getProperty(key) : true
202
+ if (defaults[key] !== undefined && defaults[key] === value && removeDef) {
203
+ delete targetObj[key]
204
+ }
205
+ })
206
+ }
207
+
208
+ export function transformRoutes(document: Document, context: Context, documentMap: DocumentMap) {
209
+ const restArgsNode = getArgs(document)
210
+ const changedData = document.changedData ? document.changedData!['data'] : {}
211
+ const transformedRouteResp = RestRouteTransformer.safeParse(changedData)
212
+ if (!transformedRouteResp.success) {
213
+ return false
214
+ }
215
+
216
+ const rawRouteData = transformedRouteResp.data
217
+ const { script, web_service_version, enforce_acl, ...rest } = rawRouteData
218
+ let version: number | undefined = undefined
219
+ if (web_service_version) {
220
+ version = getDocument(web_service_version, 'record', documentMap).data!['data']['version'] as number
221
+ }
222
+ const parsedRouteData = {
223
+ ...rest,
224
+ ...(version && { version }),
225
+ }
226
+ const routesArrayNode = getOrCreatePropertyAssignment(restArgsNode, 'routes', '[]')
227
+ const routeArrayIterator = new ArrayIterator(routesArrayNode)
228
+ const expression = routeArrayIterator.getExpression()
229
+ let isHandled = false
230
+ while (routeArrayIterator.hasNext() && !isHandled) {
231
+ const element = routeArrayIterator.next()
232
+ const routeId = getNodeId(element.asKindOrThrow(SyntaxKind.ObjectLiteralExpression))!
233
+ const routeSysId = context.keys.explicit[routeId]?.id
234
+ if (routeSysId === document.guid) {
235
+ if (document.action === 'DELETE') {
236
+ expression.removeElement(element)
237
+ } else {
238
+ const eleObj = element.asKindOrThrow(SyntaxKind.ObjectLiteralExpression)
239
+ transformEnforceAcl(eleObj, enforce_acl, context)
240
+ if (script !== undefined) {
241
+ processScript(eleObj, 'script', script, document.guid, 'sys_ws_operation', context)
242
+ }
243
+ cleanUpDefault(RouteSchema, parsedRouteData, eleObj)
244
+ mergeDataIntoObjectLiteral(eleObj, parsedRouteData)
245
+ }
246
+ isHandled = true
247
+ }
248
+ }
249
+
250
+ if (!isHandled && document.action !== 'DELETE') {
251
+ cleanUpDefault(RouteSchema, parsedRouteData)
252
+ const element = expression
253
+ .addElement(stringify(parsedRouteData))
254
+ .asKindOrThrow(SyntaxKind.ObjectLiteralExpression)
255
+ transformEnforceAcl(element, enforce_acl, context)
256
+ if (script !== undefined) {
257
+ processScript(element, 'script', script, document.guid, 'sys_ws_operation', context)
258
+ }
259
+ transformId(element, '$id', document.guid, 'sys_ws_operation', context)
260
+ return true
261
+ }
262
+ return isHandled
263
+ }
264
+
265
+ export function transformRouteAttrsMap<T>(
266
+ document: Document,
267
+ context: Context,
268
+ documentMap: DocumentMap,
269
+ routeAttrSchema: z.ZodSchema<T>,
270
+ argName: string,
271
+ callback: (data: T) => { operationId: string; attrId: string }
272
+ ) {
273
+ const restArgsNode = getArgs(document)
274
+ const restId = getNodeId(restArgsNode)
275
+ const routeAttrSchemaResp = routeAttrSchema.safeParse(document.data!['data'])
276
+ if (!routeAttrSchemaResp.success) {
277
+ return false
278
+ }
279
+
280
+ /** To handle scenario where the mapping record initially was mapping route1 to parameter1 has changed
281
+ * to route2 to parameter1 within the same rest api. We remove the old reference and its keys and update the new one.
282
+ * This also helps in case of delete action, where the user modified the route before delete and performed the delete action.
283
+ */
284
+ const idx = context.keys.composite.findIndex((k) => k.id === document.guid)
285
+ const routeKey = idx !== -1 ? context.keys.composite[idx]!.key['route'] : undefined
286
+ const routeId = routeKey ? context.keys.explicit[routeKey]?.id : undefined
287
+ const { attrId, operationId } = callback(routeAttrSchemaResp.data)
288
+ let deletedParamID: string | undefined = undefined
289
+ let removedAttrId: string | undefined = undefined
290
+ let removalKey: { attr: string; route: string; rest: string } | undefined = undefined
291
+ if (routeId && operationId !== routeId) {
292
+ removalKey = context.keys.composite[idx]!.key as { route: string; attr: string; rest: string }
293
+ context.keys.composite.splice(idx, 1)
294
+ removedAttrId = context.keys.explicit[removalKey?.attr]?.id
295
+ }
296
+
297
+ let parsedAttrData = {}
298
+ if (document.action !== 'DELETE') {
299
+ const attrDoc = getDocument(attrId, 'record', documentMap)
300
+ /**
301
+ * Case where the only transformed doc is mapping record and the referenced parameter or header is associated to a record call.
302
+ * Remove the record call and associated key and consume it with the rest.
303
+ */
304
+ if (
305
+ attrDoc.node &&
306
+ !attrDoc.node.wasForgotten() &&
307
+ getCallExpressionName(attrDoc.node as ts.CallExpression) === Record.name
308
+ ) {
309
+ const attrId = getNodeId(getArgs(attrDoc))!
310
+ removeNode(attrDoc.node)
311
+ delete context.keys.explicit[attrId]
312
+ }
313
+ const attrInfo = attrDoc.data!['data']
314
+ const parsedRestAttr = RouteAttrWithOutIdSchema.safeParse(attrInfo)
315
+
316
+ if (!parsedRestAttr.success) {
317
+ return false
318
+ }
319
+
320
+ parsedAttrData = parsedRestAttr.data
321
+ }
322
+
323
+ const routesArrayNode = getOrCreatePropertyAssignment(restArgsNode, 'routes', '[]')
324
+ const routeArrayIterator = new ArrayIterator(routesArrayNode)
325
+ while (routeArrayIterator.hasNext()) {
326
+ const element = routeArrayIterator.next()
327
+ const routeId = getNodeId(element.asKindOrThrow(SyntaxKind.ObjectLiteralExpression))!
328
+ const routeSysId = context.keys.explicit[routeId]?.id
329
+ if (routeSysId === operationId || (removalKey && removalKey.route === routeId)) {
330
+ const routeAttrsNode = getOrCreatePropertyAssignment(
331
+ element.asKindOrThrow(SyntaxKind.ObjectLiteralExpression),
332
+ argName,
333
+ '[]'
334
+ )
335
+ const routeAttrIterator = new ArrayIterator(routeAttrsNode)
336
+ const attrExpression = routeAttrIterator.getExpression()
337
+ let isHandled = false
338
+ while (routeAttrIterator.hasNext()) {
339
+ const attrElement = routeAttrIterator.next()
340
+ const attrNodeId = getNodeId(attrElement.asKindOrThrow(SyntaxKind.ObjectLiteralExpression))
341
+ const attrMapId = context.keys.composite.find((k) =>
342
+ isEqual(k.key, {
343
+ attr: attrNodeId!,
344
+ route: routeId!,
345
+ rest: restId!,
346
+ })
347
+ )?.id
348
+ if (removalKey && removalKey.attr === attrNodeId) {
349
+ deletedParamID = removedAttrId
350
+ attrExpression.removeElement(attrElement)
351
+ }
352
+ if (attrMapId === document.guid && routeSysId === operationId) {
353
+ if (document.action === 'DELETE') {
354
+ deletedParamID = attrId
355
+ attrExpression.removeElement(attrElement)
356
+ } else {
357
+ const attrEleObj = attrElement.asKindOrThrow(SyntaxKind.ObjectLiteralExpression)
358
+ cleanUpDefault(RouteAttrWithOutIdSchema, parsedAttrData, attrEleObj)
359
+ mergeDataIntoObjectLiteral(attrEleObj, parsedAttrData)
360
+ }
361
+ isHandled = true
362
+ }
363
+ }
364
+ if (!isHandled && document.action !== 'DELETE' && routeSysId === operationId) {
365
+ cleanUpDefault(RouteAttrWithOutIdSchema, parsedAttrData)
366
+ const element = attrExpression
367
+ .addElement(stringify(parsedAttrData))
368
+ .asKindOrThrow(SyntaxKind.ObjectLiteralExpression)
369
+ const table = argName === 'headers' ? 'sys_ws_header' : 'sys_ws_query_parameter'
370
+ const foundAttrId = context.keys.findExplicitKeyById(attrId)
371
+
372
+ let uniqueId: string | undefined = ''
373
+ if (foundAttrId) {
374
+ uniqueId = attrId
375
+ const currentKey = context.keys.findExplicitKeyById(uniqueId!)!
376
+ getOrCreatePropertyAssignment(element, '$id', stringify(currentKey))
377
+ } else {
378
+ uniqueId = transformId(element, '$id', attrId, table, context)
379
+ }
380
+ context.keys.registerCompositeId(
381
+ `${table}_map`,
382
+ {
383
+ attr: context.keys.findExplicitKeyById(uniqueId!)!,
384
+ route: routeId!,
385
+ rest: restId!,
386
+ },
387
+ document.guid
388
+ )
389
+ }
390
+ }
391
+ }
392
+
393
+ if (deletedParamID) {
394
+ /**
395
+ * Conditions under which record call needs to be generated
396
+ * 1. Check if this mapping record is the last record that associate the param to the rest.
397
+ * 2. Check if its mentioned in potentailIds.
398
+ * If both of the above conditions pass then create a record call, because this param is not consumed
399
+ * anywhere and is going to be permanently removed from the rest.
400
+ */
401
+ const attrName = document.data!['table'] === 'sys_ws_header_map' ? 'headers' : 'parameters'
402
+ if (
403
+ !isParamAssociated(document, attrName, context, deletedParamID) &&
404
+ potentialRecordCallRouteParams.has(deletedParamID)
405
+ ) {
406
+ createRecordCall(deletedParamID, context)
407
+ }
408
+ }
409
+ return true
410
+ }
@@ -0,0 +1,227 @@
1
+ import { RestApiSchema, RouteAttributes } from '@servicenow/sdk-core/runtime/rest'
2
+ import { Record, Data } from '@servicenow/sdk-core/runtime/db'
3
+ import { z } from 'zod'
4
+ import * as _ from 'lodash'
5
+ import { Context } from '@servicenow/sdk-build-core'
6
+ import type { TableName } from '@servicenow/sdk-core/runtime/db'
7
+
8
+ export type Versions = z.infer<typeof RestApiSchema.shape.versions>
9
+ export type Routes = z.infer<typeof RestApiSchema.shape.routes>
10
+ export type EnforceAcl = z.infer<typeof RestApiSchema.shape.enforce_acl>
11
+ type Route = Routes[number]
12
+
13
+ export const mergeACLIds = (acls: any[]) => _.join(acls, ',')
14
+
15
+ export function generateVersionRecords(
16
+ context: Context,
17
+ defaultVersion: string,
18
+ versions: Versions,
19
+ apiRecord: Record<'sys_ws_definition'>
20
+ ) {
21
+ const versionsRecords: Record[] = []
22
+ const versionRecordMap: Map<string, Record<'sys_ws_version'>> = new Map()
23
+
24
+ for (const version of versions) {
25
+ const versionId = `v${version.version}`
26
+ const versionRecord = Record({
27
+ table: 'sys_ws_version',
28
+ $id: context.keys.registerExplicitId('sys_ws_version', version.$id),
29
+ data: {
30
+ active: version.active,
31
+ deprecated: version.deprecated,
32
+ is_default: defaultVersion === versionId ? true : false,
33
+ short_description: version.short_description,
34
+ version: version.version,
35
+ version_id: versionId,
36
+ web_service_definition: apiRecord,
37
+ },
38
+ })
39
+ versionsRecords.push(versionRecord)
40
+ /** versions have to be unique */
41
+ if (versionRecordMap.has(versionId)) {
42
+ throw Error(
43
+ `Found duplicated version: ${version.version} in ${apiRecord.$id}. All versions must be unique.`
44
+ )
45
+ }
46
+ versionRecordMap.set(versionId, versionRecord)
47
+ }
48
+
49
+ return {
50
+ records: versionsRecords,
51
+ mapping: versionRecordMap,
52
+ }
53
+ }
54
+
55
+ export function generateRouteAttributesRecords<T extends TableName, M extends TableName>(
56
+ context: Context,
57
+ restId: string | number,
58
+ routeId: string | number,
59
+ attributes: RouteAttributes[],
60
+ collection: T,
61
+ mapping_collection: M,
62
+ attrField: string,
63
+ apiRecord: Record<'sys_ws_definition'>,
64
+ routeRecord: Record<'sys_ws_operation'>,
65
+ recordMap: Map<string, Record>
66
+ ) {
67
+ const attributeRecords: Record[] = []
68
+
69
+ for (const attribute of attributes) {
70
+ const attrId = context.keys.registerExplicitId(collection as string, attribute.$id)
71
+ const attributeRecord = Record({
72
+ table: collection,
73
+ $id: attrId,
74
+ data: {
75
+ name: attribute.name,
76
+ required: attribute.required,
77
+ short_description: attribute.short_description,
78
+ example_value: attribute.example_value,
79
+ web_service_definition: apiRecord,
80
+ } as Data<T>,
81
+ })
82
+ /**
83
+ * In RestApi since we associate same parameter and headers with multiple routes
84
+ * during serialization we would end up generating duplicated records for headers
85
+ * and parameters. Here we throw error if two duplicated record have different data.
86
+ * Note: two duplicated records would have different data when user refercening a parameter
87
+ * in multiple routes updated in just one place.
88
+ */
89
+ checkForDuplicateRecords(recordMap, attrId, attributeRecord, attributeRecords)
90
+
91
+ const attrMapId = context.keys.registerCompositeId(mapping_collection as string, {
92
+ attr: attribute.$id,
93
+ route: routeId,
94
+ rest: restId,
95
+ })
96
+ const attrMapRecord = Record({
97
+ table: mapping_collection,
98
+ $id: attrMapId,
99
+ data: {
100
+ web_service_operation: routeRecord,
101
+ [attrField]: attributeRecord,
102
+ } as unknown as Data<M>,
103
+ })
104
+ attributeRecords.push(attrMapRecord)
105
+ }
106
+
107
+ return attributeRecords
108
+ }
109
+
110
+ function checkForDuplicateRecords(
111
+ recordMap: Map<string, Record>,
112
+ recordId: string,
113
+ record: Record,
114
+ attrRecords: Record[]
115
+ ) {
116
+ if (!recordMap.has(recordId)) {
117
+ recordMap.set(recordId, record)
118
+ attrRecords.push(record)
119
+ } else {
120
+ const matchRec = recordMap.get(recordId)
121
+ const { web_service_definition: _mwd, ...mRest } = matchRec!.data as any
122
+ const { web_service_definition: _wd, ...rest } = record.data as any
123
+ if (!_.isEqual(rest, mRest)) {
124
+ throw Error(
125
+ `Found duplicate route attribute map records starting with id: ${recordId}, to have conflicting data.`
126
+ )
127
+ }
128
+ }
129
+ }
130
+
131
+ export function generateRoutesAndRouteAttrRecords(
132
+ context: Context,
133
+ restId: string | number,
134
+ routes: Routes,
135
+ apiData: any,
136
+ apiRecord: Record<'sys_ws_definition'>,
137
+ versionMap: Map<string, Record<'sys_ws_version'>>
138
+ ) {
139
+ const routesRecords: Record[] = []
140
+ const headerRecordMap: Map<string, Record> = new Map()
141
+ const paramRecordMap: Map<string, Record> = new Map()
142
+
143
+ for (const route of routes) {
144
+ const routeParameters = route.parameters
145
+ const routeHeaders = route.headers
146
+ const defaultOperationURI = `${apiData.base_uri}${route.path}`
147
+ const baseOperationURI = `/api/${apiData.namespace}`
148
+ const remOperationURI = `${apiData.service_id}${route.path}`
149
+ const operationURI = route.version
150
+ ? `${baseOperationURI}/v${route.version}/${remOperationURI}`
151
+ : `${baseOperationURI}/${remOperationURI}`
152
+
153
+ const routeRecord = Record({
154
+ table: 'sys_ws_operation',
155
+ $id: context.keys.registerExplicitId('sys_ws_operation', route.$id),
156
+ data: {
157
+ name: route.name,
158
+ active: route.active,
159
+ consumes: route.consumes,
160
+ consumes_customized: apiData.consumes === route.consumes ? false : true,
161
+ default_operation_uri: `v${route.version}` === apiData.default_version ? defaultOperationURI : '',
162
+ http_method: route.method,
163
+ operation_script: route.script,
164
+ operation_uri: operationURI,
165
+ produces: route.produces,
166
+ produces_customized: apiData.produces === route.produces ? false : true,
167
+ relative_path: route.path,
168
+ enforce_acl: mergeACLIds(route.enforce_acl) as any,
169
+ requires_acl_authorization: route.authorization,
170
+ requires_authentication: route.authentication,
171
+ requires_snc_internal_role: route.internalRole,
172
+ short_description: route.short_description,
173
+ request_example: route.request_example,
174
+ web_service_definition: apiRecord,
175
+ web_service_version: getRestRouteVersion(route, versionMap),
176
+ ...(route.policy && { sys_policy: route.policy }),
177
+ },
178
+ })
179
+ routesRecords.push(routeRecord)
180
+
181
+ /** generates the sys_ws_query_parameter and sys_ws_query_parameter_map records */
182
+ const routeParamRecords = generateRouteAttributesRecords(
183
+ context,
184
+ restId,
185
+ route.$id,
186
+ routeParameters,
187
+ 'sys_ws_query_parameter',
188
+ 'sys_ws_query_parameter_map',
189
+ 'web_service_query_parameter',
190
+ apiRecord,
191
+ routeRecord,
192
+ paramRecordMap
193
+ )
194
+
195
+ /** generates the sys_ws_header and sys_ws_header_map records */
196
+ const routeHeaderRecords = generateRouteAttributesRecords(
197
+ context,
198
+ restId,
199
+ route.$id,
200
+ routeHeaders,
201
+ 'sys_ws_header',
202
+ 'sys_ws_header_map',
203
+ 'web_service_header',
204
+ apiRecord,
205
+ routeRecord,
206
+ headerRecordMap
207
+ )
208
+ routesRecords.push(...[...routeParamRecords, ...routeHeaderRecords])
209
+ }
210
+
211
+ return routesRecords
212
+ }
213
+
214
+ function getRestRouteVersion(route: Route, versionMap: Map<string, Record<'sys_ws_version'>>) {
215
+ /** throws error when a route specifies version not mentioned in versions array of rest*/
216
+ const version = ''
217
+ if (route.version) {
218
+ if (!versionMap.has(`v${route.version}`)) {
219
+ throw Error(
220
+ `Unable to resolve version record for version ${route.version}. Check if the version is listed in your API versions list.`
221
+ )
222
+ }
223
+ return versionMap.get(`v${route.version}`)!
224
+ }
225
+
226
+ return version
227
+ }