@hatk/hatk 0.0.1-alpha.2 → 0.0.1-alpha.21
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/backfill.d.ts +60 -1
- package/dist/backfill.d.ts.map +1 -1
- package/dist/backfill.js +154 -32
- package/dist/car.d.ts +59 -1
- package/dist/car.d.ts.map +1 -1
- package/dist/car.js +179 -7
- package/dist/cbor.d.ts +37 -0
- package/dist/cbor.d.ts.map +1 -1
- package/dist/cbor.js +36 -3
- package/dist/cid.d.ts +37 -0
- package/dist/cid.d.ts.map +1 -1
- package/dist/cid.js +38 -3
- package/dist/cli.js +76 -15
- package/dist/config.d.ts +10 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +35 -9
- package/dist/db.d.ts +1 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +4 -38
- package/dist/fts.d.ts.map +1 -1
- package/dist/fts.js +5 -0
- package/dist/indexer.d.ts +1 -0
- package/dist/indexer.d.ts.map +1 -1
- package/dist/indexer.js +14 -2
- package/dist/main.js +42 -21
- package/dist/mst.d.ts +3 -1
- package/dist/mst.d.ts.map +1 -1
- package/dist/mst.js +6 -8
- package/dist/server.d.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +52 -8
- package/dist/test.js +4 -4
- package/dist/vite-plugin.d.ts.map +1 -1
- package/dist/vite-plugin.js +2 -1
- package/package.json +8 -2
- package/public/robots.txt +2 -0
package/dist/indexer.js
CHANGED
|
@@ -18,7 +18,7 @@ let ftsRebuildInterval = 500;
|
|
|
18
18
|
const pendingBuffers = new Map();
|
|
19
19
|
// Track in-flight backfills to avoid duplicates
|
|
20
20
|
const backfillInFlight = new Set();
|
|
21
|
-
const
|
|
21
|
+
const pendingReschedule = new Set();
|
|
22
22
|
// In-memory cache of repo status to avoid flooding the DB read queue
|
|
23
23
|
const repoStatusCache = new Map();
|
|
24
24
|
// Set by startIndexer
|
|
@@ -27,6 +27,7 @@ let indexerSignalCollections;
|
|
|
27
27
|
let indexerPinnedRepos = null;
|
|
28
28
|
let indexerFetchTimeout;
|
|
29
29
|
let indexerMaxRetries;
|
|
30
|
+
let maxConcurrentBackfills = 3;
|
|
30
31
|
async function flushBuffer() {
|
|
31
32
|
if (buffer.length === 0)
|
|
32
33
|
return;
|
|
@@ -113,6 +114,16 @@ function bufferWrite(item) {
|
|
|
113
114
|
export async function triggerAutoBackfill(did, attempt = 0) {
|
|
114
115
|
if (backfillInFlight.has(did))
|
|
115
116
|
return;
|
|
117
|
+
if (backfillInFlight.size >= maxConcurrentBackfills) {
|
|
118
|
+
if (!pendingReschedule.has(did)) {
|
|
119
|
+
pendingReschedule.add(did);
|
|
120
|
+
setTimeout(() => {
|
|
121
|
+
pendingReschedule.delete(did);
|
|
122
|
+
triggerAutoBackfill(did, attempt);
|
|
123
|
+
}, 10_000);
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
116
127
|
backfillInFlight.add(did);
|
|
117
128
|
pendingBuffers.set(did, []);
|
|
118
129
|
if (attempt === 0)
|
|
@@ -193,6 +204,7 @@ export async function startIndexer(opts) {
|
|
|
193
204
|
indexerPinnedRepos = opts.pinnedRepos || null;
|
|
194
205
|
indexerFetchTimeout = fetchTimeout;
|
|
195
206
|
indexerMaxRetries = opts.maxRetries;
|
|
207
|
+
maxConcurrentBackfills = opts.parallelism ?? 3;
|
|
196
208
|
// Pre-populate repo status cache from DB so non-signal updates
|
|
197
209
|
// (e.g. profile changes) are processed for already-tracked DIDs
|
|
198
210
|
if (repoStatusCache.size === 0) {
|
|
@@ -264,7 +276,7 @@ function processMessage(bytes, collections) {
|
|
|
264
276
|
repoStatusCache.set(did, 'unknown');
|
|
265
277
|
}
|
|
266
278
|
if (hasSignalOp && (!indexerPinnedRepos || indexerPinnedRepos.has(did))) {
|
|
267
|
-
if (repoStatus === null && backfillInFlight.size <
|
|
279
|
+
if (repoStatus === null && backfillInFlight.size < maxConcurrentBackfills) {
|
|
268
280
|
repoStatusCache.set(did, 'pending');
|
|
269
281
|
triggerAutoBackfill(did);
|
|
270
282
|
}
|
package/dist/main.js
CHANGED
|
@@ -5,7 +5,7 @@ import { log } from "./logger.js";
|
|
|
5
5
|
import { loadConfig } from "./config.js";
|
|
6
6
|
import { loadLexicons, storeLexicons, discoverCollections, generateTableSchema, generateCreateTableSQL, } from "./schema.js";
|
|
7
7
|
import { discoverViews } from "./views.js";
|
|
8
|
-
import { initDatabase, getCursor, querySQL
|
|
8
|
+
import { initDatabase, getCursor, querySQL } from "./db.js";
|
|
9
9
|
import { initFeeds, listFeeds } from "./feeds.js";
|
|
10
10
|
import { initXrpc, listXrpc, configureRelay } from "./xrpc.js";
|
|
11
11
|
import { initOpengraph } from "./opengraph.js";
|
|
@@ -19,10 +19,15 @@ import { runBackfill } from "./backfill.js";
|
|
|
19
19
|
import { initOAuth } from "./oauth/server.js";
|
|
20
20
|
import { loadOnLoginHook } from "./oauth/hooks.js";
|
|
21
21
|
import { initSetup } from "./setup.js";
|
|
22
|
-
|
|
22
|
+
function logMemory(phase) {
|
|
23
|
+
const mem = process.memoryUsage();
|
|
24
|
+
log(`[mem] ${phase}: heap=${Math.round(mem.heapUsed / 1024 / 1024)}MB rss=${Math.round(mem.rss / 1024 / 1024)}MB external=${Math.round(mem.external / 1024 / 1024)}MB arrayBuffers=${Math.round(mem.arrayBuffers / 1024 / 1024)}MB`);
|
|
25
|
+
}
|
|
26
|
+
const configPath = process.argv[2] || 'hatk.config.ts';
|
|
23
27
|
const configDir = dirname(resolve(configPath));
|
|
28
|
+
logMemory('startup');
|
|
24
29
|
// 1. Load config
|
|
25
|
-
const config = loadConfig(configPath);
|
|
30
|
+
const config = await loadConfig(configPath);
|
|
26
31
|
configureRelay(config.relay);
|
|
27
32
|
// 2. Load lexicons, validate schemas, and discover collections
|
|
28
33
|
const lexicons = loadLexicons(resolve(configDir, 'lexicons'));
|
|
@@ -74,9 +79,8 @@ if (config.database !== ':memory:') {
|
|
|
74
79
|
mkdirSync(dirname(config.database), { recursive: true });
|
|
75
80
|
}
|
|
76
81
|
await initDatabase(config.database, schemas, ddlStatements);
|
|
82
|
+
logMemory('after-db-init');
|
|
77
83
|
log(`[main] DuckDB initialized (${config.database === ':memory:' ? 'in-memory' : config.database})`);
|
|
78
|
-
// 3a. Backfill child tables for decomposed arrays (one-time migration)
|
|
79
|
-
await backfillChildTables();
|
|
80
84
|
// 3b. Run setup hooks (after DB init, before server)
|
|
81
85
|
await initSetup(resolve(configDir, 'setup'));
|
|
82
86
|
// Detect orphaned tables
|
|
@@ -106,9 +110,34 @@ if (config.oauth) {
|
|
|
106
110
|
await initOAuth(config.oauth, config.plc, config.relay);
|
|
107
111
|
log(`[main] OAuth initialized (issuer: ${config.oauth.issuer})`);
|
|
108
112
|
}
|
|
113
|
+
logMemory('before-server');
|
|
109
114
|
// 5. Start server immediately (don't wait for backfill)
|
|
110
115
|
const collectionSet = new Set(collections);
|
|
111
|
-
|
|
116
|
+
const backfillOpts = {
|
|
117
|
+
pdsUrl: relayHttpUrl(config.relay),
|
|
118
|
+
plcUrl: config.plc,
|
|
119
|
+
collections: collectionSet,
|
|
120
|
+
config: config.backfill,
|
|
121
|
+
};
|
|
122
|
+
function runBackfillAndRestart() {
|
|
123
|
+
runBackfill(backfillOpts)
|
|
124
|
+
.then((recordCount) => {
|
|
125
|
+
log('[main] Backfill complete, rebuilding FTS indexes...');
|
|
126
|
+
return rebuildAllIndexes(collections).then(() => recordCount);
|
|
127
|
+
})
|
|
128
|
+
.then((recordCount) => {
|
|
129
|
+
log('[main] FTS indexes ready');
|
|
130
|
+
if (recordCount > 0) {
|
|
131
|
+
logMemory('after-backfill');
|
|
132
|
+
log('[main] Restarting to reclaim memory...');
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
.catch((err) => {
|
|
137
|
+
console.error('[main] Backfill error:', err.message);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
startServer(config.port, collections, config.publicDir, config.oauth, config.admins, undefined, runBackfillAndRestart);
|
|
112
141
|
log(`\nhatk running:`);
|
|
113
142
|
log(` Relay: ${config.relay}`);
|
|
114
143
|
log(` Database: ${config.database}`);
|
|
@@ -117,6 +146,7 @@ log(` Collections: ${collections.join(', ')}`);
|
|
|
117
146
|
log(` Feeds: ${listFeeds()
|
|
118
147
|
.map((f) => f.name)
|
|
119
148
|
.join(', ')}`);
|
|
149
|
+
logMemory('after-server');
|
|
120
150
|
// 6. Start indexer with cursor
|
|
121
151
|
const cursor = await getCursor('relay');
|
|
122
152
|
startIndexer({
|
|
@@ -127,22 +157,13 @@ startIndexer({
|
|
|
127
157
|
cursor,
|
|
128
158
|
fetchTimeout: config.backfill.fetchTimeout,
|
|
129
159
|
maxRetries: config.backfill.maxRetries,
|
|
160
|
+
parallelism: config.backfill.parallelism,
|
|
130
161
|
ftsRebuildInterval: config.ftsRebuildInterval,
|
|
131
162
|
});
|
|
132
163
|
// 7. Run backfill in background
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
})
|
|
139
|
-
.then(() => {
|
|
140
|
-
log('[main] Backfill complete, rebuilding FTS indexes...');
|
|
141
|
-
return rebuildAllIndexes(collections);
|
|
142
|
-
})
|
|
143
|
-
.then(() => {
|
|
144
|
-
log('[main] FTS indexes ready');
|
|
145
|
-
})
|
|
146
|
-
.catch((err) => {
|
|
147
|
-
console.error('[main] Backfill error:', err.message);
|
|
164
|
+
runBackfillAndRestart();
|
|
165
|
+
// Graceful shutdown
|
|
166
|
+
process.on('SIGTERM', () => {
|
|
167
|
+
log('[main] Received SIGTERM, shutting down...');
|
|
168
|
+
process.exit(0);
|
|
148
169
|
});
|
package/dist/mst.d.ts
CHANGED
|
@@ -2,5 +2,7 @@ export interface MstEntry {
|
|
|
2
2
|
path: string;
|
|
3
3
|
cid: string;
|
|
4
4
|
}
|
|
5
|
-
export declare function walkMst(blocks:
|
|
5
|
+
export declare function walkMst(blocks: {
|
|
6
|
+
get(cid: string): Uint8Array | undefined;
|
|
7
|
+
}, rootCid: string): Generator<MstEntry>;
|
|
6
8
|
//# sourceMappingURL=mst.d.ts.map
|
package/dist/mst.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mst.d.ts","sourceRoot":"","sources":["../src/mst.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,
|
|
1
|
+
{"version":3,"file":"mst.d.ts","sourceRoot":"","sources":["../src/mst.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,wBAAiB,OAAO,CAAC,MAAM,EAAE;IAAE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAA;CAAE,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,CA8BnH"}
|
package/dist/mst.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { cborDecode } from "./cbor.js";
|
|
2
|
-
export function walkMst(blocks, rootCid) {
|
|
3
|
-
|
|
4
|
-
function visit(cid, prefix) {
|
|
2
|
+
export function* walkMst(blocks, rootCid) {
|
|
3
|
+
function* visit(cid, prefix) {
|
|
5
4
|
const data = blocks.get(cid);
|
|
6
5
|
if (!data)
|
|
7
6
|
return prefix;
|
|
8
7
|
const { value: node } = cborDecode(data);
|
|
9
8
|
// Visit left subtree
|
|
10
9
|
if (node.l?.$link)
|
|
11
|
-
visit(node.l.$link, prefix);
|
|
10
|
+
yield* visit(node.l.$link, prefix);
|
|
12
11
|
let lastKey = prefix;
|
|
13
12
|
for (const entry of node.e || []) {
|
|
14
13
|
const keySuffix = entry.k instanceof Uint8Array ? new TextDecoder().decode(entry.k) : entry.k;
|
|
@@ -16,15 +15,14 @@ export function walkMst(blocks, rootCid) {
|
|
|
16
15
|
const fullKey = lastKey.substring(0, prefixLen) + keySuffix;
|
|
17
16
|
lastKey = fullKey;
|
|
18
17
|
if (entry.v?.$link) {
|
|
19
|
-
|
|
18
|
+
yield { path: fullKey, cid: entry.v.$link };
|
|
20
19
|
}
|
|
21
20
|
// Visit right subtree
|
|
22
21
|
if (entry.t?.$link) {
|
|
23
|
-
visit(entry.t.$link, lastKey);
|
|
22
|
+
yield* visit(entry.t.$link, lastKey);
|
|
24
23
|
}
|
|
25
24
|
}
|
|
26
25
|
return lastKey;
|
|
27
26
|
}
|
|
28
|
-
visit(rootCid, '');
|
|
29
|
-
return entries;
|
|
27
|
+
yield* visit(rootCid, '');
|
|
30
28
|
}
|
package/dist/server.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ import { type Server, type IncomingMessage } from 'node:http';
|
|
|
2
2
|
import type { OAuthConfig } from './config.ts';
|
|
3
3
|
export declare function startServer(port: number, collections: string[], publicDir: string | null, oauth: OAuthConfig | null, admins?: string[], resolveViewer?: (req: IncomingMessage) => {
|
|
4
4
|
did: string;
|
|
5
|
-
} | null): Server;
|
|
5
|
+
} | null, onResync?: () => void): Server;
|
|
6
6
|
//# sourceMappingURL=server.d.ts.map
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,MAAM,EAAE,KAAK,eAAe,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,MAAM,EAAE,KAAK,eAAe,EAAE,MAAM,WAAW,CAAA;AAmD3E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AA2B9C,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EAAE,EACrB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,KAAK,EAAE,WAAW,GAAG,IAAI,EACzB,MAAM,GAAE,MAAM,EAAO,EACrB,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,EAChE,QAAQ,CAAC,EAAE,MAAM,IAAI,GACpB,MAAM,CA28BR"}
|
package/dist/server.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { createServer } from 'node:http';
|
|
2
|
+
import { gzipSync } from 'node:zlib';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
2
4
|
import { readFile } from 'node:fs/promises';
|
|
3
5
|
import { join, extname } from 'node:path';
|
|
4
6
|
import { queryRecords, getRecordByUri, searchRecords, getSchema, reshapeRow, setRepoStatus, getRepoStatus, getRepoRetryInfo, querySQL, insertRecord, deleteRecord, queryLabelsForUris, insertLabels, searchAccounts, listReposPaginated, getCollectionCounts, normalizeValue, getSchemaDump, getPreferences, putPreference, } from "./db.js";
|
|
@@ -37,14 +39,15 @@ function readBodyRaw(req) {
|
|
|
37
39
|
req.on('error', reject);
|
|
38
40
|
});
|
|
39
41
|
}
|
|
40
|
-
export function startServer(port, collections, publicDir, oauth, admins = [], resolveViewer) {
|
|
42
|
+
export function startServer(port, collections, publicDir, oauth, admins = [], resolveViewer, onResync) {
|
|
41
43
|
const coreXrpc = (method) => `/xrpc/dev.hatk.${method}`;
|
|
44
|
+
const devMode = process.env.DEV_MODE === '1';
|
|
42
45
|
function requireAdmin(viewer, res) {
|
|
43
46
|
if (!viewer) {
|
|
44
47
|
jsonError(res, 401, 'Authentication required');
|
|
45
48
|
return false;
|
|
46
49
|
}
|
|
47
|
-
if (!admins.includes(viewer.did)) {
|
|
50
|
+
if (!devMode && !admins.includes(viewer.did)) {
|
|
48
51
|
jsonError(res, 403, 'Admin access required');
|
|
49
52
|
return false;
|
|
50
53
|
}
|
|
@@ -447,9 +450,10 @@ export function startServer(port, collections, publicDir, oauth, admins = [], re
|
|
|
447
450
|
}
|
|
448
451
|
for (const did of repoList) {
|
|
449
452
|
await setRepoStatus(did, 'pending');
|
|
450
|
-
triggerAutoBackfill(did);
|
|
451
453
|
}
|
|
452
454
|
jsonResponse(res, { resyncing: repoList.length });
|
|
455
|
+
if (onResync)
|
|
456
|
+
onResync();
|
|
453
457
|
return;
|
|
454
458
|
}
|
|
455
459
|
// POST /admin/repos/remove — remove DIDs from tracking
|
|
@@ -478,7 +482,14 @@ export function startServer(port, collections, publicDir, oauth, admins = [], re
|
|
|
478
482
|
const sizeRows = await querySQL(`SELECT database_size, memory_usage, memory_limit FROM pragma_database_size()`);
|
|
479
483
|
const dbInfo = sizeRows[0] ?? {};
|
|
480
484
|
const collectionCounts = await getCollectionCounts();
|
|
481
|
-
|
|
485
|
+
const mem = process.memoryUsage();
|
|
486
|
+
const node = {
|
|
487
|
+
rss: `${(mem.rss / 1024 / 1024).toFixed(1)} MiB`,
|
|
488
|
+
heapUsed: `${(mem.heapUsed / 1024 / 1024).toFixed(1)} MiB`,
|
|
489
|
+
heapTotal: `${(mem.heapTotal / 1024 / 1024).toFixed(1)} MiB`,
|
|
490
|
+
external: `${(mem.external / 1024 / 1024).toFixed(1)} MiB`,
|
|
491
|
+
};
|
|
492
|
+
jsonResponse(res, { repos: counts, duckdb: dbInfo, node, collections: collectionCounts });
|
|
482
493
|
return;
|
|
483
494
|
}
|
|
484
495
|
// GET /admin/info/:did — repo status info
|
|
@@ -864,6 +875,21 @@ export function startServer(port, collections, publicDir, oauth, admins = [], re
|
|
|
864
875
|
throw err;
|
|
865
876
|
}
|
|
866
877
|
}
|
|
878
|
+
// GET /robots.txt — serve from user's public dir or fall back to hatk default
|
|
879
|
+
if (url.pathname === '/robots.txt') {
|
|
880
|
+
const userRobots = publicDir ? join(publicDir, 'robots.txt') : null;
|
|
881
|
+
const defaultRobots = join(import.meta.dirname, '../public/robots.txt');
|
|
882
|
+
const robotsPath = userRobots && existsSync(userRobots) ? userRobots : defaultRobots;
|
|
883
|
+
try {
|
|
884
|
+
const content = await readFile(robotsPath);
|
|
885
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
886
|
+
res.end(content);
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
catch {
|
|
890
|
+
// fall through
|
|
891
|
+
}
|
|
892
|
+
}
|
|
867
893
|
// Static file serving
|
|
868
894
|
if (publicDir) {
|
|
869
895
|
try {
|
|
@@ -912,15 +938,33 @@ export function startServer(port, collections, publicDir, oauth, admins = [], re
|
|
|
912
938
|
server.listen(port, () => log(`[server] ${oauth?.issuer || `http://localhost:${port}`}`));
|
|
913
939
|
return server;
|
|
914
940
|
}
|
|
941
|
+
function sendJson(res, status, body) {
|
|
942
|
+
const acceptEncoding = res.req?.headers['accept-encoding'] || '';
|
|
943
|
+
if (body.length > 1024 && /\bgzip\b/.test(acceptEncoding)) {
|
|
944
|
+
const compressed = gzipSync(body);
|
|
945
|
+
res.writeHead(status, {
|
|
946
|
+
'Content-Type': 'application/json',
|
|
947
|
+
'Content-Encoding': 'gzip',
|
|
948
|
+
Vary: 'Accept-Encoding',
|
|
949
|
+
...(status === 200 ? { 'Cache-Control': 'no-store' } : {}),
|
|
950
|
+
});
|
|
951
|
+
res.end(compressed);
|
|
952
|
+
}
|
|
953
|
+
else {
|
|
954
|
+
res.writeHead(status, {
|
|
955
|
+
'Content-Type': 'application/json',
|
|
956
|
+
...(status === 200 ? { 'Cache-Control': 'no-store' } : {}),
|
|
957
|
+
});
|
|
958
|
+
res.end(body);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
915
961
|
function jsonResponse(res, data) {
|
|
916
|
-
res.
|
|
917
|
-
res.end(JSON.stringify(data, (_, v) => normalizeValue(v)));
|
|
962
|
+
sendJson(res, 200, Buffer.from(JSON.stringify(data, (_, v) => normalizeValue(v))));
|
|
918
963
|
}
|
|
919
964
|
function jsonError(res, status, message) {
|
|
920
965
|
if (res.headersSent)
|
|
921
966
|
return;
|
|
922
|
-
res
|
|
923
|
-
res.end(JSON.stringify({ error: message }));
|
|
967
|
+
sendJson(res, status, Buffer.from(JSON.stringify({ error: message })));
|
|
924
968
|
}
|
|
925
969
|
/** Proxy a request to the user's PDS with DPoP + automatic nonce retry + token refresh. */
|
|
926
970
|
async function proxyToPds(oauthConfig, session, method, pdsUrl, body) {
|
package/dist/test.js
CHANGED
|
@@ -14,14 +14,14 @@ import { validateLexicons } from '@bigmoves/lexicon';
|
|
|
14
14
|
import { packCursor, unpackCursor, isTakendownDid, filterTakendownDids } from "./db.js";
|
|
15
15
|
import { seed as createSeedHelpers } from "./seed.js";
|
|
16
16
|
/**
|
|
17
|
-
* Find the project's config.
|
|
18
|
-
* Returns the resolved config path, or falls back to 'config.
|
|
17
|
+
* Find the project's hatk.config.ts by walking up from cwd.
|
|
18
|
+
* Returns the resolved config path, or falls back to 'hatk.config.ts'.
|
|
19
19
|
*/
|
|
20
20
|
function findConfigPath() {
|
|
21
21
|
const explicit = process.env.APPVIEW_CONFIG;
|
|
22
22
|
if (explicit)
|
|
23
23
|
return resolve(explicit);
|
|
24
|
-
return resolve('config.
|
|
24
|
+
return resolve('hatk.config.ts');
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* Boot an in-memory hatk context for unit tests.
|
|
@@ -34,7 +34,7 @@ function findConfigPath() {
|
|
|
34
34
|
*/
|
|
35
35
|
export async function createTestContext() {
|
|
36
36
|
const configPath = findConfigPath();
|
|
37
|
-
const config = loadConfig(configPath);
|
|
37
|
+
const config = await loadConfig(configPath);
|
|
38
38
|
const configDir = dirname(resolve(configPath));
|
|
39
39
|
configureRelay(config.relay);
|
|
40
40
|
// Load and validate lexicons
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["../src/vite-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAKlC,wBAAgB,IAAI,CAAC,IAAI,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["../src/vite-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAKlC,wBAAgB,IAAI,CAAC,IAAI,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAuFrD"}
|
package/dist/vite-plugin.js
CHANGED
|
@@ -64,13 +64,14 @@ export function hatk(opts) {
|
|
|
64
64
|
const mainPath = resolve(import.meta.dirname, 'main.js');
|
|
65
65
|
const watchDirs = ['xrpc', 'feeds', 'labels', 'jobs', 'setup', 'lexicons'].filter((d) => existsSync(d));
|
|
66
66
|
const watchArgs = watchDirs.flatMap((d) => ['--watch-path', d]);
|
|
67
|
-
serverProcess = spawn('npx', ['tsx', 'watch', ...watchArgs, mainPath, 'config.
|
|
67
|
+
serverProcess = spawn('npx', ['tsx', 'watch', ...watchArgs, mainPath, 'hatk.config.ts'], {
|
|
68
68
|
stdio: 'inherit',
|
|
69
69
|
cwd: process.cwd(),
|
|
70
70
|
env: {
|
|
71
71
|
...process.env,
|
|
72
72
|
PORT: String(backendPort),
|
|
73
73
|
OAUTH_ISSUER: process.env.OAUTH_ISSUER || issuer,
|
|
74
|
+
DEV_MODE: '1',
|
|
74
75
|
},
|
|
75
76
|
});
|
|
76
77
|
server.httpServer?.on('close', () => {
|
package/package.json
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hatk/hatk",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.21",
|
|
4
|
+
"license": "MIT",
|
|
4
5
|
"bin": {
|
|
5
6
|
"hatk": "dist/cli.js"
|
|
6
7
|
},
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"fonts",
|
|
11
|
+
"public"
|
|
12
|
+
],
|
|
7
13
|
"type": "module",
|
|
8
14
|
"exports": {
|
|
9
15
|
"./feeds": "./dist/feeds.js",
|
|
@@ -17,9 +23,9 @@
|
|
|
17
23
|
"./setup": "./dist/setup.js",
|
|
18
24
|
"./test": "./dist/test.js",
|
|
19
25
|
"./test/browser": "./dist/test-browser.js",
|
|
26
|
+
"./config": "./dist/config.js",
|
|
20
27
|
"./vite-plugin": "./dist/vite-plugin.js"
|
|
21
28
|
},
|
|
22
|
-
"files": ["dist", "fonts", "public"],
|
|
23
29
|
"scripts": {
|
|
24
30
|
"build": "tsc -p tsconfig.build.json",
|
|
25
31
|
"prepublishOnly": "npm run build"
|