@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.
- package/dist/adapter.d.ts +19 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +113 -0
- package/dist/backfill.d.ts +36 -2
- package/dist/backfill.d.ts.map +1 -1
- package/dist/backfill.js +153 -37
- package/dist/car.js +1 -1
- package/dist/classifiers.d.ts +228 -0
- package/dist/classifiers.d.ts.map +1 -0
- package/dist/classifiers.js +688 -0
- package/dist/cli.js +248 -1043
- package/dist/config.d.ts +179 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +78 -8
- package/dist/database/adapter-factory.d.ts +6 -0
- package/dist/database/adapter-factory.d.ts.map +1 -0
- package/dist/database/adapter-factory.js +20 -0
- package/dist/database/adapters/duckdb-search.d.ts +12 -0
- package/dist/database/adapters/duckdb-search.d.ts.map +1 -0
- package/dist/database/adapters/duckdb-search.js +27 -0
- package/dist/database/adapters/duckdb.d.ts +25 -0
- package/dist/database/adapters/duckdb.d.ts.map +1 -0
- package/dist/database/adapters/duckdb.js +161 -0
- package/dist/database/adapters/sqlite-search.d.ts +16 -0
- package/dist/database/adapters/sqlite-search.d.ts.map +1 -0
- package/dist/database/adapters/sqlite-search.js +76 -0
- package/dist/database/adapters/sqlite.d.ts +18 -0
- package/dist/database/adapters/sqlite.d.ts.map +1 -0
- package/dist/database/adapters/sqlite.js +88 -0
- package/dist/database/db.d.ts +290 -0
- package/dist/database/db.d.ts.map +1 -0
- package/dist/database/db.js +2073 -0
- package/dist/database/dialect.d.ts +45 -0
- package/dist/database/dialect.d.ts.map +1 -0
- package/dist/database/dialect.js +72 -0
- package/dist/{fts.d.ts → database/fts.d.ts} +7 -0
- package/dist/database/fts.d.ts.map +1 -0
- package/dist/{fts.js → database/fts.js} +122 -34
- package/dist/database/index.d.ts +7 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +6 -0
- package/dist/database/ports.d.ts +50 -0
- package/dist/database/ports.d.ts.map +1 -0
- package/dist/database/ports.js +1 -0
- package/dist/database/schema.d.ts +91 -0
- package/dist/database/schema.d.ts.map +1 -0
- package/dist/{schema.js → database/schema.js} +153 -54
- package/dist/dev-entry.d.ts +8 -0
- package/dist/dev-entry.d.ts.map +1 -0
- package/dist/dev-entry.js +131 -0
- package/dist/feeds.d.ts +26 -8
- package/dist/feeds.d.ts.map +1 -1
- package/dist/feeds.js +72 -16
- package/dist/hooks.d.ts +102 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +186 -0
- package/dist/hydrate.d.ts +55 -6
- package/dist/hydrate.d.ts.map +1 -1
- package/dist/hydrate.js +21 -20
- package/dist/indexer.d.ts +194 -3
- package/dist/indexer.d.ts.map +1 -1
- package/dist/indexer.js +627 -92
- package/dist/jetstream.d.ts +118 -0
- package/dist/jetstream.d.ts.map +1 -0
- package/dist/jetstream.js +238 -0
- package/dist/labels.d.ts +36 -0
- package/dist/labels.d.ts.map +1 -1
- package/dist/labels.js +71 -6
- package/dist/lex-types.d.ts +2 -0
- package/dist/lex-types.d.ts.map +1 -1
- package/dist/lexicon-resolve.d.ts.map +1 -1
- package/dist/lexicon-resolve.js +27 -112
- package/dist/lexicons/com/atproto/label/defs.json +75 -0
- package/dist/lexicons/com/atproto/moderation/defs.json +30 -0
- package/dist/lexicons/com/atproto/repo/strongRef.json +21 -0
- package/dist/lexicons/dev/hatk/applyWrites.json +87 -0
- package/dist/lexicons/dev/hatk/createRecord.json +40 -0
- package/dist/lexicons/dev/hatk/createReport.json +48 -0
- package/dist/lexicons/dev/hatk/deleteRecord.json +25 -0
- package/dist/lexicons/dev/hatk/describeCollections.json +41 -0
- package/dist/lexicons/dev/hatk/describeFeeds.json +29 -0
- package/dist/lexicons/dev/hatk/describeLabels.json +45 -0
- package/dist/lexicons/dev/hatk/getFeed.json +30 -0
- package/dist/lexicons/dev/hatk/getPreferences.json +19 -0
- package/dist/lexicons/dev/hatk/getRecord.json +26 -0
- package/dist/lexicons/dev/hatk/getRecords.json +32 -0
- package/dist/lexicons/dev/hatk/putPreference.json +28 -0
- package/dist/lexicons/dev/hatk/putRecord.json +41 -0
- package/dist/lexicons/dev/hatk/searchRecords.json +32 -0
- package/dist/lexicons/dev/hatk/uploadBlob.json +23 -0
- package/dist/logger.d.ts +29 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +29 -0
- package/dist/main.js +212 -75
- package/dist/mst.d.ts +15 -0
- package/dist/mst.d.ts.map +1 -1
- package/dist/mst.js +13 -0
- package/dist/oauth/db.d.ts +27 -1
- package/dist/oauth/db.d.ts.map +1 -1
- package/dist/oauth/db.js +80 -19
- package/dist/oauth/discovery.d.ts +10 -0
- package/dist/oauth/discovery.d.ts.map +1 -1
- package/dist/oauth/discovery.js +54 -2
- package/dist/oauth/server.d.ts +131 -1
- package/dist/oauth/server.d.ts.map +1 -1
- package/dist/oauth/server.js +504 -39
- package/dist/oauth/service-describe.d.ts +42 -0
- package/dist/oauth/service-describe.d.ts.map +1 -0
- package/dist/oauth/service-describe.js +108 -0
- package/dist/oauth/session.d.ts +32 -0
- package/dist/oauth/session.d.ts.map +1 -0
- package/dist/oauth/session.js +131 -0
- package/dist/opengraph.d.ts +10 -0
- package/dist/opengraph.d.ts.map +1 -1
- package/dist/opengraph.js +94 -42
- package/dist/pds-proxy.d.ts +92 -0
- package/dist/pds-proxy.d.ts.map +1 -0
- package/dist/pds-proxy.js +396 -0
- package/dist/private-collections.d.ts +13 -0
- package/dist/private-collections.d.ts.map +1 -0
- package/dist/private-collections.js +27 -0
- package/dist/push.d.ts +54 -0
- package/dist/push.d.ts.map +1 -0
- package/dist/push.js +367 -0
- package/dist/renderer.d.ts +27 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +46 -0
- package/dist/resolve-hatk.d.ts +6 -0
- package/dist/resolve-hatk.d.ts.map +1 -0
- package/dist/resolve-hatk.js +20 -0
- package/dist/response.d.ts +16 -0
- package/dist/response.d.ts.map +1 -0
- package/dist/response.js +69 -0
- package/dist/scanner.d.ts +22 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +92 -0
- package/dist/seed.d.ts +19 -0
- package/dist/seed.d.ts.map +1 -1
- package/dist/seed.js +43 -4
- package/dist/server-init.d.ts +8 -0
- package/dist/server-init.d.ts.map +1 -0
- package/dist/server-init.js +75 -0
- package/dist/server.d.ts +46 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1137 -649
- package/dist/setup.d.ts +28 -1
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +50 -3
- package/dist/spaces/blob.d.ts +112 -0
- package/dist/spaces/blob.d.ts.map +1 -0
- package/dist/spaces/blob.js +460 -0
- package/dist/spaces/credential.d.ts +82 -0
- package/dist/spaces/credential.d.ts.map +1 -0
- package/dist/spaces/credential.js +220 -0
- package/dist/spaces/engine.d.ts +97 -0
- package/dist/spaces/engine.d.ts.map +1 -0
- package/dist/spaces/engine.js +563 -0
- package/dist/spaces/guard.d.ts +41 -0
- package/dist/spaces/guard.d.ts.map +1 -0
- package/dist/spaces/guard.js +79 -0
- package/dist/spaces/identity.d.ts +37 -0
- package/dist/spaces/identity.d.ts.map +1 -0
- package/dist/spaces/identity.js +93 -0
- package/dist/spaces/index.d.ts +56 -0
- package/dist/spaces/index.d.ts.map +1 -0
- package/dist/spaces/index.js +134 -0
- package/dist/spaces/notify.d.ts +60 -0
- package/dist/spaces/notify.d.ts.map +1 -0
- package/dist/spaces/notify.js +128 -0
- package/dist/spaces/records.d.ts +37 -0
- package/dist/spaces/records.d.ts.map +1 -0
- package/dist/spaces/records.js +63 -0
- package/dist/spaces/signature.d.ts +36 -0
- package/dist/spaces/signature.d.ts.map +1 -0
- package/dist/spaces/signature.js +83 -0
- package/dist/spaces/store.d.ts +51 -0
- package/dist/spaces/store.d.ts.map +1 -0
- package/dist/spaces/store.js +99 -0
- package/dist/spaces/uri.d.ts +60 -0
- package/dist/spaces/uri.d.ts.map +1 -0
- package/dist/spaces/uri.js +97 -0
- package/dist/spaces/verify.d.ts +46 -0
- package/dist/spaces/verify.d.ts.map +1 -0
- package/dist/spaces/verify.js +94 -0
- package/dist/spaces/viewer.d.ts +51 -0
- package/dist/spaces/viewer.d.ts.map +1 -0
- package/dist/spaces/viewer.js +167 -0
- package/dist/spaces/visibility.d.ts +57 -0
- package/dist/spaces/visibility.d.ts.map +1 -0
- package/dist/spaces/visibility.js +81 -0
- package/dist/templates/feed.tpl +18 -0
- package/dist/templates/hook.tpl +5 -0
- package/dist/templates/label.tpl +15 -0
- package/dist/templates/og.tpl +17 -0
- package/dist/templates/seed.tpl +11 -0
- package/dist/templates/setup.tpl +5 -0
- package/dist/templates/test-feed.tpl +19 -0
- package/dist/templates/test-xrpc.tpl +19 -0
- package/dist/templates/xrpc.tpl +53 -0
- package/dist/test.d.ts +3 -1
- package/dist/test.d.ts.map +1 -1
- package/dist/test.js +60 -36
- package/dist/views.js +1 -1
- package/dist/vite-plugin.d.ts +1 -1
- package/dist/vite-plugin.d.ts.map +1 -1
- package/dist/vite-plugin.js +254 -66
- package/dist/xrpc.d.ts +143 -12
- package/dist/xrpc.d.ts.map +1 -1
- package/dist/xrpc.js +282 -55
- package/package.json +29 -9
- package/public/admin-auth.js +271 -306
- package/public/admin.html +652 -61
- package/dist/db.d.ts +0 -135
- package/dist/db.d.ts.map +0 -1
- package/dist/db.js +0 -1365
- package/dist/fts.d.ts.map +0 -1
- package/dist/oauth/hooks.d.ts +0 -10
- package/dist/oauth/hooks.d.ts.map +0 -1
- package/dist/oauth/hooks.js +0 -40
- package/dist/schema.d.ts +0 -51
- package/dist/schema.d.ts.map +0 -1
- package/dist/test-browser.d.ts +0 -14
- package/dist/test-browser.d.ts.map +0 -1
- package/dist/test-browser.js +0 -26
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Getting into a space to read it.
|
|
3
|
+
*
|
|
4
|
+
* hatk is not a member of anything. It has no DID that any community has
|
|
5
|
+
* admitted, and asking every authority to admit an indexer would make indexing
|
|
6
|
+
* a per-community act of trust before a single record could be read.
|
|
7
|
+
*
|
|
8
|
+
* So it reads as somebody who is already inside: a viewer whose OAuth session
|
|
9
|
+
* it already holds, whose PDS will mint a delegation token for the space, and
|
|
10
|
+
* whom the authority already lets in. The exchange is the same one the
|
|
11
|
+
* community's own web app makes from the browser —
|
|
12
|
+
*
|
|
13
|
+
* the reader's PDS signs a delegation token (60s, single-use, addressed to
|
|
14
|
+
* the authority's space host)
|
|
15
|
+
* the authority checks it, checks the reader against the space's policy, and
|
|
16
|
+
* returns a credential bound to the key that signed the exchange (10 min)
|
|
17
|
+
*
|
|
18
|
+
* — and the credential is then presented to every writer's host in the space.
|
|
19
|
+
* It carries no audience, which is why it must be bound to a key rather than
|
|
20
|
+
* held as a bearer token: each request signs the credential together with the
|
|
21
|
+
* DID it is addressed to, so a host handed one to serve its own repo cannot
|
|
22
|
+
* replay it against any other account. See signature.ts.
|
|
23
|
+
*
|
|
24
|
+
* The consequence to be honest about: a space is only readable while somebody
|
|
25
|
+
* who can read it has a live session here. That is the same bound the data has
|
|
26
|
+
* anyway — nobody outside the space's audience was ever entitled to this — but
|
|
27
|
+
* it does mean indexing lapses when the last member logs out.
|
|
28
|
+
*/
|
|
29
|
+
import { pdsXrpc } from "../pds-proxy.js";
|
|
30
|
+
import { emit } from "../logger.js";
|
|
31
|
+
import { spaceHostEndpoint } from "./identity.js";
|
|
32
|
+
import { generateSpaceSigKey, spaceSigHeaders } from "./signature.js";
|
|
33
|
+
import { parseSpaceRef } from "./uri.js";
|
|
34
|
+
/**
|
|
35
|
+
* Re-mint this long before the credential's own expiry rather than racing it.
|
|
36
|
+
* Credentials live ten minutes, so this keeps nine of them.
|
|
37
|
+
*/
|
|
38
|
+
const RENEW_LEAD_MS = 60 * 1000;
|
|
39
|
+
export class SpaceCredentialError extends Error {
|
|
40
|
+
status;
|
|
41
|
+
code;
|
|
42
|
+
constructor(status, code, message) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.status = status;
|
|
45
|
+
this.code = code;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** A refusal about who is asking, rather than about the space or the network. */
|
|
49
|
+
export function isNotAuthorized(err) {
|
|
50
|
+
return err instanceof SpaceCredentialError && (err.code === 'UserNotAuthorized' || err.code === 'NotAuthorized');
|
|
51
|
+
}
|
|
52
|
+
export function isSpaceGone(err) {
|
|
53
|
+
return err instanceof SpaceCredentialError && (err.code === 'SpaceDeleted' || err.code === 'SpaceNotFound');
|
|
54
|
+
}
|
|
55
|
+
function expiryOf(jwt) {
|
|
56
|
+
try {
|
|
57
|
+
const payload = JSON.parse(Buffer.from(jwt.split('.')[1], 'base64url').toString());
|
|
58
|
+
return typeof payload.exp === 'number' ? payload.exp * 1000 : Date.now() + 60 * 60 * 1000;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// An unreadable expiry is not a reason to refuse a credential the authority
|
|
62
|
+
// just issued; treat it as short-lived and re-mint sooner.
|
|
63
|
+
return Date.now() + 10 * 60 * 1000;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Exchange a delegation token for a credential at the authority, binding it to
|
|
68
|
+
* `key` by signing the token with it.
|
|
69
|
+
*/
|
|
70
|
+
async function exchange(authorityHost, space, delegationToken, key) {
|
|
71
|
+
const url = `${authorityHost}/xrpc/com.atproto.space.getSpaceCredential`;
|
|
72
|
+
const res = await fetch(url, {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers: {
|
|
75
|
+
'Content-Type': 'application/json',
|
|
76
|
+
Accept: 'application/json',
|
|
77
|
+
...(await spaceSigHeaders(key, `Bearer ${delegationToken}`)),
|
|
78
|
+
},
|
|
79
|
+
body: JSON.stringify({ space }),
|
|
80
|
+
});
|
|
81
|
+
const body = (await res.json().catch(() => ({})));
|
|
82
|
+
if (!res.ok || !body.credential) {
|
|
83
|
+
throw new SpaceCredentialError(res.status, body.error ?? (res.status >= 500 ? 'UpstreamFailure' : 'InvalidRequest'), body.message ?? `getSpaceCredential refused (${res.status})`);
|
|
84
|
+
}
|
|
85
|
+
return body.credential;
|
|
86
|
+
}
|
|
87
|
+
/** Mint a credential for `space` using `readerDid`'s stored session. */
|
|
88
|
+
export async function mintSpaceCredential(oauthConfig, space, readerDid) {
|
|
89
|
+
const ref = parseSpaceRef(space);
|
|
90
|
+
if (!ref)
|
|
91
|
+
throw new SpaceCredentialError(400, 'InvalidRequest', `Not a space ref: ${space}`);
|
|
92
|
+
// The reader's own PDS signs this, under the whole-space read grant their
|
|
93
|
+
// session carries. A session without that scope is refused here, before the
|
|
94
|
+
// authority is ever contacted.
|
|
95
|
+
let token;
|
|
96
|
+
try {
|
|
97
|
+
const out = await pdsXrpc(oauthConfig, { did: readerDid }, 'com.atproto.space.getDelegationToken', {
|
|
98
|
+
params: { space },
|
|
99
|
+
});
|
|
100
|
+
if (typeof out.token !== 'string')
|
|
101
|
+
throw new Error('PDS returned no delegation token');
|
|
102
|
+
token = out.token;
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
throw new SpaceCredentialError(err?.status ?? 502, 'DelegationFailed', err?.message ?? 'delegation failed');
|
|
106
|
+
}
|
|
107
|
+
// A key per credential, held only as long as the credential is.
|
|
108
|
+
const key = await generateSpaceSigKey();
|
|
109
|
+
const authorityHost = await spaceHostEndpoint(ref.authority);
|
|
110
|
+
const credential = await exchange(authorityHost, space, token, key);
|
|
111
|
+
// Signatures depend only on the credential and the audience, so each is made
|
|
112
|
+
// once and reused for as long as the credential lives.
|
|
113
|
+
const signed = new Map();
|
|
114
|
+
const credentialFetch = async (input, init) => {
|
|
115
|
+
const url = typeof input === 'string' ? input : input.toString();
|
|
116
|
+
const audience = new URL(url).searchParams.get('repo') ?? ref.authority;
|
|
117
|
+
let sig = signed.get(audience);
|
|
118
|
+
if (!sig) {
|
|
119
|
+
sig = spaceSigHeaders(key, `Atproto-Space ${credential}`, audience);
|
|
120
|
+
signed.set(audience, sig);
|
|
121
|
+
}
|
|
122
|
+
const headers = new Headers(init?.headers);
|
|
123
|
+
for (const [name, value] of Object.entries(await sig))
|
|
124
|
+
headers.set(name, value);
|
|
125
|
+
return fetch(url, { ...init, headers });
|
|
126
|
+
};
|
|
127
|
+
return { space, readerDid, expiresAt: expiryOf(credential), fetch: credentialFetch };
|
|
128
|
+
}
|
|
129
|
+
// --- Cache ---
|
|
130
|
+
const cache = new Map();
|
|
131
|
+
function fresh(cred) {
|
|
132
|
+
return !!cred && cred.expiresAt - RENEW_LEAD_MS > Date.now();
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* A credential for `space`, minted with whichever candidate session works.
|
|
136
|
+
*
|
|
137
|
+
* Candidates are tried in order and the first that succeeds is cached. A
|
|
138
|
+
* refusal about the reader — they were ejected, or never belonged — moves on to
|
|
139
|
+
* the next candidate; a refusal about the space itself stops the whole attempt,
|
|
140
|
+
* since no other reader would fare better.
|
|
141
|
+
*/
|
|
142
|
+
export async function getSpaceCredential(oauthConfig, space, candidates, opts = {}) {
|
|
143
|
+
if (!opts.refresh) {
|
|
144
|
+
const cached = cache.get(space);
|
|
145
|
+
if (fresh(cached))
|
|
146
|
+
return cached;
|
|
147
|
+
}
|
|
148
|
+
let lastError;
|
|
149
|
+
for (const readerDid of candidates) {
|
|
150
|
+
try {
|
|
151
|
+
const credential = await mintSpaceCredential(oauthConfig, space, readerDid);
|
|
152
|
+
cache.set(space, credential);
|
|
153
|
+
return credential;
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
if (isSpaceGone(err))
|
|
157
|
+
throw err;
|
|
158
|
+
lastError = err;
|
|
159
|
+
emit('spaces', 'credential_refused', {
|
|
160
|
+
space,
|
|
161
|
+
reader_did: readerDid,
|
|
162
|
+
error: err instanceof Error ? err.message : String(err),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
cache.delete(space);
|
|
167
|
+
throw lastError ?? new SpaceCredentialError(403, 'NoReader', `No session on this instance can read ${space}`);
|
|
168
|
+
}
|
|
169
|
+
export function forgetSpaceCredential(space) {
|
|
170
|
+
cache.delete(space);
|
|
171
|
+
}
|
|
172
|
+
// --- Per reader ---
|
|
173
|
+
/**
|
|
174
|
+
* Credentials minted as one particular reader, for reads made on their behalf.
|
|
175
|
+
*
|
|
176
|
+
* Kept apart from the cache above, which holds whichever session works for a
|
|
177
|
+
* space and is right for indexing it. A read made for a viewer has to be made
|
|
178
|
+
* as that viewer: the authority's answer is the authorization.
|
|
179
|
+
*/
|
|
180
|
+
const readerCache = new Map();
|
|
181
|
+
/** Pairs held at once. Past this the least recently used goes. */
|
|
182
|
+
const MAX_READER_CREDENTIALS = 5000;
|
|
183
|
+
/**
|
|
184
|
+
* `readerDid`'s own credential for `space`, minted with their stored session.
|
|
185
|
+
*
|
|
186
|
+
* Throws a {@link SpaceCredentialError} when they may not read it, carrying the
|
|
187
|
+
* authority's status and code, and caches only successes — a refusal is asked
|
|
188
|
+
* again next time rather than remembered. `refresh` mints anew, for a read the
|
|
189
|
+
* host refused despite a credential that had not expired by our clock.
|
|
190
|
+
*/
|
|
191
|
+
export async function readerSpaceCredential(oauthConfig, readerDid, space, opts = {}) {
|
|
192
|
+
const key = `${readerDid} ${space}`;
|
|
193
|
+
const cached = readerCache.get(key);
|
|
194
|
+
readerCache.delete(key);
|
|
195
|
+
if (!opts.refresh && fresh(cached)) {
|
|
196
|
+
readerCache.set(key, cached);
|
|
197
|
+
return cached;
|
|
198
|
+
}
|
|
199
|
+
const credential = await mintSpaceCredential(oauthConfig, space, readerDid);
|
|
200
|
+
readerCache.set(key, credential);
|
|
201
|
+
while (readerCache.size > MAX_READER_CREDENTIALS) {
|
|
202
|
+
const oldest = readerCache.keys().next();
|
|
203
|
+
if (oldest.done)
|
|
204
|
+
break;
|
|
205
|
+
readerCache.delete(oldest.value);
|
|
206
|
+
}
|
|
207
|
+
return credential;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Drop `readerDid`'s cached credential for `space`, after something that may
|
|
211
|
+
* have changed their standing in it — leaving it, say. The next read mints anew.
|
|
212
|
+
*/
|
|
213
|
+
export function forgetReaderSpaceCredential(readerDid, space) {
|
|
214
|
+
readerCache.delete(`${readerDid} ${space}`);
|
|
215
|
+
}
|
|
216
|
+
/** For tests: drop every cached credential. */
|
|
217
|
+
export function resetSpaceCredentials() {
|
|
218
|
+
cache.clear();
|
|
219
|
+
readerCache.clear();
|
|
220
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keeping the index level with a permissioned space.
|
|
3
|
+
*
|
|
4
|
+
* A space never reaches a firehose. There is no stream to tail and no relay
|
|
5
|
+
* that has seen it, so the only way to know a space has moved is to ask the
|
|
6
|
+
* authority who has written into it and then ask each of those writers' hosts
|
|
7
|
+
* what changed. Two loops do that:
|
|
8
|
+
*
|
|
9
|
+
* reconcile the authority's `listRepos` gives the writer set with each
|
|
10
|
+
* writer's current rev. Anything whose rev has moved is synced;
|
|
11
|
+
* anything the authority no longer names is dropped.
|
|
12
|
+
* sync one writer's repo, forward from the rev we last read, through
|
|
13
|
+
* `listRepoOps` — or wholesale through `listRecords` when there
|
|
14
|
+
* is no rev to start from, or when the op log has been compacted
|
|
15
|
+
* past it.
|
|
16
|
+
*
|
|
17
|
+
* Write notices exist too and are far cheaper, but they are best-effort by
|
|
18
|
+
* design: the reference host forwards them once from an in-memory queue and
|
|
19
|
+
* logs the failure. So reconcile is the thing that makes sync correct, and a
|
|
20
|
+
* notice only makes it prompt.
|
|
21
|
+
*
|
|
22
|
+
* What is deliberately not done here is verification. The alpha signs a commit
|
|
23
|
+
* over the repo's LtHash state, and a syncer can check it. hatk does not check
|
|
24
|
+
* the equivalent on the firehose path either — it reads records out of relayed
|
|
25
|
+
* CAR blocks and trusts them — so checking here would claim a guarantee the
|
|
26
|
+
* rest of the index does not make. Adding it means depending on `@atproto/space`
|
|
27
|
+
* for LtHash and the commit verifier, and is worth doing to both paths at once
|
|
28
|
+
* rather than to this one alone.
|
|
29
|
+
*/
|
|
30
|
+
import type { OAuthConfig } from '../config.ts';
|
|
31
|
+
import { type SpaceCredential } from './credential.ts';
|
|
32
|
+
import { type SpaceWatch } from './store.ts';
|
|
33
|
+
export interface SpaceEngineOptions {
|
|
34
|
+
oauth: OAuthConfig;
|
|
35
|
+
/** Space type NSIDs this instance will follow. Anything else is refused. */
|
|
36
|
+
types: Set<string>;
|
|
37
|
+
/** Collections hatk indexes at all; a space collection outside this is skipped. */
|
|
38
|
+
collections: Set<string>;
|
|
39
|
+
/**
|
|
40
|
+
* This instance's service identifier — `did:web:...#atproto_space_syncer`.
|
|
41
|
+
* Absent when the instance receives no notices, in which case the sweep is
|
|
42
|
+
* the only thing that notices a write.
|
|
43
|
+
*/
|
|
44
|
+
serviceId?: string;
|
|
45
|
+
}
|
|
46
|
+
export declare function configureSpaceEngine(opts: SpaceEngineOptions): void;
|
|
47
|
+
/**
|
|
48
|
+
* The collections a space of this type holds, narrowed to the ones hatk has a
|
|
49
|
+
* table for.
|
|
50
|
+
*
|
|
51
|
+
* A space type's lexicon declares its collections, which is what makes a space
|
|
52
|
+
* readable without the app being told what is in it — but a space may name
|
|
53
|
+
* collections this instance does not index, and those are simply not its
|
|
54
|
+
* business.
|
|
55
|
+
*/
|
|
56
|
+
export declare function collectionsForSpaceType(spaceType: string): string[];
|
|
57
|
+
/** Bring one writer's repo in a space up to date, however that has to happen. */
|
|
58
|
+
export declare function syncSpaceRepo(watch: SpaceWatch, writer: string, credential: SpaceCredential): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Compare the authority's writer set against what the index holds, and close
|
|
61
|
+
* the difference.
|
|
62
|
+
*
|
|
63
|
+
* The writer set is what the authority has been told, not what is true: a
|
|
64
|
+
* repo's own host is the source of truth, and after a community migrates hosts
|
|
65
|
+
* the set is empty until each writer writes again. So a writer the authority
|
|
66
|
+
* stops naming has their rows dropped, but the rev comparison is only ever a
|
|
67
|
+
* reason to read — never a reason to believe a repo has not moved.
|
|
68
|
+
*/
|
|
69
|
+
export declare function reconcileSpace(watch: SpaceWatch): Promise<void>;
|
|
70
|
+
/** Follow a space from now on, and read it once. */
|
|
71
|
+
export declare function watchSpace(space: string): Promise<SpaceWatch>;
|
|
72
|
+
/** Stop following a space and drop everything indexed from it. */
|
|
73
|
+
export declare function unwatchSpace(space: string): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Act on a write notice: read the one repo it names.
|
|
76
|
+
*
|
|
77
|
+
* Only for a space already followed. A notice about anything else is not an
|
|
78
|
+
* instruction to start following it — that decision belongs to config or to the
|
|
79
|
+
* app, never to whoever can reach this endpoint.
|
|
80
|
+
*
|
|
81
|
+
* The notice's `rev` is deliberately not trusted as the new position: it is
|
|
82
|
+
* read from the repo's own host, which is the source of truth, by the ordinary
|
|
83
|
+
* sync path. The notice only says "look again".
|
|
84
|
+
*/
|
|
85
|
+
export declare function handleWriteNotice(notice: {
|
|
86
|
+
space: string;
|
|
87
|
+
repo: string;
|
|
88
|
+
}): Promise<boolean>;
|
|
89
|
+
/**
|
|
90
|
+
* Reconcile every followed space.
|
|
91
|
+
*
|
|
92
|
+
* Failures are per space and recorded rather than thrown: one community's host
|
|
93
|
+
* being unreachable is not a reason to stop reading the others, and a space
|
|
94
|
+
* whose last reader logged out should go quiet rather than take the sweep down.
|
|
95
|
+
*/
|
|
96
|
+
export declare function reconcileAll(): Promise<void>;
|
|
97
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/spaces/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAQ/C,OAAO,EAA0D,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAE9G,OAAO,EAUL,KAAK,UAAU,EAChB,MAAM,YAAY,CAAA;AAMnB,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,WAAW,CAAA;IAClB,4EAA4E;IAC5E,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAClB,mFAAmF;IACnF,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACxB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAaD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,kBAAkB,GAAG,IAAI,CAEnE;AAOD;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAQnE;AAqUD,iFAAiF;AACjF,wBAAsB,aAAa,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBjH;AAqED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAwCrE;AAID,oDAAoD;AACpD,wBAAsB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAUnE;AAED,kEAAkE;AAClE,wBAAsB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAW/D;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAKjG;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAclD"}
|