@servicenow/sdk-build-core 3.0.3 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. package/dist/app.d.ts +25 -0
  2. package/dist/app.js +8 -0
  3. package/dist/app.js.map +1 -0
  4. package/dist/compiler.d.ts +60 -0
  5. package/dist/compiler.js +320 -0
  6. package/dist/compiler.js.map +1 -0
  7. package/dist/compression.d.ts +7 -0
  8. package/dist/compression.js +79 -0
  9. package/dist/compression.js.map +1 -0
  10. package/dist/crypto.d.ts +1 -0
  11. package/dist/crypto.js +9 -0
  12. package/dist/crypto.js.map +1 -0
  13. package/dist/diagnostic.d.ts +41 -0
  14. package/dist/diagnostic.js +130 -0
  15. package/dist/diagnostic.js.map +1 -0
  16. package/dist/{plugins/Diagnostic.d.ts → fluent-diagnostic.d.ts} +3 -2
  17. package/dist/fluent-diagnostic.js +23 -0
  18. package/dist/fluent-diagnostic.js.map +1 -0
  19. package/dist/fluent-directive.d.ts +8 -0
  20. package/dist/fluent-directive.js +54 -0
  21. package/dist/fluent-directive.js.map +1 -0
  22. package/dist/fluent-file.d.ts +5 -0
  23. package/dist/fluent-file.js +15 -0
  24. package/dist/fluent-file.js.map +1 -0
  25. package/dist/formatter.d.ts +11 -0
  26. package/dist/formatter.js +77 -0
  27. package/dist/formatter.js.map +1 -0
  28. package/dist/fs.d.ts +174 -0
  29. package/dist/fs.js +313 -0
  30. package/dist/fs.js.map +1 -0
  31. package/dist/guid.d.ts +2 -0
  32. package/dist/{GUID.js → guid.js} +3 -6
  33. package/dist/guid.js.map +1 -0
  34. package/dist/index.d.ts +19 -5
  35. package/dist/index.js +19 -5
  36. package/dist/index.js.map +1 -1
  37. package/dist/json.d.ts +5 -0
  38. package/dist/json.js +43 -0
  39. package/dist/json.js.map +1 -0
  40. package/dist/keys-registry.d.ts +64 -0
  41. package/dist/keys-registry.js +339 -0
  42. package/dist/keys-registry.js.map +1 -0
  43. package/dist/logger.d.ts +8 -0
  44. package/dist/logger.js +17 -0
  45. package/dist/logger.js.map +1 -0
  46. package/dist/now-config.d.ts +348 -0
  47. package/dist/now-config.js +283 -0
  48. package/dist/now-config.js.map +1 -0
  49. package/dist/path.d.ts +3 -0
  50. package/dist/path.js +12 -0
  51. package/dist/path.js.map +1 -0
  52. package/dist/plugins/cache.d.ts +20 -0
  53. package/dist/plugins/cache.js +46 -0
  54. package/dist/plugins/cache.js.map +1 -0
  55. package/dist/plugins/context.d.ts +85 -0
  56. package/dist/plugins/{Context.js → context.js} +1 -1
  57. package/dist/plugins/context.js.map +1 -0
  58. package/dist/plugins/database.d.ts +27 -0
  59. package/dist/plugins/database.js +102 -0
  60. package/dist/plugins/database.js.map +1 -0
  61. package/dist/plugins/file.d.ts +10 -0
  62. package/dist/plugins/{behaviors/Arranger.js → file.js} +1 -1
  63. package/dist/plugins/file.js.map +1 -0
  64. package/dist/plugins/index.d.ts +9 -5
  65. package/dist/plugins/index.js +9 -6
  66. package/dist/plugins/index.js.map +1 -1
  67. package/dist/plugins/plugin.d.ts +478 -0
  68. package/dist/plugins/plugin.js +533 -0
  69. package/dist/plugins/plugin.js.map +1 -0
  70. package/dist/plugins/product.d.ts +15 -0
  71. package/dist/plugins/product.js +38 -0
  72. package/dist/plugins/product.js.map +1 -0
  73. package/dist/plugins/project.d.ts +25 -0
  74. package/dist/plugins/{behaviors/Generator.js → project.js} +1 -1
  75. package/dist/plugins/project.js.map +1 -0
  76. package/dist/plugins/shape.d.ts +424 -0
  77. package/dist/plugins/shape.js +1181 -0
  78. package/dist/plugins/shape.js.map +1 -0
  79. package/dist/plugins/time.d.ts +12 -0
  80. package/dist/plugins/time.js +84 -0
  81. package/dist/plugins/time.js.map +1 -0
  82. package/dist/plugins/usage.d.ts +11 -0
  83. package/dist/plugins/usage.js +26 -0
  84. package/dist/plugins/usage.js.map +1 -0
  85. package/dist/prettier/config-loader.d.ts +13 -0
  86. package/dist/prettier/config-loader.js +105 -0
  87. package/dist/prettier/config-loader.js.map +1 -0
  88. package/dist/telemetry/index.d.ts +25 -0
  89. package/dist/telemetry/index.js +18 -0
  90. package/dist/telemetry/index.js.map +1 -0
  91. package/dist/typescript.d.ts +293 -0
  92. package/dist/typescript.js +454 -0
  93. package/dist/typescript.js.map +1 -0
  94. package/dist/util/get-file-type.d.ts +2 -0
  95. package/dist/util/get-file-type.js +13 -0
  96. package/dist/util/get-file-type.js.map +1 -0
  97. package/dist/util/index.d.ts +2 -6
  98. package/dist/util/index.js +2 -6
  99. package/dist/util/index.js.map +1 -1
  100. package/dist/util/{Scope.js → is-sn-scope.js} +1 -1
  101. package/dist/util/is-sn-scope.js.map +1 -0
  102. package/dist/xml.d.ts +24 -0
  103. package/dist/xml.js +71 -0
  104. package/dist/xml.js.map +1 -0
  105. package/now.config.schema.json +336 -0
  106. package/package.json +22 -12
  107. package/src/app.ts +33 -0
  108. package/src/compiler.ts +384 -0
  109. package/src/compression.ts +93 -0
  110. package/src/crypto.ts +5 -0
  111. package/src/diagnostic.ts +108 -0
  112. package/src/{plugins/Diagnostic.ts → fluent-diagnostic.ts} +3 -10
  113. package/src/fluent-directive.ts +63 -0
  114. package/src/fluent-file.ts +13 -0
  115. package/src/formatter.ts +58 -0
  116. package/src/fs.ts +438 -0
  117. package/src/{GUID.ts → guid.ts} +2 -6
  118. package/src/index.ts +19 -5
  119. package/src/json.ts +20 -0
  120. package/src/keys-registry.ts +384 -0
  121. package/src/logger.ts +20 -0
  122. package/src/now-config.ts +337 -0
  123. package/src/path.ts +9 -0
  124. package/src/plugins/cache.ts +45 -0
  125. package/src/plugins/context.ts +93 -0
  126. package/src/plugins/database.ts +121 -0
  127. package/src/plugins/file.ts +19 -0
  128. package/src/plugins/index.ts +9 -5
  129. package/src/plugins/plugin.ts +995 -0
  130. package/src/plugins/product.ts +44 -0
  131. package/src/plugins/project.ts +39 -0
  132. package/src/plugins/shape.ts +1532 -0
  133. package/src/plugins/time.ts +108 -0
  134. package/src/plugins/usage.ts +26 -0
  135. package/src/prettier/config-loader.ts +130 -0
  136. package/src/telemetry/index.ts +27 -0
  137. package/src/typescript.ts +502 -0
  138. package/src/util/get-file-type.ts +11 -0
  139. package/src/util/index.ts +2 -6
  140. package/src/xml.ts +86 -0
  141. package/dist/GUID.d.ts +0 -2
  142. package/dist/GUID.js.map +0 -1
  143. package/dist/IncludePaths.d.ts +0 -25
  144. package/dist/IncludePaths.js +0 -97
  145. package/dist/IncludePaths.js.map +0 -1
  146. package/dist/Keys.d.ts +0 -32
  147. package/dist/Keys.js +0 -245
  148. package/dist/Keys.js.map +0 -1
  149. package/dist/TypeScript.d.ts +0 -5
  150. package/dist/TypeScript.js +0 -58
  151. package/dist/TypeScript.js.map +0 -1
  152. package/dist/XML.d.ts +0 -32
  153. package/dist/XML.js +0 -83
  154. package/dist/XML.js.map +0 -1
  155. package/dist/plugins/Context.d.ts +0 -190
  156. package/dist/plugins/Context.js.map +0 -1
  157. package/dist/plugins/Diagnostic.js +0 -28
  158. package/dist/plugins/Diagnostic.js.map +0 -1
  159. package/dist/plugins/Plugin.d.ts +0 -175
  160. package/dist/plugins/Plugin.js +0 -15
  161. package/dist/plugins/Plugin.js.map +0 -1
  162. package/dist/plugins/behaviors/Arranger.d.ts +0 -26
  163. package/dist/plugins/behaviors/Arranger.js.map +0 -1
  164. package/dist/plugins/behaviors/Composer.d.ts +0 -102
  165. package/dist/plugins/behaviors/Composer.js +0 -15
  166. package/dist/plugins/behaviors/Composer.js.map +0 -1
  167. package/dist/plugins/behaviors/Diagnostics.d.ts +0 -7
  168. package/dist/plugins/behaviors/Diagnostics.js +0 -3
  169. package/dist/plugins/behaviors/Diagnostics.js.map +0 -1
  170. package/dist/plugins/behaviors/Generator.d.ts +0 -21
  171. package/dist/plugins/behaviors/Generator.js.map +0 -1
  172. package/dist/plugins/behaviors/OwnedTables.d.ts +0 -6
  173. package/dist/plugins/behaviors/OwnedTables.js +0 -3
  174. package/dist/plugins/behaviors/OwnedTables.js.map +0 -1
  175. package/dist/plugins/behaviors/PostProcessor.d.ts +0 -5
  176. package/dist/plugins/behaviors/PostProcessor.js +0 -3
  177. package/dist/plugins/behaviors/PostProcessor.js.map +0 -1
  178. package/dist/plugins/behaviors/Serializer.d.ts +0 -30
  179. package/dist/plugins/behaviors/Serializer.js +0 -3
  180. package/dist/plugins/behaviors/Serializer.js.map +0 -1
  181. package/dist/plugins/behaviors/Transformer.d.ts +0 -23
  182. package/dist/plugins/behaviors/Transformer.js +0 -3
  183. package/dist/plugins/behaviors/Transformer.js.map +0 -1
  184. package/dist/plugins/behaviors/extractors/Data.d.ts +0 -119
  185. package/dist/plugins/behaviors/extractors/Data.js +0 -244
  186. package/dist/plugins/behaviors/extractors/Data.js.map +0 -1
  187. package/dist/plugins/behaviors/extractors/Extractors.d.ts +0 -63
  188. package/dist/plugins/behaviors/extractors/Extractors.js +0 -3
  189. package/dist/plugins/behaviors/extractors/Extractors.js.map +0 -1
  190. package/dist/plugins/behaviors/extractors/index.d.ts +0 -2
  191. package/dist/plugins/behaviors/extractors/index.js +0 -19
  192. package/dist/plugins/behaviors/extractors/index.js.map +0 -1
  193. package/dist/plugins/behaviors/index.d.ts +0 -9
  194. package/dist/plugins/behaviors/index.js +0 -26
  195. package/dist/plugins/behaviors/index.js.map +0 -1
  196. package/dist/plugins/util/CallExpression.d.ts +0 -5
  197. package/dist/plugins/util/CallExpression.js +0 -88
  198. package/dist/plugins/util/CallExpression.js.map +0 -1
  199. package/dist/plugins/util/CodeTransformation.d.ts +0 -95
  200. package/dist/plugins/util/CodeTransformation.js +0 -624
  201. package/dist/plugins/util/CodeTransformation.js.map +0 -1
  202. package/dist/plugins/util/ObjectLiteral.d.ts +0 -9
  203. package/dist/plugins/util/ObjectLiteral.js +0 -37
  204. package/dist/plugins/util/ObjectLiteral.js.map +0 -1
  205. package/dist/plugins/util/index.d.ts +0 -3
  206. package/dist/plugins/util/index.js +0 -20
  207. package/dist/plugins/util/index.js.map +0 -1
  208. package/dist/util/Debug.d.ts +0 -4
  209. package/dist/util/Debug.js +0 -20
  210. package/dist/util/Debug.js.map +0 -1
  211. package/dist/util/Directive.d.ts +0 -16
  212. package/dist/util/Directive.js +0 -107
  213. package/dist/util/Directive.js.map +0 -1
  214. package/dist/util/RuntimeTableSchema.d.ts +0 -5
  215. package/dist/util/RuntimeTableSchema.js +0 -58
  216. package/dist/util/RuntimeTableSchema.js.map +0 -1
  217. package/dist/util/Scope.js.map +0 -1
  218. package/dist/util/Util.d.ts +0 -1
  219. package/dist/util/Util.js +0 -12
  220. package/dist/util/Util.js.map +0 -1
  221. package/dist/util/XMLUploadParser.d.ts +0 -22
  222. package/dist/util/XMLUploadParser.js +0 -67
  223. package/dist/util/XMLUploadParser.js.map +0 -1
  224. package/src/IncludePaths.ts +0 -122
  225. package/src/Keys.ts +0 -274
  226. package/src/TypeScript.ts +0 -65
  227. package/src/XML.ts +0 -98
  228. package/src/plugins/Context.ts +0 -239
  229. package/src/plugins/Plugin.ts +0 -278
  230. package/src/plugins/behaviors/Arranger.ts +0 -42
  231. package/src/plugins/behaviors/Composer.ts +0 -125
  232. package/src/plugins/behaviors/Diagnostics.ts +0 -12
  233. package/src/plugins/behaviors/Generator.ts +0 -31
  234. package/src/plugins/behaviors/OwnedTables.ts +0 -5
  235. package/src/plugins/behaviors/PostProcessor.ts +0 -6
  236. package/src/plugins/behaviors/Serializer.ts +0 -40
  237. package/src/plugins/behaviors/Transformer.ts +0 -32
  238. package/src/plugins/behaviors/extractors/Data.ts +0 -332
  239. package/src/plugins/behaviors/extractors/Extractors.ts +0 -73
  240. package/src/plugins/behaviors/extractors/index.ts +0 -2
  241. package/src/plugins/behaviors/index.ts +0 -9
  242. package/src/plugins/util/CallExpression.ts +0 -110
  243. package/src/plugins/util/CodeTransformation.ts +0 -731
  244. package/src/plugins/util/ObjectLiteral.ts +0 -37
  245. package/src/plugins/util/index.ts +0 -3
  246. package/src/util/Debug.ts +0 -24
  247. package/src/util/Directive.ts +0 -123
  248. package/src/util/RuntimeTableSchema.ts +0 -44
  249. package/src/util/Util.ts +0 -7
  250. package/src/util/XMLUploadParser.ts +0 -90
  251. /package/dist/util/{Scope.d.ts → is-sn-scope.d.ts} +0 -0
  252. /package/src/util/{Scope.ts → is-sn-scope.ts} +0 -0
@@ -0,0 +1,384 @@
1
+ import { path } from './path'
2
+ import { ts, parseType } from './typescript'
3
+ import type { App } from './app'
4
+ import type { Compiler } from './compiler'
5
+ import { GUID, isGUID } from './guid'
6
+ import * as z from 'zod'
7
+ import type { CoalesceKeys } from './plugins'
8
+ import { JSON5 } from './json'
9
+ import { Formatter } from './formatter'
10
+ import type { FileSystem } from './fs'
11
+
12
+ export const KEYS_MODULE = '@servicenow/sdk/global'
13
+ export const KEYS_FILE_NAME = 'keys.ts'
14
+
15
+ const NestedCoalesceKeysSchema: z.ZodType<Now.Internal.KeysRegistry['composite'][number]['key']> = z.lazy(() =>
16
+ z.record(
17
+ z.string().or(
18
+ z.object({
19
+ id: z.string(),
20
+ key: NestedCoalesceKeysSchema,
21
+ })
22
+ )
23
+ )
24
+ )
25
+
26
+ const CompositeKeySchema = z.object({
27
+ table: z.string(),
28
+ id: z.string(),
29
+ deleted: z.boolean().optional(),
30
+ key: NestedCoalesceKeysSchema,
31
+ })
32
+
33
+ const KeysSchema = z.object({
34
+ explicit: z
35
+ .record(
36
+ z.string().or(z.number()),
37
+ z.object({
38
+ table: z.string(),
39
+ id: z.string(),
40
+ deleted: z.boolean().optional(),
41
+ })
42
+ )
43
+ .default({}),
44
+ composite: z.array(CompositeKeySchema).default([]),
45
+ deleted: z.record(z.string(), z.array(z.string())).default({}),
46
+ })
47
+
48
+ export class KeysRegistry {
49
+ private readonly formatter: Formatter
50
+ private readonly file: ts.SourceFile
51
+ private readonly usedIds: Set<string> = new Set()
52
+ private registry: Now.Internal.KeysRegistry
53
+
54
+ constructor(compiler: Compiler, project: App, fs: FileSystem) {
55
+ this.formatter = new Formatter(fs)
56
+ const keysFilePath = path.resolve(project.rootDir, project.config.generatedDir, KEYS_FILE_NAME)
57
+ const existingFile = compiler.addSourceFileAtPathIfExists(keysFilePath)
58
+
59
+ if (existingFile) {
60
+ this.registry = KeysRegistry.parseKeysFile(existingFile)
61
+ this.file = existingFile
62
+ } else {
63
+ this.registry = KeysSchema.parse({})
64
+ this.file = compiler.createSourceFile(keysFilePath, KeysRegistry.getCode({}), {
65
+ overwrite: true,
66
+ scriptKind: ts.ScriptKind.TS,
67
+ })
68
+ }
69
+ }
70
+
71
+ static parseKeysFile(keysFile: ts.SourceFile): Now.Internal.KeysRegistry {
72
+ const keysInterface = KeysRegistry.getKeysInterface(keysFile)
73
+ const parsedType = parseType(keysInterface.getType(), keysInterface, (unparsableType) => {
74
+ if (unparsableType.isArray()) {
75
+ return [] // If the keys file doesn't have composite keys, it will be an unparsable array so just return an empty one
76
+ } else {
77
+ throw `Unparsable type in keys.ts: ${unparsableType.getText()}`
78
+ }
79
+ })
80
+
81
+ const result = KeysSchema.safeParse(parsedType)
82
+ if (!result.success) {
83
+ throw new Error(`Keys registry is malformed: ${JSON5.stringify(result.error.format(), { space: 4 })}`)
84
+ }
85
+
86
+ return result.data
87
+ }
88
+
89
+ private static getKeysInterface(keysFile: ts.SourceFile) {
90
+ return keysFile
91
+ .getModuleOrThrow('global')
92
+ .getModuleOrThrow('Now')
93
+ .getModuleOrThrow('Internal')
94
+ .getInterfaceOrThrow('Keys')
95
+ }
96
+
97
+ private use<const K extends Now.Internal.ExplicitKey | Now.Internal.CompositeKey>(key: K): K {
98
+ if (key.deleted) {
99
+ key.deleted = false // If it was previously registered as deleted, reverse it
100
+ }
101
+
102
+ this.usedIds.add(key.id)
103
+ return key
104
+ }
105
+
106
+ getSourceFile(): ts.SourceFile {
107
+ return this.file
108
+ }
109
+
110
+ private queryExplicitKey(table: string, key: string | number): Now.Internal.ExplicitKey | undefined {
111
+ const existing = this.registry.explicit[key]
112
+ return existing?.table === table ? existing : undefined
113
+ }
114
+
115
+ queryExplicitId(table: string, key: string | number): string | undefined {
116
+ return this.queryExplicitKey(table, key)?.id
117
+ }
118
+
119
+ private registerExplicitKey(table: string, key: string | number, guidOverride?: string): Now.Internal.ExplicitKey {
120
+ const existing = this.queryExplicitKey(table, key)
121
+ if (existing) {
122
+ return this.use(existing)
123
+ }
124
+
125
+ let id: string
126
+ if (guidOverride) {
127
+ id = guidOverride
128
+ } else if (typeof key === 'string' && isGUID(key)) {
129
+ id = key
130
+ } else {
131
+ id = GUID()
132
+ }
133
+
134
+ const newKey = { table, id }
135
+ this.registry.explicit[key] = newKey
136
+ return this.use(newKey)
137
+ }
138
+
139
+ registerExplicitId(table: string, key: string | number, guidOverride?: string): string {
140
+ return this.registerExplicitKey(table, key, guidOverride).id
141
+ }
142
+
143
+ private deleteExplicitKey(table: string, key: string | number, guidOverride?: string): Now.Internal.ExplicitKey {
144
+ const registeredKey = this.registerExplicitKey(table, key, guidOverride)
145
+ registeredKey.deleted = true
146
+ return registeredKey
147
+ }
148
+
149
+ deleteExplicitId(table: string, key: string | number, guidOverride?: string): string {
150
+ return this.deleteExplicitKey(table, key, guidOverride).id
151
+ }
152
+
153
+ private queryCompositeKey(
154
+ table: string,
155
+ query: CoalesceKeys,
156
+ guid?: string
157
+ ): Now.Internal.CompositeKey | undefined {
158
+ const serializedQuery = this.serializeCoalesceKeys(query)
159
+ return this.registry.composite.find(
160
+ (existing) =>
161
+ existing.table === table &&
162
+ (existing.id === guid || this.coalesceKeysAreEqual(existing.key, serializedQuery, table))
163
+ )
164
+ }
165
+
166
+ queryCompositeId(table: string, query: CoalesceKeys): string | undefined {
167
+ return this.queryCompositeKey(table, query)?.id
168
+ }
169
+
170
+ private registerCompositeKey(table: string, key: CoalesceKeys, guidOverride?: string): Now.Internal.CompositeKey {
171
+ const existing = this.queryCompositeKey(table, key, guidOverride)
172
+ if (existing) {
173
+ return this.use(existing)
174
+ }
175
+
176
+ const newKey = {
177
+ table,
178
+ id: guidOverride ?? GUID(),
179
+ key: this.serializeCoalesceKeys(key),
180
+ }
181
+
182
+ const registry = (this.registry.composite ??= [])
183
+ registry.push(newKey)
184
+ return this.use(newKey)
185
+ }
186
+
187
+ registerCompositeId(table: string, key: CoalesceKeys, guidOverride?: string): string {
188
+ return this.registerCompositeKey(table, key, guidOverride).id
189
+ }
190
+
191
+ private deleteCompositeKey(table: string, key: CoalesceKeys, guidOverride?: string): Now.Internal.CompositeKey {
192
+ const registeredKey = this.registerCompositeKey(table, key, guidOverride)
193
+ registeredKey.deleted = true
194
+ return registeredKey
195
+ }
196
+
197
+ deleteCompositeId(table: string, key: CoalesceKeys, guidOverride?: string): string {
198
+ return this.deleteCompositeKey(table, key, guidOverride).id
199
+ }
200
+
201
+ private serializeCoalesceKeys(keys: CoalesceKeys): Now.Internal.KeysRegistry['composite'][number]['key'] {
202
+ if (Object.keys(keys).length <= 0) {
203
+ throw new Error('Coalesce keys object is empty.')
204
+ }
205
+
206
+ return Object.fromEntries(
207
+ Object.entries(keys).map(([k, v]) => {
208
+ if (!v) {
209
+ return [k, 'NULL'] // Nullish or empty values should be represented as "NULL" to be consistent with how the platform handles coalesce strategies
210
+ } else if (typeof v === 'string') {
211
+ return [k, v]
212
+ } else {
213
+ const nestedKeys = v.getKeys()
214
+ if (nestedKeys) {
215
+ return [k, { id: v.getValue(), key: this.serializeCoalesceKeys(nestedKeys) }]
216
+ } else {
217
+ return [k, v.getValue()]
218
+ }
219
+ }
220
+ })
221
+ )
222
+ }
223
+
224
+ /**
225
+ * Checks if the coalesce keys from `left` are equal to those specified in `right`. If there
226
+ * are keys in `left` that aren't present in `right`, they are ignored. However, if there are
227
+ * keys in `right` that aren't present in `left`, the two are considered NOT equal.
228
+ *
229
+ * If any nested coalesce keys are present on either side, they are considered equal when any
230
+ * ONE of the following conditions are met:
231
+ *
232
+ * A) One of the values is a string that matches the nested key's ID
233
+ *
234
+ * { id: '123', key: { k: 'v' } } == '123' }
235
+ *
236
+ * B) Both values are nested keys with matching IDs
237
+ *
238
+ * { id: '123', key: { k: 'v' } } == { id: '123', key: { ... } }
239
+ *
240
+ * C) Both values are nested keys with matching coalesce values
241
+ *
242
+ * { id: '123', key: { k: 'v' } } == { id: '456', key: { k: 'v' } }
243
+ *
244
+ * For condition C, the nested keys are compared recursively, so if they contain any of their
245
+ * own nested keys, they will be compared in the same way.
246
+ */
247
+ private coalesceKeysAreEqual(
248
+ leftOrig: Now.Internal.KeysRegistry['composite'][number]['key'],
249
+ rightOrig: Now.Internal.KeysRegistry['composite'][number]['key'],
250
+ table?: string
251
+ ): boolean {
252
+ const left = table ? this.mapLegacyKeys(leftOrig, table) : leftOrig
253
+ const right = table ? this.mapLegacyKeys(rightOrig, table) : rightOrig
254
+ for (const [k, rightValue] of Object.entries(right)) {
255
+ const match = () => {
256
+ const leftValue = left[k]
257
+ switch (typeof leftValue) {
258
+ case 'string': {
259
+ // Left is a plain value or ID
260
+ const newLeft = this.queryExplicitId(k, leftValue) ?? leftValue // query if we have a legacy explicit ID and use that sys_Id if found
261
+ return typeof rightValue === 'string'
262
+ ? newLeft === rightValue // Right is the same plain value or ID
263
+ : newLeft === rightValue.id // Right is a nested key with the same ID
264
+ }
265
+ case 'object': // Left is a nested key
266
+ return typeof rightValue === 'string'
267
+ ? leftValue.id === rightValue // Right is the same ID
268
+ : leftValue.id === rightValue.id || // Right is a nested key with the same ID
269
+ this.coalesceKeysAreEqual(leftValue.key, rightValue.key) // Right is a nested key with matching coalesce values
270
+ default:
271
+ return false
272
+ }
273
+ }
274
+
275
+ if (!match()) {
276
+ return false // Give up as soon as we find any mismatch
277
+ }
278
+ }
279
+
280
+ return true // All keys matched
281
+ }
282
+
283
+ mapLegacyKeys(obj: NonNullable<object>, table?: string) {
284
+ // Copy to avoid messing up future iterations
285
+ const newObj = Object.assign({}, obj)
286
+
287
+ // Map old and new values to the table name in case we need to query explicit IDs
288
+ const legacyKeyMap = {
289
+ list: 'sys_ui_list',
290
+ list_id: 'sys_ui_list',
291
+ route: 'sys_ws_operation',
292
+ web_service_operation: 'sys_ws_operation',
293
+ web_service_header: 'sys_ws_header',
294
+ attr: table === 'sys_ws_query_parameter_map' ? 'sys_ws_query_parameter' : 'sys_ws_header', // attr maps to a different table depending on the type of record
295
+ web_service_query_parameter: 'sys_ws_query_parameter',
296
+ }
297
+
298
+ // Legacy key equivalence for comparison
299
+ Object.entries(newObj).forEach(([key, value]) => {
300
+ if (legacyKeyMap[key]) {
301
+ newObj[legacyKeyMap[key]] = value
302
+ delete newObj[key]
303
+ }
304
+ })
305
+ if (newObj['rest']) {
306
+ delete newObj['rest']
307
+ }
308
+ return newObj
309
+ }
310
+
311
+ getEffectiveExplicitKeys(): Now.Internal.KeysRegistry['explicit'] {
312
+ return Object.fromEntries(
313
+ Object.entries(this.registry.explicit)
314
+ .sort(([a], [b]) => a.localeCompare(b))
315
+ .filter(([, v]) => !this.registry.composite.some((k) => k.table === v.table && k.id === v.id)) // Filter out any explicit IDs that became coalescing IDs later
316
+ .map(([k, v]) => [k, this.usedIds.has(v.id) ? v : { ...v, deleted: true }])
317
+ )
318
+ }
319
+
320
+ getEffectiveCompositeKeys(): Now.Internal.KeysRegistry['composite'] {
321
+ return this.registry.composite
322
+ .sort((a, b) => a.id.localeCompare(b.id))
323
+ .map((v) =>
324
+ this.usedIds.has(v.id)
325
+ ? 'deleted' in v
326
+ ? // Seems silly but this is to make sure the position of the "delete" prop is consistent
327
+ { table: v.table, id: v.id, deleted: v.deleted, key: v.key }
328
+ : v
329
+ : { table: v.table, id: v.id, deleted: true, key: v.key }
330
+ )
331
+ }
332
+
333
+ getEffectiveDeletedKeys(): Now.Internal.AnyKey[] {
334
+ const explicit = Object.values(this.getEffectiveExplicitKeys()).filter((k) => k.deleted)
335
+ const composite = this.getEffectiveCompositeKeys().filter((k) => k.deleted)
336
+ const deleted = Object.entries(this.registry.deleted).flatMap(([table, keys]) =>
337
+ keys.map((id) => ({ table, id }))
338
+ )
339
+
340
+ return [...explicit, ...composite, ...deleted].sort((a, b) => a.id.localeCompare(b.id))
341
+ }
342
+
343
+ async commit(formatter: Formatter = this.formatter): Promise<void> {
344
+ const explicit = this.getEffectiveExplicitKeys()
345
+ const composite = this.getEffectiveCompositeKeys()
346
+ const deleted = this.registry.deleted
347
+
348
+ const registry: Partial<Now.Internal.KeysRegistry> = {
349
+ ...(Object.keys(explicit).length > 0 ? { explicit } : {}),
350
+ ...(composite.length > 0 ? { composite } : {}),
351
+ ...(Object.keys(deleted).length > 0 ? { deleted } : {}),
352
+ }
353
+
354
+ const formatted = await formatter.format(KeysRegistry.getCode(registry), this.file.getFilePath())
355
+ this.file.replaceText([this.file.getPos(), this.file.getEnd()], formatted)
356
+ this.registry = KeysRegistry.parseKeysFile(this.file)
357
+ }
358
+
359
+ private static getCode(registry: Partial<Now.Internal.KeysRegistry>): string {
360
+ return `import '${KEYS_MODULE}'
361
+
362
+ declare global {
363
+ namespace Now {
364
+ namespace Internal {
365
+ interface Keys extends KeysRegistry ${JSON5.stringify(registry, { space: 4 })}
366
+ }
367
+ }
368
+ }
369
+ `
370
+ }
371
+
372
+ reload(): void {
373
+ this.registry = KeysRegistry.parseKeysFile(this.file)
374
+ this.usedIds.clear()
375
+ }
376
+
377
+ getValue(): Now.Internal.KeysRegistry {
378
+ return {
379
+ explicit: this.registry.explicit,
380
+ composite: this.registry.composite,
381
+ deleted: this.registry.deleted,
382
+ }
383
+ }
384
+ }
package/src/logger.ts ADDED
@@ -0,0 +1,20 @@
1
+ export type Logger = {
2
+ info: (...data: any[]) => void
3
+ warn: (...data: any[]) => void
4
+ error: (...data: any[]) => void
5
+ debug: (...data: any[]) => void
6
+ }
7
+
8
+ // TODO: This is not needed, get rid of it
9
+ export function createConsoleLogger(): Logger {
10
+ return console
11
+ }
12
+
13
+ export function createSilentLogger(): Logger {
14
+ return {
15
+ info: () => {},
16
+ warn: () => {},
17
+ error: () => {},
18
+ debug: () => {},
19
+ }
20
+ }