@hatk/hatk 0.0.1-alpha.11 → 0.0.1-alpha.111

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 (224) hide show
  1. package/dist/adapter.d.ts +19 -0
  2. package/dist/adapter.d.ts.map +1 -0
  3. package/dist/adapter.js +113 -0
  4. package/dist/backfill.d.ts +36 -2
  5. package/dist/backfill.d.ts.map +1 -1
  6. package/dist/backfill.js +153 -37
  7. package/dist/car.js +1 -1
  8. package/dist/classifiers.d.ts +228 -0
  9. package/dist/classifiers.d.ts.map +1 -0
  10. package/dist/classifiers.js +688 -0
  11. package/dist/cli.js +248 -1043
  12. package/dist/config.d.ts +179 -1
  13. package/dist/config.d.ts.map +1 -1
  14. package/dist/config.js +78 -8
  15. package/dist/database/adapter-factory.d.ts +6 -0
  16. package/dist/database/adapter-factory.d.ts.map +1 -0
  17. package/dist/database/adapter-factory.js +20 -0
  18. package/dist/database/adapters/duckdb-search.d.ts +12 -0
  19. package/dist/database/adapters/duckdb-search.d.ts.map +1 -0
  20. package/dist/database/adapters/duckdb-search.js +27 -0
  21. package/dist/database/adapters/duckdb.d.ts +25 -0
  22. package/dist/database/adapters/duckdb.d.ts.map +1 -0
  23. package/dist/database/adapters/duckdb.js +161 -0
  24. package/dist/database/adapters/sqlite-search.d.ts +16 -0
  25. package/dist/database/adapters/sqlite-search.d.ts.map +1 -0
  26. package/dist/database/adapters/sqlite-search.js +76 -0
  27. package/dist/database/adapters/sqlite.d.ts +18 -0
  28. package/dist/database/adapters/sqlite.d.ts.map +1 -0
  29. package/dist/database/adapters/sqlite.js +88 -0
  30. package/dist/database/db.d.ts +290 -0
  31. package/dist/database/db.d.ts.map +1 -0
  32. package/dist/database/db.js +2073 -0
  33. package/dist/database/dialect.d.ts +45 -0
  34. package/dist/database/dialect.d.ts.map +1 -0
  35. package/dist/database/dialect.js +72 -0
  36. package/dist/{fts.d.ts → database/fts.d.ts} +7 -0
  37. package/dist/database/fts.d.ts.map +1 -0
  38. package/dist/{fts.js → database/fts.js} +122 -34
  39. package/dist/database/index.d.ts +7 -0
  40. package/dist/database/index.d.ts.map +1 -0
  41. package/dist/database/index.js +6 -0
  42. package/dist/database/ports.d.ts +50 -0
  43. package/dist/database/ports.d.ts.map +1 -0
  44. package/dist/database/ports.js +1 -0
  45. package/dist/database/schema.d.ts +91 -0
  46. package/dist/database/schema.d.ts.map +1 -0
  47. package/dist/{schema.js → database/schema.js} +153 -54
  48. package/dist/dev-entry.d.ts +8 -0
  49. package/dist/dev-entry.d.ts.map +1 -0
  50. package/dist/dev-entry.js +131 -0
  51. package/dist/feeds.d.ts +26 -8
  52. package/dist/feeds.d.ts.map +1 -1
  53. package/dist/feeds.js +72 -16
  54. package/dist/hooks.d.ts +102 -0
  55. package/dist/hooks.d.ts.map +1 -0
  56. package/dist/hooks.js +186 -0
  57. package/dist/hydrate.d.ts +55 -6
  58. package/dist/hydrate.d.ts.map +1 -1
  59. package/dist/hydrate.js +21 -20
  60. package/dist/indexer.d.ts +194 -3
  61. package/dist/indexer.d.ts.map +1 -1
  62. package/dist/indexer.js +627 -92
  63. package/dist/jetstream.d.ts +118 -0
  64. package/dist/jetstream.d.ts.map +1 -0
  65. package/dist/jetstream.js +238 -0
  66. package/dist/labels.d.ts +36 -0
  67. package/dist/labels.d.ts.map +1 -1
  68. package/dist/labels.js +71 -6
  69. package/dist/lex-types.d.ts +2 -0
  70. package/dist/lex-types.d.ts.map +1 -1
  71. package/dist/lexicon-resolve.d.ts.map +1 -1
  72. package/dist/lexicon-resolve.js +27 -112
  73. package/dist/lexicons/com/atproto/label/defs.json +75 -0
  74. package/dist/lexicons/com/atproto/moderation/defs.json +30 -0
  75. package/dist/lexicons/com/atproto/repo/strongRef.json +21 -0
  76. package/dist/lexicons/dev/hatk/applyWrites.json +87 -0
  77. package/dist/lexicons/dev/hatk/createRecord.json +40 -0
  78. package/dist/lexicons/dev/hatk/createReport.json +48 -0
  79. package/dist/lexicons/dev/hatk/deleteRecord.json +25 -0
  80. package/dist/lexicons/dev/hatk/describeCollections.json +41 -0
  81. package/dist/lexicons/dev/hatk/describeFeeds.json +29 -0
  82. package/dist/lexicons/dev/hatk/describeLabels.json +45 -0
  83. package/dist/lexicons/dev/hatk/getFeed.json +30 -0
  84. package/dist/lexicons/dev/hatk/getPreferences.json +19 -0
  85. package/dist/lexicons/dev/hatk/getRecord.json +26 -0
  86. package/dist/lexicons/dev/hatk/getRecords.json +32 -0
  87. package/dist/lexicons/dev/hatk/putPreference.json +28 -0
  88. package/dist/lexicons/dev/hatk/putRecord.json +41 -0
  89. package/dist/lexicons/dev/hatk/searchRecords.json +32 -0
  90. package/dist/lexicons/dev/hatk/uploadBlob.json +23 -0
  91. package/dist/logger.d.ts +29 -0
  92. package/dist/logger.d.ts.map +1 -1
  93. package/dist/logger.js +29 -0
  94. package/dist/main.js +212 -75
  95. package/dist/mst.d.ts +15 -0
  96. package/dist/mst.d.ts.map +1 -1
  97. package/dist/mst.js +13 -0
  98. package/dist/oauth/db.d.ts +27 -1
  99. package/dist/oauth/db.d.ts.map +1 -1
  100. package/dist/oauth/db.js +80 -19
  101. package/dist/oauth/discovery.d.ts +10 -0
  102. package/dist/oauth/discovery.d.ts.map +1 -1
  103. package/dist/oauth/discovery.js +54 -2
  104. package/dist/oauth/server.d.ts +131 -1
  105. package/dist/oauth/server.d.ts.map +1 -1
  106. package/dist/oauth/server.js +504 -39
  107. package/dist/oauth/service-describe.d.ts +42 -0
  108. package/dist/oauth/service-describe.d.ts.map +1 -0
  109. package/dist/oauth/service-describe.js +108 -0
  110. package/dist/oauth/session.d.ts +32 -0
  111. package/dist/oauth/session.d.ts.map +1 -0
  112. package/dist/oauth/session.js +131 -0
  113. package/dist/opengraph.d.ts +10 -0
  114. package/dist/opengraph.d.ts.map +1 -1
  115. package/dist/opengraph.js +94 -42
  116. package/dist/pds-proxy.d.ts +92 -0
  117. package/dist/pds-proxy.d.ts.map +1 -0
  118. package/dist/pds-proxy.js +396 -0
  119. package/dist/private-collections.d.ts +13 -0
  120. package/dist/private-collections.d.ts.map +1 -0
  121. package/dist/private-collections.js +27 -0
  122. package/dist/push.d.ts +54 -0
  123. package/dist/push.d.ts.map +1 -0
  124. package/dist/push.js +367 -0
  125. package/dist/renderer.d.ts +27 -0
  126. package/dist/renderer.d.ts.map +1 -0
  127. package/dist/renderer.js +46 -0
  128. package/dist/resolve-hatk.d.ts +6 -0
  129. package/dist/resolve-hatk.d.ts.map +1 -0
  130. package/dist/resolve-hatk.js +20 -0
  131. package/dist/response.d.ts +16 -0
  132. package/dist/response.d.ts.map +1 -0
  133. package/dist/response.js +69 -0
  134. package/dist/scanner.d.ts +22 -0
  135. package/dist/scanner.d.ts.map +1 -0
  136. package/dist/scanner.js +92 -0
  137. package/dist/seed.d.ts +19 -0
  138. package/dist/seed.d.ts.map +1 -1
  139. package/dist/seed.js +43 -4
  140. package/dist/server-init.d.ts +8 -0
  141. package/dist/server-init.d.ts.map +1 -0
  142. package/dist/server-init.js +75 -0
  143. package/dist/server.d.ts +46 -3
  144. package/dist/server.d.ts.map +1 -1
  145. package/dist/server.js +1137 -649
  146. package/dist/setup.d.ts +28 -1
  147. package/dist/setup.d.ts.map +1 -1
  148. package/dist/setup.js +50 -3
  149. package/dist/spaces/blob.d.ts +112 -0
  150. package/dist/spaces/blob.d.ts.map +1 -0
  151. package/dist/spaces/blob.js +460 -0
  152. package/dist/spaces/credential.d.ts +82 -0
  153. package/dist/spaces/credential.d.ts.map +1 -0
  154. package/dist/spaces/credential.js +220 -0
  155. package/dist/spaces/engine.d.ts +97 -0
  156. package/dist/spaces/engine.d.ts.map +1 -0
  157. package/dist/spaces/engine.js +563 -0
  158. package/dist/spaces/guard.d.ts +41 -0
  159. package/dist/spaces/guard.d.ts.map +1 -0
  160. package/dist/spaces/guard.js +79 -0
  161. package/dist/spaces/identity.d.ts +37 -0
  162. package/dist/spaces/identity.d.ts.map +1 -0
  163. package/dist/spaces/identity.js +93 -0
  164. package/dist/spaces/index.d.ts +56 -0
  165. package/dist/spaces/index.d.ts.map +1 -0
  166. package/dist/spaces/index.js +134 -0
  167. package/dist/spaces/notify.d.ts +60 -0
  168. package/dist/spaces/notify.d.ts.map +1 -0
  169. package/dist/spaces/notify.js +128 -0
  170. package/dist/spaces/records.d.ts +37 -0
  171. package/dist/spaces/records.d.ts.map +1 -0
  172. package/dist/spaces/records.js +63 -0
  173. package/dist/spaces/signature.d.ts +36 -0
  174. package/dist/spaces/signature.d.ts.map +1 -0
  175. package/dist/spaces/signature.js +83 -0
  176. package/dist/spaces/store.d.ts +51 -0
  177. package/dist/spaces/store.d.ts.map +1 -0
  178. package/dist/spaces/store.js +99 -0
  179. package/dist/spaces/uri.d.ts +60 -0
  180. package/dist/spaces/uri.d.ts.map +1 -0
  181. package/dist/spaces/uri.js +97 -0
  182. package/dist/spaces/verify.d.ts +46 -0
  183. package/dist/spaces/verify.d.ts.map +1 -0
  184. package/dist/spaces/verify.js +94 -0
  185. package/dist/spaces/viewer.d.ts +51 -0
  186. package/dist/spaces/viewer.d.ts.map +1 -0
  187. package/dist/spaces/viewer.js +167 -0
  188. package/dist/spaces/visibility.d.ts +57 -0
  189. package/dist/spaces/visibility.d.ts.map +1 -0
  190. package/dist/spaces/visibility.js +81 -0
  191. package/dist/templates/feed.tpl +18 -0
  192. package/dist/templates/hook.tpl +5 -0
  193. package/dist/templates/label.tpl +15 -0
  194. package/dist/templates/og.tpl +17 -0
  195. package/dist/templates/seed.tpl +11 -0
  196. package/dist/templates/setup.tpl +5 -0
  197. package/dist/templates/test-feed.tpl +19 -0
  198. package/dist/templates/test-xrpc.tpl +19 -0
  199. package/dist/templates/xrpc.tpl +53 -0
  200. package/dist/test.d.ts +3 -1
  201. package/dist/test.d.ts.map +1 -1
  202. package/dist/test.js +60 -36
  203. package/dist/views.js +1 -1
  204. package/dist/vite-plugin.d.ts +1 -1
  205. package/dist/vite-plugin.d.ts.map +1 -1
  206. package/dist/vite-plugin.js +254 -66
  207. package/dist/xrpc.d.ts +143 -12
  208. package/dist/xrpc.d.ts.map +1 -1
  209. package/dist/xrpc.js +282 -55
  210. package/package.json +29 -9
  211. package/public/admin-auth.js +271 -306
  212. package/public/admin.html +652 -61
  213. package/dist/db.d.ts +0 -135
  214. package/dist/db.d.ts.map +0 -1
  215. package/dist/db.js +0 -1365
  216. package/dist/fts.d.ts.map +0 -1
  217. package/dist/oauth/hooks.d.ts +0 -10
  218. package/dist/oauth/hooks.d.ts.map +0 -1
  219. package/dist/oauth/hooks.js +0 -40
  220. package/dist/schema.d.ts +0 -51
  221. package/dist/schema.d.ts.map +0 -1
  222. package/dist/test-browser.d.ts +0 -14
  223. package/dist/test-browser.d.ts.map +0 -1
  224. package/dist/test-browser.js +0 -26
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The HTTP message signature a space credential travels with.
3
+ *
4
+ * RFC 9421, under the label `atproto-space`, signed ecdsa-p256-sha256 by a key
5
+ * made for one credential and named by its did:key. Two shapes:
6
+ *
7
+ * exchange the delegation token is signed alone, and the signature names
8
+ * its key — that key is what the authority binds the credential
9
+ * to (`cnf.kid`)
10
+ * use the credential is signed together with the DID the request is
11
+ * addressed to, so a credential lifted off one request cannot be
12
+ * replayed at another account by anyone without the key
13
+ *
14
+ * The signature is the raw 64-byte r || s, normalized to low-S: the reference
15
+ * verifier accepts either, but a stricter one (noble's default) only low-S.
16
+ */
17
+ /** Encode base58btc. The inverse of `decodeBase58btc` in verify.ts. */
18
+ export declare function encodeBase58btc(bytes: Uint8Array): string;
19
+ export interface SpaceSigKey {
20
+ /** The public key as a P-256 did:key. */
21
+ did: string;
22
+ sign: (data: Uint8Array) => Promise<Uint8Array>;
23
+ }
24
+ /** A fresh P-256 key, held in memory only and never exported. */
25
+ export declare function generateSpaceSigKey(): Promise<SpaceSigKey>;
26
+ /**
27
+ * The bytes a signature covers: one line per covered field, then the
28
+ * signature parameters, joined by LF with no trailing newline.
29
+ */
30
+ export declare function signatureBase(authorization: string, params: string, audience?: string): Uint8Array;
31
+ /**
32
+ * Authorization, audience and signature headers for a space request. Without
33
+ * an audience this is the exchange; with one, a use of the credential.
34
+ */
35
+ export declare function spaceSigHeaders(key: SpaceSigKey, authorization: string, audience?: string): Promise<Record<string, string>>;
36
+ //# sourceMappingURL=signature.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signature.d.ts","sourceRoot":"","sources":["../../src/spaces/signature.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAQH,uEAAuE;AACvE,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAqBzD;AAED,MAAM,WAAW,WAAW;IAC1B,yCAAyC;IACzC,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,CAAA;CAChD;AAED,iEAAiE;AACjE,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,WAAW,CAAC,CAYhE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,UAAU,CAKlG;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,WAAW,EAChB,aAAa,EAAE,MAAM,EACrB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAUjC"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * The HTTP message signature a space credential travels with.
3
+ *
4
+ * RFC 9421, under the label `atproto-space`, signed ecdsa-p256-sha256 by a key
5
+ * made for one credential and named by its did:key. Two shapes:
6
+ *
7
+ * exchange the delegation token is signed alone, and the signature names
8
+ * its key — that key is what the authority binds the credential
9
+ * to (`cnf.kid`)
10
+ * use the credential is signed together with the DID the request is
11
+ * addressed to, so a credential lifted off one request cannot be
12
+ * replayed at another account by anyone without the key
13
+ *
14
+ * The signature is the raw 64-byte r || s, normalized to low-S: the reference
15
+ * verifier accepts either, but a stricter one (noble's default) only low-S.
16
+ */
17
+ import { signEs256 } from "../oauth/crypto.js";
18
+ const LABEL = 'atproto-space';
19
+ const B58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
20
+ /** Encode base58btc. The inverse of `decodeBase58btc` in verify.ts. */
21
+ export function encodeBase58btc(bytes) {
22
+ const digits = [0];
23
+ for (const byte of bytes) {
24
+ let carry = byte;
25
+ for (let i = 0; i < digits.length; i++) {
26
+ carry += digits[i] << 8;
27
+ digits[i] = carry % 58;
28
+ carry = (carry / 58) | 0;
29
+ }
30
+ while (carry) {
31
+ digits.push(carry % 58);
32
+ carry = (carry / 58) | 0;
33
+ }
34
+ }
35
+ let out = '';
36
+ for (const byte of bytes) {
37
+ if (byte !== 0)
38
+ break;
39
+ out += '1';
40
+ }
41
+ for (let i = digits.length - 1; i >= 0; i--)
42
+ out += B58[digits[i]];
43
+ return out;
44
+ }
45
+ /** A fresh P-256 key, held in memory only and never exported. */
46
+ export async function generateSpaceSigKey() {
47
+ const pair = await crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign', 'verify']);
48
+ // 0x04 || x || y. A did:key carries the compressed point behind the
49
+ // p256-pub multicodec prefix (0x1200, varint 0x80 0x24).
50
+ const raw = new Uint8Array(await crypto.subtle.exportKey('raw', pair.publicKey));
51
+ const compressed = new Uint8Array(33);
52
+ compressed[0] = raw[64] & 1 ? 0x03 : 0x02;
53
+ compressed.set(raw.subarray(1, 33), 1);
54
+ return {
55
+ did: `did:key:z${encodeBase58btc(new Uint8Array([0x80, 0x24, ...compressed]))}`,
56
+ sign: (data) => signEs256(pair.privateKey, data),
57
+ };
58
+ }
59
+ /**
60
+ * The bytes a signature covers: one line per covered field, then the
61
+ * signature parameters, joined by LF with no trailing newline.
62
+ */
63
+ export function signatureBase(authorization, params, audience) {
64
+ const lines = [`"authorization": ${authorization}`];
65
+ if (audience !== undefined)
66
+ lines.push(`"atproto-space-audience": ${audience}`);
67
+ lines.push(`"@signature-params": ${params}`);
68
+ return new TextEncoder().encode(lines.join('\n'));
69
+ }
70
+ /**
71
+ * Authorization, audience and signature headers for a space request. Without
72
+ * an audience this is the exchange; with one, a use of the credential.
73
+ */
74
+ export async function spaceSigHeaders(key, authorization, audience) {
75
+ const params = audience === undefined ? `("authorization");keyid="${key.did}"` : '("authorization" "atproto-space-audience")';
76
+ const signature = await key.sign(signatureBase(authorization, params, audience));
77
+ return {
78
+ Authorization: authorization,
79
+ ...(audience !== undefined ? { 'Atproto-Space-Audience': audience } : {}),
80
+ 'Signature-Input': `${LABEL}=${params}`,
81
+ Signature: `${LABEL}=:${Buffer.from(signature).toString('base64')}:`,
82
+ };
83
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Sync state for permissioned spaces: which spaces this instance follows, and
3
+ * how far each writer's repo in them has been read.
4
+ *
5
+ * Deliberately separate from `_repos`, which is firehose and backfill
6
+ * bookkeeping keyed by DID alone. A writer's position is per space — the same
7
+ * account can hold a repo in several, at different revisions — so a DID is not
8
+ * a key here.
9
+ */
10
+ export interface SpaceWatch {
11
+ space: string;
12
+ authority: string;
13
+ spaceType: string;
14
+ /** The account whose session last minted a credential for this space, if any. */
15
+ readerDid: string | null;
16
+ /** Expiry of the current `registerNotify` registration, ISO 8601. */
17
+ registeredUntil: string | null;
18
+ lastError: string | null;
19
+ }
20
+ export interface SpaceRepo {
21
+ space: string;
22
+ did: string;
23
+ /** The PDS holding this writer's repo — their own, not the authority's. */
24
+ pds: string | null;
25
+ /** The revision this writer's repo had been read to. */
26
+ rev: string | null;
27
+ }
28
+ export declare function listSpaceWatches(): Promise<SpaceWatch[]>;
29
+ export declare function getSpaceWatch(space: string): Promise<SpaceWatch | null>;
30
+ /**
31
+ * Start following a space, or refresh what is known about one already followed.
32
+ *
33
+ * Never clears `reader_did` or `registered_until`: a second call to watch an
34
+ * existing space is a re-assertion of interest, not a reset of its progress.
35
+ */
36
+ export declare function putSpaceWatch(input: {
37
+ space: string;
38
+ authority: string;
39
+ spaceType: string;
40
+ }): Promise<void>;
41
+ export declare function updateSpaceWatch(space: string, patch: {
42
+ readerDid?: string | null;
43
+ registeredUntil?: string | null;
44
+ lastError?: string | null;
45
+ }): Promise<void>;
46
+ export declare function deleteSpaceWatch(space: string): Promise<void>;
47
+ export declare function listSpaceRepos(space: string): Promise<SpaceRepo[]>;
48
+ export declare function getSpaceRepo(space: string, did: string): Promise<SpaceRepo | null>;
49
+ export declare function putSpaceRepo(input: SpaceRepo): Promise<void>;
50
+ export declare function deleteSpaceRepo(space: string, did: string): Promise<void>;
51
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/spaces/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,iFAAiF;IACjF,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,qEAAqE;IACrE,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,2EAA2E;IAC3E,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,wDAAwD;IACxD,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CACnB;AA6BD,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAG9D;AAED,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAG7E;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBjH;AAED,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC/F,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGnE;AAED,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAKxE;AAED,wBAAsB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAMxF;AAED,wBAAsB,YAAY,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAQlE;AAED,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/E"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Sync state for permissioned spaces: which spaces this instance follows, and
3
+ * how far each writer's repo in them has been read.
4
+ *
5
+ * Deliberately separate from `_repos`, which is firehose and backfill
6
+ * bookkeeping keyed by DID alone. A writer's position is per space — the same
7
+ * account can hold a repo in several, at different revisions — so a DID is not
8
+ * a key here.
9
+ */
10
+ import { querySQL, runSQL } from "../database/db.js";
11
+ const toWatch = (row) => ({
12
+ space: row.space,
13
+ authority: row.authority,
14
+ spaceType: row.space_type,
15
+ readerDid: row.reader_did,
16
+ registeredUntil: row.registered_until,
17
+ lastError: row.last_error,
18
+ });
19
+ const WATCH_COLS = 'space, authority, space_type, reader_did, registered_until, last_error';
20
+ export async function listSpaceWatches() {
21
+ const rows = (await querySQL(`SELECT ${WATCH_COLS} FROM _space_watch ORDER BY space`));
22
+ return rows.map(toWatch);
23
+ }
24
+ export async function getSpaceWatch(space) {
25
+ const rows = (await querySQL(`SELECT ${WATCH_COLS} FROM _space_watch WHERE space = $1`, [space]));
26
+ return rows[0] ? toWatch(rows[0]) : null;
27
+ }
28
+ /**
29
+ * Start following a space, or refresh what is known about one already followed.
30
+ *
31
+ * Never clears `reader_did` or `registered_until`: a second call to watch an
32
+ * existing space is a re-assertion of interest, not a reset of its progress.
33
+ */
34
+ export async function putSpaceWatch(input) {
35
+ const existing = await getSpaceWatch(input.space);
36
+ if (existing) {
37
+ await runSQL(`UPDATE _space_watch SET authority = $1, space_type = $2, updated_at = $3 WHERE space = $4`, [
38
+ input.authority,
39
+ input.spaceType,
40
+ new Date().toISOString(),
41
+ input.space,
42
+ ]);
43
+ return;
44
+ }
45
+ await runSQL(`INSERT INTO _space_watch (space, authority, space_type, reader_did, registered_until, last_error, updated_at)
46
+ VALUES ($1, $2, $3, NULL, NULL, NULL, $4)`, [input.space, input.authority, input.spaceType, new Date().toISOString()]);
47
+ }
48
+ export async function updateSpaceWatch(space, patch) {
49
+ const sets = [];
50
+ const params = [];
51
+ let idx = 1;
52
+ if (patch.readerDid !== undefined) {
53
+ sets.push(`reader_did = $${idx++}`);
54
+ params.push(patch.readerDid);
55
+ }
56
+ if (patch.registeredUntil !== undefined) {
57
+ sets.push(`registered_until = $${idx++}`);
58
+ params.push(patch.registeredUntil);
59
+ }
60
+ if (patch.lastError !== undefined) {
61
+ sets.push(`last_error = $${idx++}`);
62
+ params.push(patch.lastError);
63
+ }
64
+ if (sets.length === 0)
65
+ return;
66
+ sets.push(`updated_at = $${idx++}`);
67
+ params.push(new Date().toISOString());
68
+ params.push(space);
69
+ await runSQL(`UPDATE _space_watch SET ${sets.join(', ')} WHERE space = $${idx}`, params);
70
+ }
71
+ export async function deleteSpaceWatch(space) {
72
+ await runSQL(`DELETE FROM _space_watch WHERE space = $1`, [space]);
73
+ await runSQL(`DELETE FROM _space_repos WHERE space = $1`, [space]);
74
+ }
75
+ export async function listSpaceRepos(space) {
76
+ const rows = (await querySQL(`SELECT space, did, pds, rev FROM _space_repos WHERE space = $1 ORDER BY did`, [
77
+ space,
78
+ ]));
79
+ return rows;
80
+ }
81
+ export async function getSpaceRepo(space, did) {
82
+ const rows = (await querySQL(`SELECT space, did, pds, rev FROM _space_repos WHERE space = $1 AND did = $2`, [
83
+ space,
84
+ did,
85
+ ]));
86
+ return rows[0] ?? null;
87
+ }
88
+ export async function putSpaceRepo(input) {
89
+ await runSQL(`INSERT OR REPLACE INTO _space_repos (space, did, pds, rev, synced_at) VALUES ($1, $2, $3, $4, $5)`, [
90
+ input.space,
91
+ input.did,
92
+ input.pds,
93
+ input.rev,
94
+ new Date().toISOString(),
95
+ ]);
96
+ }
97
+ export async function deleteSpaceRepo(space, did) {
98
+ await runSQL(`DELETE FROM _space_repos WHERE space = $1 AND did = $2`, [space, did]);
99
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * AT-URIs for permissioned spaces (proposal 0016).
3
+ *
4
+ * A space is named by a three-segment ref, and a record inside one carries the
5
+ * writer, collection and rkey after it:
6
+ *
7
+ * at://{authority}/space/{type}/{skey} — the space
8
+ * at://{authority}/space/{type}/{skey}/{writer}/{collection}/{rkey} — a record in it
9
+ *
10
+ * Seven segments where an ordinary record URI has five, and `space` sitting
11
+ * where a collection would. Anything that reads a collection out of a URI by
12
+ * position has to know the difference or it reads back the literal 'space'.
13
+ *
14
+ * The authority is always a DID rather than a handle: a space's membership is
15
+ * keyed on DIDs, so a handle could not be resolved to a member.
16
+ */
17
+ export interface SpaceRef {
18
+ /** The DID the space is anchored on, and the only party that issues credentials for it. */
19
+ authority: string;
20
+ /** The space type NSID, whose lexicon declares which collections the space holds. */
21
+ type: string;
22
+ skey: string;
23
+ }
24
+ export interface SpaceRecordRef extends SpaceRef {
25
+ /** The three-segment ref of the space this record lives in. */
26
+ space: string;
27
+ /** The account whose repo holds the record. Records stay with their writer. */
28
+ writer: string;
29
+ collection: string;
30
+ rkey: string;
31
+ }
32
+ export declare function spaceRefUri(ref: SpaceRef): string;
33
+ /** Parse `at://{authority}/space/{type}/{skey}` — the space itself, not a record in it. */
34
+ export declare function parseSpaceRef(uri: string): SpaceRef | null;
35
+ /** Whether a URI names a space or anything inside one. */
36
+ export declare function isSpaceUri(uri: string): boolean;
37
+ /** `at://{authority}/space/{type}/{skey}` from its parts; `self` is the skey a singleton space uses. */
38
+ export declare function spaceUri(authority: string, type: string, skey?: string): string;
39
+ /** The record key: the last segment of either URI shape. */
40
+ export declare function rkeyOf(uri: string): string;
41
+ export declare function spaceRecordUri(space: string, writer: string, collection: string, rkey: string): string;
42
+ export declare function parseSpaceRecordUri(uri: string): SpaceRecordRef | null;
43
+ /** The space a record URI belongs to, or undefined for an ordinary repo URI. */
44
+ export declare function spaceFromUri(uri: string): string | undefined;
45
+ /**
46
+ * The collection a record URI names, whichever shape it is.
47
+ *
48
+ * The one place the two URI forms are reconciled; every positional read of a
49
+ * collection goes through here.
50
+ */
51
+ export declare function collectionFromRecordUri(uri: string): string | undefined;
52
+ /**
53
+ * The audience a delegation token for this space is addressed to.
54
+ *
55
+ * An authority that publishes no `#atproto_space_host` entry is still reached
56
+ * at its `#atproto_pds` endpoint — the fragment names the audience, not the
57
+ * address.
58
+ */
59
+ export declare function spaceHostAud(authority: string): string;
60
+ //# sourceMappingURL=uri.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uri.d.ts","sourceRoot":"","sources":["../../src/spaces/uri.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,MAAM,WAAW,QAAQ;IACvB,2FAA2F;IAC3F,SAAS,EAAE,MAAM,CAAA;IACjB,qFAAqF;IACrF,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,cAAe,SAAQ,QAAQ;IAC9C,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAA;IACb,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;CACb;AAOD,wBAAgB,WAAW,CAAC,GAAG,EAAE,QAAQ,GAAG,MAAM,CAEjD;AAED,2FAA2F;AAC3F,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAM1D;AAED,0DAA0D;AAC1D,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAG/C;AAED,wGAAwG;AACxG,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,SAAS,GAAG,MAAM,CAE/E;AAED,4DAA4D;AAC5D,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE1C;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtG;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CActE;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE5D;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKvE;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEtD"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * AT-URIs for permissioned spaces (proposal 0016).
3
+ *
4
+ * A space is named by a three-segment ref, and a record inside one carries the
5
+ * writer, collection and rkey after it:
6
+ *
7
+ * at://{authority}/space/{type}/{skey} — the space
8
+ * at://{authority}/space/{type}/{skey}/{writer}/{collection}/{rkey} — a record in it
9
+ *
10
+ * Seven segments where an ordinary record URI has five, and `space` sitting
11
+ * where a collection would. Anything that reads a collection out of a URI by
12
+ * position has to know the difference or it reads back the literal 'space'.
13
+ *
14
+ * The authority is always a DID rather than a handle: a space's membership is
15
+ * keyed on DIDs, so a handle could not be resolved to a member.
16
+ */
17
+ const SPACE_MARKER = 'space';
18
+ const AT_PREFIX = 'at://';
19
+ function segments(uri) {
20
+ if (!uri.startsWith(AT_PREFIX))
21
+ return null;
22
+ return uri.slice(AT_PREFIX.length).split('/');
23
+ }
24
+ export function spaceRefUri(ref) {
25
+ return `${AT_PREFIX}${ref.authority}/${SPACE_MARKER}/${ref.type}/${ref.skey}`;
26
+ }
27
+ /** Parse `at://{authority}/space/{type}/{skey}` — the space itself, not a record in it. */
28
+ export function parseSpaceRef(uri) {
29
+ const parts = segments(uri);
30
+ if (!parts || parts.length !== 4)
31
+ return null;
32
+ const [authority, marker, type, skey] = parts;
33
+ if (marker !== SPACE_MARKER || !authority.startsWith('did:') || !type || !skey)
34
+ return null;
35
+ return { authority, type, skey };
36
+ }
37
+ /** Whether a URI names a space or anything inside one. */
38
+ export function isSpaceUri(uri) {
39
+ const parts = segments(uri);
40
+ return parts != null && parts.length >= 4 && parts[1] === SPACE_MARKER && parts[0].startsWith('did:');
41
+ }
42
+ /** `at://{authority}/space/{type}/{skey}` from its parts; `self` is the skey a singleton space uses. */
43
+ export function spaceUri(authority, type, skey = 'self') {
44
+ return `${AT_PREFIX}${authority}/${SPACE_MARKER}/${type}/${skey}`;
45
+ }
46
+ /** The record key: the last segment of either URI shape. */
47
+ export function rkeyOf(uri) {
48
+ return uri.slice(uri.lastIndexOf('/') + 1);
49
+ }
50
+ export function spaceRecordUri(space, writer, collection, rkey) {
51
+ return `${space}/${writer}/${collection}/${rkey}`;
52
+ }
53
+ export function parseSpaceRecordUri(uri) {
54
+ const parts = segments(uri);
55
+ if (!parts || parts.length !== 7)
56
+ return null;
57
+ const [authority, marker, type, skey, writer, collection, rkey] = parts;
58
+ if (marker !== SPACE_MARKER || !authority.startsWith('did:') || !collection || !rkey)
59
+ return null;
60
+ return {
61
+ authority,
62
+ type,
63
+ skey,
64
+ writer,
65
+ collection,
66
+ rkey,
67
+ space: `${AT_PREFIX}${authority}/${SPACE_MARKER}/${type}/${skey}`,
68
+ };
69
+ }
70
+ /** The space a record URI belongs to, or undefined for an ordinary repo URI. */
71
+ export function spaceFromUri(uri) {
72
+ return parseSpaceRecordUri(uri)?.space ?? undefined;
73
+ }
74
+ /**
75
+ * The collection a record URI names, whichever shape it is.
76
+ *
77
+ * The one place the two URI forms are reconciled; every positional read of a
78
+ * collection goes through here.
79
+ */
80
+ export function collectionFromRecordUri(uri) {
81
+ const parts = segments(uri);
82
+ if (!parts)
83
+ return undefined;
84
+ if (parts.length === 7 && parts[1] === SPACE_MARKER)
85
+ return parts[5];
86
+ return parts.length >= 3 ? parts[1] : undefined;
87
+ }
88
+ /**
89
+ * The audience a delegation token for this space is addressed to.
90
+ *
91
+ * An authority that publishes no `#atproto_space_host` entry is still reached
92
+ * at its `#atproto_pds` endpoint — the fragment names the audience, not the
93
+ * address.
94
+ */
95
+ export function spaceHostAud(authority) {
96
+ return `${authority}#atproto_space_host`;
97
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Verifying a signature made by somebody else's atproto signing key.
3
+ *
4
+ * Everything hatk signs, it signs with ES256, because it chose its own keys and
5
+ * WebCrypto implements that curve. Everything it has to *verify* here was
6
+ * signed by an account it does not control, and most `did:plc` accounts sign
7
+ * with secp256k1 — a curve WebCrypto does not implement at all. So the two
8
+ * curves atproto allows are handled explicitly, over @noble/curves.
9
+ *
10
+ * A key arrives as a multibase string in a DID document's verification method
11
+ * (`publicKeyMultibase` on a `Multikey`): base58btc, prefixed with a multicodec
12
+ * varint naming the curve, then the compressed point. The prefix is the only
13
+ * thing that says which curve it is, so it is read rather than guessed.
14
+ */
15
+ export type AtprotoCurve = 'secp256k1' | 'p256';
16
+ export declare class SignatureError extends Error {
17
+ }
18
+ /**
19
+ * Decode base58btc. Written out rather than pulled in: it is short, it is the
20
+ * only encoding needed, and a wrong answer here fails closed rather than
21
+ * silently accepting something.
22
+ */
23
+ export declare function decodeBase58btc(input: string): Uint8Array;
24
+ export interface PublicKey {
25
+ curve: AtprotoCurve;
26
+ /** The compressed point, 33 bytes. */
27
+ bytes: Uint8Array;
28
+ }
29
+ /** Read a `publicKeyMultibase` from a DID document's verification method. */
30
+ export declare function parseMultibaseKey(multibase: string): PublicKey;
31
+ /**
32
+ * Verify a raw `r || s` signature over `message`.
33
+ *
34
+ * Low-S is required — atproto mandates it and a malleable signature would let
35
+ * the same assertion be presented twice with different bytes — and is what
36
+ * @noble enforces by default.
37
+ *
38
+ * `prehash: false` says the second argument is already a digest, and is not
39
+ * optional: @noble/curves hashes it again without it, so every signature from
40
+ * a real counterparty is rejected while one this module both makes and checks
41
+ * still matches. A test that signs through the same call cannot see that, so
42
+ * the vectors in the suite are fixed bytes from the library atproto signs
43
+ * with rather than a round trip through this one.
44
+ */
45
+ export declare function verifySignature(key: PublicKey, signature: Uint8Array, message: Uint8Array): boolean;
46
+ //# sourceMappingURL=verify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/spaces/verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAYH,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,MAAM,CAAA;AAE/C,qBAAa,cAAe,SAAQ,KAAK;CAAG;AAE5C;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAuBzD;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,YAAY,CAAA;IACnB,sCAAsC;IACtC,KAAK,EAAE,UAAU,CAAA;CAClB;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAO9D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAUnG"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Verifying a signature made by somebody else's atproto signing key.
3
+ *
4
+ * Everything hatk signs, it signs with ES256, because it chose its own keys and
5
+ * WebCrypto implements that curve. Everything it has to *verify* here was
6
+ * signed by an account it does not control, and most `did:plc` accounts sign
7
+ * with secp256k1 — a curve WebCrypto does not implement at all. So the two
8
+ * curves atproto allows are handled explicitly, over @noble/curves.
9
+ *
10
+ * A key arrives as a multibase string in a DID document's verification method
11
+ * (`publicKeyMultibase` on a `Multikey`): base58btc, prefixed with a multicodec
12
+ * varint naming the curve, then the compressed point. The prefix is the only
13
+ * thing that says which curve it is, so it is read rather than guessed.
14
+ */
15
+ import { p256 } from '@noble/curves/nist.js';
16
+ import { secp256k1 } from '@noble/curves/secp256k1.js';
17
+ import { sha256 } from '@noble/hashes/sha2.js';
18
+ const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
19
+ /** Multicodec varints for the two curves atproto allows. */
20
+ const SECP256K1_PREFIX = [0xe7, 0x01];
21
+ const P256_PREFIX = [0x80, 0x24];
22
+ export class SignatureError extends Error {
23
+ }
24
+ /**
25
+ * Decode base58btc. Written out rather than pulled in: it is short, it is the
26
+ * only encoding needed, and a wrong answer here fails closed rather than
27
+ * silently accepting something.
28
+ */
29
+ export function decodeBase58btc(input) {
30
+ if (input.length === 0)
31
+ return new Uint8Array(0);
32
+ const bytes = [0];
33
+ for (const char of input) {
34
+ const value = BASE58_ALPHABET.indexOf(char);
35
+ if (value < 0)
36
+ throw new SignatureError(`Not base58btc: ${char}`);
37
+ let carry = value;
38
+ for (let i = 0; i < bytes.length; i++) {
39
+ carry += bytes[i] * 58;
40
+ bytes[i] = carry & 0xff;
41
+ carry >>= 8;
42
+ }
43
+ while (carry > 0) {
44
+ bytes.push(carry & 0xff);
45
+ carry >>= 8;
46
+ }
47
+ }
48
+ // Every leading '1' is a leading zero byte that the arithmetic above drops.
49
+ for (const char of input) {
50
+ if (char !== '1')
51
+ break;
52
+ bytes.push(0);
53
+ }
54
+ return new Uint8Array(bytes.reverse());
55
+ }
56
+ /** Read a `publicKeyMultibase` from a DID document's verification method. */
57
+ export function parseMultibaseKey(multibase) {
58
+ if (!multibase.startsWith('z'))
59
+ throw new SignatureError('Key is not base58btc multibase');
60
+ const decoded = decodeBase58btc(multibase.slice(1));
61
+ const starts = (prefix) => prefix.every((b, i) => decoded[i] === b);
62
+ if (starts(SECP256K1_PREFIX))
63
+ return { curve: 'secp256k1', bytes: decoded.slice(SECP256K1_PREFIX.length) };
64
+ if (starts(P256_PREFIX))
65
+ return { curve: 'p256', bytes: decoded.slice(P256_PREFIX.length) };
66
+ throw new SignatureError('Key is neither secp256k1 nor p256');
67
+ }
68
+ /**
69
+ * Verify a raw `r || s` signature over `message`.
70
+ *
71
+ * Low-S is required — atproto mandates it and a malleable signature would let
72
+ * the same assertion be presented twice with different bytes — and is what
73
+ * @noble enforces by default.
74
+ *
75
+ * `prehash: false` says the second argument is already a digest, and is not
76
+ * optional: @noble/curves hashes it again without it, so every signature from
77
+ * a real counterparty is rejected while one this module both makes and checks
78
+ * still matches. A test that signs through the same call cannot see that, so
79
+ * the vectors in the suite are fixed bytes from the library atproto signs
80
+ * with rather than a round trip through this one.
81
+ */
82
+ export function verifySignature(key, signature, message) {
83
+ if (signature.length !== 64)
84
+ return false;
85
+ const digest = sha256(message);
86
+ try {
87
+ const curve = key.curve === 'secp256k1' ? secp256k1 : p256;
88
+ return curve.verify(signature, digest, key.bytes, { prehash: false });
89
+ }
90
+ catch {
91
+ // A malformed point or scalar is a failed verification, not a crash.
92
+ return false;
93
+ }
94
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Which spaces a given viewer may be shown.
3
+ *
4
+ * The question has exactly one authoritative answer, and hatk is not the one
5
+ * holding it: a space's authority decides who may read it, at the moment it
6
+ * mints a credential, from records and rules that live on the community's own
7
+ * host. Re-deriving that here — reading the access and membership records out
8
+ * of the index and evaluating them — would be a second implementation of
9
+ * somebody else's authorization, drifting from it in exactly the cases that
10
+ * matter. Ejected members keep their content readable in the alpha; an invites
11
+ * space is write-for-anyone and read-for-owner; a host may change its rules
12
+ * without telling us.
13
+ *
14
+ * So the check is the real thing: ask the authority to mint a credential as
15
+ * this viewer, and take the answer. It succeeds precisely when the viewer may
16
+ * read the space, because that is the same call their own browser would make.
17
+ *
18
+ * What that costs is a round trip per space, so the answer is cached — which
19
+ * means revocation lags by up to the cache's lifetime. Worth stating plainly:
20
+ * somebody ejected from a community may still be served that community's rows
21
+ * for a few minutes. The alternative is two round trips on every request, and
22
+ * the space host itself already lets a minted credential outlive a revocation
23
+ * by up to ten minutes.
24
+ */
25
+ import type { OAuthConfig } from '../config.ts';
26
+ import { type SpaceCredential } from './credential.ts';
27
+ /**
28
+ * The spaces this viewer may be shown, as the scope for a request.
29
+ *
30
+ * Empty for a signed-out visitor, always: obtaining a credential begins with a
31
+ * delegation token from the reader's own PDS, so there is no anonymous read
32
+ * path into a space at all. `readableBy: ["public"]` means any authenticated
33
+ * reader, never anyone.
34
+ */
35
+ export declare function readableSpacesFor(oauth: OAuthConfig | null, viewer: {
36
+ did: string;
37
+ } | null): Promise<string[]>;
38
+ /**
39
+ * This viewer's own credential for a space, or null if they may not read it.
40
+ *
41
+ * For the reads that cannot go through the index — a blob, which has no public
42
+ * URL by design and must be fetched from the repo that holds it with the same
43
+ * credential as the record naming it.
44
+ */
45
+ export declare function viewerCredential(oauth: OAuthConfig | null, viewer: {
46
+ did: string;
47
+ } | null, space: string): Promise<SpaceCredential | null>;
48
+ /** Drop a viewer's cached answer — after they sign out, or to stop honouring it early. */
49
+ export declare function forgetViewerSpaces(viewerDid: string): void;
50
+ export declare function resetViewerSpaces(): void;
51
+ //# sourceMappingURL=viewer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viewer.d.ts","sourceRoot":"","sources":["../../src/spaces/viewer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE/C,OAAO,EAAoC,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAsGxF;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,EAAE,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAIpH;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,WAAW,GAAG,IAAI,EACzB,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,EAC9B,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAkBjC;AAED,0FAA0F;AAC1F,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAE1D;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAGxC"}