@declaro/core 2.0.0-beta.8 → 2.0.0-y.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 (90) hide show
  1. package/dist/app/app-context.d.ts +8 -0
  2. package/dist/app/app-lifecycle.d.ts +4 -0
  3. package/dist/app/app.d.ts +22 -0
  4. package/dist/app/index.d.ts +3 -20
  5. package/dist/auth/permission-validator.d.ts +34 -0
  6. package/dist/auth/permission-validator.test.d.ts +1 -0
  7. package/dist/context/context.d.ts +88 -13
  8. package/dist/context/legacy-context.test.d.ts +1 -0
  9. package/dist/errors/errors.d.ts +36 -0
  10. package/dist/events/event-manager.d.ts +11 -6
  11. package/dist/http/headers.d.ts +4 -0
  12. package/dist/http/headers.spec.d.ts +1 -0
  13. package/dist/http/request-context.d.ts +12 -0
  14. package/dist/http/request-context.spec.d.ts +1 -0
  15. package/dist/http/request.d.ts +8 -0
  16. package/dist/http/request.spec.d.ts +1 -0
  17. package/dist/http/url.d.ts +8 -0
  18. package/dist/http/url.spec.d.ts +1 -0
  19. package/dist/index.d.ts +9 -3
  20. package/dist/pkg.cjs +30 -2
  21. package/dist/pkg.mjs +56461 -207
  22. package/dist/schema/application.d.ts +83 -0
  23. package/dist/schema/application.test.d.ts +1 -0
  24. package/dist/schema/define-model.d.ts +7 -4
  25. package/dist/schema/index.d.ts +7 -0
  26. package/dist/schema/labels.d.ts +13 -0
  27. package/dist/schema/labels.test.d.ts +1 -0
  28. package/dist/schema/module.d.ts +7 -0
  29. package/dist/schema/module.test.d.ts +1 -0
  30. package/dist/schema/properties.d.ts +19 -0
  31. package/dist/schema/response.d.ts +31 -0
  32. package/dist/schema/response.test.d.ts +1 -0
  33. package/dist/schema/transform-model.d.ts +1 -1
  34. package/dist/schema/types.d.ts +81 -15
  35. package/dist/schema/types.test.d.ts +1 -0
  36. package/dist/typescript/constant-manipulation/snake-case.d.ts +22 -0
  37. package/dist/typescript/index.d.ts +1 -0
  38. package/dist/typescript/objects.d.ts +6 -0
  39. package/package.json +8 -3
  40. package/src/app/app-context.ts +14 -0
  41. package/src/app/app-lifecycle.ts +14 -0
  42. package/src/app/app.ts +45 -0
  43. package/src/app/index.ts +3 -34
  44. package/src/auth/permission-validator.test.ts +209 -0
  45. package/src/auth/permission-validator.ts +135 -0
  46. package/src/context/context.test.ts +585 -94
  47. package/src/context/context.ts +348 -32
  48. package/src/context/legacy-context.test.ts +141 -0
  49. package/src/errors/errors.ts +73 -0
  50. package/src/events/event-manager.spec.ts +54 -8
  51. package/src/events/event-manager.ts +40 -24
  52. package/src/http/headers.spec.ts +48 -0
  53. package/src/http/headers.ts +16 -0
  54. package/src/http/request-context.spec.ts +39 -0
  55. package/src/http/request-context.ts +43 -0
  56. package/src/http/request.spec.ts +52 -0
  57. package/src/http/request.ts +22 -0
  58. package/src/http/url.spec.ts +87 -0
  59. package/src/http/url.ts +48 -0
  60. package/src/index.ts +9 -3
  61. package/src/schema/application.test.ts +286 -0
  62. package/src/schema/application.ts +150 -0
  63. package/src/schema/define-model.test.ts +48 -2
  64. package/src/schema/define-model.ts +40 -9
  65. package/src/schema/index.ts +7 -0
  66. package/src/schema/labels.test.ts +60 -0
  67. package/src/schema/labels.ts +30 -0
  68. package/src/schema/module.test.ts +39 -0
  69. package/src/schema/module.ts +6 -0
  70. package/src/schema/properties.ts +40 -0
  71. package/src/schema/response.test.ts +101 -0
  72. package/src/schema/response.ts +93 -0
  73. package/src/schema/transform-model.ts +1 -1
  74. package/src/schema/types.test.ts +28 -0
  75. package/src/schema/types.ts +135 -15
  76. package/src/typescript/constant-manipulation/snake-case.md +496 -0
  77. package/src/typescript/constant-manipulation/snake-case.ts +76 -0
  78. package/src/typescript/index.ts +1 -0
  79. package/src/typescript/objects.ts +8 -5
  80. package/tsconfig.json +4 -1
  81. package/dist/context/index.d.ts +0 -3
  82. package/dist/interfaces/IDatastoreProvider.d.ts +0 -16
  83. package/dist/interfaces/IStore.d.ts +0 -4
  84. package/dist/interfaces/index.d.ts +0 -2
  85. package/dist/server/index.d.ts +0 -2
  86. package/src/context/index.ts +0 -3
  87. package/src/interfaces/IDatastoreProvider.ts +0 -23
  88. package/src/interfaces/IStore.ts +0 -4
  89. package/src/interfaces/index.ts +0 -2
  90. package/src/server/index.ts +0 -3
@@ -0,0 +1,209 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { PermissionError, PermissionRuleType, PermissionValidator } from './permission-validator'
3
+
4
+ describe('Permission Builder', () => {
5
+ it('should create a builder instance statically or with a constructor', () => {
6
+ const viaStatic = PermissionValidator.create()
7
+ const viaConstructor = new PermissionValidator()
8
+
9
+ expect(viaStatic).toBeInstanceOf(PermissionValidator)
10
+ expect(viaConstructor).toBeInstanceOf(PermissionValidator)
11
+ })
12
+
13
+ it('should be able to add validation rules', () => {
14
+ const validator = PermissionValidator.create().addRule({
15
+ type: PermissionRuleType.ALL_OF,
16
+ permissions: ['some', 'permissions'],
17
+ errorMessage: 'Tisk tisk',
18
+ })
19
+
20
+ expect(validator).toBeInstanceOf(PermissionValidator)
21
+ expect(validator.rules.length).toBe(1)
22
+ })
23
+
24
+ it('should be able to validate rules', () => {
25
+ const validator = PermissionValidator.create().addRule({
26
+ type: PermissionRuleType.ALL_OF,
27
+ permissions: ['some', 'permissions'],
28
+ errorMessage: 'Tisk tisk',
29
+ })
30
+
31
+ const validResult = validator.validate(['look', 'we', 'have', 'some', 'permissions', 'and', 'some', 'more'])
32
+
33
+ expect(validResult).toBe(true)
34
+ expect(() => validator.validate(['we', 'are', 'not', 'authorized'])).toThrowError('Tisk tisk')
35
+ })
36
+
37
+ it('should be able to safe validate rules, so they do not throw permission errors', () => {
38
+ const validator = PermissionValidator.create().addRule({
39
+ type: PermissionRuleType.ALL_OF,
40
+ permissions: ['some', 'permissions'],
41
+ errorMessage: 'Tisk tisk',
42
+ })
43
+
44
+ const validResult = validator.safeValidate(['look', 'we', 'have', 'some', 'permissions', 'and', 'some', 'more'])
45
+
46
+ expect(validResult).to.deep.equal({
47
+ valid: true,
48
+ errorMessage: '',
49
+ errors: [],
50
+ })
51
+
52
+ expect(() => validator.safeValidate(['we', 'are', 'not', 'authorized'])).not.toThrowError()
53
+ const result = validator.safeValidate(['we', 'are', 'not', 'authorized'])
54
+ expect(result).to.deep.equal({
55
+ valid: false,
56
+ errorMessage: 'Tisk tisk',
57
+ errors: [new PermissionError('Tisk tisk', validator.rules[0], ['we', 'are', 'not', 'authorized'])],
58
+ })
59
+ })
60
+
61
+ it('should support requiring all of a list of permissions', () => {
62
+ const validator = PermissionValidator.create().allOf(['permissions', 'you', 'need'])
63
+
64
+ const validResult = validator.validate(['i', 'have', 'the', 'permissions', 'you', 'need'])
65
+
66
+ expect(validResult).toBe(true)
67
+ })
68
+
69
+ it('should support requiring none of a list of permissions', () => {
70
+ const validator = PermissionValidator.create().noneOf(['bad', 'things'], 'NOPE')
71
+
72
+ const validResult = validator.validate(['i', 'have', 'the', 'permissions', 'you', 'need'])
73
+
74
+ expect(validResult).toBe(true)
75
+
76
+ expect(() => validator.validate(['i', 'have', 'bad', 'stuff'])).toThrowError('NOPE')
77
+ })
78
+
79
+ it('should support requiring some of a list of permissions', () => {
80
+ const validator = PermissionValidator.create().someOf(['some', 'optional', 'permissions'], 'You had one job')
81
+
82
+ const validResult = validator.validate(['i', 'have', 'the', 'permissions', 'you', 'need'])
83
+
84
+ expect(validResult).toBe(true)
85
+
86
+ expect(() => validator.validate(['i', 'have', 'bad', 'stuff'])).toThrowError('You had one job')
87
+ })
88
+
89
+ it('should be able to mix and match permissions', () => {
90
+ const validator = PermissionValidator.create()
91
+ .allOf(['luke', 'leia', 'han', 'chewie'], 'Where are my heroes?')
92
+ .someOf(['obi-wan', 'yoda'], 'Requires 1 master jedi')
93
+ .noneOf(['darth-vader', 'storm-trooper', 'emperor'], 'No sith allowed')
94
+
95
+ const validResult = validator.validate(['leia', 'han', 'yoda', 'chewie', 'c3po', 'r2d2', 'han', 'luke'])
96
+
97
+ expect(validResult).toBe(true)
98
+
99
+ expect(() =>
100
+ validator.validate(['leia', 'han', 'yoda', 'chewie', 'c3po', 'r2d2', 'han', 'luke', 'storm-trooper']),
101
+ ).toThrowError('No sith allowed')
102
+
103
+ expect(() => {
104
+ validator.validate(['leia', 'han', 'yoda', 'chewie', 'c3po', 'r2d2', 'han'])
105
+ }).toThrowError('Where are my heroes?')
106
+
107
+ expect(() => {
108
+ validator.validate(['leia', 'han', 'chewie', 'c3po', 'r2d2', 'han', 'luke'])
109
+ }).toThrowError('Requires 1 master jedi')
110
+ })
111
+
112
+ it('should validate multiple permission validators at once', () => {
113
+ const validator = PermissionValidator.create()
114
+ .allOf(['luke', 'leia', 'han', 'chewie'], 'Where are my heroes?')
115
+ .someOf(['obi-wan', 'yoda'], 'Requires 1 master jedi')
116
+
117
+ const validator2 = PermissionValidator.create().noneOf(
118
+ ['darth-vader', 'storm-trooper', 'emperor'],
119
+ 'No sith allowed',
120
+ )
121
+
122
+ const combinedValidator = PermissionValidator.create().extend(validator, validator2)
123
+
124
+ const validResult = combinedValidator.validate(['leia', 'han', 'yoda', 'chewie', 'c3po', 'r2d2', 'han', 'luke'])
125
+
126
+ expect(validResult).toBe(true)
127
+
128
+ expect(() =>
129
+ combinedValidator.validate([
130
+ 'leia',
131
+ 'han',
132
+ 'yoda',
133
+ 'chewie',
134
+ 'c3po',
135
+ 'r2d2',
136
+ 'han',
137
+ 'luke',
138
+ 'storm-trooper',
139
+ ]),
140
+ ).toThrowError('No sith allowed')
141
+
142
+ expect(() => {
143
+ combinedValidator.validate(['leia', 'han', 'yoda', 'chewie', 'c3po', 'r2d2', 'han'])
144
+ }).toThrowError('Where are my heroes?')
145
+
146
+ expect(() => {
147
+ combinedValidator.validate(['leia', 'han', 'chewie', 'c3po', 'r2d2', 'han', 'luke'])
148
+ }).toThrowError('Requires 1 master jedi')
149
+ })
150
+
151
+ it('Should be able to use validators interchangably with rules', () => {
152
+ const heros = PermissionValidator.create().allOf(['luke', 'leia', 'han', 'chewie'])
153
+
154
+ const jedi = PermissionValidator.create().someOf(['obi-wan', 'yoda'])
155
+
156
+ const sith = PermissionValidator.create().someOf(['darth-vadar', 'emperor', 'darth-maul'])
157
+
158
+ const lightSide = PermissionValidator.create().someOf([heros, jedi], 'The light side must be heroes or jedi')
159
+ const darkSide = PermissionValidator.create().someOf(['palpatine', sith], 'Only dark side allowed')
160
+ const lightOnly = PermissionValidator.create()
161
+ .someOf([lightSide], 'Light side only')
162
+ .noneOf([darkSide], 'No dark side allowed')
163
+
164
+ expect(lightOnly.validate(['luke', 'leia', 'yoda'])).toBe(true)
165
+ expect(() => lightOnly.validate(['luke', 'leia', 'yoda', 'darth-vadar'])).toThrowError('No dark side allowed')
166
+ expect(() => lightOnly.validate(['luke', 'leia', 'yoda', 'darth-vadar', 'darth-maul'])).toThrowError(
167
+ 'No dark side allowed',
168
+ )
169
+
170
+ const dumbCharacters = PermissionValidator.create().someOf(['jar-jar-binx'], 'Oh George, what have you done?')
171
+
172
+ const coolCharacters = PermissionValidator.create()
173
+ .someOf([lightSide, darkSide], 'Must contain Star Wars characters')
174
+ .noneOf([dumbCharacters], 'Oh George, what have you done?')
175
+
176
+ expect(coolCharacters.validate(['luke', 'leia', 'han', 'chewie', 'yoda'])).toBe(true)
177
+ expect(coolCharacters.validate(['luke', 'leia', 'han', 'chewie', 'yoda', 'darth-vadar'])).toBe(true)
178
+ expect(coolCharacters.validate(['darth-vadar', 'darth-maul'])).toBe(true)
179
+ expect(() => coolCharacters.validate(['luke', 'leia', 'han', 'chewie', 'yoda', 'jar-jar-binx'])).toThrowError(
180
+ 'Oh George, what have you done?',
181
+ )
182
+ })
183
+
184
+ it('should not error when passed undefined instead of a permission array to validate', () => {
185
+ const validator = PermissionValidator.create().someOf(['luke', 'leia', 'han', 'chewie'], 'Where are my heroes?')
186
+ expect(() => validator.safeValidate(undefined)).not.toThrowError()
187
+ })
188
+
189
+ it('should support wildcards', () => {
190
+ const validator = PermissionValidator.create().allOf(['*'])
191
+
192
+ expect(validator.safeValidate(['anything']).valid).toBe(true)
193
+ expect(validator.safeValidate(['everything']).valid).toBe(true)
194
+ expect(validator.safeValidate(['nothing']).valid).toBe(true)
195
+ expect(validator.safeValidate(['something']).valid).toBe(true)
196
+
197
+ const validator2 = PermissionValidator.create().allOf(['namespace::resource.action:*'])
198
+
199
+ expect(validator2.safeValidate(['namespace::resource.action:read']).valid).toBe(true)
200
+ expect(validator2.safeValidate(['namespace::resource.action:write']).valid).toBe(true)
201
+ expect(validator2.safeValidate(['namespace::resource.action:delete']).valid).toBe(true)
202
+
203
+ const validator3 = PermissionValidator.create().allOf(['namespace::resource.*:*'])
204
+
205
+ expect(validator3.safeValidate(['namespace::resource.action:read']).valid).toBe(true)
206
+ expect(validator3.safeValidate(['namespace::resource.action1:write']).valid).toBe(true)
207
+ expect(validator3.safeValidate(['namespace::resource.action2:delete']).valid).toBe(true)
208
+ })
209
+ })
@@ -0,0 +1,135 @@
1
+ import { minimatch } from 'minimatch'
2
+
3
+ export enum PermissionRuleType {
4
+ ALL_OF = 'ALL_OF',
5
+ NONE_OF = 'NONE_OF',
6
+ SOME_OF = 'SOME_OF',
7
+ }
8
+
9
+ export type RulePermission = string | PermissionValidator
10
+
11
+ export type PermissionRule = {
12
+ type: PermissionRuleType
13
+ permissions: RulePermission[]
14
+ errorMessage: string
15
+ }
16
+
17
+ export class PermissionError extends Error {
18
+ constructor(message: string, public rule: PermissionRule, public permissions: RulePermission[]) {
19
+ super(message ?? 'Permission Error')
20
+ }
21
+ }
22
+
23
+ export type PermissionValidationResults = {
24
+ valid: boolean
25
+ errorMessage: string
26
+ errors: PermissionError[]
27
+ }
28
+
29
+ export class PermissionValidator {
30
+ readonly rules: PermissionRule[] = []
31
+ static create() {
32
+ return new PermissionValidator()
33
+ }
34
+
35
+ addRule(...rule: PermissionRule[]) {
36
+ this.rules.push(...rule)
37
+ return this
38
+ }
39
+
40
+ extend(...validators: PermissionValidator[]) {
41
+ validators.forEach((validator) => {
42
+ this.rules.push(...validator.rules)
43
+ })
44
+ return this
45
+ }
46
+
47
+ allOf(permissions: RulePermission[], errorMessage?: string) {
48
+ this.addRule({
49
+ type: PermissionRuleType.ALL_OF,
50
+ permissions,
51
+ errorMessage,
52
+ })
53
+ return this
54
+ }
55
+
56
+ noneOf(permissions: RulePermission[], errorMessage?: string) {
57
+ this.addRule({
58
+ type: PermissionRuleType.NONE_OF,
59
+ permissions,
60
+ errorMessage,
61
+ })
62
+ return this
63
+ }
64
+
65
+ someOf(permissions: RulePermission[], errorMessage?: string) {
66
+ this.addRule({
67
+ type: PermissionRuleType.SOME_OF,
68
+ permissions,
69
+ errorMessage,
70
+ })
71
+ return this
72
+ }
73
+
74
+ private validateRule(rule: PermissionRule, permissions: string[]) {
75
+ if (rule.type === PermissionRuleType.ALL_OF) {
76
+ return rule.permissions.every((permission) => this.hasPermission(permission, permissions))
77
+ } else if (rule.type === PermissionRuleType.NONE_OF) {
78
+ return !rule.permissions.some((permission) => this.hasPermission(permission, permissions))
79
+ } else if (rule.type === PermissionRuleType.SOME_OF) {
80
+ return rule.permissions.some((permission) => this.hasPermission(permission, permissions))
81
+ } else {
82
+ throw new Error(`Invalid permission rule type ${rule.type}`)
83
+ }
84
+ }
85
+
86
+ private hasPermission(permission: RulePermission, permissions: string[]) {
87
+ if (typeof permission === 'string') {
88
+ const matches = minimatch.match(permissions, permission)
89
+ return matches.length > 0
90
+ // return permissions.some((p) => minimatch(p, permission))
91
+ } else {
92
+ return permission.safeValidate(permissions).valid
93
+ }
94
+ }
95
+
96
+ validate(permissions: string[] = []) {
97
+ return this.rules.reduce((status, rule) => {
98
+ const valid = this.validateRule(rule, permissions)
99
+
100
+ if (!valid) {
101
+ throw new PermissionError(rule.errorMessage, rule, permissions)
102
+ }
103
+
104
+ return status && valid
105
+ }, true)
106
+ }
107
+
108
+ safeValidate(permissions: string[] = []): PermissionValidationResults {
109
+ const errors = this.rules
110
+ .map((rule) => {
111
+ const valid = this.validateRule(rule, permissions)
112
+
113
+ if (!valid) {
114
+ return new PermissionError(rule.errorMessage, rule, permissions)
115
+ } else {
116
+ return undefined
117
+ }
118
+ })
119
+ .filter((item) => !!item)
120
+
121
+ if (errors.length > 0) {
122
+ return {
123
+ valid: false,
124
+ errorMessage: errors[0].message,
125
+ errors,
126
+ }
127
+ } else {
128
+ return {
129
+ valid: true,
130
+ errorMessage: '',
131
+ errors: [],
132
+ }
133
+ }
134
+ }
135
+ }