@hatk/hatk 0.0.1-alpha.9 → 0.0.1-alpha.92

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 (218) 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 +29 -2
  5. package/dist/backfill.d.ts.map +1 -1
  6. package/dist/backfill.js +142 -51
  7. package/dist/car.d.ts +20 -0
  8. package/dist/car.d.ts.map +1 -1
  9. package/dist/car.js +108 -0
  10. package/dist/cli.js +247 -1043
  11. package/dist/config.d.ts +145 -1
  12. package/dist/config.d.ts.map +1 -1
  13. package/dist/config.js +61 -8
  14. package/dist/database/adapter-factory.d.ts +6 -0
  15. package/dist/database/adapter-factory.d.ts.map +1 -0
  16. package/dist/database/adapter-factory.js +20 -0
  17. package/dist/database/adapters/duckdb-search.d.ts +12 -0
  18. package/dist/database/adapters/duckdb-search.d.ts.map +1 -0
  19. package/dist/database/adapters/duckdb-search.js +27 -0
  20. package/dist/database/adapters/duckdb.d.ts +25 -0
  21. package/dist/database/adapters/duckdb.d.ts.map +1 -0
  22. package/dist/database/adapters/duckdb.js +161 -0
  23. package/dist/database/adapters/sqlite-search.d.ts +16 -0
  24. package/dist/database/adapters/sqlite-search.d.ts.map +1 -0
  25. package/dist/database/adapters/sqlite-search.js +76 -0
  26. package/dist/database/adapters/sqlite.d.ts +18 -0
  27. package/dist/database/adapters/sqlite.d.ts.map +1 -0
  28. package/dist/database/adapters/sqlite.js +88 -0
  29. package/dist/{db.d.ts → database/db.d.ts} +68 -6
  30. package/dist/database/db.d.ts.map +1 -0
  31. package/dist/database/db.js +1775 -0
  32. package/dist/database/dialect.d.ts +45 -0
  33. package/dist/database/dialect.d.ts.map +1 -0
  34. package/dist/database/dialect.js +72 -0
  35. package/dist/{fts.d.ts → database/fts.d.ts} +7 -0
  36. package/dist/database/fts.d.ts.map +1 -0
  37. package/dist/{fts.js → database/fts.js} +122 -34
  38. package/dist/database/index.d.ts +7 -0
  39. package/dist/database/index.d.ts.map +1 -0
  40. package/dist/database/index.js +6 -0
  41. package/dist/database/ports.d.ts +50 -0
  42. package/dist/database/ports.d.ts.map +1 -0
  43. package/dist/database/ports.js +1 -0
  44. package/dist/{schema.d.ts → database/schema.d.ts} +16 -3
  45. package/dist/database/schema.d.ts.map +1 -0
  46. package/dist/{schema.js → database/schema.js} +126 -44
  47. package/dist/dev-entry.d.ts +8 -0
  48. package/dist/dev-entry.d.ts.map +1 -0
  49. package/dist/dev-entry.js +127 -0
  50. package/dist/feeds.d.ts +26 -8
  51. package/dist/feeds.d.ts.map +1 -1
  52. package/dist/feeds.js +72 -16
  53. package/dist/hooks.d.ts +102 -0
  54. package/dist/hooks.d.ts.map +1 -0
  55. package/dist/hooks.js +186 -0
  56. package/dist/hydrate.d.ts +49 -6
  57. package/dist/hydrate.d.ts.map +1 -1
  58. package/dist/hydrate.js +21 -20
  59. package/dist/indexer.d.ts +167 -3
  60. package/dist/indexer.d.ts.map +1 -1
  61. package/dist/indexer.js +556 -94
  62. package/dist/jetstream.d.ts +118 -0
  63. package/dist/jetstream.d.ts.map +1 -0
  64. package/dist/jetstream.js +225 -0
  65. package/dist/labels.d.ts +36 -0
  66. package/dist/labels.d.ts.map +1 -1
  67. package/dist/labels.js +71 -6
  68. package/dist/lex-types.d.ts +2 -0
  69. package/dist/lex-types.d.ts.map +1 -1
  70. package/dist/lexicon-resolve.d.ts.map +1 -1
  71. package/dist/lexicon-resolve.js +27 -112
  72. package/dist/lexicons/com/atproto/label/defs.json +75 -0
  73. package/dist/lexicons/com/atproto/moderation/defs.json +30 -0
  74. package/dist/lexicons/com/atproto/repo/strongRef.json +21 -0
  75. package/dist/lexicons/dev/hatk/applyWrites.json +87 -0
  76. package/dist/lexicons/dev/hatk/createRecord.json +40 -0
  77. package/dist/lexicons/dev/hatk/createReport.json +48 -0
  78. package/dist/lexicons/dev/hatk/deleteRecord.json +25 -0
  79. package/dist/lexicons/dev/hatk/describeCollections.json +41 -0
  80. package/dist/lexicons/dev/hatk/describeFeeds.json +29 -0
  81. package/dist/lexicons/dev/hatk/describeLabels.json +45 -0
  82. package/dist/lexicons/dev/hatk/getFeed.json +30 -0
  83. package/dist/lexicons/dev/hatk/getPreferences.json +19 -0
  84. package/dist/lexicons/dev/hatk/getRecord.json +26 -0
  85. package/dist/lexicons/dev/hatk/getRecords.json +32 -0
  86. package/dist/lexicons/dev/hatk/putPreference.json +28 -0
  87. package/dist/lexicons/dev/hatk/putRecord.json +41 -0
  88. package/dist/lexicons/dev/hatk/searchRecords.json +32 -0
  89. package/dist/lexicons/dev/hatk/uploadBlob.json +23 -0
  90. package/dist/logger.d.ts +29 -0
  91. package/dist/logger.d.ts.map +1 -1
  92. package/dist/logger.js +29 -0
  93. package/dist/main.js +198 -75
  94. package/dist/mst.d.ts +15 -0
  95. package/dist/mst.d.ts.map +1 -1
  96. package/dist/mst.js +13 -0
  97. package/dist/oauth/db.d.ts +16 -1
  98. package/dist/oauth/db.d.ts.map +1 -1
  99. package/dist/oauth/db.js +74 -19
  100. package/dist/oauth/discovery.d.ts +10 -0
  101. package/dist/oauth/discovery.d.ts.map +1 -1
  102. package/dist/oauth/discovery.js +54 -2
  103. package/dist/oauth/server.d.ts +93 -1
  104. package/dist/oauth/server.d.ts.map +1 -1
  105. package/dist/oauth/server.js +416 -39
  106. package/dist/oauth/service-describe.d.ts +42 -0
  107. package/dist/oauth/service-describe.d.ts.map +1 -0
  108. package/dist/oauth/service-describe.js +108 -0
  109. package/dist/oauth/session.d.ts +32 -0
  110. package/dist/oauth/session.d.ts.map +1 -0
  111. package/dist/oauth/session.js +131 -0
  112. package/dist/opengraph.d.ts +10 -0
  113. package/dist/opengraph.d.ts.map +1 -1
  114. package/dist/opengraph.js +94 -42
  115. package/dist/pds-proxy.d.ts +82 -0
  116. package/dist/pds-proxy.d.ts.map +1 -0
  117. package/dist/pds-proxy.js +356 -0
  118. package/dist/private-collections.d.ts +13 -0
  119. package/dist/private-collections.d.ts.map +1 -0
  120. package/dist/private-collections.js +27 -0
  121. package/dist/push.d.ts +54 -0
  122. package/dist/push.d.ts.map +1 -0
  123. package/dist/push.js +367 -0
  124. package/dist/renderer.d.ts +27 -0
  125. package/dist/renderer.d.ts.map +1 -0
  126. package/dist/renderer.js +46 -0
  127. package/dist/resolve-hatk.d.ts +6 -0
  128. package/dist/resolve-hatk.d.ts.map +1 -0
  129. package/dist/resolve-hatk.js +20 -0
  130. package/dist/response.d.ts +16 -0
  131. package/dist/response.d.ts.map +1 -0
  132. package/dist/response.js +69 -0
  133. package/dist/scanner.d.ts +21 -0
  134. package/dist/scanner.d.ts.map +1 -0
  135. package/dist/scanner.js +88 -0
  136. package/dist/seed.d.ts +19 -0
  137. package/dist/seed.d.ts.map +1 -1
  138. package/dist/seed.js +43 -4
  139. package/dist/server-init.d.ts +8 -0
  140. package/dist/server-init.d.ts.map +1 -0
  141. package/dist/server-init.js +62 -0
  142. package/dist/server.d.ts +34 -3
  143. package/dist/server.d.ts.map +1 -1
  144. package/dist/server.js +1032 -649
  145. package/dist/setup.d.ts +28 -1
  146. package/dist/setup.d.ts.map +1 -1
  147. package/dist/setup.js +50 -3
  148. package/dist/spaces/blob.d.ts +51 -0
  149. package/dist/spaces/blob.d.ts.map +1 -0
  150. package/dist/spaces/blob.js +111 -0
  151. package/dist/spaces/credential.d.ts +62 -0
  152. package/dist/spaces/credential.d.ts.map +1 -0
  153. package/dist/spaces/credential.js +184 -0
  154. package/dist/spaces/engine.d.ts +97 -0
  155. package/dist/spaces/engine.d.ts.map +1 -0
  156. package/dist/spaces/engine.js +559 -0
  157. package/dist/spaces/guard.d.ts +41 -0
  158. package/dist/spaces/guard.d.ts.map +1 -0
  159. package/dist/spaces/guard.js +79 -0
  160. package/dist/spaces/identity.d.ts +37 -0
  161. package/dist/spaces/identity.d.ts.map +1 -0
  162. package/dist/spaces/identity.js +88 -0
  163. package/dist/spaces/index.d.ts +55 -0
  164. package/dist/spaces/index.d.ts.map +1 -0
  165. package/dist/spaces/index.js +133 -0
  166. package/dist/spaces/notify.d.ts +51 -0
  167. package/dist/spaces/notify.d.ts.map +1 -0
  168. package/dist/spaces/notify.js +121 -0
  169. package/dist/spaces/records.d.ts +37 -0
  170. package/dist/spaces/records.d.ts.map +1 -0
  171. package/dist/spaces/records.js +63 -0
  172. package/dist/spaces/store.d.ts +51 -0
  173. package/dist/spaces/store.d.ts.map +1 -0
  174. package/dist/spaces/store.js +99 -0
  175. package/dist/spaces/uri.d.ts +60 -0
  176. package/dist/spaces/uri.d.ts.map +1 -0
  177. package/dist/spaces/uri.js +97 -0
  178. package/dist/spaces/verify.d.ts +39 -0
  179. package/dist/spaces/verify.d.ts.map +1 -0
  180. package/dist/spaces/verify.js +87 -0
  181. package/dist/spaces/viewer.d.ts +51 -0
  182. package/dist/spaces/viewer.d.ts.map +1 -0
  183. package/dist/spaces/viewer.js +167 -0
  184. package/dist/spaces/visibility.d.ts +57 -0
  185. package/dist/spaces/visibility.d.ts.map +1 -0
  186. package/dist/spaces/visibility.js +81 -0
  187. package/dist/templates/feed.tpl +18 -0
  188. package/dist/templates/hook.tpl +5 -0
  189. package/dist/templates/label.tpl +15 -0
  190. package/dist/templates/og.tpl +17 -0
  191. package/dist/templates/seed.tpl +11 -0
  192. package/dist/templates/setup.tpl +5 -0
  193. package/dist/templates/test-feed.tpl +19 -0
  194. package/dist/templates/test-xrpc.tpl +19 -0
  195. package/dist/templates/xrpc.tpl +53 -0
  196. package/dist/test.d.ts +3 -1
  197. package/dist/test.d.ts.map +1 -1
  198. package/dist/test.js +60 -36
  199. package/dist/views.js +1 -1
  200. package/dist/vite-plugin.d.ts +1 -1
  201. package/dist/vite-plugin.d.ts.map +1 -1
  202. package/dist/vite-plugin.js +254 -66
  203. package/dist/xrpc.d.ts +101 -12
  204. package/dist/xrpc.d.ts.map +1 -1
  205. package/dist/xrpc.js +255 -55
  206. package/package.json +21 -9
  207. package/public/admin-auth.js +271 -306
  208. package/public/admin.html +177 -61
  209. package/dist/db.d.ts.map +0 -1
  210. package/dist/db.js +0 -1361
  211. package/dist/fts.d.ts.map +0 -1
  212. package/dist/oauth/hooks.d.ts +0 -10
  213. package/dist/oauth/hooks.d.ts.map +0 -1
  214. package/dist/oauth/hooks.js +0 -40
  215. package/dist/schema.d.ts.map +0 -1
  216. package/dist/test-browser.d.ts +0 -14
  217. package/dist/test-browser.d.ts.map +0 -1
  218. package/dist/test-browser.js +0 -26
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Resolving the services a space is reached through.
3
+ *
4
+ * Two different endpoints matter, and they are usually but not always the same
5
+ * host:
6
+ *
7
+ * the authority's space host — issues credentials, enumerates writers
8
+ * each writer's own PDS — holds that writer's records and blobs
9
+ *
10
+ * A space is the aggregation of one repo per writer, each on the writer's own
11
+ * server, so reading one means talking to every writer's host with a credential
12
+ * the authority signed. There is no single server that has all of it.
13
+ */
14
+ import { type PublicKey } from './verify.ts';
15
+ export declare function configureSpaceIdentity(url: string): void;
16
+ /**
17
+ * Where to send credential and writer-set requests for a space.
18
+ *
19
+ * The authority may publish a dedicated `#atproto_space_host`; one that does
20
+ * not is still reached at its `#atproto_pds`, which is the case for every
21
+ * community host built on the reference implementation.
22
+ */
23
+ export declare function spaceHostEndpoint(authority: string): Promise<string>;
24
+ /** Where a writer's repo in a space lives: their own PDS, wherever that is. */
25
+ export declare function repoEndpoint(did: string): Promise<string>;
26
+ /** Drop cached DID documents. For tests, and for a host that has just moved. */
27
+ export declare function clearSpaceIdentityCache(): void;
28
+ /**
29
+ * An account's atproto signing key, as its DID document publishes it.
30
+ *
31
+ * Used to check that a write notice really came from the space authority it
32
+ * claims to. `#atproto` is the entry every atproto DID document carries;
33
+ * `#atproto_space` is the optional dedicated credential-signing key, preferred
34
+ * when an authority publishes one.
35
+ */
36
+ export declare function atprotoSigningKey(did: string): Promise<PublicKey | null>;
37
+ //# sourceMappingURL=identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/spaces/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,aAAa,CAAA;AAS/D,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAExD;AA2BD;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK1E;AAED,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAEzD;AAED,gFAAgF;AAChF,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAW9E"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Resolving the services a space is reached through.
3
+ *
4
+ * Two different endpoints matter, and they are usually but not always the same
5
+ * host:
6
+ *
7
+ * the authority's space host — issues credentials, enumerates writers
8
+ * each writer's own PDS — holds that writer's records and blobs
9
+ *
10
+ * A space is the aggregation of one repo per writer, each on the writer's own
11
+ * server, so reading one means talking to every writer's host with a credential
12
+ * the authority signed. There is no single server that has all of it.
13
+ */
14
+ import { pdsFor } from "../backfill.js";
15
+ import { parseMultibaseKey } from "./verify.js";
16
+ let plcUrl = 'https://plc.directory';
17
+ export function configureSpaceIdentity(url) {
18
+ plcUrl = url;
19
+ }
20
+ const docCache = new Map();
21
+ function fetchDidDoc(did) {
22
+ const cached = docCache.get(did);
23
+ if (cached)
24
+ return cached;
25
+ const pending = (async () => {
26
+ try {
27
+ const url = did.startsWith('did:web:')
28
+ ? `https://${decodeURIComponent(did.slice('did:web:'.length)).replace(/:/g, '/')}/.well-known/did.json`
29
+ : `${plcUrl}/${did}`;
30
+ const res = await fetch(url);
31
+ if (!res.ok)
32
+ return null;
33
+ return (await res.json());
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ })();
39
+ docCache.set(did, pending);
40
+ return pending;
41
+ }
42
+ function serviceEndpoint(doc, id) {
43
+ return doc?.service?.find((s) => s.id === id || s.id?.endsWith(id))?.serviceEndpoint;
44
+ }
45
+ /**
46
+ * Where to send credential and writer-set requests for a space.
47
+ *
48
+ * The authority may publish a dedicated `#atproto_space_host`; one that does
49
+ * not is still reached at its `#atproto_pds`, which is the case for every
50
+ * community host built on the reference implementation.
51
+ */
52
+ export async function spaceHostEndpoint(authority) {
53
+ const doc = await fetchDidDoc(authority);
54
+ const endpoint = serviceEndpoint(doc, '#atproto_space_host') ?? serviceEndpoint(doc, '#atproto_pds');
55
+ if (!endpoint)
56
+ throw new Error(`No space host or PDS endpoint in DID document for ${authority}`);
57
+ return endpoint;
58
+ }
59
+ /** Where a writer's repo in a space lives: their own PDS, wherever that is. */
60
+ export function repoEndpoint(did) {
61
+ return pdsFor(did);
62
+ }
63
+ /** Drop cached DID documents. For tests, and for a host that has just moved. */
64
+ export function clearSpaceIdentityCache() {
65
+ docCache.clear();
66
+ }
67
+ /**
68
+ * An account's atproto signing key, as its DID document publishes it.
69
+ *
70
+ * Used to check that a write notice really came from the space authority it
71
+ * claims to. `#atproto` is the entry every atproto DID document carries;
72
+ * `#atproto_space` is the optional dedicated credential-signing key, preferred
73
+ * when an authority publishes one.
74
+ */
75
+ export async function atprotoSigningKey(did) {
76
+ const doc = await fetchDidDoc(did);
77
+ const methods = doc?.verificationMethod ?? [];
78
+ const named = (id) => methods.find((m) => m.id === id || m.id?.endsWith(id));
79
+ const method = named('#atproto_space') ?? named('#atproto');
80
+ if (!method?.publicKeyMultibase)
81
+ return null;
82
+ try {
83
+ return parseMultibaseKey(method.publicKeyMultibase);
84
+ }
85
+ catch {
86
+ return null;
87
+ }
88
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Permissioned-space indexing, as a whole.
3
+ *
4
+ * Off unless `spaces` is configured. With it off nothing in here runs, no
5
+ * space row can be written, and the read gate serves none — which is the state
6
+ * every deployment that predates this is in.
7
+ */
8
+ import type { OAuthConfig, SpacesConfig } from '../config.ts';
9
+ import { unwatchSpace, watchSpace } from './engine.ts';
10
+ export { collectionsForSpaceType, reconcileAll, reconcileSpace, syncSpaceRepo, unwatchSpace, watchSpace, } from './engine.ts';
11
+ export { getSpaceCredential, mintSpaceCredential, isNotAuthorized, isSpaceGone } from './credential.ts';
12
+ export { listSpaceRepos, listSpaceWatches, type SpaceWatch } from './store.ts';
13
+ export { isSpaceReadable, readableSpaces, withReadableSpaces } from './visibility.ts';
14
+ export { UngatedSpaceQueryError, spaceBackedCollections } from './guard.ts';
15
+ export { forgetViewerSpaces, readableSpacesFor, viewerCredential } from './viewer.ts';
16
+ /**
17
+ * A DPoP proof signed by this instance's own key, for an app that has to hold
18
+ * a DPoP-bound token itself — a community host minting one for a member to act
19
+ * as the community, say. The key is the one hatk already presents to PDSes.
20
+ */
21
+ export declare function serverDpopProof(method: string, url: string, accessToken?: string, nonce?: string): Promise<string>;
22
+ export { handleWriteNotice } from './engine.ts';
23
+ export { clearPendingNotices, NoticeError, parseWriteNotice, scheduleNoticeSync, verifyNotice, type WriteNotice, } from './notify.ts';
24
+ export * from './uri.ts';
25
+ export { blobCid, spaceBlobUrl } from './blob.ts';
26
+ export { records, requireSpace, spaceRecords } from './records.ts';
27
+ export declare function spaceServiceId(): string | null;
28
+ /**
29
+ * The DID document an authority resolves to find where to deliver notices.
30
+ *
31
+ * It publishes a service entry and nothing else. A syncer signs nothing — it
32
+ * verifies inbound notices and presents credentials bound to an ephemeral key
33
+ * — so there is no verification method to publish and no key here to steal.
34
+ */
35
+ export declare function spaceDidDocument(did: string, endpoint: string, fragment: string): Record<string, unknown>;
36
+ export interface StartSpacesOptions {
37
+ spaces: SpacesConfig;
38
+ oauth: OAuthConfig | null;
39
+ plc: string;
40
+ /** Every collection hatk has a table for. */
41
+ collections: Set<string>;
42
+ }
43
+ /**
44
+ * Configure the engine and start the reconcile sweep.
45
+ *
46
+ * Returns without starting anything if the instance has no OAuth: reading a
47
+ * space begins with a delegation token from a member's own PDS, so an instance
48
+ * that holds no sessions has no way to obtain one and would sweep forever
49
+ * finding nothing.
50
+ */
51
+ export declare function startSpaces(opts: StartSpacesOptions): void;
52
+ export declare function stopSpaces(): void;
53
+ /** Re-exported so an app's on-login hook can start following a space it just learned about. */
54
+ export { watchSpace as followSpace, unwatchSpace as unfollowSpace };
55
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/spaces/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAG7D,OAAO,EAA+D,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAOnH,OAAO,EACL,uBAAuB,EACvB,YAAY,EACZ,cAAc,EACd,aAAa,EACb,YAAY,EACZ,UAAU,GACX,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AACvG,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAA;AAC9E,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACrF,OAAO,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAC3E,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAErF;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,WAAW,CAAC,EAAE,MAAM,EACpB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAIjB;AACD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,gBAAgB,EAChB,kBAAkB,EAClB,YAAY,EACZ,KAAK,WAAW,GACjB,MAAM,aAAa,CAAA;AACpB,cAAc,UAAU,CAAA;AACxB,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAWlE,wBAAgB,cAAc,IAAI,MAAM,GAAG,IAAI,CAE9C;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAYzG;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,YAAY,CAAA;IACpB,KAAK,EAAE,WAAW,GAAG,IAAI,CAAA;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,6CAA6C;IAC7C,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CACzB;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,kBAAkB,GAAG,IAAI,CAqD1D;AAED,wBAAgB,UAAU,IAAI,IAAI,CAMjC;AAED,+FAA+F;AAC/F,OAAO,EAAE,UAAU,IAAI,WAAW,EAAE,YAAY,IAAI,aAAa,EAAE,CAAA"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Permissioned-space indexing, as a whole.
3
+ *
4
+ * Off unless `spaces` is configured. With it off nothing in here runs, no
5
+ * space row can be written, and the read gate serves none — which is the state
6
+ * every deployment that predates this is in.
7
+ */
8
+ import { emit } from "../logger.js";
9
+ import { log } from "../logger.js";
10
+ import { collectionsForSpaceType, configureSpaceEngine, reconcileAll, unwatchSpace, watchSpace } from "./engine.js";
11
+ import { setSpaceBackedCollections } from "./guard.js";
12
+ import { configureSpaceIdentity } from "./identity.js";
13
+ import { clearPendingNotices } from "./notify.js";
14
+ import { getServerKey } from "../oauth/db.js";
15
+ import { createDpopProof } from "../oauth/dpop.js";
16
+ export { collectionsForSpaceType, reconcileAll, reconcileSpace, syncSpaceRepo, unwatchSpace, watchSpace, } from "./engine.js";
17
+ export { getSpaceCredential, mintSpaceCredential, isNotAuthorized, isSpaceGone } from "./credential.js";
18
+ export { listSpaceRepos, listSpaceWatches } from "./store.js";
19
+ export { isSpaceReadable, readableSpaces, withReadableSpaces } from "./visibility.js";
20
+ export { UngatedSpaceQueryError, spaceBackedCollections } from "./guard.js";
21
+ export { forgetViewerSpaces, readableSpacesFor, viewerCredential } from "./viewer.js";
22
+ /**
23
+ * A DPoP proof signed by this instance's own key, for an app that has to hold
24
+ * a DPoP-bound token itself — a community host minting one for a member to act
25
+ * as the community, say. The key is the one hatk already presents to PDSes.
26
+ */
27
+ export async function serverDpopProof(method, url, accessToken, nonce) {
28
+ const key = await getServerKey('appview-oauth-key');
29
+ if (!key)
30
+ throw new Error('No server key: is OAuth configured?');
31
+ return createDpopProof(JSON.parse(key.privateKey), JSON.parse(key.publicKey), method, url, accessToken, nonce);
32
+ }
33
+ export { handleWriteNotice } from "./engine.js";
34
+ export { clearPendingNotices, NoticeError, parseWriteNotice, scheduleNoticeSync, verifyNotice, } from "./notify.js";
35
+ export * from "./uri.js";
36
+ export { blobCid, spaceBlobUrl } from "./blob.js";
37
+ export { records, requireSpace, spaceRecords } from "./records.js";
38
+ let sweepTimer = null;
39
+ /**
40
+ * What a notice must be addressed to for this instance to act on it, and what
41
+ * every registration names. Null when no service DID is configured, which is
42
+ * also what makes the inbound routes refuse everything.
43
+ */
44
+ let serviceId = null;
45
+ export function spaceServiceId() {
46
+ return serviceId;
47
+ }
48
+ /**
49
+ * The DID document an authority resolves to find where to deliver notices.
50
+ *
51
+ * It publishes a service entry and nothing else. A syncer signs nothing — it
52
+ * verifies inbound notices and presents credentials bound to an ephemeral key
53
+ * — so there is no verification method to publish and no key here to steal.
54
+ */
55
+ export function spaceDidDocument(did, endpoint, fragment) {
56
+ return {
57
+ '@context': ['https://www.w3.org/ns/did/v1'],
58
+ id: did,
59
+ service: [
60
+ {
61
+ id: `#${fragment}`,
62
+ type: 'AtprotoSpaceService',
63
+ serviceEndpoint: endpoint,
64
+ },
65
+ ],
66
+ };
67
+ }
68
+ /**
69
+ * Configure the engine and start the reconcile sweep.
70
+ *
71
+ * Returns without starting anything if the instance has no OAuth: reading a
72
+ * space begins with a delegation token from a member's own PDS, so an instance
73
+ * that holds no sessions has no way to obtain one and would sweep forever
74
+ * finding nothing.
75
+ */
76
+ export function startSpaces(opts) {
77
+ const { spaces, oauth, plc, collections } = opts;
78
+ if (!oauth) {
79
+ log("[spaces] configured but OAuth is not — a space is read with a member's delegation, so nothing to do");
80
+ return;
81
+ }
82
+ if (spaces.types.length === 0) {
83
+ log('[spaces] configured with no space types — nothing to index');
84
+ return;
85
+ }
86
+ configureSpaceIdentity(plc);
87
+ serviceId = spaces.serviceDid ? `${spaces.serviceDid}#${spaces.serviceFragment ?? 'atproto_space_syncer'}` : null;
88
+ configureSpaceEngine({
89
+ oauth,
90
+ types: new Set(spaces.types),
91
+ collections,
92
+ ...(serviceId ? { serviceId } : {}),
93
+ });
94
+ // The tables a space row can land in, so raw SQL over one of them without
95
+ // the gate is refused by name rather than quietly served to everyone.
96
+ const backed = new Set(spaces.types.flatMap((type) => collectionsForSpaceType(type)));
97
+ setSpaceBackedCollections(backed);
98
+ if (backed.size > 0) {
99
+ log(`[spaces] raw SQL over ${[...backed].join(', ')} must carry ctx.spaceFilter`);
100
+ }
101
+ const intervalMs = Math.max(30, spaces.reconcileInterval ?? 300) * 1000;
102
+ const sweep = async () => {
103
+ for (const space of spaces.watch ?? []) {
104
+ try {
105
+ await watchSpace(space);
106
+ }
107
+ catch (err) {
108
+ emit('spaces', 'watch_failed', {
109
+ space,
110
+ error: err instanceof Error ? err.message : String(err),
111
+ });
112
+ }
113
+ }
114
+ await reconcileAll();
115
+ };
116
+ // Deferred rather than run inline: the server is already listening by the
117
+ // time this is called, and a first sweep that has to reach several hosts
118
+ // should not sit in front of the first request.
119
+ setTimeout(() => void sweep().catch(() => { }), 1000).unref();
120
+ sweepTimer = setInterval(() => void sweep().catch(() => { }), intervalMs);
121
+ sweepTimer.unref();
122
+ log(`[spaces] indexing ${spaces.types.join(', ')} — reconciling every ${intervalMs / 1000}s`);
123
+ }
124
+ export function stopSpaces() {
125
+ if (sweepTimer)
126
+ clearInterval(sweepTimer);
127
+ sweepTimer = null;
128
+ serviceId = null;
129
+ setSpaceBackedCollections([]);
130
+ clearPendingNotices();
131
+ }
132
+ /** Re-exported so an app's on-login hook can start following a space it just learned about. */
133
+ export { watchSpace as followSpace, unwatchSpace as unfollowSpace };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Receiving write notices from a space authority.
3
+ *
4
+ * A space's writes never reach a firehose, so an authority tells its
5
+ * subscribers directly: register once, and every write into the space arrives
6
+ * as a `notifyWrite` carrying the repo and the revision it reached — and no
7
+ * records. The notice says something moved; reading it is still the syncer's
8
+ * job, with its own credential.
9
+ *
10
+ * This only makes sync prompt, never correct. Delivery is best-effort by
11
+ * design: the reference host forwards each notice once from an in-memory queue
12
+ * and logs the failure, so a missed one is recovered by the reconcile sweep or
13
+ * by the next write. Nothing here may be load-bearing.
14
+ *
15
+ * ## Why this needs its own verifier
16
+ *
17
+ * A forwarded notice does not look like the one a PDS sends its own authority.
18
+ * At origin, a writer's host signs with `iss` = the writer and `aud` = the bare
19
+ * authority DID. Forwarded onward, the authority re-signs with `iss` = itself
20
+ * and `aud` = the registered service identifier *including its fragment*. The
21
+ * reference PDS's own inbound handler rejects exactly that shape — it requires
22
+ * `iss` to equal the claimed writer and `aud` to equal the bare authority — so
23
+ * a receiver cannot borrow it and has to check the forwarded shape itself.
24
+ */
25
+ export declare class NoticeError extends Error {
26
+ readonly status: number;
27
+ constructor(status: number, message: string);
28
+ }
29
+ /**
30
+ * Check a service-auth JWT that claims to come from `expectedIss`.
31
+ *
32
+ * The audience must match this instance's own service identifier exactly,
33
+ * fragment included: a token addressed to somebody else is not ours to act on
34
+ * however valid its signature. The method is checked for the same reason — a
35
+ * token minted for one call must not be replayable against another.
36
+ */
37
+ export declare function verifyNotice(authorization: string | null, expected: {
38
+ iss: string;
39
+ aud: string;
40
+ lxm: string;
41
+ }): Promise<void>;
42
+ export interface WriteNotice {
43
+ space: string;
44
+ repo: string;
45
+ rev: string;
46
+ }
47
+ export declare function parseWriteNotice(body: unknown): WriteNotice | null;
48
+ export declare function scheduleNoticeSync(key: string, run: () => Promise<void>, delay?: number): void;
49
+ /** Drop every pending sync. For shutdown, and for tests. */
50
+ export declare function clearPendingNotices(): void;
51
+ //# sourceMappingURL=notify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../src/spaces/notify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAUH,qBAAa,WAAY,SAAQ,KAAK;IAElC,QAAQ,CAAC,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM,EACvB,OAAO,EAAE,MAAM;CAIlB;AAcD;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAChC,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,QAAQ,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAClD,OAAO,CAAC,IAAI,CAAC,CAiCf;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,WAAW,GAAG,IAAI,CAMlE;AAcD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,SAAc,GAAG,IAAI,CASnG;AAED,4DAA4D;AAC5D,wBAAgB,mBAAmB,IAAI,IAAI,CAG1C"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Receiving write notices from a space authority.
3
+ *
4
+ * A space's writes never reach a firehose, so an authority tells its
5
+ * subscribers directly: register once, and every write into the space arrives
6
+ * as a `notifyWrite` carrying the repo and the revision it reached — and no
7
+ * records. The notice says something moved; reading it is still the syncer's
8
+ * job, with its own credential.
9
+ *
10
+ * This only makes sync prompt, never correct. Delivery is best-effort by
11
+ * design: the reference host forwards each notice once from an in-memory queue
12
+ * and logs the failure, so a missed one is recovered by the reconcile sweep or
13
+ * by the next write. Nothing here may be load-bearing.
14
+ *
15
+ * ## Why this needs its own verifier
16
+ *
17
+ * A forwarded notice does not look like the one a PDS sends its own authority.
18
+ * At origin, a writer's host signs with `iss` = the writer and `aud` = the bare
19
+ * authority DID. Forwarded onward, the authority re-signs with `iss` = itself
20
+ * and `aud` = the registered service identifier *including its fragment*. The
21
+ * reference PDS's own inbound handler rejects exactly that shape — it requires
22
+ * `iss` to equal the claimed writer and `aud` to equal the bare authority — so
23
+ * a receiver cannot borrow it and has to check the forwarded shape itself.
24
+ */
25
+ import { emit } from "../logger.js";
26
+ import { atprotoSigningKey } from "./identity.js";
27
+ import { parseSpaceRef } from "./uri.js";
28
+ import { verifySignature } from "./verify.js";
29
+ /** Clock skew tolerated on a notice's expiry. The reference signs them for 60s. */
30
+ const SKEW_SEC = 60;
31
+ export class NoticeError extends Error {
32
+ status;
33
+ constructor(status, message) {
34
+ super(message);
35
+ this.status = status;
36
+ }
37
+ }
38
+ function base64UrlToBytes(value) {
39
+ const padded = value.replace(/-/g, '+').replace(/_/g, '/');
40
+ return new Uint8Array(Buffer.from(padded, 'base64'));
41
+ }
42
+ /**
43
+ * Check a service-auth JWT that claims to come from `expectedIss`.
44
+ *
45
+ * The audience must match this instance's own service identifier exactly,
46
+ * fragment included: a token addressed to somebody else is not ours to act on
47
+ * however valid its signature. The method is checked for the same reason — a
48
+ * token minted for one call must not be replayable against another.
49
+ */
50
+ export async function verifyNotice(authorization, expected) {
51
+ const token = authorization?.match(/^Bearer (.+)$/)?.[1];
52
+ if (!token)
53
+ throw new NoticeError(401, 'Missing service auth');
54
+ const parts = token.split('.');
55
+ if (parts.length !== 3)
56
+ throw new NoticeError(401, 'Malformed service auth');
57
+ let payload;
58
+ try {
59
+ payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString());
60
+ }
61
+ catch {
62
+ throw new NoticeError(401, 'Malformed service auth');
63
+ }
64
+ // The issuer is checked against the space named in the body rather than
65
+ // trusted from the token, so a valid token from one authority cannot be used
66
+ // to speak about another authority's space.
67
+ if (payload.iss?.split('#')[0] !== expected.iss) {
68
+ throw new NoticeError(403, 'Notice issuer is not the space authority');
69
+ }
70
+ if (payload.aud !== expected.aud)
71
+ throw new NoticeError(403, 'Notice is addressed elsewhere');
72
+ if (payload.lxm !== expected.lxm)
73
+ throw new NoticeError(403, 'Notice is for a different method');
74
+ if (typeof payload.exp !== 'number' || payload.exp + SKEW_SEC < Date.now() / 1000) {
75
+ throw new NoticeError(401, 'Notice has expired');
76
+ }
77
+ const key = await atprotoSigningKey(expected.iss);
78
+ if (!key)
79
+ throw new NoticeError(502, `Could not resolve a signing key for ${expected.iss}`);
80
+ const message = new TextEncoder().encode(`${parts[0]}.${parts[1]}`);
81
+ if (!verifySignature(key, base64UrlToBytes(parts[2]), message)) {
82
+ throw new NoticeError(403, 'Notice signature does not verify');
83
+ }
84
+ }
85
+ export function parseWriteNotice(body) {
86
+ if (!body || typeof body !== 'object')
87
+ return null;
88
+ const { space, repo, rev } = body;
89
+ if (typeof space !== 'string' || typeof repo !== 'string' || typeof rev !== 'string')
90
+ return null;
91
+ if (!parseSpaceRef(space) || !repo.startsWith('did:'))
92
+ return null;
93
+ return { space, repo, rev };
94
+ }
95
+ /**
96
+ * Collapse a burst of notices for the same repo into one sync.
97
+ *
98
+ * A member writing a gallery produces a notice per record, and syncing per
99
+ * notice would read the same repo a dozen times over a few seconds. A short
100
+ * trailing delay turns that into one read, which is also what keeps a flood —
101
+ * forged or otherwise — from costing more than one sync per interval.
102
+ */
103
+ const DEBOUNCE_MS = 750;
104
+ const pending = new Map();
105
+ export function scheduleNoticeSync(key, run, delay = DEBOUNCE_MS) {
106
+ const existing = pending.get(key);
107
+ if (existing)
108
+ clearTimeout(existing);
109
+ const timer = setTimeout(() => {
110
+ pending.delete(key);
111
+ void run().catch((err) => emit('spaces', 'notice_sync_failed', { key, error: err?.message ?? String(err) }));
112
+ }, delay);
113
+ timer.unref?.();
114
+ pending.set(key, timer);
115
+ }
116
+ /** Drop every pending sync. For shutdown, and for tests. */
117
+ export function clearPendingNotices() {
118
+ for (const timer of pending.values())
119
+ clearTimeout(timer);
120
+ pending.clear();
121
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The reads an app over permissioned spaces needs and should not have to write.
3
+ *
4
+ * Each of these is two things done in the right order — a select that carries
5
+ * the space gate, then resolution into properly shaped rows — and an app that
6
+ * writes them itself has to know both. Getting the first wrong serves a private
7
+ * space to whoever asks. So they are the framework's.
8
+ */
9
+ import { getRecordsByUris, reshapeRow } from '../database/db.ts';
10
+ import type { Row } from '../lex-types.ts';
11
+ /**
12
+ * Every record of one collection in one space the viewer may read, oldest
13
+ * first.
14
+ *
15
+ * A space outside the viewer's scope matches no rows, so the result is empty
16
+ * rather than refused; `requireSpace` is the explicit refusal for a page that
17
+ * should say "members only" instead of showing nothing.
18
+ */
19
+ export declare function spaceRecords<R = unknown>(collection: string, space: string): Promise<Row<R>[]>;
20
+ /**
21
+ * Every record of one collection where `field` is one of `values`, oldest
22
+ * first — the many-rows counterpart of `ctx.lookup`, which keeps one per key.
23
+ *
24
+ * Gated like every other read: a space row appears only inside a scope that
25
+ * names its space, and a public row always.
26
+ */
27
+ export declare function records<R = unknown>(collection: string, field: string, values: string[]): Promise<Row<R>[]>;
28
+ /**
29
+ * Refuse the way the space itself would.
30
+ *
31
+ * A viewer the space's authority does not admit gets `NotAuthorized`, which
32
+ * is the ordinary shape of "not a member" — the same answer their own browser
33
+ * would have had from a refused credential.
34
+ */
35
+ export declare function requireSpace(space: string): void;
36
+ export { getRecordsByUris, reshapeRow };
37
+ //# sourceMappingURL=records.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../../src/spaces/records.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,gBAAgB,EAAa,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAE3E,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAA;AAK1C;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAQpG;AAED;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAejH;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAEhD;AAGD,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,CAAA"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * The reads an app over permissioned spaces needs and should not have to write.
3
+ *
4
+ * Each of these is two things done in the right order — a select that carries
5
+ * the space gate, then resolution into properly shaped rows — and an app that
6
+ * writes them itself has to know both. Getting the first wrong serves a private
7
+ * space to whoever asks. So they are the framework's.
8
+ */
9
+ import { getRecordsByUris, getSchema, reshapeRow } from "../database/db.js";
10
+ import { resolveRecords } from "../hydrate.js";
11
+ import { InvalidRequestError } from "../xrpc.js";
12
+ import { spaceFilterSql, isSpaceReadable } from "./visibility.js";
13
+ import { guardedQuerySQL } from "./guard.js";
14
+ /**
15
+ * Every record of one collection in one space the viewer may read, oldest
16
+ * first.
17
+ *
18
+ * A space outside the viewer's scope matches no rows, so the result is empty
19
+ * rather than refused; `requireSpace` is the explicit refusal for a page that
20
+ * should say "members only" instead of showing nothing.
21
+ */
22
+ export async function spaceRecords(collection, space) {
23
+ if (!getSchema(collection))
24
+ return [];
25
+ const gate = spaceFilterSql('t', 2);
26
+ const rows = (await guardedQuerySQL(`SELECT t.uri FROM "${collection}" t WHERE t.space = $1 AND ${gate.sql} ORDER BY t.indexed_at ASC`, [space, ...gate.params]));
27
+ return resolveRecords(rows.map((r) => r.uri));
28
+ }
29
+ /**
30
+ * Every record of one collection where `field` is one of `values`, oldest
31
+ * first — the many-rows counterpart of `ctx.lookup`, which keeps one per key.
32
+ *
33
+ * Gated like every other read: a space row appears only inside a scope that
34
+ * names its space, and a public row always.
35
+ */
36
+ export async function records(collection, field, values) {
37
+ const schema = getSchema(collection);
38
+ if (!schema || values.length === 0)
39
+ return [];
40
+ const known = new Set(schema.columns.map((c) => c.name));
41
+ known.add('did');
42
+ // The field goes into SQL by name; a name the schema does not know is
43
+ // either a typo or an attempt, and both are refused the same way.
44
+ if (!known.has(field))
45
+ throw new InvalidRequestError(`Unknown field ${field} on ${collection}`);
46
+ const placeholders = values.map((_, i) => `$${i + 1}`).join(',');
47
+ const gate = spaceFilterSql('t', values.length + 1);
48
+ const rows = (await guardedQuerySQL(`SELECT t.uri FROM "${collection}" t WHERE t.${field} IN (${placeholders}) AND ${gate.sql} ORDER BY t.indexed_at ASC`, [...values, ...gate.params]));
49
+ return resolveRecords(rows.map((r) => r.uri));
50
+ }
51
+ /**
52
+ * Refuse the way the space itself would.
53
+ *
54
+ * A viewer the space's authority does not admit gets `NotAuthorized`, which
55
+ * is the ordinary shape of "not a member" — the same answer their own browser
56
+ * would have had from a refused credential.
57
+ */
58
+ export function requireSpace(space) {
59
+ if (!isSpaceReadable(space))
60
+ throw new InvalidRequestError('Not a member of this space', 'NotAuthorized');
61
+ }
62
+ // Re-exported so a handler can shape a row the way a read shaped it.
63
+ export { getRecordsByUris, reshapeRow };
@@ -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"}