@atproto/lex-schema 0.0.1 → 0.0.3

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 (289) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/dist/core/$type.d.ts +6 -3
  3. package/dist/core/$type.d.ts.map +1 -1
  4. package/dist/core/$type.js +1 -0
  5. package/dist/core/$type.js.map +1 -1
  6. package/dist/core/record-key.d.ts +3 -3
  7. package/dist/core/record-key.d.ts.map +1 -1
  8. package/dist/core/record-key.js +12 -6
  9. package/dist/core/record-key.js.map +1 -1
  10. package/dist/core/result.d.ts.map +1 -1
  11. package/dist/core/result.js +6 -0
  12. package/dist/core/result.js.map +1 -1
  13. package/dist/core/string-format.d.ts +30 -27
  14. package/dist/core/string-format.d.ts.map +1 -1
  15. package/dist/core/string-format.js +56 -42
  16. package/dist/core/string-format.js.map +1 -1
  17. package/dist/core/types.d.ts +9 -1
  18. package/dist/core/types.d.ts.map +1 -1
  19. package/dist/core/types.js.map +1 -1
  20. package/dist/external.d.ts +31 -28
  21. package/dist/external.d.ts.map +1 -1
  22. package/dist/external.js +33 -17
  23. package/dist/external.js.map +1 -1
  24. package/dist/schema/_parameters.d.ts +2 -2
  25. package/dist/schema/_parameters.d.ts.map +1 -1
  26. package/dist/schema/array.d.ts +5 -6
  27. package/dist/schema/array.d.ts.map +1 -1
  28. package/dist/schema/array.js +5 -6
  29. package/dist/schema/array.js.map +1 -1
  30. package/dist/schema/blob.d.ts +2 -3
  31. package/dist/schema/blob.d.ts.map +1 -1
  32. package/dist/schema/blob.js +1 -2
  33. package/dist/schema/blob.js.map +1 -1
  34. package/dist/schema/boolean.d.ts +4 -5
  35. package/dist/schema/boolean.d.ts.map +1 -1
  36. package/dist/schema/boolean.js +2 -3
  37. package/dist/schema/boolean.js.map +1 -1
  38. package/dist/schema/bytes.d.ts +3 -4
  39. package/dist/schema/bytes.d.ts.map +1 -1
  40. package/dist/schema/bytes.js +2 -3
  41. package/dist/schema/bytes.js.map +1 -1
  42. package/dist/schema/cid.d.ts +13 -6
  43. package/dist/schema/cid.d.ts.map +1 -1
  44. package/dist/schema/cid.js +2 -4
  45. package/dist/schema/cid.js.map +1 -1
  46. package/dist/schema/custom.d.ts +3 -4
  47. package/dist/schema/custom.d.ts.map +1 -1
  48. package/dist/schema/custom.js +4 -3
  49. package/dist/schema/custom.js.map +1 -1
  50. package/dist/schema/dict.d.ts +3 -3
  51. package/dist/schema/dict.d.ts.map +1 -1
  52. package/dist/schema/dict.js +1 -1
  53. package/dist/schema/dict.js.map +1 -1
  54. package/dist/schema/discriminated-union.d.ts +15 -24
  55. package/dist/schema/discriminated-union.d.ts.map +1 -1
  56. package/dist/schema/discriminated-union.js +40 -64
  57. package/dist/schema/discriminated-union.js.map +1 -1
  58. package/dist/schema/enum.d.ts +8 -4
  59. package/dist/schema/enum.d.ts.map +1 -1
  60. package/dist/schema/enum.js +5 -3
  61. package/dist/schema/enum.js.map +1 -1
  62. package/dist/schema/integer.d.ts +3 -4
  63. package/dist/schema/integer.d.ts.map +1 -1
  64. package/dist/schema/integer.js +3 -4
  65. package/dist/schema/integer.js.map +1 -1
  66. package/dist/schema/intersection.d.ts +22 -14
  67. package/dist/schema/intersection.d.ts.map +1 -1
  68. package/dist/schema/intersection.js +12 -22
  69. package/dist/schema/intersection.js.map +1 -1
  70. package/dist/schema/literal.d.ts +8 -4
  71. package/dist/schema/literal.d.ts.map +1 -1
  72. package/dist/schema/literal.js +5 -3
  73. package/dist/schema/literal.js.map +1 -1
  74. package/dist/schema/never.d.ts +2 -2
  75. package/dist/schema/never.d.ts.map +1 -1
  76. package/dist/schema/never.js +1 -1
  77. package/dist/schema/never.js.map +1 -1
  78. package/dist/schema/null.d.ts +2 -3
  79. package/dist/schema/null.d.ts.map +1 -1
  80. package/dist/schema/null.js +1 -2
  81. package/dist/schema/null.js.map +1 -1
  82. package/dist/schema/nullable.d.ts +7 -0
  83. package/dist/schema/nullable.d.ts.map +1 -0
  84. package/dist/schema/nullable.js +19 -0
  85. package/dist/schema/nullable.js.map +1 -0
  86. package/dist/schema/object.d.ts +10 -44
  87. package/dist/schema/object.d.ts.map +1 -1
  88. package/dist/schema/object.js +13 -56
  89. package/dist/schema/object.js.map +1 -1
  90. package/dist/schema/optional.d.ts +7 -0
  91. package/dist/schema/optional.d.ts.map +1 -0
  92. package/dist/schema/optional.js +25 -0
  93. package/dist/schema/optional.js.map +1 -0
  94. package/dist/schema/params.d.ts +14 -19
  95. package/dist/schema/params.d.ts.map +1 -1
  96. package/dist/schema/params.js +10 -24
  97. package/dist/schema/params.js.map +1 -1
  98. package/dist/schema/payload.d.ts +4 -4
  99. package/dist/schema/payload.d.ts.map +1 -1
  100. package/dist/schema/payload.js.map +1 -1
  101. package/dist/schema/permission-set.d.ts +6 -6
  102. package/dist/schema/permission-set.d.ts.map +1 -1
  103. package/dist/schema/permission-set.js +1 -2
  104. package/dist/schema/permission-set.js.map +1 -1
  105. package/dist/schema/permission.d.ts +0 -1
  106. package/dist/schema/permission.d.ts.map +1 -1
  107. package/dist/schema/permission.js +0 -1
  108. package/dist/schema/permission.js.map +1 -1
  109. package/dist/schema/procedure.d.ts +8 -9
  110. package/dist/schema/procedure.d.ts.map +1 -1
  111. package/dist/schema/procedure.js +0 -1
  112. package/dist/schema/procedure.js.map +1 -1
  113. package/dist/schema/query.d.ts +7 -8
  114. package/dist/schema/query.d.ts.map +1 -1
  115. package/dist/schema/query.js +0 -1
  116. package/dist/schema/query.js.map +1 -1
  117. package/dist/schema/record.d.ts +34 -28
  118. package/dist/schema/record.d.ts.map +1 -1
  119. package/dist/schema/record.js +1 -2
  120. package/dist/schema/record.js.map +1 -1
  121. package/dist/schema/ref.d.ts +2 -3
  122. package/dist/schema/ref.d.ts.map +1 -1
  123. package/dist/schema/ref.js +1 -2
  124. package/dist/schema/ref.js.map +1 -1
  125. package/dist/schema/refine.d.ts +18 -0
  126. package/dist/schema/refine.d.ts.map +1 -0
  127. package/dist/schema/refine.js +33 -0
  128. package/dist/schema/refine.js.map +1 -0
  129. package/dist/schema/regexp.d.ts +7 -0
  130. package/dist/schema/regexp.d.ts.map +1 -0
  131. package/dist/schema/regexp.js +22 -0
  132. package/dist/schema/regexp.js.map +1 -0
  133. package/dist/schema/string.d.ts +4 -8
  134. package/dist/schema/string.d.ts.map +1 -1
  135. package/dist/schema/string.js +6 -3
  136. package/dist/schema/string.js.map +1 -1
  137. package/dist/schema/subscription.d.ts +7 -6
  138. package/dist/schema/subscription.d.ts.map +1 -1
  139. package/dist/schema/subscription.js.map +1 -1
  140. package/dist/schema/token.d.ts +2 -3
  141. package/dist/schema/token.d.ts.map +1 -1
  142. package/dist/schema/token.js +1 -2
  143. package/dist/schema/token.js.map +1 -1
  144. package/dist/schema/typed-object.d.ts +29 -27
  145. package/dist/schema/typed-object.d.ts.map +1 -1
  146. package/dist/schema/typed-object.js +1 -2
  147. package/dist/schema/typed-object.js.map +1 -1
  148. package/dist/schema/typed-ref.d.ts +2 -2
  149. package/dist/schema/typed-ref.d.ts.map +1 -1
  150. package/dist/schema/typed-ref.js +1 -1
  151. package/dist/schema/typed-ref.js.map +1 -1
  152. package/dist/schema/typed-union.d.ts +3 -4
  153. package/dist/schema/typed-union.d.ts.map +1 -1
  154. package/dist/schema/typed-union.js +3 -10
  155. package/dist/schema/typed-union.js.map +1 -1
  156. package/dist/schema/union.d.ts +2 -2
  157. package/dist/schema/union.d.ts.map +1 -1
  158. package/dist/schema/union.js +1 -1
  159. package/dist/schema/union.js.map +1 -1
  160. package/dist/schema/unknown-object.d.ts +2 -3
  161. package/dist/schema/unknown-object.d.ts.map +1 -1
  162. package/dist/schema/unknown-object.js +1 -2
  163. package/dist/schema/unknown-object.js.map +1 -1
  164. package/dist/schema/unknown.d.ts +2 -2
  165. package/dist/schema/unknown.d.ts.map +1 -1
  166. package/dist/schema/unknown.js +1 -1
  167. package/dist/schema/unknown.js.map +1 -1
  168. package/dist/schema.d.ts +4 -0
  169. package/dist/schema.d.ts.map +1 -1
  170. package/dist/schema.js +6 -1
  171. package/dist/schema.js.map +1 -1
  172. package/dist/util/array-agg.d.ts.map +1 -1
  173. package/dist/util/array-agg.js +1 -0
  174. package/dist/util/array-agg.js.map +1 -1
  175. package/dist/util/lazy-property.d.ts +2 -0
  176. package/dist/util/lazy-property.d.ts.map +1 -0
  177. package/dist/util/lazy-property.js +14 -0
  178. package/dist/util/lazy-property.js.map +1 -0
  179. package/dist/validation/schema.d.ts +24 -0
  180. package/dist/validation/schema.d.ts.map +1 -0
  181. package/dist/validation/schema.js +57 -0
  182. package/dist/validation/schema.js.map +1 -0
  183. package/dist/validation/validation-error.d.ts +3 -3
  184. package/dist/validation/validation-error.d.ts.map +1 -1
  185. package/dist/validation/validation-error.js +32 -4
  186. package/dist/validation/validation-error.js.map +1 -1
  187. package/dist/validation/validation-issue.d.ts +32 -24
  188. package/dist/validation/validation-issue.d.ts.map +1 -1
  189. package/dist/validation/validation-issue.js +136 -92
  190. package/dist/validation/validation-issue.js.map +1 -1
  191. package/dist/validation/validator.d.ts +20 -50
  192. package/dist/validation/validator.d.ts.map +1 -1
  193. package/dist/validation/validator.js +40 -134
  194. package/dist/validation/validator.js.map +1 -1
  195. package/dist/validation.d.ts +1 -0
  196. package/dist/validation.d.ts.map +1 -1
  197. package/dist/validation.js +1 -0
  198. package/dist/validation.js.map +1 -1
  199. package/package.json +8 -4
  200. package/src/core/$type.ts +7 -4
  201. package/src/core/record-key.ts +12 -5
  202. package/src/core/result.ts +6 -0
  203. package/src/core/string-format.ts +97 -61
  204. package/src/core/types.ts +12 -6
  205. package/src/external.ts +92 -70
  206. package/src/schema/_parameters.test.ts +416 -0
  207. package/src/schema/array.test.ts +237 -0
  208. package/src/schema/array.ts +17 -11
  209. package/src/schema/blob.test.ts +506 -0
  210. package/src/schema/blob.ts +3 -5
  211. package/src/schema/boolean.test.ts +116 -0
  212. package/src/schema/boolean.ts +5 -7
  213. package/src/schema/bytes.test.ts +226 -0
  214. package/src/schema/bytes.ts +4 -6
  215. package/src/schema/cid.test.ts +155 -0
  216. package/src/schema/cid.ts +14 -8
  217. package/src/schema/custom.test.ts +413 -0
  218. package/src/schema/custom.ts +10 -8
  219. package/src/schema/dict.test.ts +198 -0
  220. package/src/schema/dict.ts +6 -8
  221. package/src/schema/discriminated-union.test.ts +675 -0
  222. package/src/schema/discriminated-union.ts +68 -95
  223. package/src/schema/enum.test.ts +396 -0
  224. package/src/schema/enum.ts +12 -5
  225. package/src/schema/integer.test.ts +312 -0
  226. package/src/schema/integer.ts +5 -7
  227. package/src/schema/intersection.test.ts +32 -0
  228. package/src/schema/intersection.ts +37 -40
  229. package/src/schema/literal.test.ts +531 -0
  230. package/src/schema/literal.ts +12 -5
  231. package/src/schema/never.test.ts +174 -0
  232. package/src/schema/never.ts +3 -10
  233. package/src/schema/null.test.ts +79 -0
  234. package/src/schema/null.ts +3 -5
  235. package/src/schema/nullable.test.ts +480 -0
  236. package/src/schema/nullable.ts +23 -0
  237. package/src/schema/object.test.ts +47 -115
  238. package/src/schema/object.ts +23 -134
  239. package/src/schema/optional.test.ts +485 -0
  240. package/src/schema/optional.ts +31 -0
  241. package/src/schema/params.test.ts +582 -0
  242. package/src/schema/params.ts +37 -55
  243. package/src/schema/payload.test.ts +345 -0
  244. package/src/schema/payload.ts +5 -5
  245. package/src/schema/permission-set.test.ts +679 -0
  246. package/src/schema/permission-set.ts +6 -8
  247. package/src/schema/permission.test.ts +536 -0
  248. package/src/schema/permission.ts +0 -2
  249. package/src/schema/procedure.test.ts +443 -0
  250. package/src/schema/procedure.ts +11 -13
  251. package/src/schema/query.test.ts +408 -0
  252. package/src/schema/query.ts +9 -11
  253. package/src/schema/record.test.ts +694 -0
  254. package/src/schema/record.ts +38 -36
  255. package/src/schema/ref.test.ts +365 -0
  256. package/src/schema/ref.ts +8 -5
  257. package/src/schema/refine.test.ts +578 -0
  258. package/src/schema/refine.ts +85 -0
  259. package/src/schema/regexp.test.ts +580 -0
  260. package/src/schema/regexp.ts +22 -0
  261. package/src/schema/string.test.ts +612 -0
  262. package/src/schema/string.ts +11 -17
  263. package/src/schema/subscription.test.ts +689 -0
  264. package/src/schema/subscription.ts +13 -8
  265. package/src/schema/token.test.ts +428 -0
  266. package/src/schema/token.ts +3 -5
  267. package/src/schema/typed-object.test.ts +612 -0
  268. package/src/schema/typed-object.ts +23 -20
  269. package/src/schema/typed-ref.test.ts +823 -0
  270. package/src/schema/typed-ref.ts +10 -5
  271. package/src/schema/typed-union.test.ts +378 -0
  272. package/src/schema/typed-union.ts +6 -15
  273. package/src/schema/union.test.ts +200 -0
  274. package/src/schema/union.ts +5 -4
  275. package/src/schema/unknown-object.test.ts +592 -0
  276. package/src/schema/unknown-object.ts +3 -5
  277. package/src/schema/unknown.test.ts +312 -0
  278. package/src/schema/unknown.ts +3 -3
  279. package/src/schema.ts +7 -1
  280. package/src/util/array-agg.ts +1 -0
  281. package/src/util/lazy-property.ts +14 -0
  282. package/src/validation/schema.ts +92 -0
  283. package/src/validation/validation-error.ts +60 -9
  284. package/src/validation/validation-issue.ts +141 -144
  285. package/src/validation/validator.ts +67 -206
  286. package/src/validation.ts +1 -0
  287. package/tsconfig.build.json +12 -0
  288. package/tsconfig.json +7 -0
  289. package/tsconfig.tests.json +9 -0
@@ -0,0 +1,536 @@
1
+ import { Permission } from './permission.js'
2
+
3
+ describe('Permission', () => {
4
+ describe('basic construction', () => {
5
+ it('creates a permission with resource and empty options', () => {
6
+ const permission = new Permission('read', {})
7
+ expect(permission.resource).toBe('read')
8
+ expect(permission.options).toEqual({})
9
+ })
10
+
11
+ it('creates a permission with resource and options', () => {
12
+ const options = { limit: 100 }
13
+ const permission = new Permission('read', options)
14
+ expect(permission.resource).toBe('read')
15
+ expect(permission.options).toEqual({ limit: 100 })
16
+ })
17
+
18
+ it('preserves the options object reference', () => {
19
+ const options = { limit: 100 }
20
+ const permission = new Permission('read', options)
21
+ expect(permission.options).toBe(options)
22
+ })
23
+
24
+ it('preserves resource as const literal type', () => {
25
+ const permission = new Permission('read' as const, {})
26
+ expect(permission.resource).toBe('read')
27
+ })
28
+ })
29
+
30
+ describe('resource strings', () => {
31
+ it('handles simple resource names', () => {
32
+ const permission = new Permission('read', {})
33
+ expect(permission.resource).toBe('read')
34
+ })
35
+
36
+ it('handles namespaced resource names', () => {
37
+ const permission = new Permission('com.example.read', {})
38
+ expect(permission.resource).toBe('com.example.read')
39
+ })
40
+
41
+ it('handles resource names with dashes', () => {
42
+ const permission = new Permission('read-posts', {})
43
+ expect(permission.resource).toBe('read-posts')
44
+ })
45
+
46
+ it('handles resource names with underscores', () => {
47
+ const permission = new Permission('read_posts', {})
48
+ expect(permission.resource).toBe('read_posts')
49
+ })
50
+
51
+ it('handles resource names with colons', () => {
52
+ const permission = new Permission('posts:read', {})
53
+ expect(permission.resource).toBe('posts:read')
54
+ })
55
+
56
+ it('handles resource names with slashes', () => {
57
+ const permission = new Permission('posts/read', {})
58
+ expect(permission.resource).toBe('posts/read')
59
+ })
60
+
61
+ it('handles resource names with wildcards', () => {
62
+ const permission = new Permission('posts:*', {})
63
+ expect(permission.resource).toBe('posts:*')
64
+ })
65
+
66
+ it('handles empty resource string', () => {
67
+ const permission = new Permission('', {})
68
+ expect(permission.resource).toBe('')
69
+ })
70
+
71
+ it('handles very long resource strings', () => {
72
+ const longResource = 'com.example.service.'.repeat(50) + 'read'
73
+ const permission = new Permission(longResource, {})
74
+ expect(permission.resource).toBe(longResource)
75
+ })
76
+
77
+ it('handles resource strings with unicode characters', () => {
78
+ const permission = new Permission('リソース', {})
79
+ expect(permission.resource).toBe('リソース')
80
+ })
81
+
82
+ it('handles resource strings with special characters', () => {
83
+ const permission = new Permission('resource@#$%', {})
84
+ expect(permission.resource).toBe('resource@#$%')
85
+ })
86
+ })
87
+
88
+ describe('options with string parameters', () => {
89
+ it('accepts empty options object', () => {
90
+ const permission = new Permission('read', {})
91
+ expect(permission.options).toEqual({})
92
+ })
93
+
94
+ it('accepts options with string value', () => {
95
+ const permission = new Permission('read', { format: 'json' })
96
+ expect(permission.options).toEqual({ format: 'json' })
97
+ })
98
+
99
+ it('accepts options with multiple string values', () => {
100
+ const permission = new Permission('read', {
101
+ format: 'json',
102
+ encoding: 'utf-8',
103
+ })
104
+ expect(permission.options).toEqual({ format: 'json', encoding: 'utf-8' })
105
+ })
106
+
107
+ it('accepts options with empty string value', () => {
108
+ const permission = new Permission('read', { filter: '' })
109
+ expect(permission.options).toEqual({ filter: '' })
110
+ })
111
+
112
+ it('accepts options with very long string values', () => {
113
+ const longString = 'value'.repeat(1000)
114
+ const permission = new Permission('read', { data: longString })
115
+ expect(permission.options.data).toBe(longString)
116
+ })
117
+ })
118
+
119
+ describe('options with integer parameters', () => {
120
+ it('accepts options with positive integer', () => {
121
+ const permission = new Permission('read', { limit: 100 })
122
+ expect(permission.options).toEqual({ limit: 100 })
123
+ })
124
+
125
+ it('accepts options with zero', () => {
126
+ const permission = new Permission('read', { offset: 0 })
127
+ expect(permission.options).toEqual({ offset: 0 })
128
+ })
129
+
130
+ it('accepts options with negative integer', () => {
131
+ const permission = new Permission('read', { delta: -50 })
132
+ expect(permission.options).toEqual({ delta: -50 })
133
+ })
134
+
135
+ it('accepts options with multiple integers', () => {
136
+ const permission = new Permission('read', {
137
+ limit: 100,
138
+ offset: 20,
139
+ maxRetries: 3,
140
+ })
141
+ expect(permission.options).toEqual({
142
+ limit: 100,
143
+ offset: 20,
144
+ maxRetries: 3,
145
+ })
146
+ })
147
+
148
+ it('accepts options with large integers', () => {
149
+ const permission = new Permission('read', { maxSize: 2147483647 })
150
+ expect(permission.options).toEqual({ maxSize: 2147483647 })
151
+ })
152
+ })
153
+
154
+ describe('options with boolean parameters', () => {
155
+ it('accepts options with true boolean', () => {
156
+ const permission = new Permission('read', { includeDeleted: true })
157
+ expect(permission.options).toEqual({ includeDeleted: true })
158
+ })
159
+
160
+ it('accepts options with false boolean', () => {
161
+ const permission = new Permission('read', { includeDeleted: false })
162
+ expect(permission.options).toEqual({ includeDeleted: false })
163
+ })
164
+
165
+ it('accepts options with multiple booleans', () => {
166
+ const permission = new Permission('read', {
167
+ includeDeleted: true,
168
+ includeDrafts: false,
169
+ includeArchived: true,
170
+ })
171
+ expect(permission.options).toEqual({
172
+ includeDeleted: true,
173
+ includeDrafts: false,
174
+ includeArchived: true,
175
+ })
176
+ })
177
+ })
178
+
179
+ describe('options with array parameters', () => {
180
+ it('accepts options with string array', () => {
181
+ const permission = new Permission('read', { fields: ['id', 'name'] })
182
+ expect(permission.options).toEqual({ fields: ['id', 'name'] })
183
+ })
184
+
185
+ it('accepts options with integer array', () => {
186
+ const permission = new Permission('read', { ids: [1, 2, 3] })
187
+ expect(permission.options).toEqual({ ids: [1, 2, 3] })
188
+ })
189
+
190
+ it('accepts options with boolean array', () => {
191
+ const permission = new Permission('read', { flags: [true, false, true] })
192
+ expect(permission.options).toEqual({ flags: [true, false, true] })
193
+ })
194
+
195
+ it('accepts options with empty array', () => {
196
+ const permission = new Permission('read', { fields: [] })
197
+ expect(permission.options).toEqual({ fields: [] })
198
+ })
199
+
200
+ it('preserves array reference in options', () => {
201
+ const fields = ['id', 'name']
202
+ const permission = new Permission('read', { fields })
203
+ expect(permission.options.fields).toBe(fields)
204
+ })
205
+ })
206
+
207
+ describe('options with mixed parameter types', () => {
208
+ it('accepts options with string, integer, and boolean', () => {
209
+ const permission = new Permission('read', {
210
+ format: 'json',
211
+ limit: 100,
212
+ includeDeleted: true,
213
+ })
214
+ expect(permission.options).toEqual({
215
+ format: 'json',
216
+ limit: 100,
217
+ includeDeleted: true,
218
+ })
219
+ })
220
+
221
+ it('accepts options with all parameter types', () => {
222
+ const permission = new Permission('read', {
223
+ format: 'json',
224
+ limit: 100,
225
+ includeDeleted: true,
226
+ fields: ['id', 'name'],
227
+ })
228
+ expect(permission.options).toEqual({
229
+ format: 'json',
230
+ limit: 100,
231
+ includeDeleted: true,
232
+ fields: ['id', 'name'],
233
+ })
234
+ })
235
+
236
+ it('accepts options with many parameters', () => {
237
+ const permission = new Permission('read', {
238
+ param1: 'value1',
239
+ param2: 'value2',
240
+ param3: 'value3',
241
+ param4: 123,
242
+ param5: 456,
243
+ param6: true,
244
+ param7: false,
245
+ param8: ['a', 'b'],
246
+ })
247
+ expect(permission.options).toEqual({
248
+ param1: 'value1',
249
+ param2: 'value2',
250
+ param3: 'value3',
251
+ param4: 123,
252
+ param5: 456,
253
+ param6: true,
254
+ param7: false,
255
+ param8: ['a', 'b'],
256
+ })
257
+ })
258
+ })
259
+
260
+ describe('options with undefined values', () => {
261
+ it('accepts options with undefined values', () => {
262
+ const permission = new Permission('read', {
263
+ optionalParam: undefined,
264
+ })
265
+ expect(permission.options).toEqual({ optionalParam: undefined })
266
+ })
267
+
268
+ it('accepts options with mix of defined and undefined values', () => {
269
+ const permission = new Permission('read', {
270
+ required: 'value',
271
+ optional: undefined,
272
+ })
273
+ expect(permission.options).toEqual({
274
+ required: 'value',
275
+ optional: undefined,
276
+ })
277
+ })
278
+
279
+ it('preserves undefined in options object', () => {
280
+ const options = { param: undefined }
281
+ const permission = new Permission('read', options)
282
+ expect('param' in permission.options).toBe(true)
283
+ expect(permission.options.param).toBeUndefined()
284
+ })
285
+ })
286
+
287
+ describe('multiple permission instances', () => {
288
+ it('creates independent instances', () => {
289
+ const permission1 = new Permission('read', { limit: 100 })
290
+ const permission2 = new Permission('write', { format: 'json' })
291
+
292
+ expect(permission1.resource).toBe('read')
293
+ expect(permission2.resource).toBe('write')
294
+ expect(permission1.options).toEqual({ limit: 100 })
295
+ expect(permission2.options).toEqual({ format: 'json' })
296
+ })
297
+
298
+ it('instances with same values are not equal', () => {
299
+ const permission1 = new Permission('read', { limit: 100 })
300
+ const permission2 = new Permission('read', { limit: 100 })
301
+
302
+ expect(permission1).not.toBe(permission2)
303
+ expect(permission1.resource).toBe(permission2.resource)
304
+ expect(permission1.options).not.toBe(permission2.options)
305
+ expect(permission1.options).toEqual(permission2.options)
306
+ })
307
+
308
+ it('instances sharing options object reference', () => {
309
+ const options = { limit: 100 }
310
+ const permission1 = new Permission('read', options)
311
+ const permission2 = new Permission('write', options)
312
+
313
+ expect(permission1.options).toBe(permission2.options)
314
+ expect(permission1.options).toBe(options)
315
+ expect(permission2.options).toBe(options)
316
+ })
317
+ })
318
+
319
+ describe('common permission patterns', () => {
320
+ it('creates read permission', () => {
321
+ const permission = new Permission('read', {})
322
+ expect(permission.resource).toBe('read')
323
+ })
324
+
325
+ it('creates write permission', () => {
326
+ const permission = new Permission('write', {})
327
+ expect(permission.resource).toBe('write')
328
+ })
329
+
330
+ it('creates delete permission', () => {
331
+ const permission = new Permission('delete', {})
332
+ expect(permission.resource).toBe('delete')
333
+ })
334
+
335
+ it('creates admin permission', () => {
336
+ const permission = new Permission('admin', {})
337
+ expect(permission.resource).toBe('admin')
338
+ })
339
+
340
+ it('creates scoped resource permission', () => {
341
+ const permission = new Permission('posts:read', { limit: 50 })
342
+ expect(permission.resource).toBe('posts:read')
343
+ expect(permission.options).toEqual({ limit: 50 })
344
+ })
345
+
346
+ it('creates namespaced permission', () => {
347
+ const permission = new Permission('com.example.posts.read', {
348
+ includeDeleted: false,
349
+ })
350
+ expect(permission.resource).toBe('com.example.posts.read')
351
+ expect(permission.options).toEqual({ includeDeleted: false })
352
+ })
353
+
354
+ it('creates CRUD permissions', () => {
355
+ const create = new Permission('create', {})
356
+ const read = new Permission('read', {})
357
+ const update = new Permission('update', {})
358
+ const deleteP = new Permission('delete', {})
359
+
360
+ expect(create.resource).toBe('create')
361
+ expect(read.resource).toBe('read')
362
+ expect(update.resource).toBe('update')
363
+ expect(deleteP.resource).toBe('delete')
364
+ })
365
+
366
+ it('creates permission with scope and filters', () => {
367
+ const permission = new Permission('posts:read', {
368
+ scope: 'public',
369
+ limit: 100,
370
+ includeDeleted: false,
371
+ })
372
+ expect(permission.resource).toBe('posts:read')
373
+ expect(permission.options).toEqual({
374
+ scope: 'public',
375
+ limit: 100,
376
+ includeDeleted: false,
377
+ })
378
+ })
379
+ })
380
+
381
+ describe('edge cases', () => {
382
+ it('handles permission with all parameter types in options', () => {
383
+ const permission = new Permission('complex', {
384
+ stringParam: 'value',
385
+ intParam: 42,
386
+ boolParam: true,
387
+ arrayParam: [1, 2, 3],
388
+ undefinedParam: undefined,
389
+ })
390
+
391
+ expect(permission.options.stringParam).toBe('value')
392
+ expect(permission.options.intParam).toBe(42)
393
+ expect(permission.options.boolParam).toBe(true)
394
+ expect(permission.options.arrayParam).toEqual([1, 2, 3])
395
+ expect(permission.options.undefinedParam).toBeUndefined()
396
+ })
397
+
398
+ it('handles resource with whitespace', () => {
399
+ const permission = new Permission('read posts', {})
400
+ expect(permission.resource).toBe('read posts')
401
+ })
402
+
403
+ it('handles resource with leading/trailing whitespace', () => {
404
+ const permission = new Permission(' read ', {})
405
+ expect(permission.resource).toBe(' read ')
406
+ })
407
+
408
+ it('handles options with numeric string keys', () => {
409
+ const permission = new Permission('read', { '123': 'value' })
410
+ expect(permission.options['123']).toBe('value')
411
+ })
412
+
413
+ it('handles options with special character keys', () => {
414
+ const permission = new Permission('read', { 'key-name': 'value' })
415
+ expect(permission.options['key-name']).toBe('value')
416
+ })
417
+ })
418
+
419
+ describe('type safety', () => {
420
+ it('preserves resource type as literal', () => {
421
+ const permission = new Permission('read' as const, {})
422
+ // At compile time, TypeScript should infer the type as 'read'
423
+ expect(permission.resource).toBe('read')
424
+ })
425
+
426
+ it('preserves options type', () => {
427
+ const options = { limit: 100 } as const
428
+ const permission = new Permission('read', options)
429
+ // At compile time, TypeScript should infer the exact type
430
+ expect(permission.options.limit).toBe(100)
431
+ })
432
+
433
+ it('handles generic string resource type', () => {
434
+ const resource: string = 'dynamic'
435
+ const permission = new Permission(resource, {})
436
+ expect(permission.resource).toBe('dynamic')
437
+ })
438
+
439
+ it('handles union resource types', () => {
440
+ type ResourceType = 'read' | 'write' | 'delete'
441
+ const resource: ResourceType = 'read'
442
+ const permission = new Permission(resource, {})
443
+ expect(permission.resource).toBe('read')
444
+ })
445
+ })
446
+
447
+ describe('constructor behavior', () => {
448
+ it('requires both resource and options arguments', () => {
449
+ // TypeScript enforces this at compile time
450
+ const permission = new Permission('read', {})
451
+ expect(permission.resource).toBeDefined()
452
+ expect(permission.options).toBeDefined()
453
+ })
454
+
455
+ it('does not modify input options object', () => {
456
+ const options = { limit: 100 }
457
+ const originalOptions = { ...options }
458
+ new Permission('read', options)
459
+ expect(options).toEqual(originalOptions)
460
+ })
461
+
462
+ it('accepts options as object literal', () => {
463
+ const permission = new Permission('read', { limit: 100 })
464
+ expect(permission.options).toEqual({ limit: 100 })
465
+ })
466
+
467
+ it('accepts options as variable', () => {
468
+ const options = { limit: 100 }
469
+ const permission = new Permission('read', options)
470
+ expect(permission.options).toEqual({ limit: 100 })
471
+ })
472
+
473
+ it('accepts resource as string literal', () => {
474
+ const permission = new Permission('read', {})
475
+ expect(permission.resource).toBe('read')
476
+ })
477
+
478
+ it('accepts resource as variable', () => {
479
+ const resource = 'read'
480
+ const permission = new Permission(resource, {})
481
+ expect(permission.resource).toBe('read')
482
+ })
483
+ })
484
+
485
+ describe('object enumeration', () => {
486
+ it('enumerates all properties', () => {
487
+ const permission = new Permission('read', { limit: 100 })
488
+ const keys = Object.keys(permission)
489
+ expect(keys).toContain('resource')
490
+ expect(keys).toContain('options')
491
+ })
492
+
493
+ it('can be spread into object', () => {
494
+ const permission = new Permission('read', { limit: 100 })
495
+ const spread = { ...permission }
496
+ expect(spread.resource).toBe('read')
497
+ expect(spread.options).toEqual({ limit: 100 })
498
+ })
499
+ })
500
+
501
+ describe('JSON serialization', () => {
502
+ it('can be JSON stringified', () => {
503
+ const permission = new Permission('read', { limit: 100 })
504
+ const json = JSON.stringify(permission)
505
+ const parsed = JSON.parse(json)
506
+ expect(parsed.resource).toBe('read')
507
+ expect(parsed.options).toEqual({ limit: 100 })
508
+ })
509
+
510
+ it('handles complex options in JSON', () => {
511
+ const permission = new Permission('read', {
512
+ fields: ['id', 'name'],
513
+ limit: 100,
514
+ includeDeleted: false,
515
+ })
516
+ const json = JSON.stringify(permission)
517
+ const parsed = JSON.parse(json)
518
+ expect(parsed.options).toEqual({
519
+ fields: ['id', 'name'],
520
+ limit: 100,
521
+ includeDeleted: false,
522
+ })
523
+ })
524
+
525
+ it('preserves undefined in JSON serialization', () => {
526
+ const permission = new Permission('read', {
527
+ defined: 'value',
528
+ undefined: undefined,
529
+ })
530
+ // JSON.stringify removes undefined values by default
531
+ const json = JSON.stringify(permission)
532
+ const parsed = JSON.parse(json)
533
+ expect('undefined' in parsed.options).toBe(false)
534
+ })
535
+ })
536
+ })
@@ -6,8 +6,6 @@ export class Permission<
6
6
  const Resource extends string = any,
7
7
  const Options extends PermissionOptions = any,
8
8
  > {
9
- readonly lexiconType = 'permission' as const
10
-
11
9
  constructor(
12
10
  readonly resource: Resource,
13
11
  readonly options: Options,