@tremho/mist-lift 2.2.9 → 2.5.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 (173) hide show
  1. package/README.md +14 -1
  2. package/build/QSTest/functions/IntegrationTest/IntegrationTest.test.js +47 -0
  3. package/build/QSTest/functions/IntegrationTest/IntegrationTest.test.js.map +1 -0
  4. package/build/QSTest/functions/IntegrationTest/src/local.js +33 -0
  5. package/build/QSTest/functions/IntegrationTest/src/local.js.map +1 -0
  6. package/build/QSTest/functions/IntegrationTest/src/main.js +28 -0
  7. package/build/QSTest/functions/IntegrationTest/src/main.js.map +1 -0
  8. package/build/{commands → src/commands}/actions/initQuestions.js +18 -9
  9. package/build/src/commands/actions/initQuestions.js.map +1 -0
  10. package/build/src/commands/actions/makePackageJson.js.map +1 -0
  11. package/build/{commands → src/commands}/actions/setupPackageJson.js +20 -11
  12. package/build/src/commands/actions/setupPackageJson.js.map +1 -0
  13. package/build/src/commands/actions/updateDeployedPermissions.js +118 -0
  14. package/build/src/commands/actions/updateDeployedPermissions.js.map +1 -0
  15. package/build/{commands → src/commands}/build.js +18 -9
  16. package/build/src/commands/build.js.map +1 -0
  17. package/build/{commands → src/commands}/builtin/ApiDocMaker.js +21 -14
  18. package/build/src/commands/builtin/ApiDocMaker.js.map +1 -0
  19. package/build/{commands → src/commands}/builtin/BuiltInHandler.js +26 -14
  20. package/build/src/commands/builtin/BuiltInHandler.js.map +1 -0
  21. package/build/{commands → src/commands}/builtin/DeployBuiltInZip.js +1 -2
  22. package/build/src/commands/builtin/DeployBuiltInZip.js.map +1 -0
  23. package/build/src/commands/builtin/ExportWebroot.js +253 -0
  24. package/build/src/commands/builtin/ExportWebroot.js.map +1 -0
  25. package/build/{commands → src/commands}/builtin/StageWebrootZip.js +28 -15
  26. package/build/src/commands/builtin/StageWebrootZip.js.map +1 -0
  27. package/build/src/commands/builtin/webroot-export/s3webroot.js +128 -0
  28. package/build/src/commands/builtin/webroot-export/s3webroot.js.map +1 -0
  29. package/build/{commands → src/commands}/create.js +18 -9
  30. package/build/src/commands/create.js.map +1 -0
  31. package/build/{commands → src/commands}/deploy.js +25 -14
  32. package/build/src/commands/deploy.js.map +1 -0
  33. package/build/{commands → src/commands}/doctor.js +19 -10
  34. package/build/src/commands/doctor.js.map +1 -0
  35. package/build/{commands → src/commands}/help.js +34 -24
  36. package/build/src/commands/help.js.map +1 -0
  37. package/build/{commands → src/commands}/info.js +18 -9
  38. package/build/src/commands/info.js.map +1 -0
  39. package/build/{commands → src/commands}/init.js +67 -18
  40. package/build/src/commands/init.js.map +1 -0
  41. package/build/{commands → src/commands}/package.js +51 -10
  42. package/build/src/commands/package.js.map +1 -0
  43. package/build/{commands → src/commands}/publish.js +58 -22
  44. package/build/src/commands/publish.js.map +1 -0
  45. package/build/{commands → src/commands}/settings.js +18 -9
  46. package/build/src/commands/settings.js.map +1 -0
  47. package/build/{commands → src/commands}/start.js +20 -10
  48. package/build/src/commands/start.js.map +1 -0
  49. package/build/{commands → src/commands}/test.js +18 -9
  50. package/build/src/commands/test.js.map +1 -0
  51. package/build/{commands → src/commands}/update.js +19 -9
  52. package/build/src/commands/update.js.map +1 -0
  53. package/build/src/commands/user.js.map +1 -0
  54. package/build/{expressRoutes → src/expressRoutes}/all.js +19 -9
  55. package/build/src/expressRoutes/all.js.map +1 -0
  56. package/build/src/expressRoutes/api.js.map +1 -0
  57. package/build/{expressRoutes → src/expressRoutes}/functionBinder.js +181 -26
  58. package/build/src/expressRoutes/functionBinder.js.map +1 -0
  59. package/build/{integration-tests → src/integration-tests}/quickstart-scenario.test.js +1 -1
  60. package/build/src/integration-tests/quickstart-scenario.test.js.map +1 -0
  61. package/build/{lib → src/lib}/CaseUtils.js +5 -6
  62. package/build/src/lib/CaseUtils.js.map +1 -0
  63. package/build/{lib → src/lib}/DirectoryUtils.js +4 -4
  64. package/build/src/lib/DirectoryUtils.js.map +1 -0
  65. package/build/{lib → src/lib}/ESBuild.js +19 -10
  66. package/build/src/lib/ESBuild.js.map +1 -0
  67. package/build/src/lib/IdSrc.js +67 -0
  68. package/build/src/lib/IdSrc.js.map +1 -0
  69. package/build/{lib → src/lib}/LiftConfig.js +7 -7
  70. package/build/src/lib/LiftConfig.js.map +1 -0
  71. package/build/{lib → src/lib}/LiftVersion.js +21 -11
  72. package/build/src/lib/LiftVersion.js.map +1 -0
  73. package/build/{lib → src/lib}/Tests/fileCompare.test.js +17 -7
  74. package/build/src/lib/Tests/fileCompare.test.js.map +1 -0
  75. package/build/src/lib/TypeCheck.js +1206 -0
  76. package/build/src/lib/TypeCheck.js.map +1 -0
  77. package/build/{lib → src/lib}/askQuestion.js +18 -9
  78. package/build/src/lib/askQuestion.js.map +1 -0
  79. package/build/{lib → src/lib}/executeCommand.js +2 -3
  80. package/build/src/lib/executeCommand.js.map +1 -0
  81. package/build/{lib → src/lib}/fileCompare.js +2 -3
  82. package/build/src/lib/fileCompare.js.map +1 -0
  83. package/build/{lib → src/lib}/openAPI/ApiBuildCollector.js +1 -2
  84. package/build/src/lib/openAPI/ApiBuildCollector.js.map +1 -0
  85. package/build/{lib → src/lib}/openAPI/WebrootFileSupport.js +18 -9
  86. package/build/src/lib/openAPI/WebrootFileSupport.js.map +1 -0
  87. package/build/src/lib/openAPI/openApiConstruction.js +435 -0
  88. package/build/src/lib/openAPI/openApiConstruction.js.map +1 -0
  89. package/build/{lib → src/lib}/pathResolve.js +1 -2
  90. package/build/src/lib/pathResolve.js.map +1 -0
  91. package/build/{lib → src/lib}/utils.js +20 -11
  92. package/build/src/lib/utils.js.map +1 -0
  93. package/build/{lift.js → src/lift.js} +19 -9
  94. package/build/src/lift.js.map +1 -0
  95. package/package.json +6 -4
  96. package/src/commands/actions/updateDeployedPermissions.ts +80 -0
  97. package/src/commands/builtin/ApiDocMaker.ts +17 -10
  98. package/src/commands/builtin/BuiltInHandler.ts +7 -2
  99. package/src/commands/builtin/ExportWebroot.ts +195 -0
  100. package/src/commands/builtin/StageWebrootZip.ts +13 -5
  101. package/src/commands/builtin/prebuilt-zips/API.zip +0 -0
  102. package/src/commands/builtin/prebuilt-zips/FileServe.zip +0 -0
  103. package/src/commands/builtin/prebuilt-zips/Webroot.zip +0 -0
  104. package/src/commands/builtin/webroot-export/s3webroot.ts +82 -0
  105. package/src/commands/deploy.ts +6 -4
  106. package/src/commands/help.ts +4 -2
  107. package/src/commands/init.ts +51 -8
  108. package/src/commands/package.ts +35 -2
  109. package/src/commands/publish.ts +38 -13
  110. package/src/commands/start.ts +2 -1
  111. package/src/commands/update.ts +1 -0
  112. package/src/expressRoutes/all.ts +1 -0
  113. package/src/expressRoutes/functionBinder.ts +155 -15
  114. package/src/integration-tests/quickstart-scenario.test.ts +1 -1
  115. package/src/lib/DirectoryUtils.ts +2 -1
  116. package/src/lib/IdSrc.ts +17 -4
  117. package/src/lib/TypeCheck.ts +1171 -0
  118. package/src/lib/executeCommand.ts +1 -1
  119. package/src/lib/openAPI/openApiConstruction.ts +230 -41
  120. package/src/lift.ts +2 -2
  121. package/templateData/app/MainPage.tsx +10 -0
  122. package/templateData/app/app.tsx +39 -0
  123. package/templateData/function-main-ts +8 -1
  124. package/templateData/webroot/app.js +7 -7
  125. package/build/commands/actions/initQuestions.js.map +0 -1
  126. package/build/commands/actions/makePackageJson.js.map +0 -1
  127. package/build/commands/actions/setupPackageJson.js.map +0 -1
  128. package/build/commands/build.js.map +0 -1
  129. package/build/commands/builtin/ApiDocMaker.js.map +0 -1
  130. package/build/commands/builtin/BuiltInHandler.js.map +0 -1
  131. package/build/commands/builtin/DeployBuiltInZip.js.map +0 -1
  132. package/build/commands/builtin/StageWebrootZip.js.map +0 -1
  133. package/build/commands/builtin/prebuilt-zips/API.zip +0 -0
  134. package/build/commands/builtin/prebuilt-zips/FileServe.zip +0 -0
  135. package/build/commands/builtin/prebuilt-zips/Webroot.zip +0 -0
  136. package/build/commands/create.js.map +0 -1
  137. package/build/commands/deploy.js.map +0 -1
  138. package/build/commands/doctor.js.map +0 -1
  139. package/build/commands/help.js.map +0 -1
  140. package/build/commands/info.js.map +0 -1
  141. package/build/commands/init.js.map +0 -1
  142. package/build/commands/package.js.map +0 -1
  143. package/build/commands/publish.js.map +0 -1
  144. package/build/commands/settings.js.map +0 -1
  145. package/build/commands/start.js.map +0 -1
  146. package/build/commands/test.js.map +0 -1
  147. package/build/commands/update.js.map +0 -1
  148. package/build/commands/user.js.map +0 -1
  149. package/build/expressRoutes/all.js.map +0 -1
  150. package/build/expressRoutes/api.js.map +0 -1
  151. package/build/expressRoutes/functionBinder.js.map +0 -1
  152. package/build/integration-tests/quickstart-scenario.test.js.map +0 -1
  153. package/build/lib/CaseUtils.js.map +0 -1
  154. package/build/lib/DirectoryUtils.js.map +0 -1
  155. package/build/lib/ESBuild.js.map +0 -1
  156. package/build/lib/IdSrc.js +0 -43
  157. package/build/lib/IdSrc.js.map +0 -1
  158. package/build/lib/LiftConfig.js.map +0 -1
  159. package/build/lib/LiftVersion.js.map +0 -1
  160. package/build/lib/Tests/fileCompare.test.js.map +0 -1
  161. package/build/lib/askQuestion.js.map +0 -1
  162. package/build/lib/executeCommand.js.map +0 -1
  163. package/build/lib/fileCompare.js.map +0 -1
  164. package/build/lib/openAPI/ApiBuildCollector.js.map +0 -1
  165. package/build/lib/openAPI/WebrootFileSupport.js.map +0 -1
  166. package/build/lib/openAPI/openApiConstruction.js +0 -237
  167. package/build/lib/openAPI/openApiConstruction.js.map +0 -1
  168. package/build/lib/pathResolve.js.map +0 -1
  169. package/build/lib/utils.js.map +0 -1
  170. package/build/lift.js.map +0 -1
  171. /package/build/{commands → src/commands}/actions/makePackageJson.js +0 -0
  172. /package/build/{commands → src/commands}/user.js +0 -0
  173. /package/build/{expressRoutes → src/expressRoutes}/api.js +0 -0
@@ -0,0 +1,1171 @@
1
+ /*
2
+ Module for Constraint definitions and TypeCheck support
3
+ */
4
+
5
+ /* eslint-disable */ // too screwed up. This file originated in an old JS project and was converted to TS quickly. TS-Standard is too restrictive for the style.
6
+ /**
7
+ * Enumeration of basic types
8
+ *
9
+ * - see [stringFromValueType](#module_TypeCheck..stringFromValueType)
10
+ * - see [valueTypeFromString](#module_TypeCheck..valueTypeFromString)
11
+ */
12
+ export enum ValueType {
13
+ none,
14
+ number,
15
+ string,
16
+ boolean,
17
+ object,
18
+ array,
19
+ regex
20
+ }
21
+
22
+ /**
23
+ * Base for all Constraint errors.
24
+ * Defines the identifying class archetype and consistent error message prefix
25
+ */
26
+ class ConstraintError extends Error {
27
+ constructor () {
28
+ super()
29
+ this.message = 'Constraint Error: '
30
+ }
31
+ }
32
+
33
+ /**
34
+ * An error message for when a value fails validation.
35
+ */
36
+ class ConstraintFail extends ConstraintError {
37
+ constructor (failType: string, value: any) {
38
+ super()
39
+ this.message += `Failed ${failType}: ${value}`
40
+ }
41
+ }
42
+
43
+ /**
44
+ * An error for when the basic type is wrong
45
+ */
46
+ class ConstraintBasicTypeError extends ConstraintError {
47
+ constructor (value: any, expType: string) {
48
+ super()
49
+ this.message += `Incorrect type ${typeof value}, (${expType} expected) ${value}`
50
+ }
51
+ }
52
+
53
+ /**
54
+ * An error for when we expected null or undefined
55
+ */
56
+ // class NullConstraintError extends ConstraintError {
57
+ // constructor() {
58
+ // super();
59
+ // this.message += 'Expected NULL or undefined'
60
+ // }
61
+ // }
62
+
63
+ /**
64
+ * An error for when a min/max range has been violated, including what type of range.
65
+ */
66
+ class RangeConstraintError extends ConstraintError {
67
+ constructor (value: number, comp: number, rangeType: string = 'Number') {
68
+ super()
69
+ // we don't need to test both range ends, because we know we are here because of an error one way
70
+ // or the other.
71
+ if (value < comp) {
72
+ this.message += `${rangeType} ${value} is less than range minimum of ${comp}`
73
+ } else {
74
+ this.message += `${rangeType} ${value} exceeds range maximum of ${comp}`
75
+ }
76
+ }
77
+ }
78
+
79
+ /**
80
+ * An error for when an integer was expected
81
+ */
82
+ class IntegerConstraintError extends ConstraintError {
83
+ constructor (value: any) {
84
+ super()
85
+ if (value === undefined) {
86
+ this.message += 'Integer expected'
87
+ } else {
88
+ this.message += `Value ${value} is not an integer`
89
+ }
90
+ }
91
+ }
92
+
93
+ /**
94
+ * An error for when a positive value was expected
95
+ */
96
+ class PositiveConstraintError extends ConstraintError {
97
+ constructor (value: any) {
98
+ super()
99
+ if (value === undefined) {
100
+ this.message += 'Positive value expected'
101
+ } else {
102
+ this.message += `Value ${value} is not positive`
103
+ }
104
+ }
105
+ }
106
+
107
+ /**
108
+ * An error for when a negative value was expected
109
+ */
110
+ class NegativeConstraintError extends ConstraintError {
111
+ constructor (value: any) {
112
+ super()
113
+ if (value === undefined) {
114
+ this.message += 'Positive value expected'
115
+ } else {
116
+ this.message += `Value ${value} is not negative`
117
+ }
118
+ }
119
+ }
120
+
121
+ /**
122
+ * An error for when zero was not expected.
123
+ */
124
+ class ZeroValueConstraintError extends ConstraintError {
125
+ constructor () {
126
+ super()
127
+ this.message += 'Zero is not an allowable value'
128
+ }
129
+ }
130
+
131
+ /**
132
+ * An error for declaring both ! and not ! variants of the same expression
133
+ */
134
+ class ConstraintConflictError extends ConstraintError {
135
+ constructor (conflictType: string) {
136
+ super()
137
+ this.message += `Both ${conflictType} and !${conflictType} declared`
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Base form of TypeConstraint.
143
+ * Defines the base type and the test method.
144
+ */
145
+ export class TypeConstraint {
146
+ /** The type this constraint applies to */
147
+ public readonly type: string
148
+
149
+ public badName?: string // defined only if we encounter an unrecognized constraint keyword
150
+
151
+ /** a freeform note that appears in comments. No runtime verification. */
152
+ public note?: string
153
+
154
+ constructor (typeString: string = '') {
155
+ this.type = typeString.trim().toLowerCase()
156
+ }
157
+
158
+ /**
159
+ * Perform a runtime test of the value
160
+
161
+ * returns without throw if test was okay, otherwise throws a ConstraintError explaining the violation.
162
+ *
163
+ * @param value - value to test against this constraint
164
+ *
165
+ * @throws {ConstraintError} Error is thrown if test fails its constraints
166
+ */
167
+ test (value: any): void | never {
168
+ if (typeof value !== this.type) {
169
+ throw new ConstraintBasicTypeError(value, this.type)
170
+ }
171
+ }
172
+
173
+ // Describes the constraint in printable terms (not really used, a bit redundant to describe)
174
+ toString () {
175
+ if (this.badName) return `"${this.badName}" is not a recognized constraint for ${this.type}`
176
+ if (this.note) return this.note
177
+ return '- No Constraint'
178
+ }
179
+
180
+ // describe the constraints in human terms.
181
+ describe () {
182
+ if (this.badName) return `"${this.badName}" is not a recognized constraint for ${this.type}`
183
+ if (this.note) return this.note
184
+ return 'No Constraint'
185
+ }
186
+ }
187
+
188
+ // /**
189
+ // * Enumeration of recognized status for a parameter or return constraint
190
+ // */
191
+ // enum ConstraintStatus {
192
+ // None = "", // not parsed
193
+ // NotConstraint = "NotConstraint", // doesn't start with '-', treat as description
194
+ // Error = "Error", // parsing error
195
+ // NotProvided = "NotProvided", // no constraint block
196
+ //
197
+ //
198
+ // }
199
+
200
+ /**
201
+ * Null only applies to objects.
202
+ */
203
+ // class NullConstraint extends TypeConstraint {
204
+ // test(value) {
205
+ // if(value || typeof value !== 'object') {
206
+ // throw new NullConstraintError()
207
+ // }
208
+ // }
209
+ // }
210
+
211
+ /**
212
+ * Constraints recorded on a number
213
+ * Integer, Positive, Negative, NotZero, min, max
214
+ */
215
+ class NumberConstraint extends TypeConstraint {
216
+ public min?: number // min range
217
+ public max?: number // max range, inclusive
218
+ public maxx?: number // max, exclusive
219
+ public isInteger?: boolean = false // number must be an integer
220
+ public isPositive?: boolean = false // number must be positive
221
+ public isNegative?: boolean = false // number must be negative
222
+ public notZero?: boolean = false // number must not be zero
223
+
224
+ constructor () {
225
+ super('number')
226
+ }
227
+
228
+ test (value: any) {
229
+ super.test(value)
230
+ if (this.isInteger) {
231
+ if (Math.floor(value) !== value) {
232
+ throw new IntegerConstraintError(value)
233
+ }
234
+ }
235
+ if (this.notZero) {
236
+ if (value === 0) {
237
+ throw new ZeroValueConstraintError()
238
+ }
239
+ }
240
+ if (this.isPositive && this.isNegative) {
241
+ throw new ConstraintConflictError('positive')
242
+ }
243
+ if (this.isPositive) {
244
+ if (value < 0) {
245
+ throw new PositiveConstraintError(value)
246
+ }
247
+ }
248
+ if (this.isNegative) {
249
+ if (value < 0) {
250
+ throw new NegativeConstraintError(value)
251
+ }
252
+ }
253
+ if (this.min !== undefined) {
254
+ if (value < this.min) {
255
+ throw new RangeConstraintError(value, this.min)
256
+ }
257
+ }
258
+ if (this.max !== undefined) {
259
+ if (value > this.max) {
260
+ throw new RangeConstraintError(value, this.max)
261
+ }
262
+ }
263
+ if (this.maxx !== undefined) {
264
+ if (value >= this.maxx) {
265
+ throw new RangeConstraintError(value, this.maxx)
266
+ }
267
+ }
268
+ }
269
+
270
+ toString () {
271
+ const keys: string[] = []
272
+ if (this.isInteger) keys.push('Integer')
273
+ if (this.notZero) keys.push('Not Zero')
274
+ if (this.isPositive) keys.push('Positive')
275
+ if (this.isNegative) keys.push('Negative')
276
+ if (this.min !== undefined) keys.push(`Min = ${this.min}`)
277
+ if (this.max !== undefined) keys.push(`Max = ${this.max}`)
278
+ if (this.maxx !== undefined) keys.push(`Maxx = ${this.maxx}`)
279
+ if (this.note) keys.push(this.note)
280
+ return (keys.length > 0) ? '- ' + keys.join(',') : super.toString()
281
+ }
282
+
283
+ describe (): string {
284
+ const keys: string[] = []
285
+ if (this.isInteger) keys.push('number must be an integer')
286
+ if (this.notZero) keys.push('number must not be zero')
287
+ if (this.isPositive) keys.push('number must be positive')
288
+ if (this.isNegative) keys.push('number must be negative')
289
+ if (this.min !== undefined) keys.push(`Minimum value is ${this.min}`)
290
+ if (this.max !== undefined) keys.push(`Maximum value is ${this.max}`)
291
+ if (this.maxx !== undefined) keys.push(`Maximum value is less than ${this.maxx}`)
292
+ if (this.note || this.badName) keys.push(super.describe())
293
+ return (keys.length > 0) ? keys.join('\n') : super.describe()
294
+ }
295
+ }
296
+
297
+ /**
298
+ * Constraints recorded on a string
299
+ * minLength, maxLength, (!)startsWith, (!)endsWith, (!)contains, (!)match
300
+ */
301
+ class StringConstraint extends TypeConstraint {
302
+ public minLength?: number // minimum length of allowed string
303
+ public maxLength?: number // maximum length of allowed string
304
+ public startsWith?: string // string must start with this substring
305
+ public notStartsWith?: string // string must not start with this substring
306
+ public endsWith?: string // string must end with this substring
307
+ public notEndsWith?: string // string must not end with this substring
308
+ public contains?: string // string contains this substring
309
+ public notContains?: string // string does not contain this substring
310
+ public match?: string // regular expression (as string) that must be matched
311
+ public notMatch?: string // regular expression (as string) that must not be matched
312
+
313
+ constructor () {
314
+ super('string')
315
+ }
316
+
317
+ test (value: any) {
318
+ super.test(value)
319
+ if (this.minLength) {
320
+ if (value.length < this.minLength) {
321
+ throw new RangeConstraintError(value.length, this.minLength, 'String Length')
322
+ }
323
+ }
324
+ if (this.maxLength) {
325
+ if (value.length > this.maxLength) {
326
+ throw new RangeConstraintError(value.length, this.maxLength, 'String Length')
327
+ }
328
+ }
329
+ if (this.startsWith && this.notStartsWith) {
330
+ throw new ConstraintConflictError('startsWith')
331
+ }
332
+ if (this.startsWith || this.notStartsWith) {
333
+ const comp = this.startsWith || this.notStartsWith || ''
334
+ const not = !!this.notStartsWith
335
+ if (value.substring(0, comp.length) === comp) {
336
+ if (not) throw new ConstraintFail('!startsWith', value)
337
+ } else {
338
+ if (!not) throw new ConstraintFail('startsWith', value)
339
+ }
340
+ }
341
+ if (this.endsWith && this.notEndsWith) {
342
+ throw new ConstraintConflictError('endsWith')
343
+ }
344
+ if (this.endsWith || this.notEndsWith) {
345
+ const comp = this.endsWith || this.notEndsWith || ''
346
+ const not = !!this.notEndsWith
347
+ if (value.substring(value.length - comp.length) === comp) {
348
+ if (not) throw new ConstraintFail('!endsWith', value)
349
+ } else {
350
+ if (!not) throw new ConstraintFail('endsWith', value)
351
+ }
352
+ }
353
+ if (this.contains && this.notContains) {
354
+ throw new ConstraintConflictError('contains')
355
+ }
356
+ if (this.contains || this.notContains) {
357
+ const comp = this.contains || this.notContains
358
+ const not = !!this.notContains
359
+ if (value.indexOf(comp) !== -1) {
360
+ if (not) throw new ConstraintFail('!contains', value)
361
+ } else {
362
+ if (!not) throw new ConstraintFail('contains', value)
363
+ }
364
+ }
365
+ if (this.match && this.notMatch) {
366
+ throw new ConstraintConflictError('match')
367
+ }
368
+ if (this.match || this.notMatch) {
369
+ const comp = this.match || this.notMatch
370
+ const not = !!this.notMatch
371
+ const re = new RegExp(comp || '')
372
+ if (re.test(value)) {
373
+ if (not) throw new ConstraintFail('!match', value)
374
+ } else {
375
+ if (!not) throw new ConstraintFail('match', value)
376
+ }
377
+ }
378
+ }
379
+
380
+ toString () {
381
+ const keys: string[] = []
382
+ if (this.minLength) keys.push(`Min Length = ${this.minLength}`)
383
+ if (this.maxLength) keys.push(`Max Length = ${this.maxLength}`)
384
+ if (this.startsWith) keys.push(`Starts With = ${this.startsWith}`)
385
+ if (this.notStartsWith) keys.push(`!StartsWith = ${this.startsWith}`)
386
+ if (this.endsWith) keys.push(`Ends With = ${this.endsWith}`)
387
+ if (this.notEndsWith) keys.push(`!EndsWith = ${this.endsWith}`)
388
+ if (this.contains) keys.push(`Contains = ${this.contains}`)
389
+ if (this.notContains) keys.push(`!Contains = ${this.notContains}`)
390
+ if (this.match) keys.push(`Match = ${this.match}`)
391
+ if (this.notMatch) keys.push(`!Match = ${this.notMatch}`)
392
+ if (this.note) keys.push(this.note)
393
+ return (keys.length > 0) ? '- ' + keys.join(',') : super.toString()
394
+ }
395
+
396
+ describe () {
397
+ const keys: string[] = []
398
+ if (this.minLength) keys.push(`string must be at least ${this.minLength} characters long`)
399
+ if (this.maxLength) keys.push(`string must consist of less than ${this.maxLength} characters`)
400
+ if (this.startsWith) keys.push(`string must start with "${this.startsWith}"`)
401
+ if (this.notStartsWith) keys.push(`string must NOT start with "${this.startsWith}"`)
402
+ if (this.endsWith) keys.push(`string must end with "${this.endsWith}"`)
403
+ if (this.notEndsWith) keys.push(`string must NOT end with "${this.endsWith}"`)
404
+ if (this.contains) keys.push(`must contain substring "${this.contains}"`)
405
+ if (this.notContains) keys.push(`must NOT contain substring "${this.notContains}"`)
406
+ if (this.match) keys.push(`must match Regular Expression "${this.match}"`)
407
+ if (this.notMatch) keys.push(`must NOT match RegExp "${this.notMatch}"`)
408
+ if (this.note || this.badName) keys.push(super.describe())
409
+ return (keys.length > 0) ? keys.join('\n') : super.describe()
410
+ }
411
+ }
412
+
413
+ /**
414
+ * Constraints recorded on an object
415
+ * (!)empty, (!)hasProperties, notNested, noPrototype, canSerialize, noUndefinedProps
416
+ */
417
+ class ObjectConstraint extends TypeConstraint {
418
+ public empty?: boolean // object must have no properties
419
+ public notEmpty?: boolean // object must have some properties
420
+ public hasProperties?: string[] // object must have these specific properties defined
421
+ public notHasProperties?: string[] // object must note have these specific properties defined
422
+ public notNested?: boolean // object must not contain object members (arrays are okay)
423
+ public noPrototype?: boolean // object must not have a prototype other than Object.
424
+ public canSerialize?: boolean // object must survive standard js (structured clone) serialization (i.e. stringify w/o error)
425
+ public noFalseyProps?: boolean // all property values must evaluate at truthy
426
+ public noTruthyProps?: boolean // all property values must evaluate as falsey
427
+ public instanceOf?: string // object must have been constructed as this object name
428
+ public notInstanceOf?: string // object must not be of this instance type name
429
+
430
+ constructor () {
431
+ super('object')
432
+ }
433
+
434
+ test (value: any) {
435
+ super.test(value)
436
+
437
+ if (this.empty && this.notEmpty) {
438
+ throw new ConstraintConflictError('empty')
439
+ }
440
+ if ((this.hasProperties != null) && (this.notHasProperties != null)) {
441
+ const collisions: string[] = []
442
+ for (const has of this.hasProperties) {
443
+ if (this.notHasProperties.includes(has)) {
444
+ collisions.push(has)
445
+ }
446
+ }
447
+ if (collisions.length > 0) {
448
+ throw new ConstraintConflictError('hasProperties "' + collisions.join(',') + '"')
449
+ }
450
+ }
451
+ if (this.empty) {
452
+ if (Object.getOwnPropertyNames(value).length > 0) {
453
+ throw new ConstraintFail('empty', 'object contains ' + Object.getOwnPropertyNames(value).length + ' props')
454
+ }
455
+ }
456
+ if (this.notEmpty) {
457
+ if (Object.getOwnPropertyNames(value).length === 0) {
458
+ throw new ConstraintFail('!empty', value)
459
+ }
460
+ }
461
+ if (this.hasProperties != null) {
462
+ for (const has of this.hasProperties) {
463
+ if (!value.hasOwnProperty(has)) {
464
+ throw new ConstraintFail('hasProperties', has)
465
+ }
466
+ }
467
+ }
468
+ if (this.notHasProperties != null) {
469
+ for (const hasnot of this.notHasProperties) {
470
+ if (value.hasOwnProperty(hasnot)) {
471
+ throw new ConstraintFail('!hasProperties', hasnot)
472
+ }
473
+ }
474
+ }
475
+ if (this.notNested) {
476
+ for (const p of Object.getOwnPropertyNames(value)) {
477
+ const v = value[p]
478
+ if (typeof v === 'object') {
479
+ if (!Array.isArray(v)) {
480
+ throw new ConstraintFail('notNested', p)
481
+ }
482
+ }
483
+ }
484
+ }
485
+ if (this.noPrototype) {
486
+ const prot = Object.getPrototypeOf(value)
487
+ const name = prot && prot.constructor.name
488
+ if (name && name !== 'Object') {
489
+ throw new ConstraintFail('noPrototype', value)
490
+ }
491
+ }
492
+ if (this.canSerialize) {
493
+ let json
494
+ try {
495
+ json = JSON.stringify(value)
496
+ } catch (e) {
497
+ }
498
+ if (!json) {
499
+ throw new ConstraintFail('canSerialize', value)
500
+ }
501
+ }
502
+ if (this.noFalseyProps) {
503
+ for (const p of Object.getOwnPropertyNames(value)) {
504
+ const v = value[p]
505
+ if (!v) {
506
+ throw new ConstraintFail('noFalseyProps', p)
507
+ }
508
+ }
509
+ }
510
+ if (this.noTruthyProps) {
511
+ for (const p of Object.getOwnPropertyNames(value)) {
512
+ const v = value[p]
513
+ if (v) {
514
+ throw new ConstraintFail('noTruthyProps', p)
515
+ }
516
+ }
517
+ }
518
+ if (this.instanceOf) {
519
+ if (value.constructor.name !== this.instanceOf) {
520
+ throw new ConstraintFail('instanceOf (' + this.instanceOf + ')', value.constructor.name)
521
+ }
522
+ }
523
+ if (this.notInstanceOf) {
524
+ if (value.constructor.name === this.notInstanceOf) {
525
+ throw new ConstraintFail('!instanceOf', this.notInstanceOf)
526
+ }
527
+ }
528
+ }
529
+
530
+ toString () {
531
+ const keys: string[] = []
532
+ if (this.empty) keys.push('Empty')
533
+ if (this.notEmpty) keys.push('!Empty')
534
+ if (this.hasProperties != null) keys.push(`Has Properties =${this.hasProperties.join(',')}`)
535
+ if (this.notHasProperties != null) keys.push(`!Has Properties =${this.notHasProperties}`)
536
+ if (this.notNested) keys.push('Not Nested')
537
+ if (this.noPrototype) keys.push('No Prototype')
538
+ if (this.canSerialize) keys.push('Can Serialize')
539
+ if (this.noFalseyProps) keys.push('No Falsey Props')
540
+ if (this.noTruthyProps) keys.push('No Truthy Props')
541
+ if (this.instanceOf) keys.push(`Instance Of = ${this.instanceOf}`)
542
+ if (this.notInstanceOf) keys.push(`Not an instance of ${this.notInstanceOf}`)
543
+ if (this.note) keys.push(this.note)
544
+ return (keys.length > 0) ? '- ' + keys.join(',') : super.toString()
545
+ }
546
+
547
+ describe () {
548
+ const keys: string[] = []
549
+ if (this.empty) keys.push('object must be empty')
550
+ if (this.notEmpty) keys.push('object must not be empty')
551
+ if (this.hasProperties != null) keys.push(`object must contain properties "${this.hasProperties.join(',')}"`)
552
+ if (this.notHasProperties != null) keys.push(`object must not contain properties "${this.notHasProperties.join(',')}"`)
553
+ if (this.notNested) keys.push('object must not contain nested objects')
554
+ if (this.noPrototype) keys.push('object must not derive from a prototype')
555
+ if (this.canSerialize) keys.push('object can be serialized')
556
+ if (this.noFalseyProps) keys.push('object can contain no properties that evaluate as false')
557
+ if (this.noTruthyProps) keys.push('object can contain no properties that evaluate as true')
558
+ if (this.instanceOf) keys.push(`object must be an instance of "${this.instanceOf}"`)
559
+ if (this.notInstanceOf) keys.push(`object must not be an instance of "${this.notInstanceOf}"`)
560
+ if (this.note || this.badName) keys.push(super.describe())
561
+ return (keys.length > 0) ? keys.join('\n') : super.describe()
562
+ }
563
+ }
564
+
565
+ /**
566
+ * Enumeration of checkType parsed results.
567
+ *
568
+ * parameters (p1, p2) are parsed at same time, and meaning does vary per checkType.
569
+ */
570
+ export enum ElementCheckType {
571
+ none, // don't test the elements
572
+ all, // test all the elements
573
+ random, // test up to a given number (p1) of elements, randomly chosen
574
+ step, // test every (p1) elements
575
+ first, // test all up to (p1) elements, then stop
576
+ last, // test all of the last (p1) elements
577
+ firstThenLast, // Test the first (p1) elements, and the last (p2) elements
578
+ firstThenStep, // test all up to (p1) elements, then every (p2) thereafter
579
+ firstThenRandom// test all up to (p1) elements, then up to (p2) of the remaining, chosen at random
580
+ }
581
+
582
+ /**
583
+ * Constraints recorded on an array
584
+ * minLength, maxLength, (!)contains, checkType, each
585
+ */
586
+ class ArrayConstraint extends TypeConstraint {
587
+ public minLength?: number // minimum array length
588
+ public maxLength?: number // maximum array length
589
+ public contains?: any // at least one element must be of this value
590
+ public notContains?: any // no elements can be of this value
591
+ public elementConstraints: TypeConstraint[] = [] // elements are tested for compliance under these rules
592
+ public elementCheckType: ElementCheckType = ElementCheckType.none // defines the extent of runtime coverage on elements
593
+ public elementCheckParameter: number = 0 // defined by elementCheckType
594
+ public elementCheckParameter2: number = 0 // defined by elementCheckType
595
+ constructor () {
596
+ super('array')
597
+ }
598
+
599
+ test (value: any) {
600
+ if (!Array.isArray(value)) {
601
+ throw new ConstraintBasicTypeError(value, 'array')
602
+ }
603
+ const length = value.length
604
+ if (this.minLength) {
605
+ if (length < this.minLength) {
606
+ throw new RangeConstraintError(length, this.minLength, 'Array Length')
607
+ }
608
+ }
609
+ if (this.maxLength) {
610
+ if (length > this.maxLength) {
611
+ throw new RangeConstraintError(length, this.maxLength, 'Array Length')
612
+ }
613
+ }
614
+ if (this.contains && this.notContains) {
615
+ throw new ConstraintConflictError('contains')
616
+ }
617
+ if (this.contains || this.notContains) {
618
+ const comp = this.contains || this.notContains
619
+ const not = !!this.notContains
620
+ if (value.includes(comp)) {
621
+ if (not) throw new ConstraintFail('!contains', this.notContains)
622
+ } else {
623
+ if (!not) throw new ConstraintFail('contains', this.contains)
624
+ }
625
+ }
626
+ if (this.elementConstraints || this.elementCheckType) {
627
+ const checkType = this.elementCheckType === undefined ? ElementCheckType.all : this.elementCheckType
628
+ let i = 0
629
+ let count = 0
630
+ let step = 1
631
+ let firstCount = 0
632
+ let thenCount = 0
633
+ let counting = false
634
+ const tested: any = {}
635
+ switch (checkType) {
636
+ case ElementCheckType.none:
637
+ firstCount = 0
638
+ thenCount = 0
639
+ counting = false
640
+ break
641
+ case ElementCheckType.all:
642
+ firstCount = length
643
+ step = 1
644
+ thenCount = 0
645
+ counting = true
646
+ break
647
+ case ElementCheckType.first:
648
+ firstCount = parseInt('' + this.elementCheckParameter)
649
+ step = 1
650
+ thenCount = 0
651
+ counting = true
652
+ break
653
+ case ElementCheckType.last:
654
+ firstCount = 0
655
+ step = 1
656
+ thenCount = length - this.elementCheckParameter
657
+ counting = false
658
+ break
659
+ case ElementCheckType.firstThenLast:
660
+ firstCount = parseInt('' + this.elementCheckParameter)
661
+ thenCount = length - this.elementCheckParameter
662
+ if (thenCount < 0) thenCount = length
663
+ step = 1
664
+ counting = true
665
+ break
666
+ case ElementCheckType.step:
667
+ firstCount = length
668
+ thenCount = 0
669
+ counting = true
670
+ step = parseInt('' + this.elementCheckParameter)
671
+ break
672
+ case ElementCheckType.random:
673
+ firstCount = 0
674
+ thenCount = parseInt('' + this.elementCheckParameter)
675
+ step = 0
676
+ counting = true
677
+ break
678
+ case ElementCheckType.firstThenStep:
679
+ firstCount = parseInt('' + this.elementCheckParameter)
680
+ thenCount = length - firstCount
681
+ step = parseInt('' + this.elementCheckParameter2)
682
+ counting = true
683
+ break
684
+ case ElementCheckType.firstThenRandom:
685
+ firstCount = parseInt('' + this.elementCheckParameter)
686
+ thenCount = parseInt('' + this.elementCheckParameter2)
687
+ step = 0
688
+ counting = true
689
+ break
690
+ }
691
+ while (i < length) {
692
+ if (counting) {
693
+ const ev = value[i]
694
+ let t: string = typeof ev
695
+ if (Array.isArray(ev)) t = 'array'
696
+ const m = (this.elementConstraints as unknown as Map<string, TypeConstraint>)
697
+ const c = m && m.get(t)
698
+ const tc = (c != null) || parseConstraints(t, '')
699
+ if (tc !== true && tc?.test != undefined) tc.test(ev)
700
+ count++
701
+ }
702
+ if ((checkType === ElementCheckType.last || checkType === ElementCheckType.firstThenLast) && i === thenCount) {
703
+ counting = true
704
+ }
705
+ if (checkType === ElementCheckType.firstThenLast && i === firstCount) {
706
+ counting = false
707
+ }
708
+
709
+ if (count >= firstCount) {
710
+ if (count >= firstCount + thenCount) {
711
+ break
712
+ }
713
+ }
714
+ if (step) {
715
+ i += step
716
+ } else {
717
+ while (true) {
718
+ const rr = Math.floor(Math.random() * (length - count))
719
+ i = count + rr
720
+ if (i < length && !tested[i]) {
721
+ tested[i] = true
722
+ break
723
+ }
724
+ }
725
+ }
726
+ }
727
+ }
728
+ }
729
+
730
+ toString () {
731
+ const keys: string[] = []
732
+ if (this.minLength) keys.push(`Min Length = ${this.minLength}`)
733
+ if (this.maxLength) keys.push(`Max Length = ${this.maxLength}`)
734
+ if (this.contains) keys.push(`Contains = ${this.contains}`)
735
+ if (this.notContains) keys.push(`!Contains = ${this.notContains}`)
736
+ if (this.elementConstraints) keys.push(`each element of the array has the following constraints by type ${listEachConstraints(this.elementConstraints)}`)
737
+ if (this.elementCheckType) keys.push(`(elements will be tested using the ${checkTypeToString(this.elementCheckType, this.elementCheckParameter, this.elementCheckParameter2)} method)`)
738
+ if (this.note) keys.push(this.note)
739
+ return (keys.length > 0) ? '- ' + keys.join(',') : super.toString()
740
+ }
741
+
742
+ describe () {
743
+ const keys: string[] = []
744
+ if (this.minLength) keys.push(`array must contain at least ${this.minLength} elements`)
745
+ if (this.maxLength) keys.push(`array must contain no more than ${this.maxLength} elements`)
746
+ if (this.contains) keys.push(`array must contain element value "${this.contains}"`)
747
+ if (this.notContains) keys.push(`array must not contain an element value "${this.notContains}"`)
748
+ if (this.elementConstraints) keys.push(`each element of the array has the following constraints by type ${listEachConstraints(this.elementConstraints)}`)
749
+ if (this.elementCheckType) keys.push(`(elements will be tested using the ${checkTypeToString(this.elementCheckType, this.elementCheckParameter, this.elementCheckParameter2)} method)`)
750
+ if (this.note || this.badName) keys.push(super.describe())
751
+ return (keys.length > 0) ? keys.join('\n') : super.describe()
752
+ }
753
+ }
754
+
755
+ function listEachConstraints (cmap: any) {
756
+ let out = ''
757
+ const types = cmap.keys()
758
+ const entries = cmap.entries()
759
+ let entry
760
+ while ((entry = entries.next().value)) {
761
+ out += '<br/><b>' + entry[0] + ' elements:</b><br/>&nbsp;&nbsp; -'
762
+ out += entry[1].describe().replace(/\n/g, '<br/>&nbsp;&nbsp; - ')
763
+ }
764
+ return out
765
+ }
766
+
767
+ /**
768
+ * Translates a type string (number, string, boolean, object, array, regex) into the corresponding ValueType enum
769
+ * Note that strings beside none, array, and regex are synonymous with the `typeof` operator value
770
+ * @param str
771
+ */
772
+ export function valueTypeFromString (str: string): ValueType {
773
+ switch (str.trim().toLowerCase()) {
774
+ default: return str.trim().length ? str.includes('[]') ? ValueType.array : ValueType.object : ValueType.none
775
+ case 'number': return ValueType.number
776
+ case 'string': return ValueType.string
777
+ case 'boolean': return ValueType.boolean
778
+ case 'object': return ValueType.object
779
+ case 'array': return ValueType.array
780
+ case 'regex': return ValueType.regex
781
+ case 'regexp': return ValueType.regex
782
+ }
783
+ }
784
+
785
+ /**
786
+ * Translates a ValueType enum value into the corresponding string.
787
+ * Note that strings beside none, array, and regex are synonymous with the `typeof` operator value
788
+ * @param vt
789
+ */
790
+ export function stringFromValueType (vt: ValueType): string {
791
+ switch (vt) {
792
+ case ValueType.none: return ''
793
+ case ValueType.number: return 'number'
794
+ case ValueType.string: return 'string'
795
+ case ValueType.boolean: return 'boolean'
796
+ case ValueType.object: return 'object'
797
+ case ValueType.array: return 'array'
798
+ case ValueType.regex: return 'regex'
799
+ }
800
+ }
801
+
802
+ /**
803
+ * Read either a value or a list from an expression value
804
+ * @param str
805
+ */
806
+ function constraintListParse (str = '') {
807
+ str.trim()
808
+ if (str.charAt(0) === '"' || str.charAt(0) === "'") {
809
+ str = str.substring(1, str.length - 1)
810
+ }
811
+ if (str.includes(',')) {
812
+ return str.split(',') // return the split array
813
+ }
814
+ if (isFinite(Number(str))) {
815
+ return Number(str)
816
+ }
817
+ return str // return the unquoted string value
818
+ }
819
+
820
+ /**
821
+ * Used to parse the type+constraints blocks from an "each" directive list
822
+ * @param str
823
+ */
824
+ function eachListParse (str = '') {
825
+ const map = new Map<string, TypeConstraint>()
826
+ const esplit = str.split('|')
827
+ for (const tblock of esplit) {
828
+ const ci = tblock.indexOf(',')
829
+ if (ci !== -1) {
830
+ const type = tblock.substring(0, ci).trim()
831
+ const cdef = tblock.substring(ci + 1)
832
+ const constraint = (parseConstraints(type, cdef) != null) || new TypeConstraint()
833
+ if (constraint !== undefined && constraint !== true) {
834
+ map.set(type, constraint)
835
+ }
836
+ }
837
+ }
838
+ return map
839
+ }
840
+
841
+ /**
842
+ * Parse out the checkType and return the resulting type name and the parsed parameters in a structure.
843
+ * @param ctStr
844
+ * @return {{string}name,{number}[p1],{number}[p2]}
845
+ */
846
+ function parseCheckType (ctStr: string = ''): { name: string, p1?: number, p2?: number } {
847
+ let opi = ctStr.indexOf('(')
848
+ if (opi === -1) opi = ctStr.length
849
+ const name = ctStr.substring(0, opi)
850
+ let cpi = ctStr.indexOf(')', opi)
851
+ if (cpi === -1) cpi = ctStr.length
852
+ const p = ctStr.substring(opi + 1, cpi).split(',')
853
+ let p1: any, p2: any
854
+ try {
855
+ p1 = p[0] && parseInt(p[0])
856
+ p2 = p[1] && parseInt(p[1])
857
+ } catch (e) {}
858
+ return { name, p1, p2 }
859
+ }
860
+
861
+ function checkTypeToString (ct: ElementCheckType, p1?: number | string, p2?: number | string) {
862
+ switch (ct) {
863
+ case ElementCheckType.random:
864
+ return `random(${p1})`
865
+ case ElementCheckType.step:
866
+ return `step(${p1})`
867
+ case ElementCheckType.first:
868
+ return `first(${p1})`
869
+ case ElementCheckType.last:
870
+ return `last(${p1})`
871
+ case ElementCheckType.firstThenLast:
872
+ return `firstThenLast(${p1},${p2})`
873
+ case ElementCheckType.firstThenStep:
874
+ return `firstThenStep(${p1},${p2})`
875
+ case ElementCheckType.firstThenRandom:
876
+ return `firstThenRandom(${p1},${p2})`
877
+ case ElementCheckType.none:
878
+ return 'none'
879
+ default:
880
+ case ElementCheckType.all:
881
+ return 'all'
882
+ }
883
+ }
884
+ function checkTypeFromString (ctstr: string): ElementCheckType {
885
+ switch (ctstr.trim().toLowerCase()) {
886
+ case 'random': return ElementCheckType.random
887
+ case 'step': return ElementCheckType.step
888
+ case 'first': return ElementCheckType.first
889
+ case 'last': return ElementCheckType.last
890
+ case 'firstthenlast': return ElementCheckType.firstThenLast
891
+ case 'firstthenstep': return ElementCheckType.firstThenStep
892
+ case 'firstthenrandom': return ElementCheckType.firstThenRandom
893
+ case 'none': return ElementCheckType.none
894
+ default:
895
+ case 'all': return ElementCheckType.all
896
+ }
897
+ }
898
+
899
+ // parse constraints from what may be more than one type (e.g. string|number)
900
+ export function parseConstraintsToMap (typeString: string, blockSet: string = ''): Map<string, TypeConstraint> {
901
+ const map = new Map<string, TypeConstraint>()
902
+ const types = typeString.split('|')
903
+ const blocks = blockSet.split(',')
904
+ for (let type of types) {
905
+ type = (type || '').trim()
906
+ const constraint = (parseConstraints(type, blockSet) != null) || new TypeConstraint()
907
+ if (constraint !== undefined && constraint !== true) {
908
+ map.set(type, constraint)
909
+ }
910
+ }
911
+ return map
912
+ }
913
+
914
+ /**
915
+ * Given a block of text, parse as constraints and return the set if this is a constraint declaration
916
+ * otherwise, return ConstraintStatus.NotConstraint to signify this is a description block and not a constraint declaration
917
+ * @param type - the type parsed from the param or return declaration
918
+ * @param block - the block of text to evaluate
919
+ * @param delim - the split delimiter (defaults to ',')
920
+ */
921
+ export function parseConstraints (type: string, block: string, delim = ','): TypeConstraint | undefined {
922
+ let constraint
923
+ if (!block || !type) return
924
+ const valueType = valueTypeFromString(type)
925
+ let cblock = block.trim()
926
+ // get any constraint parameters
927
+ let fpi = cblock.indexOf('(')
928
+ while (fpi !== -1) {
929
+ let cpi = cblock.indexOf(')', fpi)
930
+ if (cpi === -1) cpi = cblock.length
931
+ const swap = cblock.substring(fpi, cpi).replace(/,/g, ';;')
932
+ cblock = cblock.substring(0, fpi) + swap + cblock.substring(cpi)
933
+ fpi = cblock.indexOf('(', cpi)
934
+ }
935
+
936
+ const expressions = cblock.split(delim)
937
+ for (let expr of expressions) {
938
+ let expVal
939
+ let params
940
+ let not = false
941
+ expr = expr.trim()
942
+ if (!expr.startsWith('match') && !expr.startsWith('!match')) {
943
+ const cpi = expr.indexOf('(')
944
+ if (cpi !== -1) {
945
+ params = expr.substring(cpi).replace(/;;/g, ',').trim()
946
+ if (params.charAt(0) === '(') params = params.substring(1)
947
+ if (params.charAt(params.length - 1) === ')') params = params.substring(0, params.length - 1)
948
+ expr = expr.substring(0, cpi).trim()
949
+ if (expr === 'each') {
950
+ expVal = eachListParse(params)
951
+ } else {
952
+ expVal = constraintListParse(params)
953
+ }
954
+ }
955
+ }
956
+ if (expr.charAt(0) === '!') {
957
+ not = true
958
+ expr = expr.substring(1)
959
+ }
960
+ if (expr.includes('=')) {
961
+ const p = expr.split('=')
962
+ if (p.length > 2) {
963
+ p[1] = p.slice(1).join('=')
964
+ }
965
+ expr = p[0].trim()
966
+ if (expr === 'each') {
967
+ expVal = eachListParse(p[1])
968
+ } else {
969
+ expVal = constraintListParse(p[1])
970
+ }
971
+ }
972
+ expr = expr.trim().toLowerCase()
973
+ switch (valueType) {
974
+ case ValueType.number:
975
+ constraint = (constraint as NumberConstraint) || new NumberConstraint()
976
+ switch (expr) {
977
+ case 'noconstraint':
978
+ case 'no constraint':
979
+ return constraint // early exit if we encounter "- No Constraint"
980
+
981
+ /* Integer, Positive, Negative, NotZero, min, max */
982
+ case 'integer':
983
+ constraint.isInteger = true
984
+ break
985
+ case 'positive':
986
+ constraint.isPositive = true
987
+ break
988
+ case 'negative':
989
+ constraint.isNegative = true
990
+ break
991
+ case 'notzero':
992
+ case 'not zero':
993
+ case 'nonzero':
994
+ constraint.notZero = true
995
+ break
996
+ case 'min':
997
+ constraint.min = expVal as number
998
+ break
999
+ case 'max':
1000
+ constraint.max = expVal as number
1001
+ break
1002
+ case 'maxx':
1003
+ constraint.maxx = expVal as number
1004
+ break
1005
+
1006
+ case 'note':
1007
+ constraint.note = expVal as string
1008
+ break
1009
+ default:
1010
+ constraint.badName = expr
1011
+ break
1012
+ }
1013
+
1014
+ break
1015
+ case ValueType.string:
1016
+ // minLength, maxLength, (!)startsWith, (!)endsWith, (!)contains, (!)match
1017
+ constraint = (constraint as StringConstraint) || new StringConstraint()
1018
+ switch (expr) {
1019
+ case 'noconstraint':
1020
+ case 'no constraint':
1021
+ return constraint // early exit if we encounter "- No Constraint"
1022
+
1023
+ case 'minlength':
1024
+ constraint.minLength = expVal as number
1025
+ break
1026
+ case 'maxlength':
1027
+ constraint.maxLength = expVal as number
1028
+ break
1029
+ case 'startswith':
1030
+ not ? constraint.notStartsWith = expVal as string : constraint.startsWith = expVal as string
1031
+ break
1032
+ case 'endswith':
1033
+ not ? constraint.notEndsWith = expVal as string : constraint.endsWith = expVal as string
1034
+ break
1035
+ case 'contains':
1036
+ not ? constraint.notContains = expVal as string : constraint.contains = expVal as string
1037
+ break
1038
+ case 'match':
1039
+ not ? constraint.notMatch = expVal as string : constraint.match = expVal as string
1040
+ break
1041
+ case 'note':
1042
+ constraint.note = expVal as string
1043
+ break
1044
+ default:
1045
+ constraint.badName = expr
1046
+ break
1047
+ }
1048
+ break
1049
+ case ValueType.object:
1050
+ // (!)empty, (!)hasProperties, notNested, noPrototype, canSerialize, noUndefinedProps
1051
+ constraint = (constraint as ObjectConstraint) || new ObjectConstraint()
1052
+ switch (expr) {
1053
+ case 'noconstraint':
1054
+ case 'no constraint':
1055
+ return constraint // early exit if we encounter "- No Constraint"
1056
+
1057
+ case 'empty':
1058
+ constraint.empty = !not
1059
+ constraint.notEmpty = not
1060
+ break
1061
+ case 'hasproperties':
1062
+ case 'has properties':
1063
+ if (typeof expVal === 'string') expVal = [expVal]
1064
+ not ? constraint.notHasProperties = expVal as string[] : constraint.hasProperties = expVal as string[]
1065
+ break
1066
+ case 'notnested':
1067
+ case 'not nested':
1068
+ constraint.notNested = true
1069
+ break
1070
+ case 'noprototype':
1071
+ case 'no prototype':
1072
+ constraint.noPrototype = true
1073
+ break
1074
+ case 'canserialize':
1075
+ case 'can serialize':
1076
+ constraint.canSerialize = true
1077
+ break
1078
+ case 'notruthyprops':
1079
+ case 'no truthy props':
1080
+ constraint.noTruthyProps = true
1081
+ break
1082
+ case 'nofalseyprops':
1083
+ case 'no falsey props':
1084
+ constraint.noFalseyProps = true
1085
+ break
1086
+ case 'instanceof':
1087
+ case 'instance of':
1088
+ if (not) constraint.notInstanceOf = expVal as string
1089
+ else constraint.instanceOf = expVal as string
1090
+ break
1091
+ case 'note':
1092
+ constraint.note = expVal as string
1093
+ break
1094
+ default:
1095
+ constraint.badName = expr
1096
+ break
1097
+ }
1098
+ break
1099
+ case ValueType.array:
1100
+ // minLength, maxLength, (!)contains, each:
1101
+ constraint = (constraint as ArrayConstraint) || new ArrayConstraint()
1102
+ switch (expr) {
1103
+ case 'noconstraint':
1104
+ case 'no constraint':
1105
+ return constraint // early exit if we encounter "- No Constraint"
1106
+
1107
+ case 'minlength':
1108
+ case 'min length':
1109
+ constraint.minLength = expVal as number
1110
+ break
1111
+ case 'maxlength':
1112
+ case 'max length':
1113
+ constraint.maxLength = expVal as number
1114
+ break
1115
+ case 'contains':
1116
+ not ? constraint.notContains = expVal as string : constraint.contains = expVal as string
1117
+ break
1118
+ case 'checktype':
1119
+ case 'check type':
1120
+ const psplit = (params || '').split(',')
1121
+ const pct = parseCheckType('' + expVal)
1122
+ constraint.elementCheckType = checkTypeFromString(pct.name)
1123
+ constraint.elementCheckParameter = (psplit[0] || pct.p1) as number
1124
+ constraint.elementCheckParameter2 = (psplit[1] || pct.p2) as number
1125
+ break
1126
+ case 'each':
1127
+ const type = 'any'
1128
+ constraint.elementConstraints = expVal as any
1129
+ break
1130
+ case 'note':
1131
+ constraint.note = expVal as string
1132
+ break
1133
+ default:
1134
+ constraint.badName = expr
1135
+ break
1136
+ }
1137
+ break
1138
+ default: // none, boolean, regex
1139
+ if (expr === 'no constraint') return
1140
+ constraint = new TypeConstraint(stringFromValueType(valueType))
1141
+ break
1142
+ }
1143
+ }
1144
+ return constraint
1145
+ }
1146
+
1147
+ /**
1148
+ * Simple test to see if a value adheres to a set of constraints
1149
+ */
1150
+ export function validate (
1151
+
1152
+ value: any, // The value to test for constraints. Must be one of the basic types supported by contraints
1153
+ constraintString: string // the constraints to test it against. Constraints listed must match the type being tested. Do not include < > brackets.
1154
+
1155
+ ): string // returns '' if no violation, otherwise returns the error string of the ConstraintError encountered
1156
+ {
1157
+ let type: string = typeof value
1158
+ if (type === 'object') {
1159
+ if (Array.isArray(value)) type = 'array'
1160
+ }
1161
+ const tc = parseConstraints(type, constraintString || '')
1162
+ let ok: string = ''
1163
+ try {
1164
+ if (tc != null) tc.test(value)
1165
+ } catch (e: any) {
1166
+ ok = e.message || e.toString()
1167
+ }
1168
+ return ok
1169
+ }
1170
+
1171
+ /* eslint-enable */