@atproto/oauth-scopes 0.5.2 → 0.5.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 (40) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +10 -6
  3. package/jest.config.cjs +0 -14
  4. package/src/atproto-oauth-scope.ts +0 -79
  5. package/src/index.ts +0 -13
  6. package/src/lib/lexicon.ts +0 -21
  7. package/src/lib/mime.test.ts +0 -98
  8. package/src/lib/mime.ts +0 -71
  9. package/src/lib/nsid.ts +0 -5
  10. package/src/lib/parser.ts +0 -176
  11. package/src/lib/resource-permission.ts +0 -10
  12. package/src/lib/syntax-lexicon.ts +0 -55
  13. package/src/lib/syntax-string.test.ts +0 -130
  14. package/src/lib/syntax-string.ts +0 -132
  15. package/src/lib/syntax.test.ts +0 -43
  16. package/src/lib/syntax.ts +0 -54
  17. package/src/lib/util.ts +0 -18
  18. package/src/scope-missing-error.ts +0 -15
  19. package/src/scope-permissions-transition.test.ts +0 -122
  20. package/src/scope-permissions-transition.ts +0 -71
  21. package/src/scope-permissions.test.ts +0 -303
  22. package/src/scope-permissions.ts +0 -91
  23. package/src/scopes/account-permission.test.ts +0 -187
  24. package/src/scopes/account-permission.ts +0 -78
  25. package/src/scopes/blob-permission.test.ts +0 -126
  26. package/src/scopes/blob-permission.ts +0 -105
  27. package/src/scopes/identity-permission.test.ts +0 -80
  28. package/src/scopes/identity-permission.ts +0 -54
  29. package/src/scopes/include-scope.test.ts +0 -637
  30. package/src/scopes/include-scope.ts +0 -208
  31. package/src/scopes/repo-permission.test.ts +0 -267
  32. package/src/scopes/repo-permission.ts +0 -111
  33. package/src/scopes/rpc-permission.test.ts +0 -323
  34. package/src/scopes/rpc-permission.ts +0 -90
  35. package/src/scopes-set.test.ts +0 -47
  36. package/src/scopes-set.ts +0 -134
  37. package/tsconfig.build.json +0 -9
  38. package/tsconfig.build.tsbuildinfo +0 -1
  39. package/tsconfig.json +0 -7
  40. package/tsconfig.tests.json +0 -7
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @atproto/oauth-scopes
2
2
 
3
+ ## 0.5.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [#5099](https://github.com/bluesky-social/atproto/pull/5099) [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Update TypeScript build to rely on references to composite internal projects
8
+
9
+ - [#5099](https://github.com/bluesky-social/atproto/pull/5099) [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Bundle only necessary files in the NPM tarball, including the `CHANGELOG.md` and `README.md` files (if present).
10
+
11
+ - Updated dependencies [[`28a0b58`](https://github.com/bluesky-social/atproto/commit/28a0b588147863eaef948cd2bb8fc0f19d08cda9), [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07), [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07), [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07), [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07)]:
12
+ - @atproto/syntax@0.6.4
13
+ - @atproto/did@0.5.3
14
+
3
15
  ## 0.5.2
4
16
 
5
17
  ### Patch Changes
package/package.json CHANGED
@@ -1,9 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/oauth-scopes",
3
- "version": "0.5.2",
4
- "engines": {
5
- "node": ">=22"
6
- },
3
+ "version": "0.5.3",
7
4
  "license": "MIT",
8
5
  "description": "A library for manipulating and validating ATproto OAuth scopes in TypeScript.",
9
6
  "keywords": [
@@ -18,6 +15,10 @@
18
15
  "url": "https://github.com/bluesky-social/atproto",
19
16
  "directory": "packages/oauth/auth-scopes"
20
17
  },
18
+ "files": [
19
+ "./dist",
20
+ "./CHANGELOG.md"
21
+ ],
21
22
  "type": "module",
22
23
  "exports": {
23
24
  ".": {
@@ -25,9 +26,12 @@
25
26
  "default": "./dist/index.js"
26
27
  }
27
28
  },
29
+ "engines": {
30
+ "node": ">=22"
31
+ },
28
32
  "dependencies": {
29
- "@atproto/syntax": "^0.6.3",
30
- "@atproto/did": "^0.5.2"
33
+ "@atproto/did": "^0.5.3",
34
+ "@atproto/syntax": "^0.6.4"
31
35
  },
32
36
  "devDependencies": {
33
37
  "jest": "^30.0.0"
package/jest.config.cjs DELETED
@@ -1,14 +0,0 @@
1
- /** @type {import('jest').Config} */
2
- module.exports = {
3
- displayName: 'OAuth Scopes',
4
- transform: {
5
- '^.+\\.(t|j)s$': [
6
- '@swc/jest',
7
- { jsc: { transform: {} }, module: { type: 'es6' } },
8
- ],
9
- },
10
- extensionsToTreatAsEsm: ['.ts'],
11
- transformIgnorePatterns: [],
12
- setupFiles: ['<rootDir>/../../../test.setup.ts'],
13
- moduleNameMapper: { '^(\\.\\.?\\/.+)\\.js$': ['$1.ts', '$1.js'] },
14
- }
@@ -1,79 +0,0 @@
1
- import { ScopeStringFor, isScopeStringFor } from './lib/syntax.js'
2
- import { isNonNullable } from './lib/util.js'
3
- import { AccountPermission } from './scopes/account-permission.js'
4
- import { BlobPermission } from './scopes/blob-permission.js'
5
- import { IdentityPermission } from './scopes/identity-permission.js'
6
- import { IncludeScope } from './scopes/include-scope.js'
7
- import { RepoPermission } from './scopes/repo-permission.js'
8
- import { RpcPermission } from './scopes/rpc-permission.js'
9
-
10
- export { type ScopeStringFor, isScopeStringFor }
11
-
12
- export const STATIC_SCOPE_VALUES = Object.freeze([
13
- 'atproto',
14
- 'transition:email',
15
- 'transition:generic',
16
- 'transition:chat.bsky',
17
- ] as const)
18
-
19
- export type StaticScopeValue = (typeof STATIC_SCOPE_VALUES)[number]
20
- export function isStaticScopeValue(value: string): value is StaticScopeValue {
21
- return (STATIC_SCOPE_VALUES as readonly string[]).includes(value)
22
- }
23
-
24
- export type AtprotoOauthScope =
25
- | StaticScopeValue
26
- | ScopeStringFor<'account'>
27
- | ScopeStringFor<'blob'>
28
- | ScopeStringFor<'identity'>
29
- | ScopeStringFor<'include'>
30
- | ScopeStringFor<'repo'>
31
- | ScopeStringFor<'rpc'>
32
-
33
- /**
34
- * @note This function does not only verify the scope string format (with
35
- * {@link isScopeStringFor}), but also checks if the provided parameters are
36
- * valid according to the respective scope syntax definition. This allows
37
- * excluding scopes that cannot be fully interpreted by the current version of
38
- * the code.
39
- */
40
- export function isAtprotoOauthScope(value: string): value is AtprotoOauthScope {
41
- return (
42
- isStaticScopeValue(value) ||
43
- AccountPermission.fromString(value) != null ||
44
- BlobPermission.fromString(value) != null ||
45
- IdentityPermission.fromString(value) != null ||
46
- IncludeScope.fromString(value) != null ||
47
- RepoPermission.fromString(value) != null ||
48
- RpcPermission.fromString(value) != null
49
- )
50
- }
51
-
52
- export function normalizeAtprotoOauthScope(scope: string) {
53
- return scope
54
- .split(' ')
55
- .map(normalizeAtprotoOauthScopeValue)
56
- .filter(isNonNullable)
57
- .sort()
58
- .join(' ')
59
- }
60
-
61
- export function normalizeAtprotoOauthScopeValue(
62
- value: string,
63
- ): AtprotoOauthScope | null {
64
- if (isStaticScopeValue(value)) return value
65
-
66
- for (const Scope of [
67
- AccountPermission,
68
- BlobPermission,
69
- IdentityPermission,
70
- IncludeScope,
71
- RepoPermission,
72
- RpcPermission,
73
- ]) {
74
- const parsed = Scope.fromString(value)
75
- if (parsed) return parsed.toString()
76
- }
77
-
78
- return null
79
- }
package/src/index.ts DELETED
@@ -1,13 +0,0 @@
1
- export * from './atproto-oauth-scope.js'
2
-
3
- export * from './scope-missing-error.js'
4
- export * from './scope-permissions-transition.js'
5
- export * from './scope-permissions.js'
6
- export * from './scopes-set.js'
7
-
8
- export * from './scopes/account-permission.js'
9
- export * from './scopes/blob-permission.js'
10
- export * from './scopes/identity-permission.js'
11
- export * from './scopes/include-scope.js'
12
- export * from './scopes/repo-permission.js'
13
- export * from './scopes/rpc-permission.js'
@@ -1,21 +0,0 @@
1
- import { ParamValue } from './syntax.js'
2
-
3
- // @NOTE Not types from from '@atproto/lex-document' because we want a readonly
4
- // version here to prevent accidental mutation.
5
-
6
- export type LexiconPermission<P extends string = string> = {
7
- readonly type: 'permission'
8
- readonly resource: P
9
- readonly [x: string]: undefined | ParamValue | readonly ParamValue[]
10
- }
11
-
12
- type LangMap = { readonly [Lang in string]?: string }
13
-
14
- export type LexiconPermissionSet = {
15
- readonly type: 'permission-set'
16
- readonly permissions: readonly LexiconPermission<string>[]
17
- readonly title?: string
18
- readonly 'title:lang'?: LangMap
19
- readonly detail?: string
20
- readonly 'detail:lang'?: LangMap
21
- }
@@ -1,98 +0,0 @@
1
- import { isAccept, isMime, matchesAccept, matchesAnyAccept } from './mime.js'
2
-
3
- describe('isAccept', () => {
4
- it('should return true for valid MIME types', () => {
5
- expect(isAccept('image/png')).toBe(true)
6
- expect(isAccept('application/json')).toBe(true)
7
- expect(isAccept('text/html')).toBe(true)
8
- expect(isAccept('image/*')).toBe(true)
9
- expect(isAccept('*/*')).toBe(true)
10
- })
11
-
12
- it('should return false for invalid MIME types', () => {
13
- expect(isAccept('image//png')).toBe(false)
14
- expect(isAccept('/png')).toBe(false)
15
- expect(isAccept('image/')).toBe(false)
16
- expect(isAccept('image/**')).toBe(false)
17
- expect(isAccept('*/png')).toBe(false)
18
- expect(isAccept('*')).toBe(false)
19
- expect(isAccept('image/png/extra')).toBe(false)
20
- })
21
- })
22
-
23
- describe('isMime', () => {
24
- it('should return true for valid MIME types', () => {
25
- expect(isMime('image/png')).toBe(true)
26
- expect(isMime('application/json')).toBe(true)
27
- })
28
-
29
- it('should return false for invalid MIME types', () => {
30
- expect(isMime('image/*')).toBe(false)
31
- expect(isMime('*/*')).toBe(false)
32
- expect(isMime('image/png/extra')).toBe(false)
33
- expect(isMime('*/mime')).toBe(false)
34
- expect(isMime('/png')).toBe(false)
35
- expect(isMime('image/')).toBe(false)
36
- expect(isMime('image')).toBe(false)
37
- expect(isMime('image/ png')).toBe(false)
38
- expect(isMime('image//png')).toBe(false)
39
- })
40
- })
41
-
42
- describe('matchesAccept', () => {
43
- it('should match exact MIME type', () => {
44
- expect(matchesAccept('image/png', 'image/png')).toBe(true)
45
- })
46
-
47
- it('should match wildcard MIME type', () => {
48
- expect(matchesAccept('image/*', 'image/jpeg')).toBe(true)
49
- })
50
-
51
- it('should match subtype wildcard MIME type', () => {
52
- expect(matchesAccept('image/*', 'image/gif')).toBe(true)
53
- })
54
-
55
- it('should not match different MIME type', () => {
56
- expect(matchesAccept('image/png', 'image/jpeg')).toBe(false)
57
- })
58
-
59
- it('should not match different wildcard MIME type', () => {
60
- expect(matchesAccept('image/*', 'text/html')).toBe(false)
61
- })
62
-
63
- it('should match any MIME type with *', () => {
64
- expect(matchesAccept('*/*', 'application/json')).toBe(true)
65
- })
66
-
67
- it('should not match invalid MIME type', () => {
68
- expect(matchesAccept('image/png', '*/mime')).toBe(false)
69
- expect(matchesAccept('image/png', 'image')).toBe(false)
70
- expect(matchesAccept('image/*', 'image//png')).toBe(false)
71
- expect(matchesAccept('image/*', 'image/ png')).toBe(false)
72
- expect(matchesAccept('*/*', 'image/')).toBe(false)
73
- expect(matchesAccept('*/*', '/mime')).toBe(false)
74
- })
75
- })
76
-
77
- describe('matchesAnyAccept', () => {
78
- it('should return true if any accept matches', () => {
79
- const accepts = ['image/png', 'application/json'] as const
80
- expect(matchesAnyAccept(accepts, 'image/png')).toBe(true)
81
- expect(matchesAnyAccept(accepts, 'application/json')).toBe(true)
82
- })
83
-
84
- it('should return false if no accept matches', () => {
85
- const accepts = ['image/png', 'application/json'] as const
86
- expect(matchesAnyAccept(accepts, 'text/html')).toBe(false)
87
- })
88
-
89
- it('should handle empty accepts array', () => {
90
- expect(matchesAnyAccept([], 'image/png')).toBe(false)
91
- })
92
-
93
- it('should handle single accept', () => {
94
- const accepts = ['image/*'] as const
95
- expect(matchesAnyAccept(accepts, 'image/jpeg')).toBe(true)
96
- expect(matchesAnyAccept(accepts, 'text/html')).toBe(false)
97
- })
98
- })
package/src/lib/mime.ts DELETED
@@ -1,71 +0,0 @@
1
- // @TODO Refactor in shared location for use with other @atproto packages
2
-
3
- function isStringSlashString(value: string): value is `${string}/${string}` {
4
- const slashIndex = value.indexOf('/')
5
-
6
- if (slashIndex === -1) return false // Missing slash
7
- if (slashIndex === 0) return false // No leading part before the slash
8
- if (slashIndex === value.length - 1) return false // No trailing part after the slash
9
- if (value.includes('/', slashIndex + 1)) return false // More than one slash
10
- if (value.includes(' ')) return false // Spaces are not allowed
11
-
12
- return true
13
- }
14
-
15
- export type Mime = `${string}/${string}`
16
-
17
- export function isMime(value: string): value is Mime {
18
- return isStringSlashString(value) && !value.includes('*')
19
- }
20
-
21
- export type Accept = '*/*' | `${string}/*` | Mime
22
-
23
- export function isAccept(value: unknown): value is Accept {
24
- if (typeof value !== 'string') return false
25
- if (value === '*/*') return true // Fast path for the most common case
26
- if (!isStringSlashString(value)) return false
27
- return !value.includes('*') || value.endsWith('/*')
28
- }
29
-
30
- /**
31
- * @note "unsafe" in that it does not check if either {@link accept} or
32
- * {@link mime} are actually valid values (and could, therefore, lead to false
33
- * positives if forged values are used).
34
- */
35
- function matchesAcceptUnsafe(accept: Accept, mime: Mime): boolean {
36
- if (accept === '*/*') {
37
- return true
38
- }
39
- if (accept.endsWith('/*')) {
40
- return mime.startsWith(accept.slice(0, -1))
41
- }
42
- return accept === mime
43
- }
44
-
45
- export function matchesAccept(accept: Accept, mime: string): boolean {
46
- return isMime(mime) && matchesAcceptUnsafe(accept, mime)
47
- }
48
-
49
- /**
50
- * @note "unsafe" in that it does not check if either {@link accept} or
51
- * {@link mime} are actually valid values (and could, therefore, lead to false
52
- * positives if forged values are used).
53
- */
54
- function matchesAnyAcceptUnsafe(
55
- acceptable: Iterable<Accept>,
56
- mime: Mime,
57
- ): boolean {
58
- for (const accept of acceptable) {
59
- if (matchesAcceptUnsafe(accept, mime)) {
60
- return true
61
- }
62
- }
63
- return false
64
- }
65
-
66
- export function matchesAnyAccept(
67
- acceptable: Iterable<Accept>,
68
- mime: string,
69
- ): boolean {
70
- return isMime(mime) && matchesAnyAcceptUnsafe(acceptable, mime)
71
- }
package/src/lib/nsid.ts DELETED
@@ -1,5 +0,0 @@
1
- import { isValidNsid } from '@atproto/syntax'
2
-
3
- export type Nsid = `${string}.${string}.${string}`
4
- export const isNsid = (v: unknown): v is Nsid =>
5
- typeof v === 'string' && isValidNsid(v)
package/src/lib/parser.ts DELETED
@@ -1,176 +0,0 @@
1
- import { ScopeStringSyntax } from './syntax-string.js'
2
- import { NeRoArray, ParamValue, ScopeSyntax } from './syntax.js'
3
-
4
- type InferParamPredicate<T extends (value: ParamValue) => boolean> =
5
- T extends ((value: ParamValue) => value is infer U extends ParamValue)
6
- ? U
7
- : ParamValue
8
-
9
- type ParamsSchema = Record<
10
- string,
11
- | {
12
- multiple: false
13
- required: boolean
14
- default?: ParamValue
15
- normalize?: (value: ParamValue) => ParamValue
16
- validate: (value: ParamValue) => boolean
17
- }
18
- | {
19
- multiple: true
20
- required: boolean
21
- default?: NeRoArray<ParamValue>
22
- normalize?: (value: NeRoArray<ParamValue>) => NeRoArray<ParamValue>
23
- validate: (value: ParamValue) => boolean
24
- }
25
- >
26
-
27
- type InferParams<S extends ParamsSchema> = {
28
- [K in keyof S]:
29
- | (S[K]['required'] extends true
30
- ? never
31
- : 'default' extends keyof S[K]
32
- ? S[K]['default']
33
- : undefined)
34
- | (S[K]['multiple'] extends true
35
- ? NeRoArray<InferParamPredicate<S[K]['validate']>>
36
- : InferParamPredicate<S[K]['validate']>)
37
- } & NonNullable<unknown>
38
-
39
- export class Parser<P extends string, S extends ParamsSchema> {
40
- public readonly schemaKeys: ReadonlySet<keyof S & string>
41
-
42
- constructor(
43
- public readonly prefix: P,
44
- public readonly schema: S,
45
- public readonly positionalName?: keyof S & string,
46
- ) {
47
- this.schemaKeys = new Set(Object.keys(schema))
48
- }
49
-
50
- format(values: InferParams<S>) {
51
- const params = new URLSearchParams()
52
- let positional: string | undefined = undefined
53
-
54
- for (const key of this.schemaKeys) {
55
- const value = values[key]
56
- // Ignore undefined values
57
- if (value === undefined) continue
58
-
59
- const schema = this.schema[key]
60
-
61
- // Normalize the value if a normalization function is provided
62
- const normalized = schema.normalize
63
- ? schema.normalize(value as any)
64
- : value
65
-
66
- // Ignore values that are equal to the default value
67
- if (!schema.required) {
68
- if (schema.default === normalized) continue
69
- if (
70
- schema.multiple &&
71
- schema.default &&
72
- arrayParamEquals(schema.default, normalized as NeRoArray<string>)
73
- ) {
74
- continue
75
- }
76
- }
77
-
78
- if (Array.isArray(normalized)) {
79
- if (key === this.positionalName && normalized.length === 1) {
80
- positional = String(normalized[0]!)
81
- } else {
82
- // remove duplicates
83
- const unique = new Set(normalized.map(String))
84
- for (const v of unique) params.append(key, v)
85
- }
86
- } else {
87
- if (key === this.positionalName) {
88
- positional = String(normalized)
89
- } else {
90
- params.set(key, String(normalized))
91
- }
92
- }
93
- }
94
-
95
- return new ScopeStringSyntax(this.prefix, positional, params).toString()
96
- }
97
-
98
- // @NOTE If we needed to ever have more detailed reason as to why parsing
99
- // fails, this function could easily be updated to return a
100
- // ValidationResult<T> type that explains the reason for failure.
101
- parse(syntax: ScopeSyntax<P>) {
102
- // @NOTE no need to check prefix, since the typing (P generic) already
103
- // ensures it matches
104
-
105
- for (const key of syntax.keys()) {
106
- if (!this.schemaKeys.has(key)) return null
107
- }
108
-
109
- const result: Record<
110
- string,
111
- undefined | ParamValue | NeRoArray<ParamValue>
112
- > = Object.create(null)
113
-
114
- for (const key of this.schemaKeys) {
115
- const definition = this.schema[key]
116
-
117
- const param = definition.multiple
118
- ? syntax.getMulti(key)
119
- : syntax.getSingle(key)
120
-
121
- if (param === null) {
122
- return null // Value is not valid
123
- } else if (param !== undefined) {
124
- if (key === this.positionalName && syntax.positional !== undefined) {
125
- // Positional parameter cannot be used with named parameters
126
- return null
127
- }
128
-
129
- if (definition.multiple) {
130
- // Empty array is not valid
131
- if (!(param as ParamValue[]).length) return null
132
- if (!(param as ParamValue[]).every(definition.validate)) {
133
- return null
134
- }
135
- } else {
136
- if (!definition.validate(param as ParamValue)) {
137
- return null
138
- }
139
- }
140
-
141
- result[key] = param as ParamValue | NeRoArray<ParamValue>
142
- } else if (
143
- key === this.positionalName &&
144
- syntax.positional !== undefined
145
- ) {
146
- // No named parameters found, but there is a positional parameter
147
- const { positional } = syntax
148
- if (!definition.validate(positional)) {
149
- return null
150
- }
151
- result[key] = definition.multiple ? [positional] : positional
152
- } else if (definition.required) {
153
- return null
154
- } else {
155
- result[key] = definition.default
156
- }
157
- }
158
-
159
- return result as InferParams<S>
160
- }
161
- }
162
-
163
- /**
164
- * Two param arrays are considered equal if they contain the same values,
165
- * regardless of the order and duplicates.
166
- * @param a - The first array to compare.
167
- * @param b - The second array to compare.
168
- */
169
- function arrayParamEquals(
170
- a: readonly unknown[],
171
- b: readonly unknown[],
172
- ): boolean {
173
- for (const item of a) if (!b.includes(item)) return false
174
- for (const item of b) if (!a.includes(item)) return false
175
- return true
176
- }
@@ -1,10 +0,0 @@
1
- import { ScopeStringFor } from './syntax.js'
2
- import { Matchable } from './util.js'
3
-
4
- /**
5
- * Interface destined to provide consistency across parsed permission scopes for
6
- * resources (blob, repo, etc.).
7
- */
8
- export interface ResourcePermission<R extends string, T> extends Matchable<T> {
9
- toString(): ScopeStringFor<R>
10
- }
@@ -1,55 +0,0 @@
1
- import { LexiconPermission } from './lexicon.js'
2
- import { ScopeSyntax } from './syntax.js'
3
-
4
- const isArray: (value: unknown) => value is readonly unknown[] = Array.isArray
5
-
6
- /**
7
- * Translates a {@link LexiconPermission} into a {@link ScopeSyntax}.
8
- */
9
- export class LexPermissionSyntax<P extends string = string>
10
- implements ScopeSyntax<P>
11
- {
12
- constructor(readonly lexPermission: LexiconPermission<P>) {}
13
-
14
- get prefix() {
15
- return this.lexPermission.resource
16
- }
17
-
18
- get positional() {
19
- return undefined
20
- }
21
-
22
- get(key: string) {
23
- // Ignore reserved keywords
24
- if (key === 'type') return undefined
25
- if (key === 'resource') return undefined
26
-
27
- // Ignore inherited properties (toString(), etc.)
28
- if (!Object.hasOwn(this.lexPermission, key)) return undefined
29
-
30
- return this.lexPermission[key]
31
- }
32
-
33
- *keys() {
34
- for (const key of Object.keys(this.lexPermission)) {
35
- if (this.get(key) !== undefined) yield key
36
- }
37
- }
38
-
39
- getSingle(key: string) {
40
- const value = this.get(key)
41
- if (isArray(value)) return null
42
- return value
43
- }
44
-
45
- getMulti(key: string) {
46
- const value = this.get(key)
47
- if (value === undefined) return undefined
48
- if (!isArray(value)) return null
49
- return value
50
- }
51
-
52
- toJSON() {
53
- return this.lexPermission
54
- }
55
- }