@hatk/hatk 0.0.1-alpha.9 → 0.0.1-alpha.90
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 +29 -2
- package/dist/backfill.d.ts.map +1 -1
- package/dist/backfill.js +137 -51
- package/dist/car.d.ts +20 -0
- package/dist/car.d.ts.map +1 -1
- package/dist/car.js +108 -0
- package/dist/cli.js +247 -1043
- package/dist/config.d.ts +145 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +61 -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/{db.d.ts → database/db.d.ts} +68 -6
- package/dist/database/db.d.ts.map +1 -0
- package/dist/database/db.js +1775 -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/{schema.d.ts → database/schema.d.ts} +16 -3
- package/dist/database/schema.d.ts.map +1 -0
- package/dist/{schema.js → database/schema.js} +126 -44
- package/dist/dev-entry.d.ts +8 -0
- package/dist/dev-entry.d.ts.map +1 -0
- package/dist/dev-entry.js +127 -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 +49 -6
- package/dist/hydrate.d.ts.map +1 -1
- package/dist/hydrate.js +21 -20
- package/dist/indexer.d.ts +156 -3
- package/dist/indexer.d.ts.map +1 -1
- package/dist/indexer.js +539 -94
- package/dist/jetstream.d.ts +118 -0
- package/dist/jetstream.d.ts.map +1 -0
- package/dist/jetstream.js +225 -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 +198 -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 +16 -1
- package/dist/oauth/db.d.ts.map +1 -1
- package/dist/oauth/db.js +74 -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 +93 -1
- package/dist/oauth/server.d.ts.map +1 -1
- package/dist/oauth/server.js +416 -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 +82 -0
- package/dist/pds-proxy.d.ts.map +1 -0
- package/dist/pds-proxy.js +356 -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 +21 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +88 -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 +62 -0
- package/dist/server.d.ts +34 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1021 -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 +51 -0
- package/dist/spaces/blob.d.ts.map +1 -0
- package/dist/spaces/blob.js +111 -0
- package/dist/spaces/credential.d.ts +62 -0
- package/dist/spaces/credential.d.ts.map +1 -0
- package/dist/spaces/credential.js +184 -0
- package/dist/spaces/engine.d.ts +97 -0
- package/dist/spaces/engine.d.ts.map +1 -0
- package/dist/spaces/engine.js +559 -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 +88 -0
- package/dist/spaces/index.d.ts +55 -0
- package/dist/spaces/index.d.ts.map +1 -0
- package/dist/spaces/index.js +133 -0
- package/dist/spaces/notify.d.ts +51 -0
- package/dist/spaces/notify.d.ts.map +1 -0
- package/dist/spaces/notify.js +121 -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/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 +39 -0
- package/dist/spaces/verify.d.ts.map +1 -0
- package/dist/spaces/verify.js +87 -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 +101 -12
- package/dist/xrpc.d.ts.map +1 -1
- package/dist/xrpc.js +255 -55
- package/package.json +21 -9
- package/public/admin-auth.js +271 -306
- package/public/admin.html +177 -61
- package/dist/db.d.ts.map +0 -1
- package/dist/db.js +0 -1361
- 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.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
package/dist/feeds.js
CHANGED
|
@@ -9,8 +9,10 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
9
9
|
import { resolve } from 'node:path';
|
|
10
10
|
import { readdirSync } from 'node:fs';
|
|
11
11
|
import { log } from "./logger.js";
|
|
12
|
-
import {
|
|
13
|
-
import { resolveRecords,
|
|
12
|
+
import { packCursor, unpackCursor, isTakendownDid, filterTakendownDids, resolveHandleToDid, } from "./database/db.js";
|
|
13
|
+
import { resolveRecords, buildBaseContext } from "./hydrate.js";
|
|
14
|
+
import { spaceFilterSql } from "./spaces/visibility.js";
|
|
15
|
+
import { guardedQuerySQL, unfilteredQuerySQL } from "./spaces/guard.js";
|
|
14
16
|
export function createPaginate(deps) {
|
|
15
17
|
return async (sql, opts) => {
|
|
16
18
|
const { db, cursor, limit, packCursor: pack, unpackCursor: unpack } = deps;
|
|
@@ -25,6 +27,16 @@ export function createPaginate(deps) {
|
|
|
25
27
|
const op = order === 'ASC' ? '>' : '<';
|
|
26
28
|
let paramIdx = userParams.length + 1;
|
|
27
29
|
const sqlParams = [...userParams];
|
|
30
|
+
// The permissioned-space gate, applied to whatever table the ordering
|
|
31
|
+
// names — the same table `cid` is read from. Feed SQL is hand-written and
|
|
32
|
+
// nothing can inject a predicate into a string somebody else wrote, so
|
|
33
|
+
// building it here is the only way every feed gets it without every feed
|
|
34
|
+
// author remembering to. Outside a viewer's scope it is `space IS NULL`
|
|
35
|
+
// and binds nothing, which is what it compiles to on an instance that
|
|
36
|
+
// indexes no space.
|
|
37
|
+
const gate = spaceFilterSql(prefix.replace(/\.$/, ''), paramIdx);
|
|
38
|
+
sqlParams.push(...gate.params);
|
|
39
|
+
paramIdx = gate.nextIdx;
|
|
28
40
|
// Build cursor condition
|
|
29
41
|
let cursorCondition = '';
|
|
30
42
|
if (cursor) {
|
|
@@ -35,15 +47,12 @@ export function createPaginate(deps) {
|
|
|
35
47
|
paramIdx += 3;
|
|
36
48
|
}
|
|
37
49
|
}
|
|
38
|
-
// Detect existing WHERE and append cursor condition
|
|
50
|
+
// Detect existing WHERE and append the gate and the cursor condition
|
|
39
51
|
let fullSql = sql;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
fullSql += ` WHERE ${cursorCondition}`;
|
|
46
|
-
}
|
|
52
|
+
const appended = [gate.sql, ...(cursorCondition ? [cursorCondition] : [])];
|
|
53
|
+
if (appended.length) {
|
|
54
|
+
const joiner = /\bWHERE\b/i.test(sql) ? 'AND' : 'WHERE';
|
|
55
|
+
fullSql += ` ${joiner} ${appended.join(' AND ')}`;
|
|
47
56
|
}
|
|
48
57
|
fullSql += ` ORDER BY ${orderBy} ${order}, ${cidCol} ${order} LIMIT $${paramIdx}`;
|
|
49
58
|
sqlParams.push(limit + 1);
|
|
@@ -59,7 +68,47 @@ export function createPaginate(deps) {
|
|
|
59
68
|
};
|
|
60
69
|
}
|
|
61
70
|
export function defineFeed(opts) {
|
|
62
|
-
return { ...opts, generate: (ctx) => opts.generate({ ...ctx, ok: (v) => v }) };
|
|
71
|
+
return { __type: 'feed', ...opts, generate: (ctx) => opts.generate({ ...ctx, ok: (v) => v }) };
|
|
72
|
+
}
|
|
73
|
+
/** Register a single feed from a scanned server/ module. */
|
|
74
|
+
export function registerFeed(name, generator) {
|
|
75
|
+
const handler = {
|
|
76
|
+
name,
|
|
77
|
+
label: generator.label || name,
|
|
78
|
+
collection: generator.collection,
|
|
79
|
+
view: generator.view,
|
|
80
|
+
generate: async (params, cursor, limit, viewer) => {
|
|
81
|
+
const paginateDeps = {
|
|
82
|
+
db: { query: guardedQuerySQL, unfiltered: unfilteredQuerySQL },
|
|
83
|
+
cursor,
|
|
84
|
+
limit,
|
|
85
|
+
packCursor,
|
|
86
|
+
unpackCursor,
|
|
87
|
+
};
|
|
88
|
+
const ctx = {
|
|
89
|
+
db: { query: guardedQuerySQL, unfiltered: unfilteredQuerySQL },
|
|
90
|
+
params,
|
|
91
|
+
cursor,
|
|
92
|
+
limit,
|
|
93
|
+
viewer,
|
|
94
|
+
packCursor,
|
|
95
|
+
unpackCursor,
|
|
96
|
+
isTakendown: isTakendownDid,
|
|
97
|
+
filterTakendownDids,
|
|
98
|
+
paginate: createPaginate(paginateDeps),
|
|
99
|
+
spaceFilter: spaceFilterSql,
|
|
100
|
+
};
|
|
101
|
+
const result = await generator.generate(ctx);
|
|
102
|
+
if (Array.isArray(result)) {
|
|
103
|
+
return { uris: result.map((r) => r.uri || r) };
|
|
104
|
+
}
|
|
105
|
+
return { uris: result.uris, cursor: result.cursor };
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
if (typeof generator.hydrate === 'function') {
|
|
109
|
+
handler.hydrate = generator.hydrate;
|
|
110
|
+
}
|
|
111
|
+
feeds.set(name, handler);
|
|
63
112
|
}
|
|
64
113
|
const feeds = new Map();
|
|
65
114
|
export async function initFeeds(feedsDir) {
|
|
@@ -75,7 +124,7 @@ export async function initFeeds(feedsDir) {
|
|
|
75
124
|
for (const file of files) {
|
|
76
125
|
const name = file.replace(/\.(ts|js)$/, '');
|
|
77
126
|
const scriptPath = resolve(feedsDir, file);
|
|
78
|
-
const mod = await import(__rewriteRelativeImportExtension(scriptPath));
|
|
127
|
+
const mod = await import(__rewriteRelativeImportExtension(/* @vite-ignore */ `${scriptPath}?t=${Date.now()}`));
|
|
79
128
|
const generator = mod.default;
|
|
80
129
|
const handler = {
|
|
81
130
|
name,
|
|
@@ -84,14 +133,14 @@ export async function initFeeds(feedsDir) {
|
|
|
84
133
|
view: generator.view,
|
|
85
134
|
generate: async (params, cursor, limit, viewer) => {
|
|
86
135
|
const paginateDeps = {
|
|
87
|
-
db: { query:
|
|
136
|
+
db: { query: guardedQuerySQL, unfiltered: unfilteredQuerySQL },
|
|
88
137
|
cursor,
|
|
89
138
|
limit,
|
|
90
139
|
packCursor,
|
|
91
140
|
unpackCursor,
|
|
92
141
|
};
|
|
93
142
|
const ctx = {
|
|
94
|
-
db: { query:
|
|
143
|
+
db: { query: guardedQuerySQL, unfiltered: unfilteredQuerySQL },
|
|
95
144
|
params,
|
|
96
145
|
cursor,
|
|
97
146
|
limit,
|
|
@@ -101,6 +150,7 @@ export async function initFeeds(feedsDir) {
|
|
|
101
150
|
isTakendown: isTakendownDid,
|
|
102
151
|
filterTakendownDids,
|
|
103
152
|
paginate: createPaginate(paginateDeps),
|
|
153
|
+
spaceFilter: spaceFilterSql,
|
|
104
154
|
};
|
|
105
155
|
const result = await generator.generate(ctx);
|
|
106
156
|
if (Array.isArray(result)) {
|
|
@@ -127,11 +177,17 @@ export async function executeFeed(name, params, cursor, limit, viewer) {
|
|
|
127
177
|
const handler = feeds.get(name);
|
|
128
178
|
if (!handler)
|
|
129
179
|
return null;
|
|
180
|
+
// Resolve handle-based actor param to DID
|
|
181
|
+
if (params.actor && !params.actor.startsWith('did:')) {
|
|
182
|
+
const resolved = await resolveHandleToDid(params.actor);
|
|
183
|
+
if (resolved)
|
|
184
|
+
params.actor = resolved;
|
|
185
|
+
}
|
|
130
186
|
const result = await handler.generate(params, cursor, limit, viewer || null);
|
|
131
187
|
if (handler.hydrate) {
|
|
132
188
|
const items = await resolveRecords(result.uris);
|
|
133
|
-
const ctx =
|
|
134
|
-
const hydrated = await handler.hydrate(ctx);
|
|
189
|
+
const ctx = buildBaseContext(viewer || null);
|
|
190
|
+
const hydrated = await handler.hydrate(ctx, items);
|
|
135
191
|
return { items: hydrated, cursor: result.cursor };
|
|
136
192
|
}
|
|
137
193
|
return { uris: result.uris, cursor: result.cursor };
|
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { OAuthConfig } from './config.ts';
|
|
2
|
+
import { type BaseContext } from './hydrate.ts';
|
|
3
|
+
import { type PushInterface } from './push.ts';
|
|
4
|
+
/** Context passed to the on-login hook after a successful OAuth login. */
|
|
5
|
+
export type OnLoginCtx = Omit<BaseContext, 'db'> & {
|
|
6
|
+
/** DID of the user who just logged in. */
|
|
7
|
+
did: string;
|
|
8
|
+
/** Database access with both read and write. */
|
|
9
|
+
db: {
|
|
10
|
+
query: (sql: string, params?: unknown[]) => Promise<unknown[]>;
|
|
11
|
+
run: (sql: string, params?: unknown[]) => Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Raw SQL with the permissioned-space guard off. A hook is not serving a
|
|
14
|
+
* viewer, and discovering what a space contains — which spaces a
|
|
15
|
+
* community lists, say — is exactly a read over space rows that no scope
|
|
16
|
+
* is open for. See `spaces/guard.ts`.
|
|
17
|
+
*/
|
|
18
|
+
unfiltered: (sql: string, params?: unknown[]) => Promise<unknown[]>;
|
|
19
|
+
};
|
|
20
|
+
/** Trigger a backfill for a DID and wait for it to complete. */
|
|
21
|
+
ensureRepo: (did: string) => Promise<void>;
|
|
22
|
+
/** Write a record to the user's PDS and index locally. */
|
|
23
|
+
createRecord: (collection: string, record: Record<string, unknown>, opts?: {
|
|
24
|
+
rkey?: string;
|
|
25
|
+
}) => Promise<{
|
|
26
|
+
uri?: string;
|
|
27
|
+
cid?: string;
|
|
28
|
+
}>;
|
|
29
|
+
/** Create or update a record on the user's PDS and index locally. */
|
|
30
|
+
putRecord: (collection: string, rkey: string, record: Record<string, unknown>) => Promise<{
|
|
31
|
+
uri?: string;
|
|
32
|
+
cid?: string;
|
|
33
|
+
}>;
|
|
34
|
+
/** Delete a record from the user's PDS and local index. */
|
|
35
|
+
deleteRecord: (collection: string, rkey: string) => Promise<void>;
|
|
36
|
+
};
|
|
37
|
+
/** Context passed to on-commit hooks after a record is indexed. */
|
|
38
|
+
export type OnCommitCtx = {
|
|
39
|
+
/** Whether the record was created or deleted. */
|
|
40
|
+
action: 'create' | 'delete';
|
|
41
|
+
/** The collection NSID that matched. */
|
|
42
|
+
collection: string;
|
|
43
|
+
/** The record value (null for deletes). */
|
|
44
|
+
record: Record<string, any> | null;
|
|
45
|
+
/** DID of the committing actor. */
|
|
46
|
+
repo: string;
|
|
47
|
+
/** AT URI of the record. */
|
|
48
|
+
uri: string;
|
|
49
|
+
/** Database access (read and write). */
|
|
50
|
+
db: {
|
|
51
|
+
query: (sql: string, params?: unknown[]) => Promise<unknown[]>;
|
|
52
|
+
run: (sql: string, params?: unknown[]) => Promise<void>;
|
|
53
|
+
/** Raw SQL with the permissioned-space guard off. See `spaces/guard.ts`. */
|
|
54
|
+
unfiltered: (sql: string, params?: unknown[]) => Promise<unknown[]>;
|
|
55
|
+
};
|
|
56
|
+
/** Typed record lookup (same as BaseContext). */
|
|
57
|
+
lookup: BaseContext['lookup'];
|
|
58
|
+
/** Push notification delivery. */
|
|
59
|
+
push: PushInterface;
|
|
60
|
+
};
|
|
61
|
+
export declare function defineHook(event: 'on-login', handler: (ctx: OnLoginCtx) => Promise<void>): {
|
|
62
|
+
__type: 'hook';
|
|
63
|
+
event: 'on-login';
|
|
64
|
+
handler: (ctx: OnLoginCtx) => Promise<void>;
|
|
65
|
+
};
|
|
66
|
+
export declare function defineHook(event: 'on-commit', options: {
|
|
67
|
+
collections: string[];
|
|
68
|
+
}, handler: (ctx: OnCommitCtx) => Promise<void>): {
|
|
69
|
+
__type: 'hook';
|
|
70
|
+
event: 'on-commit';
|
|
71
|
+
collections: string[];
|
|
72
|
+
handler: (ctx: OnCommitCtx) => Promise<void>;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Discover and load the on-login hook from the project's `hooks/` directory.
|
|
76
|
+
* Looks for `on-login.ts` or `on-login.js`. Safe to call if no hook exists.
|
|
77
|
+
*
|
|
78
|
+
* The documented contract is `export default defineHook('on-login', handler)`,
|
|
79
|
+
* which is an object carrying the handler — the same shape the `server/`
|
|
80
|
+
* scanner recognizes by its `__type` tag. A bare async function is still
|
|
81
|
+
* accepted, because that is what the legacy `hooks/` directory used before
|
|
82
|
+
* `defineHook` existed. Anything else is reported rather than installed: a
|
|
83
|
+
* non-callable hook would otherwise fail silently on every login, since
|
|
84
|
+
* `fireOnLoginHook` swallows what the hook throws.
|
|
85
|
+
*/
|
|
86
|
+
export declare function loadOnLoginHook(hooksDir: string): Promise<void>;
|
|
87
|
+
/** Register a hook from a scanned server/ module. */
|
|
88
|
+
export declare function registerHook(event: string, handler: Function, options?: any): void;
|
|
89
|
+
/** Fire the on-login hook if loaded. Errors are logged but never block login. */
|
|
90
|
+
export declare function fireOnLoginHook(did: string, oauthConfig: OAuthConfig | null): Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Fire on-commit hooks for a batch of indexed records.
|
|
93
|
+
* Runs async and non-blocking — errors are logged but never throw.
|
|
94
|
+
*/
|
|
95
|
+
export declare function fireOnCommitHooks(items: Array<{
|
|
96
|
+
action: 'create' | 'delete';
|
|
97
|
+
collection: string;
|
|
98
|
+
uri: string;
|
|
99
|
+
authorDid: string;
|
|
100
|
+
record: Record<string, any> | null;
|
|
101
|
+
}>): void;
|
|
102
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"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"}
|
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Lifecycle hooks that run in response to server events.
|
|
11
|
+
*
|
|
12
|
+
* Place hook modules in the `hooks/` directory. Currently supported hooks:
|
|
13
|
+
*
|
|
14
|
+
* - `on-login.ts` — called after each successful OAuth login
|
|
15
|
+
* - `on-commit-*.ts` — called after records are indexed from the firehose
|
|
16
|
+
*
|
|
17
|
+
* Each hook default-exports the result of `defineHook()`.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* // hooks/on-login.ts
|
|
22
|
+
* import { defineHook } from '$hatk'
|
|
23
|
+
*
|
|
24
|
+
* export default defineHook("on-login", async (ctx) => {
|
|
25
|
+
* await ctx.ensureRepo(ctx.did)
|
|
26
|
+
* })
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* // hooks/on-commit-favorite.ts
|
|
32
|
+
* import { defineHook } from '$hatk'
|
|
33
|
+
*
|
|
34
|
+
* export default defineHook("on-commit", { collections: ["social.grain.favorite"] },
|
|
35
|
+
* async ({ action, collection, record, repo, uri, db, lookup, push }) => {
|
|
36
|
+
* if (action !== "create") return
|
|
37
|
+
* // send push notification, etc.
|
|
38
|
+
* }
|
|
39
|
+
* )
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
import { existsSync } from 'node:fs';
|
|
43
|
+
import { resolve } from 'node:path';
|
|
44
|
+
import { pdsCreateRecord, pdsPutRecord, pdsDeleteRecord } from "./pds-proxy.js";
|
|
45
|
+
import { log, emit } from "./logger.js";
|
|
46
|
+
import { setRepoStatus, runSQL } from "./database/db.js";
|
|
47
|
+
import { triggerAutoBackfill, awaitBackfill } from "./indexer.js";
|
|
48
|
+
import { buildBaseContext } from "./hydrate.js";
|
|
49
|
+
import { buildPushInterface, isPushEnabled } from "./push.js";
|
|
50
|
+
export function defineHook(event, ...args) {
|
|
51
|
+
if (event === 'on-login') {
|
|
52
|
+
return { __type: 'hook', event, handler: args[0] };
|
|
53
|
+
}
|
|
54
|
+
if (event === 'on-commit') {
|
|
55
|
+
const options = args[0];
|
|
56
|
+
const handler = args[1];
|
|
57
|
+
return { __type: 'hook', event, collections: options.collections, handler };
|
|
58
|
+
}
|
|
59
|
+
throw new Error(`Unknown hook event: ${event}`);
|
|
60
|
+
}
|
|
61
|
+
let onLoginHook = null;
|
|
62
|
+
const onCommitHooks = [];
|
|
63
|
+
/**
|
|
64
|
+
* Discover and load the on-login hook from the project's `hooks/` directory.
|
|
65
|
+
* Looks for `on-login.ts` or `on-login.js`. Safe to call if no hook exists.
|
|
66
|
+
*
|
|
67
|
+
* The documented contract is `export default defineHook('on-login', handler)`,
|
|
68
|
+
* which is an object carrying the handler — the same shape the `server/`
|
|
69
|
+
* scanner recognizes by its `__type` tag. A bare async function is still
|
|
70
|
+
* accepted, because that is what the legacy `hooks/` directory used before
|
|
71
|
+
* `defineHook` existed. Anything else is reported rather than installed: a
|
|
72
|
+
* non-callable hook would otherwise fail silently on every login, since
|
|
73
|
+
* `fireOnLoginHook` swallows what the hook throws.
|
|
74
|
+
*/
|
|
75
|
+
export async function loadOnLoginHook(hooksDir) {
|
|
76
|
+
const tsPath = resolve(hooksDir, 'on-login.ts');
|
|
77
|
+
const jsPath = resolve(hooksDir, 'on-login.js');
|
|
78
|
+
const path = existsSync(tsPath) ? tsPath : existsSync(jsPath) ? jsPath : null;
|
|
79
|
+
if (!path)
|
|
80
|
+
return;
|
|
81
|
+
const mod = await import(__rewriteRelativeImportExtension(/* @vite-ignore */ `${path}?t=${Date.now()}`));
|
|
82
|
+
const handler = resolveHookHandler(mod.default);
|
|
83
|
+
if (!handler) {
|
|
84
|
+
emit('hooks', 'on_login_load_error', {
|
|
85
|
+
path,
|
|
86
|
+
error: 'default export is not a function or a defineHook("on-login", ...) result',
|
|
87
|
+
});
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
onLoginHook = handler;
|
|
91
|
+
log('[hooks] on-login hook loaded');
|
|
92
|
+
}
|
|
93
|
+
/** Unwrap a hook module's default export into the callable handler, if it has one. */
|
|
94
|
+
function resolveHookHandler(exported) {
|
|
95
|
+
if (typeof exported === 'function')
|
|
96
|
+
return exported;
|
|
97
|
+
const handler = exported?.handler;
|
|
98
|
+
return typeof handler === 'function' ? handler : null;
|
|
99
|
+
}
|
|
100
|
+
/** Mark a DID as pending, trigger auto-backfill, and wait for completion. */
|
|
101
|
+
async function ensureRepo(did) {
|
|
102
|
+
await setRepoStatus(did, 'pending');
|
|
103
|
+
triggerAutoBackfill(did);
|
|
104
|
+
await awaitBackfill(did);
|
|
105
|
+
}
|
|
106
|
+
/** Register a hook from a scanned server/ module. */
|
|
107
|
+
export function registerHook(event, handler, options) {
|
|
108
|
+
if (event === 'on-login') {
|
|
109
|
+
onLoginHook = handler;
|
|
110
|
+
log('[hooks] on-login hook registered');
|
|
111
|
+
}
|
|
112
|
+
else if (event === 'on-commit') {
|
|
113
|
+
const collections = new Set(options?.collections || []);
|
|
114
|
+
onCommitHooks.push({ collections, handler: handler });
|
|
115
|
+
log(`[hooks] on-commit hook registered (collections: ${[...collections].join(', ')})`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** Fire the on-login hook if loaded. Errors are logged but never block login. */
|
|
119
|
+
export async function fireOnLoginHook(did, oauthConfig) {
|
|
120
|
+
if (!onLoginHook)
|
|
121
|
+
return;
|
|
122
|
+
try {
|
|
123
|
+
const base = buildBaseContext({ did });
|
|
124
|
+
const viewer = { did };
|
|
125
|
+
const hookPromise = onLoginHook({
|
|
126
|
+
...base,
|
|
127
|
+
did,
|
|
128
|
+
db: { query: base.db.query, run: runSQL, unfiltered: base.db.unfiltered },
|
|
129
|
+
ensureRepo,
|
|
130
|
+
createRecord: async (collection, record, opts) => {
|
|
131
|
+
if (!oauthConfig)
|
|
132
|
+
throw new Error('No OAuth config — cannot write to PDS');
|
|
133
|
+
return pdsCreateRecord(oauthConfig, viewer, { collection, record, rkey: opts?.rkey });
|
|
134
|
+
},
|
|
135
|
+
putRecord: async (collection, rkey, record) => {
|
|
136
|
+
if (!oauthConfig)
|
|
137
|
+
throw new Error('No OAuth config — cannot write to PDS');
|
|
138
|
+
return pdsPutRecord(oauthConfig, viewer, { collection, rkey, record });
|
|
139
|
+
},
|
|
140
|
+
deleteRecord: async (collection, rkey) => {
|
|
141
|
+
if (!oauthConfig)
|
|
142
|
+
throw new Error('No OAuth config — cannot write to PDS');
|
|
143
|
+
await pdsDeleteRecord(oauthConfig, viewer, { collection, rkey });
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error('on-login hook timed out after 30s')), 30_000));
|
|
147
|
+
await Promise.race([hookPromise, timeout]);
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
emit('hooks', 'on_login_error', { did, error: err.message });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Fire on-commit hooks for a batch of indexed records.
|
|
155
|
+
* Runs async and non-blocking — errors are logged but never throw.
|
|
156
|
+
*/
|
|
157
|
+
export function fireOnCommitHooks(items) {
|
|
158
|
+
if (onCommitHooks.length === 0)
|
|
159
|
+
return;
|
|
160
|
+
const base = buildBaseContext(null);
|
|
161
|
+
const push = isPushEnabled() ? buildPushInterface() : { send: async () => { } };
|
|
162
|
+
for (const item of items) {
|
|
163
|
+
for (const hook of onCommitHooks) {
|
|
164
|
+
if (hook.collections.size > 0 && !hook.collections.has(item.collection))
|
|
165
|
+
continue;
|
|
166
|
+
hook
|
|
167
|
+
.handler({
|
|
168
|
+
action: item.action,
|
|
169
|
+
collection: item.collection,
|
|
170
|
+
record: item.record,
|
|
171
|
+
repo: item.authorDid,
|
|
172
|
+
uri: item.uri,
|
|
173
|
+
db: { query: base.db.query, run: runSQL, unfiltered: base.db.unfiltered },
|
|
174
|
+
lookup: base.lookup,
|
|
175
|
+
push,
|
|
176
|
+
})
|
|
177
|
+
.catch((err) => {
|
|
178
|
+
emit('hooks', 'on_commit_error', {
|
|
179
|
+
collection: item.collection,
|
|
180
|
+
uri: item.uri,
|
|
181
|
+
error: err.message,
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
package/dist/hydrate.d.ts
CHANGED
|
@@ -1,23 +1,66 @@
|
|
|
1
1
|
import type { Row } from './lex-types.ts';
|
|
2
2
|
export type { Row };
|
|
3
|
-
export interface
|
|
4
|
-
items: Row<T>[];
|
|
3
|
+
export interface BaseContext {
|
|
5
4
|
viewer: {
|
|
6
5
|
did: string;
|
|
6
|
+
handle?: string;
|
|
7
7
|
} | null;
|
|
8
8
|
db: {
|
|
9
9
|
query: (sql: string, params?: unknown[]) => Promise<unknown[]>;
|
|
10
|
+
/**
|
|
11
|
+
* Raw SQL with the permissioned-space guard off. Named so it can be found
|
|
12
|
+
* with grep: for results never served to a viewer, such as an admin
|
|
13
|
+
* rollup. See `spaces/guard.ts`.
|
|
14
|
+
*/
|
|
15
|
+
unfiltered: (sql: string, params?: unknown[]) => Promise<unknown[]>;
|
|
10
16
|
};
|
|
11
17
|
getRecords: <R = unknown>(collection: string, uris: string[]) => Promise<Map<string, Row<R>>>;
|
|
12
18
|
lookup: <R = unknown>(collection: string, field: string, values: string[]) => Promise<Map<string, Row<R>>>;
|
|
13
19
|
count: (collection: string, field: string, values: string[]) => Promise<Map<string, number>>;
|
|
14
20
|
labels: (uris: string[]) => Promise<Map<string, unknown[]>>;
|
|
15
|
-
blobUrl: (did: string, ref: unknown, preset?:
|
|
21
|
+
blobUrl: (did: string, ref: unknown, preset?: string) => string | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* The permissioned-space gate, for hand-written SQL.
|
|
24
|
+
*
|
|
25
|
+
* Every helper on this context applies it already. Raw SQL through
|
|
26
|
+
* `ctx.db.query` cannot — nothing can inject a predicate into a string
|
|
27
|
+
* somebody else wrote — so a query that selects from a collection a space
|
|
28
|
+
* writes into has to apply it itself:
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* const gate = ctx.spaceFilter('t', 2)
|
|
32
|
+
* ctx.db.query(
|
|
33
|
+
* `SELECT t.* FROM "app.example.post" t WHERE t.did = $1 AND ${gate.sql}`,
|
|
34
|
+
* ['did:plc:someone', ...gate.params],
|
|
35
|
+
* )
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* Outside a viewer's scope this is `t.space IS NULL` and binds nothing, so on
|
|
39
|
+
* an instance that indexes no space it costs one predicate on an indexed
|
|
40
|
+
* column and changes no result.
|
|
41
|
+
*/
|
|
42
|
+
spaceFilter: (alias: string, startIdx: number) => {
|
|
43
|
+
sql: string;
|
|
44
|
+
params: string[];
|
|
45
|
+
nextIdx: number;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Every record of a collection in a space the viewer may read. Empty for a
|
|
49
|
+
* space outside their scope; see `requireSpace` for the explicit refusal.
|
|
50
|
+
*/
|
|
51
|
+
spaceRecords: <R = unknown>(collection: string, space: string) => Promise<Row<R>[]>;
|
|
52
|
+
/** Every record where `field` is one of `values` — the many-rows counterpart of `lookup`. */
|
|
53
|
+
records: <R = unknown>(collection: string, field: string, values: string[]) => Promise<Row<R>[]>;
|
|
54
|
+
/** Throw `NotAuthorized` unless the viewer may read this space. */
|
|
55
|
+
requireSpace: (space: string) => void;
|
|
56
|
+
/** The URL this appview serves a space blob at, for the viewer asking. */
|
|
57
|
+
spaceBlobUrl: (space: string, repo: string, cid: string) => string;
|
|
16
58
|
}
|
|
17
59
|
/** Fetch records for URIs, reshape them, and filter out taken-down DIDs. */
|
|
18
60
|
export declare function resolveRecords(uris: string[]): Promise<Row<unknown>[]>;
|
|
19
|
-
/** Build a
|
|
20
|
-
export declare function
|
|
61
|
+
/** Build a BaseContext for hydration. */
|
|
62
|
+
export declare function buildBaseContext(viewer: {
|
|
21
63
|
did: string;
|
|
22
|
-
|
|
64
|
+
handle?: string;
|
|
65
|
+
} | null): BaseContext;
|
|
23
66
|
//# sourceMappingURL=hydrate.d.ts.map
|
package/dist/hydrate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hydrate.d.ts","sourceRoot":"","sources":["../src/hydrate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hydrate.d.ts","sourceRoot":"","sources":["../src/hydrate.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAA;AAEzC,YAAY,EAAE,GAAG,EAAE,CAAA;AAInB,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAC/C,EAAE,EAAE;QACF,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;QAC9D;;;;WAIG;QACH,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;KACpE,CAAA;IACD,UAAU,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC7F,MAAM,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC1G,KAAK,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAC5F,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,CAAA;IAC3D,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;IAC3E;;;;;;;;;;;;;;;;;;;OAmBG;IACH,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;IACpG;;;OAGG;IACH,YAAY,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IACnF,6FAA6F;IAC7F,OAAO,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IAChG,mEAAmE;IACnE,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACrC,0EAA0E;IAC1E,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;CACnE;AAID,4EAA4E;AAC5E,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAsC5E;AAID,yCAAyC;AACzC,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAAG,WAAW,CAuB7F"}
|
package/dist/hydrate.js
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getRecordsMap, countByFieldBatch, lookupByFieldBatch, queryLabelsForUris, filterTakendownDids, getRecordsByUris, reshapeRow, } from "./database/db.js";
|
|
2
2
|
import { blobUrl } from "./xrpc.js";
|
|
3
|
+
import { collectionFromRecordUri } from "./spaces/uri.js";
|
|
4
|
+
import { spaceFilterSql } from "./spaces/visibility.js";
|
|
5
|
+
import { guardedQuerySQL, unfilteredQuerySQL } from "./spaces/guard.js";
|
|
6
|
+
import { records, requireSpace, spaceRecords } from "./spaces/records.js";
|
|
7
|
+
import { spaceBlobUrl } from "./spaces/blob.js";
|
|
3
8
|
// --- Record Resolution ---
|
|
4
9
|
/** Fetch records for URIs, reshape them, and filter out taken-down DIDs. */
|
|
5
10
|
export async function resolveRecords(uris) {
|
|
6
11
|
if (uris.length === 0)
|
|
7
12
|
return [];
|
|
8
|
-
// Group URIs by collection for batch fetching
|
|
13
|
+
// Group URIs by collection for batch fetching. A space record's collection
|
|
14
|
+
// sits in a different segment than a repo record's, so the position is read
|
|
15
|
+
// by a parser that knows both shapes rather than inline.
|
|
9
16
|
const byCollection = new Map();
|
|
10
17
|
for (const uri of uris) {
|
|
11
|
-
const
|
|
12
|
-
|
|
18
|
+
const col = collectionFromRecordUri(uri);
|
|
19
|
+
if (!col)
|
|
20
|
+
continue;
|
|
13
21
|
if (!byCollection.has(col))
|
|
14
22
|
byCollection.set(col, []);
|
|
15
23
|
byCollection.get(col).push(uri);
|
|
@@ -39,24 +47,12 @@ export async function resolveRecords(uris) {
|
|
|
39
47
|
.filter((r) => r != null);
|
|
40
48
|
}
|
|
41
49
|
// --- Context Builder ---
|
|
42
|
-
/** Build a
|
|
43
|
-
export function
|
|
50
|
+
/** Build a BaseContext for hydration. */
|
|
51
|
+
export function buildBaseContext(viewer) {
|
|
44
52
|
return {
|
|
45
|
-
items,
|
|
46
53
|
viewer,
|
|
47
|
-
db: { query:
|
|
48
|
-
getRecords:
|
|
49
|
-
if (uris.length === 0)
|
|
50
|
-
return new Map();
|
|
51
|
-
const records = await getRecordsByUris(collection, uris);
|
|
52
|
-
const map = new Map();
|
|
53
|
-
for (const r of records) {
|
|
54
|
-
const shaped = reshapeRow(r, r?.__childData, r?.__unionData);
|
|
55
|
-
if (shaped)
|
|
56
|
-
map.set(shaped.uri, shaped);
|
|
57
|
-
}
|
|
58
|
-
return map;
|
|
59
|
-
},
|
|
54
|
+
db: { query: guardedQuerySQL, unfiltered: unfilteredQuerySQL },
|
|
55
|
+
getRecords: getRecordsMap,
|
|
60
56
|
lookup: async (collection, field, values) => {
|
|
61
57
|
if (values.length === 0)
|
|
62
58
|
return new Map();
|
|
@@ -71,5 +67,10 @@ export function buildHydrateContext(items, viewer) {
|
|
|
71
67
|
},
|
|
72
68
|
labels: queryLabelsForUris,
|
|
73
69
|
blobUrl,
|
|
70
|
+
spaceFilter: spaceFilterSql,
|
|
71
|
+
spaceRecords,
|
|
72
|
+
records,
|
|
73
|
+
requireSpace,
|
|
74
|
+
spaceBlobUrl,
|
|
74
75
|
};
|
|
75
76
|
}
|