@opendatalabs/vana-sdk 4.4.0 → 4.5.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 (106) hide show
  1. package/README.md +176 -0
  2. package/dist/connect/account-origin.cjs +72 -0
  3. package/dist/connect/account-origin.cjs.map +1 -0
  4. package/dist/connect/account-origin.d.ts +92 -0
  5. package/dist/connect/account-origin.js +47 -0
  6. package/dist/connect/account-origin.js.map +1 -0
  7. package/dist/connect/account-origin.test.d.ts +1 -0
  8. package/dist/connect/chrome.cjs +284 -0
  9. package/dist/connect/chrome.cjs.map +1 -0
  10. package/dist/connect/chrome.d.ts +42 -0
  11. package/dist/connect/chrome.js +256 -0
  12. package/dist/connect/chrome.js.map +1 -0
  13. package/dist/connect/http-transports.cjs +100 -0
  14. package/dist/connect/http-transports.cjs.map +1 -0
  15. package/dist/connect/http-transports.d.ts +77 -0
  16. package/dist/connect/http-transports.js +75 -0
  17. package/dist/connect/http-transports.js.map +1 -0
  18. package/dist/connect/http-transports.test.d.ts +1 -0
  19. package/dist/connect/launcher.cjs +232 -0
  20. package/dist/connect/launcher.cjs.map +1 -0
  21. package/dist/connect/launcher.d.ts +129 -0
  22. package/dist/connect/launcher.js +220 -0
  23. package/dist/connect/launcher.js.map +1 -0
  24. package/dist/connect/launcher.test.d.ts +1 -0
  25. package/dist/connect/protocol.cjs +259 -0
  26. package/dist/connect/protocol.cjs.map +1 -0
  27. package/dist/connect/protocol.d.ts +331 -0
  28. package/dist/connect/protocol.js +215 -0
  29. package/dist/connect/protocol.js.map +1 -0
  30. package/dist/connect/protocol.test.d.ts +1 -0
  31. package/dist/connect.cjs +55 -0
  32. package/dist/connect.cjs.map +1 -0
  33. package/dist/connect.d.ts +35 -0
  34. package/dist/connect.js +34 -0
  35. package/dist/connect.js.map +1 -0
  36. package/dist/direct/connect-flow-sheet.test.d.ts +1 -0
  37. package/dist/direct/connect-flow.cjs +211 -1
  38. package/dist/direct/connect-flow.cjs.map +1 -1
  39. package/dist/direct/connect-flow.d.ts +75 -0
  40. package/dist/direct/connect-flow.js +213 -1
  41. package/dist/direct/connect-flow.js.map +1 -1
  42. package/dist/direct/connect-handler.cjs +493 -0
  43. package/dist/direct/connect-handler.cjs.map +1 -0
  44. package/dist/direct/connect-handler.d.ts +246 -0
  45. package/dist/direct/connect-handler.js +476 -0
  46. package/dist/direct/connect-handler.js.map +1 -0
  47. package/dist/direct/connect-handler.test.d.ts +1 -0
  48. package/dist/direct/controller.cjs +86 -17
  49. package/dist/direct/controller.cjs.map +1 -1
  50. package/dist/direct/controller.d.ts +101 -6
  51. package/dist/direct/controller.js +86 -16
  52. package/dist/direct/controller.js.map +1 -1
  53. package/dist/direct/endpoints.cjs +5 -2
  54. package/dist/direct/endpoints.cjs.map +1 -1
  55. package/dist/direct/endpoints.js +5 -2
  56. package/dist/direct/endpoints.js.map +1 -1
  57. package/dist/direct/request-authorization.cjs +65 -0
  58. package/dist/direct/request-authorization.cjs.map +1 -0
  59. package/dist/direct/request-authorization.d.ts +65 -0
  60. package/dist/direct/request-authorization.js +39 -0
  61. package/dist/direct/request-authorization.js.map +1 -0
  62. package/dist/direct/request-authorization.test.d.ts +1 -0
  63. package/dist/direct/types.cjs.map +1 -1
  64. package/dist/direct/types.d.ts +6 -0
  65. package/dist/direct/types.js.map +1 -1
  66. package/dist/direct/use-direct-vana-connect.cjs +18 -1
  67. package/dist/direct/use-direct-vana-connect.cjs.map +1 -1
  68. package/dist/direct/use-direct-vana-connect.js +18 -1
  69. package/dist/direct/use-direct-vana-connect.js.map +1 -1
  70. package/dist/direct/use-vana-connect.cjs +84 -0
  71. package/dist/direct/use-vana-connect.cjs.map +1 -0
  72. package/dist/direct/use-vana-connect.d.ts +59 -0
  73. package/dist/direct/use-vana-connect.js +64 -0
  74. package/dist/direct/use-vana-connect.js.map +1 -0
  75. package/dist/direct/use-vana-connect.test.d.ts +1 -0
  76. package/dist/index.browser.d.ts +1 -0
  77. package/dist/index.browser.js +104 -0
  78. package/dist/index.browser.js.map +4 -4
  79. package/dist/index.node.cjs +112 -0
  80. package/dist/index.node.cjs.map +3 -3
  81. package/dist/index.node.d.ts +1 -0
  82. package/dist/index.node.js +104 -0
  83. package/dist/index.node.js.map +4 -4
  84. package/dist/protocol/fixtures/pdpp-scope-keys.json +133 -0
  85. package/dist/protocol/pdpp-scopes.cjs +134 -0
  86. package/dist/protocol/pdpp-scopes.cjs.map +1 -0
  87. package/dist/protocol/pdpp-scopes.d.ts +123 -0
  88. package/dist/protocol/pdpp-scopes.js +103 -0
  89. package/dist/protocol/pdpp-scopes.js.map +1 -0
  90. package/dist/protocol/pdpp-scopes.test.d.ts +1 -0
  91. package/dist/protocol/scopes.cjs +17 -0
  92. package/dist/protocol/scopes.cjs.map +1 -1
  93. package/dist/protocol/scopes.d.ts +1 -0
  94. package/dist/protocol/scopes.js +18 -0
  95. package/dist/protocol/scopes.js.map +1 -1
  96. package/dist/react.cjs +10 -2
  97. package/dist/react.cjs.map +1 -1
  98. package/dist/react.d.ts +4 -1
  99. package/dist/react.js +11 -1
  100. package/dist/react.js.map +1 -1
  101. package/dist/server.cjs +61 -2
  102. package/dist/server.cjs.map +1 -1
  103. package/dist/server.d.ts +16 -4
  104. package/dist/server.js +67 -3
  105. package/dist/server.js.map +1 -1
  106. package/package.json +11 -1
@@ -0,0 +1,133 @@
1
+ {
2
+ "description": "Conformance vectors for the PDPP scope key: pdppScopeKey(source_id, stream) = pdpp.<source>.<stream>. source_id must match ^https://registry\\.pdpp\\.dev/sources/([a-z0-9][a-z0-9_]*)$ and stream ^[a-z0-9][a-z0-9_]*$, matched against the whole string (no trailing newline, no trimming). Owned by vana-sdk src/protocol/pdpp-scopes.ts; copy this file verbatim to test another implementation.",
3
+ "version": 1,
4
+ "keys": [
5
+ {
6
+ "source_id": "https://registry.pdpp.dev/sources/chatgpt",
7
+ "stream": "conversations",
8
+ "key": "pdpp.chatgpt.conversations"
9
+ },
10
+ {
11
+ "source_id": "https://registry.pdpp.dev/sources/chatgpt",
12
+ "stream": "messages",
13
+ "key": "pdpp.chatgpt.messages"
14
+ },
15
+ {
16
+ "source_id": "https://registry.pdpp.dev/sources/claude",
17
+ "stream": "project_documents",
18
+ "key": "pdpp.claude.project_documents"
19
+ },
20
+ {
21
+ "source_id": "https://registry.pdpp.dev/sources/icloud_notes",
22
+ "stream": "notes",
23
+ "key": "pdpp.icloud_notes.notes"
24
+ },
25
+ {
26
+ "source_id": "https://registry.pdpp.dev/sources/oura",
27
+ "stream": "sleep",
28
+ "key": "pdpp.oura.sleep"
29
+ },
30
+ {
31
+ "source_id": "https://registry.pdpp.dev/sources/x2",
32
+ "stream": "v1_items",
33
+ "key": "pdpp.x2.v1_items"
34
+ },
35
+ {
36
+ "source_id": "https://registry.pdpp.dev/sources/0",
37
+ "stream": "9",
38
+ "key": "pdpp.0.9"
39
+ }
40
+ ],
41
+ "rejectedSourceIds": [
42
+ { "source_id": "", "why": "empty" },
43
+ { "source_id": "chatgpt", "why": "bare source key, not a registry URI" },
44
+ {
45
+ "source_id": "http://registry.pdpp.dev/sources/chatgpt",
46
+ "why": "not https"
47
+ },
48
+ {
49
+ "source_id": "https://registry.pdpp.dev/connectors/chatgpt",
50
+ "why": "connector URI, not a source URI"
51
+ },
52
+ {
53
+ "source_id": "https://REGISTRY.pdpp.dev/sources/chatgpt",
54
+ "why": "host is matched exactly"
55
+ },
56
+ {
57
+ "source_id": "https://registry.pdpp.dev.example/sources/chatgpt",
58
+ "why": "other host"
59
+ },
60
+ {
61
+ "source_id": "https://registry.pdpp.dev/sources/",
62
+ "why": "empty source segment"
63
+ },
64
+ {
65
+ "source_id": "https://registry.pdpp.dev/sources/ChatGPT",
66
+ "why": "uppercase"
67
+ },
68
+ {
69
+ "source_id": "https://registry.pdpp.dev/sources/spotify-browser",
70
+ "why": "hyphen (not slugified)"
71
+ },
72
+ {
73
+ "source_id": "https://registry.pdpp.dev/sources/chat.gpt",
74
+ "why": "dot would split the scope key"
75
+ },
76
+ {
77
+ "source_id": "https://registry.pdpp.dev/sources/_chatgpt",
78
+ "why": "leading underscore fails ScopeSchema"
79
+ },
80
+ {
81
+ "source_id": "https://registry.pdpp.dev/sources/chatgpt/",
82
+ "why": "trailing slash"
83
+ },
84
+ {
85
+ "source_id": "https://registry.pdpp.dev/sources/chatgpt/v2",
86
+ "why": "extra path segment"
87
+ },
88
+ {
89
+ "source_id": "https://registry.pdpp.dev/sources/chatgpt?v=1",
90
+ "why": "query"
91
+ },
92
+ {
93
+ "source_id": "https://registry.pdpp.dev/sources/chatgpt#x",
94
+ "why": "fragment"
95
+ },
96
+ {
97
+ "source_id": " https://registry.pdpp.dev/sources/chatgpt",
98
+ "why": "leading space (no trimming)"
99
+ },
100
+ {
101
+ "source_id": "https://registry.pdpp.dev/sources/chatgpt\n",
102
+ "why": "trailing newline ($ must not match before it)"
103
+ }
104
+ ],
105
+ "rejectedStreams": [
106
+ { "stream": "", "why": "empty" },
107
+ { "stream": "Conversations", "why": "uppercase" },
108
+ { "stream": "savedTracks", "why": "camelCase" },
109
+ { "stream": "conversation-items", "why": "hyphen (not slugified)" },
110
+ { "stream": "conversations.shared", "why": "dot would add a segment" },
111
+ { "stream": "*", "why": "wildcard is not a stream" },
112
+ { "stream": "_private", "why": "leading underscore fails ScopeSchema" },
113
+ { "stream": "write:messages", "why": "operation prefix" },
114
+ { "stream": "messages ", "why": "trailing space (no trimming)" },
115
+ { "stream": "messages\n", "why": "trailing newline" }
116
+ ],
117
+ "notPdppKeys": [
118
+ "chatgpt.conversations",
119
+ "pdpp.chatgpt",
120
+ "pdpp.chatgpt.*",
121
+ "pdpp.*",
122
+ "write:pdpp.chatgpt.conversations",
123
+ "PDPP.chatgpt.conversations",
124
+ "pdpp.ChatGPT.conversations",
125
+ "pdpp.chatgpt.Conversations",
126
+ "pdpp.chatgpt.conversations.shared",
127
+ "pdpp.spotify-browser.top_tracks",
128
+ "pdpp._chatgpt.conversations",
129
+ "pdpp..conversations",
130
+ "pdpp.chatgpt.conversations\n",
131
+ ""
132
+ ]
133
+ }
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var pdpp_scopes_exports = {};
20
+ __export(pdpp_scopes_exports, {
21
+ InvalidPdppGrantError: () => InvalidPdppGrantError,
22
+ PDPP_SCOPE_NAMESPACE: () => PDPP_SCOPE_NAMESPACE,
23
+ PDPP_SOURCE_ID_PREFIX: () => PDPP_SOURCE_ID_PREFIX,
24
+ grantRequestScopes: () => grantRequestScopes,
25
+ isPdppScopeKey: () => isPdppScopeKey,
26
+ parsePdppScopeKey: () => parsePdppScopeKey,
27
+ pdppGrantsToScopes: () => pdppGrantsToScopes,
28
+ pdppScopeKey: () => pdppScopeKey
29
+ });
30
+ module.exports = __toCommonJS(pdpp_scopes_exports);
31
+ const PDPP_SCOPE_NAMESPACE = "pdpp";
32
+ const PDPP_SOURCE_ID_PREFIX = "https://registry.pdpp.dev/sources/";
33
+ const SEGMENT = "[a-z0-9][a-z0-9_]*";
34
+ const SOURCE_ID_RE = new RegExp(
35
+ `^${PDPP_SOURCE_ID_PREFIX.replace(/[.]/g, "\\.")}(${SEGMENT})$`
36
+ );
37
+ const STREAM_RE = new RegExp(`^${SEGMENT}$`);
38
+ const KEY_RE = new RegExp(
39
+ `^${PDPP_SCOPE_NAMESPACE}\\.(${SEGMENT})\\.(${SEGMENT})$`
40
+ );
41
+ class InvalidPdppGrantError extends Error {
42
+ /** Which part was rejected. */
43
+ field;
44
+ /** The rejected value, verbatim. */
45
+ value;
46
+ constructor(field, value, reason) {
47
+ super(
48
+ `Invalid PDPP ${field}${typeof value === "string" ? ` ${JSON.stringify(value)}` : ""}: ${reason}`
49
+ );
50
+ this.name = "InvalidPdppGrantError";
51
+ this.field = field;
52
+ this.value = value;
53
+ }
54
+ }
55
+ function sourceSegment(sourceId) {
56
+ const raw = sourceId;
57
+ const match = typeof raw === "string" ? SOURCE_ID_RE.exec(raw) : null;
58
+ if (!match) {
59
+ throw new InvalidPdppGrantError(
60
+ "source_id",
61
+ raw,
62
+ `must be ${PDPP_SOURCE_ID_PREFIX}<source> where <source> is lowercase letters, digits and underscores, not starting with an underscore`
63
+ );
64
+ }
65
+ return match[1];
66
+ }
67
+ function assertStream(stream) {
68
+ const raw = stream;
69
+ if (typeof raw !== "string" || !STREAM_RE.test(raw)) {
70
+ throw new InvalidPdppGrantError(
71
+ "stream",
72
+ raw,
73
+ "must be lowercase letters, digits and underscores, not starting with an underscore"
74
+ );
75
+ }
76
+ }
77
+ function pdppScopeKey(source_id, stream) {
78
+ const source = sourceSegment(source_id);
79
+ assertStream(stream);
80
+ return `${PDPP_SCOPE_NAMESPACE}.${source}.${stream}`;
81
+ }
82
+ function parsePdppScopeKey(key) {
83
+ const raw = key;
84
+ const match = typeof raw === "string" ? KEY_RE.exec(raw) : null;
85
+ if (!match) return null;
86
+ return { source_id: `${PDPP_SOURCE_ID_PREFIX}${match[1]}`, stream: match[2] };
87
+ }
88
+ function isPdppScopeKey(key) {
89
+ return parsePdppScopeKey(key) !== null;
90
+ }
91
+ function pdppGrantsToScopes(grants) {
92
+ const raw = grants;
93
+ if (!Array.isArray(raw)) {
94
+ throw new InvalidPdppGrantError("grants", raw, "must be an array");
95
+ }
96
+ const keys = /* @__PURE__ */ new Set();
97
+ for (const grant of raw) {
98
+ if (grant === null || typeof grant !== "object") {
99
+ throw new InvalidPdppGrantError("grant", grant, "must be an object");
100
+ }
101
+ const { source_id: sourceId, streams } = grant;
102
+ if (!Array.isArray(streams) || streams.length === 0) {
103
+ throw new InvalidPdppGrantError(
104
+ "streams",
105
+ streams,
106
+ `must name at least one stream${typeof sourceId === "string" ? ` for ${sourceId}` : ""}`
107
+ );
108
+ }
109
+ for (const stream of streams) {
110
+ keys.add(pdppScopeKey(sourceId, stream));
111
+ }
112
+ }
113
+ return [...keys];
114
+ }
115
+ function grantRequestScopes(input) {
116
+ const legacy = [...input.scopes ?? []];
117
+ const present = new Set(legacy);
118
+ const derived = pdppGrantsToScopes(input.grants ?? []).filter(
119
+ (key) => !present.has(key)
120
+ );
121
+ return [...legacy, ...derived];
122
+ }
123
+ // Annotate the CommonJS export names for ESM import in node:
124
+ 0 && (module.exports = {
125
+ InvalidPdppGrantError,
126
+ PDPP_SCOPE_NAMESPACE,
127
+ PDPP_SOURCE_ID_PREFIX,
128
+ grantRequestScopes,
129
+ isPdppScopeKey,
130
+ parsePdppScopeKey,
131
+ pdppGrantsToScopes,
132
+ pdppScopeKey
133
+ });
134
+ //# sourceMappingURL=pdpp-scopes.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/protocol/pdpp-scopes.ts"],"sourcesContent":["/**\n * PDPP grant requests and the one scope key they map to.\n *\n * @remarks\n * A builder asks for PDPP data as grants: a registry `source_id` and the\n * streams it wants from that source,\n * `{ source_id: \"https://registry.pdpp.dev/sources/chatgpt\", streams: [\"messages\"] }`.\n * Everything downstream (the access request, the signed grant, the gateway,\n * the Personal Server) carries flat scope strings, and a raw URI cannot be\n * one: its `:` collides with the `write:` operation prefix, and `/` and `#`\n * break Personal Server routes and storage paths. So each (source, stream)\n * pair maps to exactly one scope key, `pdpp.<source>.<stream>`, where\n * `<source>` is the last path segment of the `source_id`.\n *\n * The key is an ordinary scope: it passes `ScopeSchema` and the\n * `[operation:]scope` grant-entry grammar unchanged (`write:pdpp.chatgpt.messages`\n * is a write entry), and `pdpp.<source>.*` covers every stream of one source\n * through the usual prefix wildcard. The `pdpp.` namespace keeps these keys\n * apart from legacy `{platform}.{scope}` strings: PDPP's ChatGPT\n * `conversations` stream is not the legacy `chatgpt.conversations` scope, and\n * the gateway keys data by the scope string.\n *\n * The mapping is strict and never slugifies. A `source_id` or stream outside\n * the grammar below throws {@link InvalidPdppGrantError}. This module owns the\n * mapping; other implementations should import it, or test against the\n * vectors in `protocol/fixtures/pdpp-scope-keys.json`.\n *\n * - `source_id`: `^https://registry\\.pdpp\\.dev/sources/([a-z0-9][a-z0-9_]*)$`\n * - `stream`: `^[a-z0-9][a-z0-9_]*$`\n *\n * Both are matched against the whole string, with no trimming.\n *\n * @category Protocol\n * @module protocol/pdpp-scopes\n */\n\n/** First segment of every PDPP scope key. */\nexport const PDPP_SCOPE_NAMESPACE = \"pdpp\";\n\n/** The only `source_id` prefix a PDPP grant may name. */\nexport const PDPP_SOURCE_ID_PREFIX = \"https://registry.pdpp.dev/sources/\";\n\n// A segment must start with a letter or digit: ScopeSchema rejects a scope\n// segment that starts with `_`, so the key would not be a valid scope.\nconst SEGMENT = \"[a-z0-9][a-z0-9_]*\";\nconst SOURCE_ID_RE = new RegExp(\n `^${PDPP_SOURCE_ID_PREFIX.replace(/[.]/g, \"\\\\.\")}(${SEGMENT})$`,\n);\nconst STREAM_RE = new RegExp(`^${SEGMENT}$`);\nconst KEY_RE = new RegExp(\n `^${PDPP_SCOPE_NAMESPACE}\\\\.(${SEGMENT})\\\\.(${SEGMENT})$`,\n);\n\n/**\n * What a builder asks for from one PDPP source.\n *\n * @remarks\n * Field names follow PDPP's own wire form (`source_id`), not camelCase, so a\n * grant can be copied from a PDPP manifest as is.\n */\nexport interface PdppGrant {\n /** Registry URI of the source, e.g. `https://registry.pdpp.dev/sources/chatgpt`. */\n source_id: string;\n /** Streams to request from that source, e.g. `[\"conversations\", \"messages\"]`. At least one. */\n streams: string[];\n}\n\n/** One (source, stream) pair, as recovered from a scope key. */\nexport interface PdppScopeKeyParts {\n source_id: string;\n stream: string;\n}\n\n/** Thrown when a PDPP grant, `source_id` or stream does not fit the grammar. */\nexport class InvalidPdppGrantError extends Error {\n /** Which part was rejected. */\n readonly field: \"grants\" | \"grant\" | \"source_id\" | \"streams\" | \"stream\";\n /** The rejected value, verbatim. */\n readonly value: unknown;\n\n constructor(\n field: InvalidPdppGrantError[\"field\"],\n value: unknown,\n reason: string,\n ) {\n super(\n `Invalid PDPP ${field}${typeof value === \"string\" ? ` ${JSON.stringify(value)}` : \"\"}: ${reason}`,\n );\n this.name = \"InvalidPdppGrantError\";\n this.field = field;\n this.value = value;\n }\n}\n\nfunction sourceSegment(sourceId: string): string {\n const raw: unknown = sourceId;\n const match = typeof raw === \"string\" ? SOURCE_ID_RE.exec(raw) : null;\n if (!match) {\n throw new InvalidPdppGrantError(\n \"source_id\",\n raw,\n `must be ${PDPP_SOURCE_ID_PREFIX}<source> where <source> is lowercase letters, digits and underscores, not starting with an underscore`,\n );\n }\n return match[1];\n}\n\nfunction assertStream(stream: string): void {\n const raw: unknown = stream;\n if (typeof raw !== \"string\" || !STREAM_RE.test(raw)) {\n throw new InvalidPdppGrantError(\n \"stream\",\n raw,\n \"must be lowercase letters, digits and underscores, not starting with an underscore\",\n );\n }\n}\n\n/**\n * The scope key for one stream of one PDPP source.\n *\n * @example\n * ```ts\n * pdppScopeKey(\"https://registry.pdpp.dev/sources/chatgpt\", \"messages\");\n * // \"pdpp.chatgpt.messages\"\n * ```\n *\n * @param source_id - Registry URI of the source.\n * @param stream - Stream name.\n * @returns `pdpp.<source>.<stream>`.\n * @throws InvalidPdppGrantError when either input does not fit the grammar.\n */\nexport function pdppScopeKey(source_id: string, stream: string): string {\n const source = sourceSegment(source_id);\n assertStream(stream);\n return `${PDPP_SCOPE_NAMESPACE}.${source}.${stream}`;\n}\n\n/**\n * Inverse of {@link pdppScopeKey}.\n *\n * @param key - A scope string. Wildcards (`pdpp.chatgpt.*`) and grant entries\n * with an operation prefix (`write:pdpp.chatgpt.messages`) are not keys.\n * @returns The `source_id` and stream, or `null` when `key` is not a PDPP key.\n */\nexport function parsePdppScopeKey(key: string): PdppScopeKeyParts | null {\n const raw: unknown = key;\n const match = typeof raw === \"string\" ? KEY_RE.exec(raw) : null;\n if (!match) return null;\n return { source_id: `${PDPP_SOURCE_ID_PREFIX}${match[1]}`, stream: match[2] };\n}\n\n/**\n * Is `key` a PDPP scope key?\n *\n * @param key - A scope string.\n * @returns `true` when {@link parsePdppScopeKey} would parse it.\n */\nexport function isPdppScopeKey(key: string): boolean {\n return parsePdppScopeKey(key) !== null;\n}\n\n/**\n * The scope keys for a list of PDPP grants: one per stream, in grant order,\n * without duplicates.\n *\n * @param grants - The grants a builder requests.\n * @returns The derived scope keys (empty for no grants).\n * @throws InvalidPdppGrantError when a grant is malformed, names no streams,\n * or has a `source_id` or stream outside the grammar.\n */\nexport function pdppGrantsToScopes(grants: readonly PdppGrant[]): string[] {\n const raw: unknown = grants;\n if (!Array.isArray(raw)) {\n throw new InvalidPdppGrantError(\"grants\", raw, \"must be an array\");\n }\n const keys = new Set<string>();\n for (const grant of raw as unknown[]) {\n if (grant === null || typeof grant !== \"object\") {\n throw new InvalidPdppGrantError(\"grant\", grant, \"must be an object\");\n }\n const { source_id: sourceId, streams } = grant as Partial<PdppGrant>;\n if (!Array.isArray(streams) || streams.length === 0) {\n throw new InvalidPdppGrantError(\n \"streams\",\n streams,\n `must name at least one stream${typeof sourceId === \"string\" ? ` for ${sourceId}` : \"\"}`,\n );\n }\n for (const stream of streams) {\n keys.add(pdppScopeKey(sourceId as string, stream));\n }\n }\n return [...keys];\n}\n\n/**\n * The scope entries one request asks for: legacy `scopes` and the keys\n * derived from `grants`.\n *\n * @remarks\n * Legacy scopes come first and pass through verbatim (order and duplicates\n * included, so a request without grants is byte-for-byte what it was before\n * grants existed). Derived keys follow, minus any already present.\n *\n * @param input - `grants` (structured) and/or legacy `scopes`\n * (`[operation:]scope` entries).\n * @returns The scope entries to request.\n * @throws InvalidPdppGrantError when a grant does not fit the grammar.\n */\nexport function grantRequestScopes(input: {\n grants?: readonly PdppGrant[];\n scopes?: readonly string[];\n}): string[] {\n const legacy = [...(input.scopes ?? [])];\n const present = new Set(legacy);\n const derived = pdppGrantsToScopes(input.grants ?? []).filter(\n (key) => !present.has(key),\n );\n return [...legacy, ...derived];\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * PDPP grant requests and the one scope key they map to.
3
+ *
4
+ * @remarks
5
+ * A builder asks for PDPP data as grants: a registry `source_id` and the
6
+ * streams it wants from that source,
7
+ * `{ source_id: "https://registry.pdpp.dev/sources/chatgpt", streams: ["messages"] }`.
8
+ * Everything downstream (the access request, the signed grant, the gateway,
9
+ * the Personal Server) carries flat scope strings, and a raw URI cannot be
10
+ * one: its `:` collides with the `write:` operation prefix, and `/` and `#`
11
+ * break Personal Server routes and storage paths. So each (source, stream)
12
+ * pair maps to exactly one scope key, `pdpp.<source>.<stream>`, where
13
+ * `<source>` is the last path segment of the `source_id`.
14
+ *
15
+ * The key is an ordinary scope: it passes `ScopeSchema` and the
16
+ * `[operation:]scope` grant-entry grammar unchanged (`write:pdpp.chatgpt.messages`
17
+ * is a write entry), and `pdpp.<source>.*` covers every stream of one source
18
+ * through the usual prefix wildcard. The `pdpp.` namespace keeps these keys
19
+ * apart from legacy `{platform}.{scope}` strings: PDPP's ChatGPT
20
+ * `conversations` stream is not the legacy `chatgpt.conversations` scope, and
21
+ * the gateway keys data by the scope string.
22
+ *
23
+ * The mapping is strict and never slugifies. A `source_id` or stream outside
24
+ * the grammar below throws {@link InvalidPdppGrantError}. This module owns the
25
+ * mapping; other implementations should import it, or test against the
26
+ * vectors in `protocol/fixtures/pdpp-scope-keys.json`.
27
+ *
28
+ * - `source_id`: `^https://registry\.pdpp\.dev/sources/([a-z0-9][a-z0-9_]*)$`
29
+ * - `stream`: `^[a-z0-9][a-z0-9_]*$`
30
+ *
31
+ * Both are matched against the whole string, with no trimming.
32
+ *
33
+ * @category Protocol
34
+ * @module protocol/pdpp-scopes
35
+ */
36
+ /** First segment of every PDPP scope key. */
37
+ export declare const PDPP_SCOPE_NAMESPACE = "pdpp";
38
+ /** The only `source_id` prefix a PDPP grant may name. */
39
+ export declare const PDPP_SOURCE_ID_PREFIX = "https://registry.pdpp.dev/sources/";
40
+ /**
41
+ * What a builder asks for from one PDPP source.
42
+ *
43
+ * @remarks
44
+ * Field names follow PDPP's own wire form (`source_id`), not camelCase, so a
45
+ * grant can be copied from a PDPP manifest as is.
46
+ */
47
+ export interface PdppGrant {
48
+ /** Registry URI of the source, e.g. `https://registry.pdpp.dev/sources/chatgpt`. */
49
+ source_id: string;
50
+ /** Streams to request from that source, e.g. `["conversations", "messages"]`. At least one. */
51
+ streams: string[];
52
+ }
53
+ /** One (source, stream) pair, as recovered from a scope key. */
54
+ export interface PdppScopeKeyParts {
55
+ source_id: string;
56
+ stream: string;
57
+ }
58
+ /** Thrown when a PDPP grant, `source_id` or stream does not fit the grammar. */
59
+ export declare class InvalidPdppGrantError extends Error {
60
+ /** Which part was rejected. */
61
+ readonly field: "grants" | "grant" | "source_id" | "streams" | "stream";
62
+ /** The rejected value, verbatim. */
63
+ readonly value: unknown;
64
+ constructor(field: InvalidPdppGrantError["field"], value: unknown, reason: string);
65
+ }
66
+ /**
67
+ * The scope key for one stream of one PDPP source.
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * pdppScopeKey("https://registry.pdpp.dev/sources/chatgpt", "messages");
72
+ * // "pdpp.chatgpt.messages"
73
+ * ```
74
+ *
75
+ * @param source_id - Registry URI of the source.
76
+ * @param stream - Stream name.
77
+ * @returns `pdpp.<source>.<stream>`.
78
+ * @throws InvalidPdppGrantError when either input does not fit the grammar.
79
+ */
80
+ export declare function pdppScopeKey(source_id: string, stream: string): string;
81
+ /**
82
+ * Inverse of {@link pdppScopeKey}.
83
+ *
84
+ * @param key - A scope string. Wildcards (`pdpp.chatgpt.*`) and grant entries
85
+ * with an operation prefix (`write:pdpp.chatgpt.messages`) are not keys.
86
+ * @returns The `source_id` and stream, or `null` when `key` is not a PDPP key.
87
+ */
88
+ export declare function parsePdppScopeKey(key: string): PdppScopeKeyParts | null;
89
+ /**
90
+ * Is `key` a PDPP scope key?
91
+ *
92
+ * @param key - A scope string.
93
+ * @returns `true` when {@link parsePdppScopeKey} would parse it.
94
+ */
95
+ export declare function isPdppScopeKey(key: string): boolean;
96
+ /**
97
+ * The scope keys for a list of PDPP grants: one per stream, in grant order,
98
+ * without duplicates.
99
+ *
100
+ * @param grants - The grants a builder requests.
101
+ * @returns The derived scope keys (empty for no grants).
102
+ * @throws InvalidPdppGrantError when a grant is malformed, names no streams,
103
+ * or has a `source_id` or stream outside the grammar.
104
+ */
105
+ export declare function pdppGrantsToScopes(grants: readonly PdppGrant[]): string[];
106
+ /**
107
+ * The scope entries one request asks for: legacy `scopes` and the keys
108
+ * derived from `grants`.
109
+ *
110
+ * @remarks
111
+ * Legacy scopes come first and pass through verbatim (order and duplicates
112
+ * included, so a request without grants is byte-for-byte what it was before
113
+ * grants existed). Derived keys follow, minus any already present.
114
+ *
115
+ * @param input - `grants` (structured) and/or legacy `scopes`
116
+ * (`[operation:]scope` entries).
117
+ * @returns The scope entries to request.
118
+ * @throws InvalidPdppGrantError when a grant does not fit the grammar.
119
+ */
120
+ export declare function grantRequestScopes(input: {
121
+ grants?: readonly PdppGrant[];
122
+ scopes?: readonly string[];
123
+ }): string[];
@@ -0,0 +1,103 @@
1
+ const PDPP_SCOPE_NAMESPACE = "pdpp";
2
+ const PDPP_SOURCE_ID_PREFIX = "https://registry.pdpp.dev/sources/";
3
+ const SEGMENT = "[a-z0-9][a-z0-9_]*";
4
+ const SOURCE_ID_RE = new RegExp(
5
+ `^${PDPP_SOURCE_ID_PREFIX.replace(/[.]/g, "\\.")}(${SEGMENT})$`
6
+ );
7
+ const STREAM_RE = new RegExp(`^${SEGMENT}$`);
8
+ const KEY_RE = new RegExp(
9
+ `^${PDPP_SCOPE_NAMESPACE}\\.(${SEGMENT})\\.(${SEGMENT})$`
10
+ );
11
+ class InvalidPdppGrantError extends Error {
12
+ /** Which part was rejected. */
13
+ field;
14
+ /** The rejected value, verbatim. */
15
+ value;
16
+ constructor(field, value, reason) {
17
+ super(
18
+ `Invalid PDPP ${field}${typeof value === "string" ? ` ${JSON.stringify(value)}` : ""}: ${reason}`
19
+ );
20
+ this.name = "InvalidPdppGrantError";
21
+ this.field = field;
22
+ this.value = value;
23
+ }
24
+ }
25
+ function sourceSegment(sourceId) {
26
+ const raw = sourceId;
27
+ const match = typeof raw === "string" ? SOURCE_ID_RE.exec(raw) : null;
28
+ if (!match) {
29
+ throw new InvalidPdppGrantError(
30
+ "source_id",
31
+ raw,
32
+ `must be ${PDPP_SOURCE_ID_PREFIX}<source> where <source> is lowercase letters, digits and underscores, not starting with an underscore`
33
+ );
34
+ }
35
+ return match[1];
36
+ }
37
+ function assertStream(stream) {
38
+ const raw = stream;
39
+ if (typeof raw !== "string" || !STREAM_RE.test(raw)) {
40
+ throw new InvalidPdppGrantError(
41
+ "stream",
42
+ raw,
43
+ "must be lowercase letters, digits and underscores, not starting with an underscore"
44
+ );
45
+ }
46
+ }
47
+ function pdppScopeKey(source_id, stream) {
48
+ const source = sourceSegment(source_id);
49
+ assertStream(stream);
50
+ return `${PDPP_SCOPE_NAMESPACE}.${source}.${stream}`;
51
+ }
52
+ function parsePdppScopeKey(key) {
53
+ const raw = key;
54
+ const match = typeof raw === "string" ? KEY_RE.exec(raw) : null;
55
+ if (!match) return null;
56
+ return { source_id: `${PDPP_SOURCE_ID_PREFIX}${match[1]}`, stream: match[2] };
57
+ }
58
+ function isPdppScopeKey(key) {
59
+ return parsePdppScopeKey(key) !== null;
60
+ }
61
+ function pdppGrantsToScopes(grants) {
62
+ const raw = grants;
63
+ if (!Array.isArray(raw)) {
64
+ throw new InvalidPdppGrantError("grants", raw, "must be an array");
65
+ }
66
+ const keys = /* @__PURE__ */ new Set();
67
+ for (const grant of raw) {
68
+ if (grant === null || typeof grant !== "object") {
69
+ throw new InvalidPdppGrantError("grant", grant, "must be an object");
70
+ }
71
+ const { source_id: sourceId, streams } = grant;
72
+ if (!Array.isArray(streams) || streams.length === 0) {
73
+ throw new InvalidPdppGrantError(
74
+ "streams",
75
+ streams,
76
+ `must name at least one stream${typeof sourceId === "string" ? ` for ${sourceId}` : ""}`
77
+ );
78
+ }
79
+ for (const stream of streams) {
80
+ keys.add(pdppScopeKey(sourceId, stream));
81
+ }
82
+ }
83
+ return [...keys];
84
+ }
85
+ function grantRequestScopes(input) {
86
+ const legacy = [...input.scopes ?? []];
87
+ const present = new Set(legacy);
88
+ const derived = pdppGrantsToScopes(input.grants ?? []).filter(
89
+ (key) => !present.has(key)
90
+ );
91
+ return [...legacy, ...derived];
92
+ }
93
+ export {
94
+ InvalidPdppGrantError,
95
+ PDPP_SCOPE_NAMESPACE,
96
+ PDPP_SOURCE_ID_PREFIX,
97
+ grantRequestScopes,
98
+ isPdppScopeKey,
99
+ parsePdppScopeKey,
100
+ pdppGrantsToScopes,
101
+ pdppScopeKey
102
+ };
103
+ //# sourceMappingURL=pdpp-scopes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/protocol/pdpp-scopes.ts"],"sourcesContent":["/**\n * PDPP grant requests and the one scope key they map to.\n *\n * @remarks\n * A builder asks for PDPP data as grants: a registry `source_id` and the\n * streams it wants from that source,\n * `{ source_id: \"https://registry.pdpp.dev/sources/chatgpt\", streams: [\"messages\"] }`.\n * Everything downstream (the access request, the signed grant, the gateway,\n * the Personal Server) carries flat scope strings, and a raw URI cannot be\n * one: its `:` collides with the `write:` operation prefix, and `/` and `#`\n * break Personal Server routes and storage paths. So each (source, stream)\n * pair maps to exactly one scope key, `pdpp.<source>.<stream>`, where\n * `<source>` is the last path segment of the `source_id`.\n *\n * The key is an ordinary scope: it passes `ScopeSchema` and the\n * `[operation:]scope` grant-entry grammar unchanged (`write:pdpp.chatgpt.messages`\n * is a write entry), and `pdpp.<source>.*` covers every stream of one source\n * through the usual prefix wildcard. The `pdpp.` namespace keeps these keys\n * apart from legacy `{platform}.{scope}` strings: PDPP's ChatGPT\n * `conversations` stream is not the legacy `chatgpt.conversations` scope, and\n * the gateway keys data by the scope string.\n *\n * The mapping is strict and never slugifies. A `source_id` or stream outside\n * the grammar below throws {@link InvalidPdppGrantError}. This module owns the\n * mapping; other implementations should import it, or test against the\n * vectors in `protocol/fixtures/pdpp-scope-keys.json`.\n *\n * - `source_id`: `^https://registry\\.pdpp\\.dev/sources/([a-z0-9][a-z0-9_]*)$`\n * - `stream`: `^[a-z0-9][a-z0-9_]*$`\n *\n * Both are matched against the whole string, with no trimming.\n *\n * @category Protocol\n * @module protocol/pdpp-scopes\n */\n\n/** First segment of every PDPP scope key. */\nexport const PDPP_SCOPE_NAMESPACE = \"pdpp\";\n\n/** The only `source_id` prefix a PDPP grant may name. */\nexport const PDPP_SOURCE_ID_PREFIX = \"https://registry.pdpp.dev/sources/\";\n\n// A segment must start with a letter or digit: ScopeSchema rejects a scope\n// segment that starts with `_`, so the key would not be a valid scope.\nconst SEGMENT = \"[a-z0-9][a-z0-9_]*\";\nconst SOURCE_ID_RE = new RegExp(\n `^${PDPP_SOURCE_ID_PREFIX.replace(/[.]/g, \"\\\\.\")}(${SEGMENT})$`,\n);\nconst STREAM_RE = new RegExp(`^${SEGMENT}$`);\nconst KEY_RE = new RegExp(\n `^${PDPP_SCOPE_NAMESPACE}\\\\.(${SEGMENT})\\\\.(${SEGMENT})$`,\n);\n\n/**\n * What a builder asks for from one PDPP source.\n *\n * @remarks\n * Field names follow PDPP's own wire form (`source_id`), not camelCase, so a\n * grant can be copied from a PDPP manifest as is.\n */\nexport interface PdppGrant {\n /** Registry URI of the source, e.g. `https://registry.pdpp.dev/sources/chatgpt`. */\n source_id: string;\n /** Streams to request from that source, e.g. `[\"conversations\", \"messages\"]`. At least one. */\n streams: string[];\n}\n\n/** One (source, stream) pair, as recovered from a scope key. */\nexport interface PdppScopeKeyParts {\n source_id: string;\n stream: string;\n}\n\n/** Thrown when a PDPP grant, `source_id` or stream does not fit the grammar. */\nexport class InvalidPdppGrantError extends Error {\n /** Which part was rejected. */\n readonly field: \"grants\" | \"grant\" | \"source_id\" | \"streams\" | \"stream\";\n /** The rejected value, verbatim. */\n readonly value: unknown;\n\n constructor(\n field: InvalidPdppGrantError[\"field\"],\n value: unknown,\n reason: string,\n ) {\n super(\n `Invalid PDPP ${field}${typeof value === \"string\" ? ` ${JSON.stringify(value)}` : \"\"}: ${reason}`,\n );\n this.name = \"InvalidPdppGrantError\";\n this.field = field;\n this.value = value;\n }\n}\n\nfunction sourceSegment(sourceId: string): string {\n const raw: unknown = sourceId;\n const match = typeof raw === \"string\" ? SOURCE_ID_RE.exec(raw) : null;\n if (!match) {\n throw new InvalidPdppGrantError(\n \"source_id\",\n raw,\n `must be ${PDPP_SOURCE_ID_PREFIX}<source> where <source> is lowercase letters, digits and underscores, not starting with an underscore`,\n );\n }\n return match[1];\n}\n\nfunction assertStream(stream: string): void {\n const raw: unknown = stream;\n if (typeof raw !== \"string\" || !STREAM_RE.test(raw)) {\n throw new InvalidPdppGrantError(\n \"stream\",\n raw,\n \"must be lowercase letters, digits and underscores, not starting with an underscore\",\n );\n }\n}\n\n/**\n * The scope key for one stream of one PDPP source.\n *\n * @example\n * ```ts\n * pdppScopeKey(\"https://registry.pdpp.dev/sources/chatgpt\", \"messages\");\n * // \"pdpp.chatgpt.messages\"\n * ```\n *\n * @param source_id - Registry URI of the source.\n * @param stream - Stream name.\n * @returns `pdpp.<source>.<stream>`.\n * @throws InvalidPdppGrantError when either input does not fit the grammar.\n */\nexport function pdppScopeKey(source_id: string, stream: string): string {\n const source = sourceSegment(source_id);\n assertStream(stream);\n return `${PDPP_SCOPE_NAMESPACE}.${source}.${stream}`;\n}\n\n/**\n * Inverse of {@link pdppScopeKey}.\n *\n * @param key - A scope string. Wildcards (`pdpp.chatgpt.*`) and grant entries\n * with an operation prefix (`write:pdpp.chatgpt.messages`) are not keys.\n * @returns The `source_id` and stream, or `null` when `key` is not a PDPP key.\n */\nexport function parsePdppScopeKey(key: string): PdppScopeKeyParts | null {\n const raw: unknown = key;\n const match = typeof raw === \"string\" ? KEY_RE.exec(raw) : null;\n if (!match) return null;\n return { source_id: `${PDPP_SOURCE_ID_PREFIX}${match[1]}`, stream: match[2] };\n}\n\n/**\n * Is `key` a PDPP scope key?\n *\n * @param key - A scope string.\n * @returns `true` when {@link parsePdppScopeKey} would parse it.\n */\nexport function isPdppScopeKey(key: string): boolean {\n return parsePdppScopeKey(key) !== null;\n}\n\n/**\n * The scope keys for a list of PDPP grants: one per stream, in grant order,\n * without duplicates.\n *\n * @param grants - The grants a builder requests.\n * @returns The derived scope keys (empty for no grants).\n * @throws InvalidPdppGrantError when a grant is malformed, names no streams,\n * or has a `source_id` or stream outside the grammar.\n */\nexport function pdppGrantsToScopes(grants: readonly PdppGrant[]): string[] {\n const raw: unknown = grants;\n if (!Array.isArray(raw)) {\n throw new InvalidPdppGrantError(\"grants\", raw, \"must be an array\");\n }\n const keys = new Set<string>();\n for (const grant of raw as unknown[]) {\n if (grant === null || typeof grant !== \"object\") {\n throw new InvalidPdppGrantError(\"grant\", grant, \"must be an object\");\n }\n const { source_id: sourceId, streams } = grant as Partial<PdppGrant>;\n if (!Array.isArray(streams) || streams.length === 0) {\n throw new InvalidPdppGrantError(\n \"streams\",\n streams,\n `must name at least one stream${typeof sourceId === \"string\" ? ` for ${sourceId}` : \"\"}`,\n );\n }\n for (const stream of streams) {\n keys.add(pdppScopeKey(sourceId as string, stream));\n }\n }\n return [...keys];\n}\n\n/**\n * The scope entries one request asks for: legacy `scopes` and the keys\n * derived from `grants`.\n *\n * @remarks\n * Legacy scopes come first and pass through verbatim (order and duplicates\n * included, so a request without grants is byte-for-byte what it was before\n * grants existed). Derived keys follow, minus any already present.\n *\n * @param input - `grants` (structured) and/or legacy `scopes`\n * (`[operation:]scope` entries).\n * @returns The scope entries to request.\n * @throws InvalidPdppGrantError when a grant does not fit the grammar.\n */\nexport function grantRequestScopes(input: {\n grants?: readonly PdppGrant[];\n scopes?: readonly string[];\n}): string[] {\n const legacy = [...(input.scopes ?? [])];\n const present = new Set(legacy);\n const derived = pdppGrantsToScopes(input.grants ?? []).filter(\n (key) => !present.has(key),\n );\n return [...legacy, ...derived];\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1 @@
1
+ export {};
@@ -18,14 +18,23 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var scopes_exports = {};
20
20
  __export(scopes_exports, {
21
+ InvalidPdppGrantError: () => import_pdpp_scopes.InvalidPdppGrantError,
22
+ PDPP_SCOPE_NAMESPACE: () => import_pdpp_scopes.PDPP_SCOPE_NAMESPACE,
23
+ PDPP_SOURCE_ID_PREFIX: () => import_pdpp_scopes.PDPP_SOURCE_ID_PREFIX,
21
24
  ScopeSchema: () => ScopeSchema,
25
+ grantRequestScopes: () => import_pdpp_scopes.grantRequestScopes,
26
+ isPdppScopeKey: () => import_pdpp_scopes.isPdppScopeKey,
27
+ parsePdppScopeKey: () => import_pdpp_scopes.parsePdppScopeKey,
22
28
  parseScope: () => parseScope,
29
+ pdppGrantsToScopes: () => import_pdpp_scopes.pdppGrantsToScopes,
30
+ pdppScopeKey: () => import_pdpp_scopes.pdppScopeKey,
23
31
  scopeCoveredByGrant: () => scopeCoveredByGrant,
24
32
  scopeMatchesPattern: () => scopeMatchesPattern,
25
33
  scopeToPathSegments: () => scopeToPathSegments
26
34
  });
27
35
  module.exports = __toCommonJS(scopes_exports);
28
36
  var import_zod = require("zod");
37
+ var import_pdpp_scopes = require("./pdpp-scopes");
29
38
  const SOURCE_RE = /^[a-z0-9][a-z0-9_]*$/;
30
39
  const TAIL_SEGMENT_RE = /^[a-zA-Z0-9][a-zA-Z0-9_]*$/;
31
40
  const ScopeSchema = import_zod.z.string().refine(
@@ -72,8 +81,16 @@ function scopeCoveredByGrant(requestedScope, grantedScopes) {
72
81
  }
73
82
  // Annotate the CommonJS export names for ESM import in node:
74
83
  0 && (module.exports = {
84
+ InvalidPdppGrantError,
85
+ PDPP_SCOPE_NAMESPACE,
86
+ PDPP_SOURCE_ID_PREFIX,
75
87
  ScopeSchema,
88
+ grantRequestScopes,
89
+ isPdppScopeKey,
90
+ parsePdppScopeKey,
76
91
  parseScope,
92
+ pdppGrantsToScopes,
93
+ pdppScopeKey,
77
94
  scopeCoveredByGrant,
78
95
  scopeMatchesPattern,
79
96
  scopeToPathSegments
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/protocol/scopes.ts"],"sourcesContent":["import { z } from \"zod\";\n\n// The source_id (first segment) must stay lowercase. Tail segments may keep\n// the historical camelCase form the contract-freeze spec explicitly preserves\n// (e.g. spotify.savedTracks, youtube.playlistItems) — the chain never validates\n// scope strings, so the SDK must not be stricter than the protocol.\nconst SOURCE_RE = /^[a-z0-9][a-z0-9_]*$/;\nconst TAIL_SEGMENT_RE = /^[a-zA-Z0-9][a-zA-Z0-9_]*$/;\n\nexport const ScopeSchema = z.string().refine(\n (scope) => {\n const parts = scope.split(\".\");\n if (parts.length < 2 || parts.length > 3) return false;\n const [source, ...tail] = parts;\n return (\n SOURCE_RE.test(source) && tail.every((part) => TAIL_SEGMENT_RE.test(part))\n );\n },\n {\n message:\n \"Scope must be {source}.{category}[.{subcategory}]; source lowercase, tail may keep the historical camelCase form (e.g. spotify.savedTracks)\",\n },\n);\n\nexport type Scope = z.infer<typeof ScopeSchema>;\n\nexport interface ParsedScope {\n source: string;\n category: string;\n subcategory?: string;\n raw: string;\n}\n\nexport function parseScope(scope: string): ParsedScope {\n const validated = ScopeSchema.parse(scope);\n const parts = validated.split(\".\");\n return {\n source: parts[0],\n category: parts[1],\n subcategory: parts[2],\n raw: validated,\n };\n}\n\nexport function scopeToPathSegments(scope: string): string[] {\n const parsed = parseScope(scope);\n const segments = [parsed.source, parsed.category];\n if (parsed.subcategory) {\n segments.push(parsed.subcategory);\n }\n return segments;\n}\n\nexport function scopeMatchesPattern(\n requestedScope: string,\n grantPattern: string,\n): boolean {\n if (grantPattern === \"*\") return true;\n\n if (grantPattern.endsWith(\".*\")) {\n const prefix = grantPattern.slice(0, -1);\n return requestedScope.startsWith(prefix);\n }\n\n return requestedScope === grantPattern;\n}\n\nexport function scopeCoveredByGrant(\n requestedScope: string,\n grantedScopes: string[],\n): boolean {\n return grantedScopes.some((pattern) =>\n scopeMatchesPattern(requestedScope, pattern),\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAAkB;AAMlB,MAAM,YAAY;AAClB,MAAM,kBAAkB;AAEjB,MAAM,cAAc,aAAE,OAAO,EAAE;AAAA,EACpC,CAAC,UAAU;AACT,UAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,QAAI,MAAM,SAAS,KAAK,MAAM,SAAS,EAAG,QAAO;AACjD,UAAM,CAAC,QAAQ,GAAG,IAAI,IAAI;AAC1B,WACE,UAAU,KAAK,MAAM,KAAK,KAAK,MAAM,CAAC,SAAS,gBAAgB,KAAK,IAAI,CAAC;AAAA,EAE7E;AAAA,EACA;AAAA,IACE,SACE;AAAA,EACJ;AACF;AAWO,SAAS,WAAW,OAA4B;AACrD,QAAM,YAAY,YAAY,MAAM,KAAK;AACzC,QAAM,QAAQ,UAAU,MAAM,GAAG;AACjC,SAAO;AAAA,IACL,QAAQ,MAAM,CAAC;AAAA,IACf,UAAU,MAAM,CAAC;AAAA,IACjB,aAAa,MAAM,CAAC;AAAA,IACpB,KAAK;AAAA,EACP;AACF;AAEO,SAAS,oBAAoB,OAAyB;AAC3D,QAAM,SAAS,WAAW,KAAK;AAC/B,QAAM,WAAW,CAAC,OAAO,QAAQ,OAAO,QAAQ;AAChD,MAAI,OAAO,aAAa;AACtB,aAAS,KAAK,OAAO,WAAW;AAAA,EAClC;AACA,SAAO;AACT;AAEO,SAAS,oBACd,gBACA,cACS;AACT,MAAI,iBAAiB,IAAK,QAAO;AAEjC,MAAI,aAAa,SAAS,IAAI,GAAG;AAC/B,UAAM,SAAS,aAAa,MAAM,GAAG,EAAE;AACvC,WAAO,eAAe,WAAW,MAAM;AAAA,EACzC;AAEA,SAAO,mBAAmB;AAC5B;AAEO,SAAS,oBACd,gBACA,eACS;AACT,SAAO,cAAc;AAAA,IAAK,CAAC,YACzB,oBAAoB,gBAAgB,OAAO;AAAA,EAC7C;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/protocol/scopes.ts"],"sourcesContent":["import { z } from \"zod\";\n\n// The source_id (first segment) must stay lowercase. Tail segments may keep\n// the historical camelCase form the contract-freeze spec explicitly preserves\n// (e.g. spotify.savedTracks, youtube.playlistItems) — the chain never validates\n// scope strings, so the SDK must not be stricter than the protocol.\nconst SOURCE_RE = /^[a-z0-9][a-z0-9_]*$/;\nconst TAIL_SEGMENT_RE = /^[a-zA-Z0-9][a-zA-Z0-9_]*$/;\n\nexport const ScopeSchema = z.string().refine(\n (scope) => {\n const parts = scope.split(\".\");\n if (parts.length < 2 || parts.length > 3) return false;\n const [source, ...tail] = parts;\n return (\n SOURCE_RE.test(source) && tail.every((part) => TAIL_SEGMENT_RE.test(part))\n );\n },\n {\n message:\n \"Scope must be {source}.{category}[.{subcategory}]; source lowercase, tail may keep the historical camelCase form (e.g. spotify.savedTracks)\",\n },\n);\n\nexport type Scope = z.infer<typeof ScopeSchema>;\n\nexport interface ParsedScope {\n source: string;\n category: string;\n subcategory?: string;\n raw: string;\n}\n\nexport function parseScope(scope: string): ParsedScope {\n const validated = ScopeSchema.parse(scope);\n const parts = validated.split(\".\");\n return {\n source: parts[0],\n category: parts[1],\n subcategory: parts[2],\n raw: validated,\n };\n}\n\nexport function scopeToPathSegments(scope: string): string[] {\n const parsed = parseScope(scope);\n const segments = [parsed.source, parsed.category];\n if (parsed.subcategory) {\n segments.push(parsed.subcategory);\n }\n return segments;\n}\n\nexport function scopeMatchesPattern(\n requestedScope: string,\n grantPattern: string,\n): boolean {\n if (grantPattern === \"*\") return true;\n\n if (grantPattern.endsWith(\".*\")) {\n const prefix = grantPattern.slice(0, -1);\n return requestedScope.startsWith(prefix);\n }\n\n return requestedScope === grantPattern;\n}\n\nexport function scopeCoveredByGrant(\n requestedScope: string,\n grantedScopes: string[],\n): boolean {\n return grantedScopes.some((pattern) =>\n scopeMatchesPattern(requestedScope, pattern),\n );\n}\n\n// PDPP grants map to scope keys in this same grammar; re-exported so the\n// scope contract has one import path (`@opendatalabs/vana-sdk/protocol/scopes`).\nexport {\n PDPP_SCOPE_NAMESPACE,\n PDPP_SOURCE_ID_PREFIX,\n InvalidPdppGrantError,\n pdppScopeKey,\n parsePdppScopeKey,\n isPdppScopeKey,\n pdppGrantsToScopes,\n grantRequestScopes,\n type PdppGrant,\n type PdppScopeKeyParts,\n} from \"./pdpp-scopes\";\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAAkB;AA8ElB,yBAWO;AAnFP,MAAM,YAAY;AAClB,MAAM,kBAAkB;AAEjB,MAAM,cAAc,aAAE,OAAO,EAAE;AAAA,EACpC,CAAC,UAAU;AACT,UAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,QAAI,MAAM,SAAS,KAAK,MAAM,SAAS,EAAG,QAAO;AACjD,UAAM,CAAC,QAAQ,GAAG,IAAI,IAAI;AAC1B,WACE,UAAU,KAAK,MAAM,KAAK,KAAK,MAAM,CAAC,SAAS,gBAAgB,KAAK,IAAI,CAAC;AAAA,EAE7E;AAAA,EACA;AAAA,IACE,SACE;AAAA,EACJ;AACF;AAWO,SAAS,WAAW,OAA4B;AACrD,QAAM,YAAY,YAAY,MAAM,KAAK;AACzC,QAAM,QAAQ,UAAU,MAAM,GAAG;AACjC,SAAO;AAAA,IACL,QAAQ,MAAM,CAAC;AAAA,IACf,UAAU,MAAM,CAAC;AAAA,IACjB,aAAa,MAAM,CAAC;AAAA,IACpB,KAAK;AAAA,EACP;AACF;AAEO,SAAS,oBAAoB,OAAyB;AAC3D,QAAM,SAAS,WAAW,KAAK;AAC/B,QAAM,WAAW,CAAC,OAAO,QAAQ,OAAO,QAAQ;AAChD,MAAI,OAAO,aAAa;AACtB,aAAS,KAAK,OAAO,WAAW;AAAA,EAClC;AACA,SAAO;AACT;AAEO,SAAS,oBACd,gBACA,cACS;AACT,MAAI,iBAAiB,IAAK,QAAO;AAEjC,MAAI,aAAa,SAAS,IAAI,GAAG;AAC/B,UAAM,SAAS,aAAa,MAAM,GAAG,EAAE;AACvC,WAAO,eAAe,WAAW,MAAM;AAAA,EACzC;AAEA,SAAO,mBAAmB;AAC5B;AAEO,SAAS,oBACd,gBACA,eACS;AACT,SAAO,cAAc;AAAA,IAAK,CAAC,YACzB,oBAAoB,gBAAgB,OAAO;AAAA,EAC7C;AACF;","names":[]}
@@ -11,3 +11,4 @@ export declare function parseScope(scope: string): ParsedScope;
11
11
  export declare function scopeToPathSegments(scope: string): string[];
12
12
  export declare function scopeMatchesPattern(requestedScope: string, grantPattern: string): boolean;
13
13
  export declare function scopeCoveredByGrant(requestedScope: string, grantedScopes: string[]): boolean;
14
+ export { PDPP_SCOPE_NAMESPACE, PDPP_SOURCE_ID_PREFIX, InvalidPdppGrantError, pdppScopeKey, parsePdppScopeKey, isPdppScopeKey, pdppGrantsToScopes, grantRequestScopes, type PdppGrant, type PdppScopeKeyParts, } from "./pdpp-scopes.js";
@@ -43,9 +43,27 @@ function scopeCoveredByGrant(requestedScope, grantedScopes) {
43
43
  (pattern) => scopeMatchesPattern(requestedScope, pattern)
44
44
  );
45
45
  }
46
+ import {
47
+ PDPP_SCOPE_NAMESPACE,
48
+ PDPP_SOURCE_ID_PREFIX,
49
+ InvalidPdppGrantError,
50
+ pdppScopeKey,
51
+ parsePdppScopeKey,
52
+ isPdppScopeKey,
53
+ pdppGrantsToScopes,
54
+ grantRequestScopes
55
+ } from "./pdpp-scopes.js";
46
56
  export {
57
+ InvalidPdppGrantError,
58
+ PDPP_SCOPE_NAMESPACE,
59
+ PDPP_SOURCE_ID_PREFIX,
47
60
  ScopeSchema,
61
+ grantRequestScopes,
62
+ isPdppScopeKey,
63
+ parsePdppScopeKey,
48
64
  parseScope,
65
+ pdppGrantsToScopes,
66
+ pdppScopeKey,
49
67
  scopeCoveredByGrant,
50
68
  scopeMatchesPattern,
51
69
  scopeToPathSegments
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/protocol/scopes.ts"],"sourcesContent":["import { z } from \"zod\";\n\n// The source_id (first segment) must stay lowercase. Tail segments may keep\n// the historical camelCase form the contract-freeze spec explicitly preserves\n// (e.g. spotify.savedTracks, youtube.playlistItems) — the chain never validates\n// scope strings, so the SDK must not be stricter than the protocol.\nconst SOURCE_RE = /^[a-z0-9][a-z0-9_]*$/;\nconst TAIL_SEGMENT_RE = /^[a-zA-Z0-9][a-zA-Z0-9_]*$/;\n\nexport const ScopeSchema = z.string().refine(\n (scope) => {\n const parts = scope.split(\".\");\n if (parts.length < 2 || parts.length > 3) return false;\n const [source, ...tail] = parts;\n return (\n SOURCE_RE.test(source) && tail.every((part) => TAIL_SEGMENT_RE.test(part))\n );\n },\n {\n message:\n \"Scope must be {source}.{category}[.{subcategory}]; source lowercase, tail may keep the historical camelCase form (e.g. spotify.savedTracks)\",\n },\n);\n\nexport type Scope = z.infer<typeof ScopeSchema>;\n\nexport interface ParsedScope {\n source: string;\n category: string;\n subcategory?: string;\n raw: string;\n}\n\nexport function parseScope(scope: string): ParsedScope {\n const validated = ScopeSchema.parse(scope);\n const parts = validated.split(\".\");\n return {\n source: parts[0],\n category: parts[1],\n subcategory: parts[2],\n raw: validated,\n };\n}\n\nexport function scopeToPathSegments(scope: string): string[] {\n const parsed = parseScope(scope);\n const segments = [parsed.source, parsed.category];\n if (parsed.subcategory) {\n segments.push(parsed.subcategory);\n }\n return segments;\n}\n\nexport function scopeMatchesPattern(\n requestedScope: string,\n grantPattern: string,\n): boolean {\n if (grantPattern === \"*\") return true;\n\n if (grantPattern.endsWith(\".*\")) {\n const prefix = grantPattern.slice(0, -1);\n return requestedScope.startsWith(prefix);\n }\n\n return requestedScope === grantPattern;\n}\n\nexport function scopeCoveredByGrant(\n requestedScope: string,\n grantedScopes: string[],\n): boolean {\n return grantedScopes.some((pattern) =>\n scopeMatchesPattern(requestedScope, pattern),\n );\n}\n"],"mappings":"AAAA,SAAS,SAAS;AAMlB,MAAM,YAAY;AAClB,MAAM,kBAAkB;AAEjB,MAAM,cAAc,EAAE,OAAO,EAAE;AAAA,EACpC,CAAC,UAAU;AACT,UAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,QAAI,MAAM,SAAS,KAAK,MAAM,SAAS,EAAG,QAAO;AACjD,UAAM,CAAC,QAAQ,GAAG,IAAI,IAAI;AAC1B,WACE,UAAU,KAAK,MAAM,KAAK,KAAK,MAAM,CAAC,SAAS,gBAAgB,KAAK,IAAI,CAAC;AAAA,EAE7E;AAAA,EACA;AAAA,IACE,SACE;AAAA,EACJ;AACF;AAWO,SAAS,WAAW,OAA4B;AACrD,QAAM,YAAY,YAAY,MAAM,KAAK;AACzC,QAAM,QAAQ,UAAU,MAAM,GAAG;AACjC,SAAO;AAAA,IACL,QAAQ,MAAM,CAAC;AAAA,IACf,UAAU,MAAM,CAAC;AAAA,IACjB,aAAa,MAAM,CAAC;AAAA,IACpB,KAAK;AAAA,EACP;AACF;AAEO,SAAS,oBAAoB,OAAyB;AAC3D,QAAM,SAAS,WAAW,KAAK;AAC/B,QAAM,WAAW,CAAC,OAAO,QAAQ,OAAO,QAAQ;AAChD,MAAI,OAAO,aAAa;AACtB,aAAS,KAAK,OAAO,WAAW;AAAA,EAClC;AACA,SAAO;AACT;AAEO,SAAS,oBACd,gBACA,cACS;AACT,MAAI,iBAAiB,IAAK,QAAO;AAEjC,MAAI,aAAa,SAAS,IAAI,GAAG;AAC/B,UAAM,SAAS,aAAa,MAAM,GAAG,EAAE;AACvC,WAAO,eAAe,WAAW,MAAM;AAAA,EACzC;AAEA,SAAO,mBAAmB;AAC5B;AAEO,SAAS,oBACd,gBACA,eACS;AACT,SAAO,cAAc;AAAA,IAAK,CAAC,YACzB,oBAAoB,gBAAgB,OAAO;AAAA,EAC7C;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/protocol/scopes.ts"],"sourcesContent":["import { z } from \"zod\";\n\n// The source_id (first segment) must stay lowercase. Tail segments may keep\n// the historical camelCase form the contract-freeze spec explicitly preserves\n// (e.g. spotify.savedTracks, youtube.playlistItems) — the chain never validates\n// scope strings, so the SDK must not be stricter than the protocol.\nconst SOURCE_RE = /^[a-z0-9][a-z0-9_]*$/;\nconst TAIL_SEGMENT_RE = /^[a-zA-Z0-9][a-zA-Z0-9_]*$/;\n\nexport const ScopeSchema = z.string().refine(\n (scope) => {\n const parts = scope.split(\".\");\n if (parts.length < 2 || parts.length > 3) return false;\n const [source, ...tail] = parts;\n return (\n SOURCE_RE.test(source) && tail.every((part) => TAIL_SEGMENT_RE.test(part))\n );\n },\n {\n message:\n \"Scope must be {source}.{category}[.{subcategory}]; source lowercase, tail may keep the historical camelCase form (e.g. spotify.savedTracks)\",\n },\n);\n\nexport type Scope = z.infer<typeof ScopeSchema>;\n\nexport interface ParsedScope {\n source: string;\n category: string;\n subcategory?: string;\n raw: string;\n}\n\nexport function parseScope(scope: string): ParsedScope {\n const validated = ScopeSchema.parse(scope);\n const parts = validated.split(\".\");\n return {\n source: parts[0],\n category: parts[1],\n subcategory: parts[2],\n raw: validated,\n };\n}\n\nexport function scopeToPathSegments(scope: string): string[] {\n const parsed = parseScope(scope);\n const segments = [parsed.source, parsed.category];\n if (parsed.subcategory) {\n segments.push(parsed.subcategory);\n }\n return segments;\n}\n\nexport function scopeMatchesPattern(\n requestedScope: string,\n grantPattern: string,\n): boolean {\n if (grantPattern === \"*\") return true;\n\n if (grantPattern.endsWith(\".*\")) {\n const prefix = grantPattern.slice(0, -1);\n return requestedScope.startsWith(prefix);\n }\n\n return requestedScope === grantPattern;\n}\n\nexport function scopeCoveredByGrant(\n requestedScope: string,\n grantedScopes: string[],\n): boolean {\n return grantedScopes.some((pattern) =>\n scopeMatchesPattern(requestedScope, pattern),\n );\n}\n\n// PDPP grants map to scope keys in this same grammar; re-exported so the\n// scope contract has one import path (`@opendatalabs/vana-sdk/protocol/scopes`).\nexport {\n PDPP_SCOPE_NAMESPACE,\n PDPP_SOURCE_ID_PREFIX,\n InvalidPdppGrantError,\n pdppScopeKey,\n parsePdppScopeKey,\n isPdppScopeKey,\n pdppGrantsToScopes,\n grantRequestScopes,\n type PdppGrant,\n type PdppScopeKeyParts,\n} from \"./pdpp-scopes\";\n"],"mappings":"AAAA,SAAS,SAAS;AAMlB,MAAM,YAAY;AAClB,MAAM,kBAAkB;AAEjB,MAAM,cAAc,EAAE,OAAO,EAAE;AAAA,EACpC,CAAC,UAAU;AACT,UAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,QAAI,MAAM,SAAS,KAAK,MAAM,SAAS,EAAG,QAAO;AACjD,UAAM,CAAC,QAAQ,GAAG,IAAI,IAAI;AAC1B,WACE,UAAU,KAAK,MAAM,KAAK,KAAK,MAAM,CAAC,SAAS,gBAAgB,KAAK,IAAI,CAAC;AAAA,EAE7E;AAAA,EACA;AAAA,IACE,SACE;AAAA,EACJ;AACF;AAWO,SAAS,WAAW,OAA4B;AACrD,QAAM,YAAY,YAAY,MAAM,KAAK;AACzC,QAAM,QAAQ,UAAU,MAAM,GAAG;AACjC,SAAO;AAAA,IACL,QAAQ,MAAM,CAAC;AAAA,IACf,UAAU,MAAM,CAAC;AAAA,IACjB,aAAa,MAAM,CAAC;AAAA,IACpB,KAAK;AAAA,EACP;AACF;AAEO,SAAS,oBAAoB,OAAyB;AAC3D,QAAM,SAAS,WAAW,KAAK;AAC/B,QAAM,WAAW,CAAC,OAAO,QAAQ,OAAO,QAAQ;AAChD,MAAI,OAAO,aAAa;AACtB,aAAS,KAAK,OAAO,WAAW;AAAA,EAClC;AACA,SAAO;AACT;AAEO,SAAS,oBACd,gBACA,cACS;AACT,MAAI,iBAAiB,IAAK,QAAO;AAEjC,MAAI,aAAa,SAAS,IAAI,GAAG;AAC/B,UAAM,SAAS,aAAa,MAAM,GAAG,EAAE;AACvC,WAAO,eAAe,WAAW,MAAM;AAAA,EACzC;AAEA,SAAO,mBAAmB;AAC5B;AAEO,SAAS,oBACd,gBACA,eACS;AACT,SAAO,cAAc;AAAA,IAAK,CAAC,YACzB,oBAAoB,gBAAgB,OAAO;AAAA,EAC7C;AACF;AAIA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;","names":[]}