@atproto/oauth-scopes 0.0.2 → 0.1.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 (169) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/atproto-oauth-scope.d.ts +12 -0
  3. package/dist/atproto-oauth-scope.d.ts.map +1 -0
  4. package/dist/atproto-oauth-scope.js +32 -0
  5. package/dist/atproto-oauth-scope.js.map +1 -0
  6. package/dist/index.d.ts +9 -13
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +9 -13
  9. package/dist/index.js.map +1 -1
  10. package/dist/lib/lexicon.d.ts +2 -0
  11. package/dist/lib/lexicon.d.ts.map +1 -0
  12. package/dist/lib/lexicon.js +3 -0
  13. package/dist/lib/lexicon.js.map +1 -0
  14. package/dist/lib/mime.d.ts +1 -1
  15. package/dist/lib/mime.d.ts.map +1 -1
  16. package/dist/lib/mime.js +2 -0
  17. package/dist/lib/mime.js.map +1 -1
  18. package/dist/lib/nsid.d.ts +2 -2
  19. package/dist/lib/nsid.d.ts.map +1 -1
  20. package/dist/lib/nsid.js +4 -6
  21. package/dist/lib/nsid.js.map +1 -1
  22. package/dist/lib/parser.d.ts +29 -0
  23. package/dist/lib/parser.d.ts.map +1 -0
  24. package/dist/lib/parser.js +152 -0
  25. package/dist/lib/parser.js.map +1 -0
  26. package/dist/lib/resource-permission.d.ts +10 -0
  27. package/dist/lib/resource-permission.d.ts.map +1 -0
  28. package/dist/lib/resource-permission.js +3 -0
  29. package/dist/lib/resource-permission.js.map +1 -0
  30. package/dist/lib/syntax-lexicon.d.ts +26 -0
  31. package/dist/lib/syntax-lexicon.d.ts.map +1 -0
  32. package/dist/lib/syntax-lexicon.js +58 -0
  33. package/dist/lib/syntax-lexicon.js.map +1 -0
  34. package/dist/lib/syntax-string.d.ts +16 -0
  35. package/dist/lib/syntax-string.d.ts.map +1 -0
  36. package/dist/lib/syntax-string.js +121 -0
  37. package/dist/lib/syntax-string.js.map +1 -0
  38. package/dist/lib/syntax.d.ts +23 -0
  39. package/dist/lib/syntax.d.ts.map +1 -0
  40. package/dist/lib/syntax.js +22 -0
  41. package/dist/lib/syntax.js.map +1 -0
  42. package/dist/lib/util.d.ts +4 -1
  43. package/dist/lib/util.d.ts.map +1 -1
  44. package/dist/lib/util.js +4 -12
  45. package/dist/lib/util.js.map +1 -1
  46. package/dist/scope-permissions-transition.d.ts +15 -0
  47. package/dist/scope-permissions-transition.d.ts.map +1 -0
  48. package/dist/{permission-set-transition.js → scope-permissions-transition.js} +5 -5
  49. package/dist/scope-permissions-transition.js.map +1 -0
  50. package/dist/scope-permissions.d.ts +22 -0
  51. package/dist/scope-permissions.d.ts.map +1 -0
  52. package/dist/{permission-set.js → scope-permissions.js} +20 -16
  53. package/dist/scope-permissions.js.map +1 -0
  54. package/dist/scopes/account-permission.d.ts +35 -0
  55. package/dist/scopes/account-permission.d.ts.map +1 -0
  56. package/dist/scopes/account-permission.js +71 -0
  57. package/dist/scopes/account-permission.js.map +1 -0
  58. package/dist/scopes/blob-permission.d.ts +27 -0
  59. package/dist/scopes/blob-permission.d.ts.map +1 -0
  60. package/dist/scopes/blob-permission.js +86 -0
  61. package/dist/scopes/blob-permission.js.map +1 -0
  62. package/dist/scopes/identity-permission.d.ts +25 -0
  63. package/dist/scopes/identity-permission.d.ts.map +1 -0
  64. package/dist/scopes/identity-permission.js +53 -0
  65. package/dist/scopes/identity-permission.js.map +1 -0
  66. package/dist/scopes/include-scope.d.ts +54 -0
  67. package/dist/scopes/include-scope.d.ts.map +1 -0
  68. package/dist/scopes/include-scope.js +156 -0
  69. package/dist/scopes/include-scope.js.map +1 -0
  70. package/dist/scopes/repo-permission.d.ts +40 -0
  71. package/dist/scopes/repo-permission.d.ts.map +1 -0
  72. package/dist/scopes/repo-permission.js +101 -0
  73. package/dist/scopes/repo-permission.js.map +1 -0
  74. package/dist/scopes/rpc-permission.d.ts +38 -0
  75. package/dist/scopes/rpc-permission.d.ts.map +1 -0
  76. package/dist/scopes/rpc-permission.js +81 -0
  77. package/dist/scopes/rpc-permission.js.map +1 -0
  78. package/dist/scopes-set.d.ts +12 -1
  79. package/dist/scopes-set.d.ts.map +1 -1
  80. package/dist/scopes-set.js +49 -3
  81. package/dist/scopes-set.js.map +1 -1
  82. package/package.json +7 -3
  83. package/src/atproto-oauth-scope.ts +43 -0
  84. package/src/index.ts +10 -14
  85. package/src/lib/lexicon.ts +1 -0
  86. package/src/lib/mime.ts +2 -1
  87. package/src/lib/nsid.ts +5 -6
  88. package/src/lib/parser.ts +176 -0
  89. package/src/lib/resource-permission.ts +10 -0
  90. package/src/lib/syntax-lexicon.ts +55 -0
  91. package/src/lib/syntax-string.test.ts +130 -0
  92. package/src/lib/syntax-string.ts +132 -0
  93. package/src/lib/syntax.test.ts +43 -0
  94. package/src/lib/syntax.ts +47 -0
  95. package/src/lib/util.ts +7 -12
  96. package/src/{permission-set-transition.test.ts → scope-permissions-transition.test.ts} +33 -20
  97. package/src/{permission-set-transition.ts → scope-permissions-transition.ts} +11 -11
  98. package/src/{permission-set.test.ts → scope-permissions.test.ts} +77 -35
  99. package/src/scope-permissions.ts +91 -0
  100. package/src/{resources/account-scope.test.ts → scopes/account-permission.test.ts} +45 -33
  101. package/src/scopes/account-permission.ts +75 -0
  102. package/src/{resources/blob-scope.test.ts → scopes/blob-permission.test.ts} +31 -23
  103. package/src/scopes/blob-permission.ts +105 -0
  104. package/src/{resources/identity-scope.test.ts → scopes/identity-permission.test.ts} +13 -13
  105. package/src/scopes/identity-permission.ts +54 -0
  106. package/src/scopes/include-scope.test.ts +626 -0
  107. package/src/scopes/include-scope.ts +168 -0
  108. package/src/{resources/repo-scope.test.ts → scopes/repo-permission.test.ts} +77 -65
  109. package/src/scopes/repo-permission.ts +111 -0
  110. package/src/scopes/rpc-permission.test.ts +323 -0
  111. package/src/scopes/rpc-permission.ts +85 -0
  112. package/src/scopes-set.test.ts +5 -5
  113. package/src/scopes-set.ts +79 -5
  114. package/tsconfig.build.tsbuildinfo +1 -1
  115. package/tsconfig.tests.tsbuildinfo +1 -1
  116. package/dist/lib/did.d.ts +0 -3
  117. package/dist/lib/did.d.ts.map +0 -1
  118. package/dist/lib/did.js +0 -6
  119. package/dist/lib/did.js.map +0 -1
  120. package/dist/parser.d.ts +0 -31
  121. package/dist/parser.d.ts.map +0 -1
  122. package/dist/parser.js +0 -118
  123. package/dist/parser.js.map +0 -1
  124. package/dist/permission-set-transition.d.ts +0 -15
  125. package/dist/permission-set-transition.d.ts.map +0 -1
  126. package/dist/permission-set-transition.js.map +0 -1
  127. package/dist/permission-set.d.ts +0 -22
  128. package/dist/permission-set.d.ts.map +0 -1
  129. package/dist/permission-set.js.map +0 -1
  130. package/dist/resources/account-scope.d.ts +0 -35
  131. package/dist/resources/account-scope.d.ts.map +0 -1
  132. package/dist/resources/account-scope.js +0 -60
  133. package/dist/resources/account-scope.js.map +0 -1
  134. package/dist/resources/blob-scope.d.ts +0 -25
  135. package/dist/resources/blob-scope.d.ts.map +0 -1
  136. package/dist/resources/blob-scope.js +0 -74
  137. package/dist/resources/blob-scope.js.map +0 -1
  138. package/dist/resources/identity-scope.d.ts +0 -25
  139. package/dist/resources/identity-scope.d.ts.map +0 -1
  140. package/dist/resources/identity-scope.js +0 -46
  141. package/dist/resources/identity-scope.js.map +0 -1
  142. package/dist/resources/repo-scope.d.ts +0 -37
  143. package/dist/resources/repo-scope.d.ts.map +0 -1
  144. package/dist/resources/repo-scope.js +0 -92
  145. package/dist/resources/repo-scope.js.map +0 -1
  146. package/dist/resources/rpc-scope.d.ts +0 -31
  147. package/dist/resources/rpc-scope.d.ts.map +0 -1
  148. package/dist/resources/rpc-scope.js +0 -74
  149. package/dist/resources/rpc-scope.js.map +0 -1
  150. package/dist/syntax.d.ts +0 -76
  151. package/dist/syntax.d.ts.map +0 -1
  152. package/dist/syntax.js +0 -249
  153. package/dist/syntax.js.map +0 -1
  154. package/dist/utilities.d.ts +0 -17
  155. package/dist/utilities.d.ts.map +0 -1
  156. package/dist/utilities.js +0 -108
  157. package/dist/utilities.js.map +0 -1
  158. package/src/lib/did.ts +0 -3
  159. package/src/parser.ts +0 -150
  160. package/src/permission-set.ts +0 -78
  161. package/src/resources/account-scope.ts +0 -66
  162. package/src/resources/blob-scope.ts +0 -86
  163. package/src/resources/identity-scope.ts +0 -49
  164. package/src/resources/repo-scope.ts +0 -101
  165. package/src/resources/rpc-scope.test.ts +0 -280
  166. package/src/resources/rpc-scope.ts +0 -77
  167. package/src/syntax.test.ts +0 -203
  168. package/src/syntax.ts +0 -325
  169. package/src/utilities.ts +0 -109
@@ -3,7 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ScopesSet = exports.ScopeMissingError = void 0;
4
4
  const scope_missing_error_js_1 = require("./scope-missing-error.js");
5
5
  Object.defineProperty(exports, "ScopeMissingError", { enumerable: true, get: function () { return scope_missing_error_js_1.ScopeMissingError; } });
6
- const utilities_js_1 = require("./utilities.js");
6
+ const account_permission_js_1 = require("./scopes/account-permission.js");
7
+ const blob_permission_js_1 = require("./scopes/blob-permission.js");
8
+ const identity_permission_js_1 = require("./scopes/identity-permission.js");
9
+ const repo_permission_js_1 = require("./scopes/repo-permission.js");
10
+ const rpc_permission_js_1 = require("./scopes/rpc-permission.js");
7
11
  /**
8
12
  * Utility class to manage a set of scopes and check if they match specific
9
13
  * options for a given resource.
@@ -15,14 +19,14 @@ class ScopesSet extends Set {
15
19
  */
16
20
  matches(resource, options) {
17
21
  for (const scope of this) {
18
- if ((0, utilities_js_1.scopeMatches)(scope, resource, options))
22
+ if (permissionScopeMatches(scope, resource, options))
19
23
  return true;
20
24
  }
21
25
  return false;
22
26
  }
23
27
  assert(resource, options) {
24
28
  if (!this.matches(resource, options)) {
25
- const scope = (0, utilities_js_1.scopeNeededFor)(resource, options);
29
+ const scope = scopeNeededFor(resource, options);
26
30
  throw new scope_missing_error_js_1.ScopeMissingError(scope);
27
31
  }
28
32
  }
@@ -52,4 +56,46 @@ class ScopesSet extends Set {
52
56
  }
53
57
  }
54
58
  exports.ScopesSet = ScopesSet;
59
+ function scopeNeededFor(resource, options) {
60
+ switch (resource) {
61
+ case 'account':
62
+ return account_permission_js_1.AccountPermission.scopeNeededFor(options);
63
+ case 'identity':
64
+ return identity_permission_js_1.IdentityPermission.scopeNeededFor(options);
65
+ case 'repo':
66
+ return repo_permission_js_1.RepoPermission.scopeNeededFor(options);
67
+ case 'rpc':
68
+ return rpc_permission_js_1.RpcPermission.scopeNeededFor(options);
69
+ case 'blob':
70
+ return blob_permission_js_1.BlobPermission.scopeNeededFor(options);
71
+ }
72
+ // @ts-expect-error
73
+ throw new TypeError(`Unknown resource: ${resource}`);
74
+ }
75
+ function permissionScopeMatches(scope, resource, options) {
76
+ // @NOTE we might want to cache the parsed scopes though, in practice, a
77
+ // single scope is unlikely to be parsed multiple times during a single
78
+ // request.
79
+ const permission = parsePermissionScope(resource, scope);
80
+ if (!permission)
81
+ return false;
82
+ // @ts-expect-error
83
+ return permission.matches(options);
84
+ }
85
+ function parsePermissionScope(resource, scope) {
86
+ switch (resource) {
87
+ case 'account':
88
+ return account_permission_js_1.AccountPermission.fromString(scope);
89
+ case 'identity':
90
+ return identity_permission_js_1.IdentityPermission.fromString(scope);
91
+ case 'repo':
92
+ return repo_permission_js_1.RepoPermission.fromString(scope);
93
+ case 'rpc':
94
+ return rpc_permission_js_1.RpcPermission.fromString(scope);
95
+ case 'blob':
96
+ return blob_permission_js_1.BlobPermission.fromString(scope);
97
+ default:
98
+ return null;
99
+ }
100
+ }
55
101
  //# sourceMappingURL=scopes-set.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"scopes-set.js","sourceRoot":"","sources":["../src/scopes-set.ts"],"names":[],"mappings":";;;AAAA,qEAA4D;AAOnD,kGAPA,0CAAiB,OAOA;AAN1B,iDAIuB;AAIvB;;;GAGG;AACH,MAAa,SAAU,SAAQ,GAAW;IACxC;;;OAGG;IACI,OAAO,CACZ,QAAW,EACX,OAA0C;QAE1C,KAAK,MAAM,KAAK,IAAI,IAAI,EAAE,CAAC;YACzB,IAAI,IAAA,2BAAY,EAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC;gBAAE,OAAO,IAAI,CAAA;QACzD,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IAEM,MAAM,CACX,QAAW,EACX,OAA0C;QAE1C,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC;YACrC,MAAM,KAAK,GAAG,IAAA,6BAAc,EAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;YAC/C,MAAM,IAAI,0CAAiB,CAAC,KAAK,CAAC,CAAA;QACpC,CAAC;IACH,CAAC;IAEM,IAAI,CAAC,EAA8B;QACxC,KAAK,MAAM,KAAK,IAAI,IAAI;YAAE,IAAI,EAAE,CAAC,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAA;QACpD,OAAO,KAAK,CAAA;IACd,CAAC;IAEM,KAAK,CAAC,EAA8B;QACzC,KAAK,MAAM,KAAK,IAAI,IAAI;YAAE,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAA;QACtD,OAAO,IAAI,CAAA;IACb,CAAC;IAEM,CAAC,MAAM,CAAC,EAA8B;QAC3C,KAAK,MAAM,KAAK,IAAI,IAAI;YAAE,IAAI,EAAE,CAAC,KAAK,CAAC;gBAAE,MAAM,KAAK,CAAA;IACtD,CAAC;IAEM,CAAC,GAAG,CAAI,EAAwB;QACrC,KAAK,MAAM,KAAK,IAAI,IAAI;YAAE,MAAM,EAAE,CAAC,KAAK,CAAC,CAAA;IAC3C,CAAC;IAED,MAAM,CAAC,UAAU,CAAC,MAAe;QAC/B,OAAO,IAAI,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAA;IAC1C,CAAC;CACF;AA9CD,8BA8CC"}
1
+ {"version":3,"file":"scopes-set.js","sourceRoot":"","sources":["../src/scopes-set.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/oauth-scopes",
3
- "version": "0.0.2",
3
+ "version": "0.1.0",
4
4
  "license": "MIT",
5
5
  "description": "A library for manipulating and validating ATproto OAuth scopes in TypeScript.",
6
6
  "keywords": [
@@ -24,13 +24,17 @@
24
24
  "default": "./dist/index.js"
25
25
  }
26
26
  },
27
- "dependencies": {},
27
+ "dependencies": {
28
+ "@atproto/did": "^0.2.0",
29
+ "@atproto/lexicon": "^0.5.0",
30
+ "@atproto/syntax": "^0.4.1"
31
+ },
28
32
  "devDependencies": {
29
33
  "jest": "^28.1.2",
30
34
  "typescript": "^5.6.3"
31
35
  },
32
36
  "scripts": {
33
- "build": "tsc --build tsconfig.json",
37
+ "build": "tsc --build tsconfig.build.json",
34
38
  "test": "jest"
35
39
  }
36
40
  }
@@ -0,0 +1,43 @@
1
+ import { ScopeStringFor, isScopeStringFor } from './lib/syntax.js'
2
+ import { AccountPermission } from './scopes/account-permission.js'
3
+ import { BlobPermission } from './scopes/blob-permission.js'
4
+ import { IdentityPermission } from './scopes/identity-permission.js'
5
+ import { IncludeScope } from './scopes/include-scope.js'
6
+ import { RepoPermission } from './scopes/repo-permission.js'
7
+ import { RpcPermission } from './scopes/rpc-permission.js'
8
+
9
+ export { type ScopeStringFor, isScopeStringFor }
10
+
11
+ export type AtprotoOauthScope =
12
+ | 'atproto'
13
+ | 'transition:email'
14
+ | 'transition:generic'
15
+ | 'transition:chat.bsky'
16
+ | ScopeStringFor<'account'>
17
+ | ScopeStringFor<'blob'>
18
+ | ScopeStringFor<'identity'>
19
+ | ScopeStringFor<'include'>
20
+ | ScopeStringFor<'repo'>
21
+ | ScopeStringFor<'rpc'>
22
+
23
+ /**
24
+ * @note This function does not only verify the scope string format (with
25
+ * {@link isScopeStringFor}), but also checks if the provided parameters are
26
+ * valid according to the respective scope syntax definition. This allows
27
+ * excluding scopes that cannot be fully interpreted by the current version of
28
+ * the code.
29
+ */
30
+ export function isAtprotoOauthScope(value: string): value is AtprotoOauthScope {
31
+ return (
32
+ value === 'atproto' ||
33
+ value === 'transition:email' ||
34
+ value === 'transition:generic' ||
35
+ value === 'transition:chat.bsky' ||
36
+ AccountPermission.fromString(value) != null ||
37
+ BlobPermission.fromString(value) != null ||
38
+ IdentityPermission.fromString(value) != null ||
39
+ IncludeScope.fromString(value) != null ||
40
+ RepoPermission.fromString(value) != null ||
41
+ RpcPermission.fromString(value) != null
42
+ )
43
+ }
package/src/index.ts CHANGED
@@ -1,17 +1,13 @@
1
+ export * from './atproto-oauth-scope.js'
2
+
1
3
  export * from './scope-missing-error.js'
2
- export * from './permission-set.js'
3
- export * from './permission-set-transition.js'
4
+ export * from './scope-permissions-transition.js'
5
+ export * from './scope-permissions.js'
4
6
  export * from './scopes-set.js'
5
- export * from './parser.js'
6
- export * from './syntax.js'
7
- export * from './utilities.js'
8
-
9
- export * from './resources/account-scope.js'
10
- export * from './resources/blob-scope.js'
11
- export * from './resources/identity-scope.js'
12
- export * from './resources/repo-scope.js'
13
- export * from './resources/rpc-scope.js'
14
7
 
15
- export * from './lib/mime.js'
16
- export * from './lib/did.js'
17
- export * from './lib/nsid.js'
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'
@@ -0,0 +1 @@
1
+ export type { LexPermission, LexPermissionSet } from '@atproto/lexicon'
package/src/lib/mime.ts CHANGED
@@ -20,7 +20,8 @@ export function isMime(value: string): value is Mime {
20
20
 
21
21
  export type Accept = '*/*' | `${string}/*` | Mime
22
22
 
23
- export function isAccept(value: string): value is Accept {
23
+ export function isAccept(value: unknown): value is Accept {
24
+ if (typeof value !== 'string') return false
24
25
  if (value === '*/*') return true // Fast path for the most common case
25
26
  if (!isStringSlashString(value)) return false
26
27
  return !value.includes('*') || value.endsWith('/*')
package/src/lib/nsid.ts CHANGED
@@ -1,6 +1,5 @@
1
- export type NSID = `${string}.${string}`
2
- export const isNSID = (value: string): value is NSID =>
3
- value.includes('.') &&
4
- !value.includes(' ') &&
5
- !value.startsWith('.') &&
6
- !value.endsWith('.')
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)
@@ -0,0 +1,176 @@
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
+ }
@@ -0,0 +1,10 @@
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
+ }
@@ -0,0 +1,55 @@
1
+ import { LexPermission } from './lexicon.js'
2
+ import { ScopeSyntax } from './syntax.js'
3
+
4
+ /**
5
+ * Translates a {@link LexPermission} into a {@link ScopeSyntax}.
6
+ */
7
+ export class LexPermissionSyntax<P extends string = string>
8
+ implements ScopeSyntax<P>
9
+ {
10
+ constructor(
11
+ readonly lexPermission: Readonly<LexPermission & { resource: P }>,
12
+ ) {}
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 (Array.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 (!Array.isArray(value)) return null
49
+ return value
50
+ }
51
+
52
+ toJSON() {
53
+ return this.lexPermission
54
+ }
55
+ }
@@ -0,0 +1,130 @@
1
+ import { ScopeStringSyntax } from './syntax-string.js'
2
+ import { ScopeStringFor } from './syntax.js'
3
+
4
+ describe('ScopeStringSyntax', () => {
5
+ for (const { scope, content } of [
6
+ {
7
+ scope: 'my-res',
8
+ content: { prefix: 'my-res' },
9
+ },
10
+ {
11
+ scope: 'my-res:my-pos',
12
+ content: { prefix: 'my-res', positional: 'my-pos' },
13
+ },
14
+ {
15
+ scope: 'my-res:',
16
+ content: { prefix: 'my-res', positional: '' },
17
+ },
18
+ {
19
+ scope: 'my-res:foo?x=value&y=value-y',
20
+ content: {
21
+ prefix: 'my-res',
22
+ positional: 'foo',
23
+ params: { x: ['value'], y: ['value-y'] },
24
+ },
25
+ },
26
+ {
27
+ scope: 'my-res?x=value&y=value-y',
28
+ content: { prefix: 'my-res', params: { x: ['value'], y: ['value-y'] } },
29
+ },
30
+ {
31
+ scope: 'my-res?x=foo&x=bar&x=baz',
32
+ content: { prefix: 'my-res', params: { x: ['foo', 'bar', 'baz'] } },
33
+ },
34
+ {
35
+ scope: 'rpc:foo.bar?aud=did:foo:bar?lxm=bar.baz',
36
+ content: {
37
+ prefix: 'rpc',
38
+ positional: 'foo.bar',
39
+ params: { aud: ['did:foo:bar?lxm=bar.baz'] },
40
+ },
41
+ },
42
+ ] satisfies Array<{
43
+ scope: ScopeStringFor<'my-res' | 'rpc'>
44
+ content: {
45
+ prefix: string
46
+ positional?: string
47
+ params?: Record<string, string[]>
48
+ }
49
+ }>) {
50
+ const syntax = ScopeStringSyntax.fromString<'my-res' | 'rpc'>(scope)
51
+
52
+ describe(scope, () => {
53
+ it('should match the expected syntax', () => {
54
+ expect(syntax).toMatchObject({
55
+ prefix: content.prefix,
56
+ positional: content.positional,
57
+ })
58
+ })
59
+
60
+ it(`should match ${scope} prefix`, () => {
61
+ expect(syntax.prefix).toBe(content.prefix)
62
+ })
63
+
64
+ it(`should return positional parameter`, () => {
65
+ expect(syntax.positional).toBe(content.positional)
66
+ })
67
+
68
+ it(`should return undefined for nonexistent single-value param`, () => {
69
+ expect(syntax.getSingle('nonexistent')).toBeUndefined()
70
+ })
71
+
72
+ it(`should return undefined for nonexistent multi-value param`, () => {
73
+ expect(syntax.getMulti('nonexistent')).toBeUndefined()
74
+ })
75
+
76
+ const { params } = content
77
+ if (params) {
78
+ it(`only contain allowed parameters`, () => {
79
+ const allowedParams = Object.keys(params) as [string, ...string[]]
80
+ expect(
81
+ Array.from(syntax.keys()).every((key) =>
82
+ allowedParams.includes(key),
83
+ ),
84
+ ).toBe(true)
85
+ })
86
+
87
+ for (const [key, values] of Object.entries(params)) {
88
+ it(`should get an array when reading "${key}"`, () => {
89
+ expect(syntax.getMulti(key)).toEqual(values)
90
+ })
91
+
92
+ if (values.length === 1) {
93
+ it(`should allow retrieving single-value params`, () => {
94
+ expect(syntax.getSingle(key)).toEqual(values[0])
95
+ })
96
+ } else {
97
+ it(`should return null for multi-value params`, () => {
98
+ expect(syntax.getSingle(key)).toBeNull()
99
+ })
100
+ }
101
+ }
102
+ }
103
+ })
104
+ }
105
+
106
+ describe('invalid positional parameters', () => {
107
+ it('should return null for positional parameters used together with named parameters', () => {
108
+ const syntax = ScopeStringSyntax.fromString('my-res:pos?x=value')
109
+ expect(syntax.getSingle('x')).toBe('value')
110
+ expect(syntax.getMulti('x')).toEqual(['value'])
111
+ })
112
+ })
113
+
114
+ describe('url encoding', () => {
115
+ it('should handle URL encoding in positional parameters', () => {
116
+ const syntax = ScopeStringSyntax.fromString('my-res:my%20pos')
117
+ expect(syntax.positional).toBe('my pos')
118
+ })
119
+
120
+ it('should handle URL encoding in named parameters', () => {
121
+ const syntax = ScopeStringSyntax.fromString('my-res?x=my%20value')
122
+ expect(syntax.getSingle('x')).toBe('my value')
123
+ })
124
+
125
+ it(`should allow colon (:) in positional parameters`, () => {
126
+ const syntax = ScopeStringSyntax.fromString('my-res:my:pos')
127
+ expect(syntax.positional).toBe('my:pos')
128
+ })
129
+ })
130
+ })