@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
package/dist/test.js
CHANGED
|
@@ -1,27 +1,32 @@
|
|
|
1
1
|
import { resolve, dirname } from 'node:path';
|
|
2
2
|
import { readdirSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { registerHatkResolveHook } from "./resolve-hatk.js";
|
|
3
4
|
import YAML from 'yaml';
|
|
4
5
|
import { loadConfig } from "./config.js";
|
|
5
|
-
import { loadLexicons, storeLexicons, discoverCollections, generateTableSchema, generateCreateTableSQL, } from "./schema.js";
|
|
6
|
-
import { initDatabase, querySQL, runSQL, insertRecord, closeDatabase } from "./db.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
6
|
+
import { validatableLexicons, loadLexicons, storeLexicons, discoverCollections, generateTableSchema, generateCreateTableSQL, } from "./database/schema.js";
|
|
7
|
+
import { initDatabase, querySQL, runSQL, insertRecord, closeDatabase } from "./database/db.js";
|
|
8
|
+
import { createAdapter } from "./database/adapter-factory.js";
|
|
9
|
+
import { SQLITE_DIALECT } from "./database/dialect.js";
|
|
10
|
+
import { rebuildAllIndexes, setSearchPort } from "./database/fts.js";
|
|
11
|
+
import { executeFeed, listFeeds, createPaginate } from "./feeds.js";
|
|
12
|
+
import { executeXrpc, listXrpc, configureRelay, configureCdn } from "./xrpc.js";
|
|
13
|
+
import { initServer } from "./server-init.js";
|
|
11
14
|
import { discoverViews } from "./views.js";
|
|
12
|
-
import { loadOnLoginHook } from "./oauth/hooks.js";
|
|
13
15
|
import { validateLexicons } from '@bigmoves/lexicon';
|
|
14
|
-
import { packCursor, unpackCursor, isTakendownDid, filterTakendownDids } from "./db.js";
|
|
16
|
+
import { packCursor, unpackCursor, isTakendownDid, filterTakendownDids } from "./database/db.js";
|
|
15
17
|
import { seed as createSeedHelpers } from "./seed.js";
|
|
18
|
+
import { setPrivateCollections } from "./private-collections.js";
|
|
19
|
+
import { spaceFilterSql } from "./spaces/visibility.js";
|
|
20
|
+
import { guardedQuerySQL, unfilteredQuerySQL } from "./spaces/guard.js";
|
|
16
21
|
/**
|
|
17
|
-
* Find the project's config.
|
|
18
|
-
* Returns the resolved config path, or falls back to 'config.
|
|
22
|
+
* Find the project's hatk.config.ts by walking up from cwd.
|
|
23
|
+
* Returns the resolved config path, or falls back to 'hatk.config.ts'.
|
|
19
24
|
*/
|
|
20
25
|
function findConfigPath() {
|
|
21
26
|
const explicit = process.env.APPVIEW_CONFIG;
|
|
22
27
|
if (explicit)
|
|
23
28
|
return resolve(explicit);
|
|
24
|
-
return resolve('config.
|
|
29
|
+
return resolve('hatk.config.ts');
|
|
25
30
|
}
|
|
26
31
|
/**
|
|
27
32
|
* Boot an in-memory hatk context for unit tests.
|
|
@@ -33,13 +38,15 @@ function findConfigPath() {
|
|
|
33
38
|
* but it will NOT work with --pool=threads (multiple tests sharing a process).
|
|
34
39
|
*/
|
|
35
40
|
export async function createTestContext() {
|
|
41
|
+
registerHatkResolveHook();
|
|
36
42
|
const configPath = findConfigPath();
|
|
37
|
-
const config = loadConfig(configPath);
|
|
43
|
+
const config = await loadConfig(configPath);
|
|
38
44
|
const configDir = dirname(resolve(configPath));
|
|
39
45
|
configureRelay(config.relay);
|
|
46
|
+
configureCdn(config.cdn);
|
|
40
47
|
// Load and validate lexicons
|
|
41
48
|
const lexicons = loadLexicons(resolve(configDir, 'lexicons'));
|
|
42
|
-
const lexiconErrors = validateLexicons(
|
|
49
|
+
const lexiconErrors = validateLexicons(validatableLexicons(lexicons));
|
|
43
50
|
if (lexiconErrors) {
|
|
44
51
|
const messages = Object.entries(lexiconErrors).flatMap(([nsid, errs]) => errs.map((e) => `${nsid}: ${e}`));
|
|
45
52
|
throw new Error(`Invalid lexicons:\n${messages.join('\n')}`);
|
|
@@ -56,25 +63,30 @@ export async function createTestContext() {
|
|
|
56
63
|
continue;
|
|
57
64
|
const schema = generateTableSchema(nsid, lexicon, lexicons);
|
|
58
65
|
schemas.push(schema);
|
|
59
|
-
ddlStatements.push(generateCreateTableSQL(schema));
|
|
66
|
+
ddlStatements.push(generateCreateTableSQL(schema, SQLITE_DIALECT));
|
|
60
67
|
}
|
|
61
|
-
// In-memory
|
|
62
|
-
await
|
|
63
|
-
|
|
68
|
+
// In-memory SQLite — faster startup, no native module issues in Vite's module runner
|
|
69
|
+
const { adapter, searchPort } = await createAdapter('sqlite');
|
|
70
|
+
setSearchPort(searchPort);
|
|
71
|
+
await initDatabase(adapter, ':memory:', schemas, ddlStatements);
|
|
72
|
+
// Create the FTS shadow tables up front. main.ts does this after backfill;
|
|
73
|
+
// a test has no backfill, so without it the tables never exist and every
|
|
74
|
+
// ctx.search() fails its BM25 phase with "no such table: _fts_...". SQLite
|
|
75
|
+
// has no fuzzy phase to fall back on — jaroWinklerSimilarity is null there —
|
|
76
|
+
// so the failure surfaces as an empty result rather than an error, which is
|
|
77
|
+
// why it went unnoticed.
|
|
78
|
+
//
|
|
79
|
+
// The indexes are empty at this point; what matters is that this also fills
|
|
80
|
+
// the search-column cache, which is what lets insertRecord() index each
|
|
81
|
+
// record as it is written. Records inserted with raw SQL bypass that path and
|
|
82
|
+
// remain unsearchable, as they do in production.
|
|
83
|
+
await rebuildAllIndexes(collections);
|
|
84
|
+
// Discover views
|
|
64
85
|
discoverViews();
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
catch { }
|
|
69
|
-
// Skip setup hooks in test context — they're for server boot-time
|
|
70
|
-
// initialization (e.g. importing large datasets) and not appropriate for tests
|
|
71
|
-
// Discover feeds, xrpc, labels
|
|
72
|
-
await initFeeds(resolve(configDir, 'feeds'));
|
|
73
|
-
await initXrpc(resolve(configDir, 'xrpc'));
|
|
74
|
-
await initOpengraph(resolve(configDir, 'og'));
|
|
75
|
-
await initLabels(resolve(configDir, 'labels'));
|
|
86
|
+
// Discover feeds, xrpc, labels, hooks, og from server/ directory (skip setup scripts in tests)
|
|
87
|
+
await initServer(resolve(configDir, 'server'), { skipSetup: true });
|
|
76
88
|
return {
|
|
77
|
-
db: { query:
|
|
89
|
+
db: { query: guardedQuerySQL, run: runSQL, unfiltered: unfilteredQuerySQL },
|
|
78
90
|
_config: config,
|
|
79
91
|
_collections: collections,
|
|
80
92
|
loadFixtures: async (dir) => {
|
|
@@ -94,7 +106,12 @@ export async function createTestContext() {
|
|
|
94
106
|
if (Array.isArray(records)) {
|
|
95
107
|
for (const rec of records) {
|
|
96
108
|
const row = interpolateHelpers(rec);
|
|
97
|
-
await runSQL(`INSERT OR IGNORE INTO _repos (did, status, handle, backfilled_at) VALUES ($1, $2, $3, $4)`,
|
|
109
|
+
await runSQL(`INSERT OR IGNORE INTO _repos (did, status, handle, backfilled_at) VALUES ($1, $2, $3, $4)`, [
|
|
110
|
+
row.did,
|
|
111
|
+
row.status || 'active',
|
|
112
|
+
row.handle || row.did.split(':').pop() + '.test',
|
|
113
|
+
new Date().toISOString(),
|
|
114
|
+
]);
|
|
98
115
|
}
|
|
99
116
|
}
|
|
100
117
|
}
|
|
@@ -119,7 +136,7 @@ export async function createTestContext() {
|
|
|
119
136
|
const row = interpolateHelpers(rec);
|
|
120
137
|
const vals = keys.map((k) => row[k]);
|
|
121
138
|
const placeholders = keys.map((_, i) => `$${i + 1}`).join(', ');
|
|
122
|
-
await runSQL(`INSERT INTO "${tableName}" (${keys.map((k) => `"${k}"`).join(', ')}) VALUES (${placeholders})`,
|
|
139
|
+
await runSQL(`INSERT INTO "${tableName}" (${keys.map((k) => `"${k}"`).join(', ')}) VALUES (${placeholders})`, vals);
|
|
123
140
|
}
|
|
124
141
|
continue;
|
|
125
142
|
}
|
|
@@ -133,7 +150,12 @@ export async function createTestContext() {
|
|
|
133
150
|
// Auto-register DID in _repos if not already present
|
|
134
151
|
if (!seenDids.has(did)) {
|
|
135
152
|
seenDids.add(did);
|
|
136
|
-
await runSQL(`INSERT OR IGNORE INTO _repos (did, status, handle, backfilled_at) VALUES ($1, $2, $3, $4)`,
|
|
153
|
+
await runSQL(`INSERT OR IGNORE INTO _repos (did, status, handle, backfilled_at) VALUES ($1, $2, $3, $4)`, [
|
|
154
|
+
did,
|
|
155
|
+
'active',
|
|
156
|
+
did.split(':').pop() + '.test',
|
|
157
|
+
new Date().toISOString(),
|
|
158
|
+
]);
|
|
137
159
|
}
|
|
138
160
|
await insertRecord(tableName, uri, cid, did, fields);
|
|
139
161
|
}
|
|
@@ -164,14 +186,14 @@ export async function createTestContext() {
|
|
|
164
186
|
},
|
|
165
187
|
feedContext: (opts) => {
|
|
166
188
|
const paginateDeps = {
|
|
167
|
-
db: { query:
|
|
189
|
+
db: { query: guardedQuerySQL, unfiltered: unfilteredQuerySQL },
|
|
168
190
|
cursor: opts?.cursor,
|
|
169
191
|
limit: opts?.limit || 30,
|
|
170
192
|
packCursor,
|
|
171
193
|
unpackCursor,
|
|
172
194
|
};
|
|
173
195
|
return {
|
|
174
|
-
db: { query:
|
|
196
|
+
db: { query: guardedQuerySQL, unfiltered: unfilteredQuerySQL },
|
|
175
197
|
params: opts?.params || {},
|
|
176
198
|
cursor: opts?.cursor,
|
|
177
199
|
limit: opts?.limit || 30,
|
|
@@ -181,6 +203,7 @@ export async function createTestContext() {
|
|
|
181
203
|
isTakendown: isTakendownDid,
|
|
182
204
|
filterTakendownDids,
|
|
183
205
|
paginate: createPaginate(paginateDeps),
|
|
206
|
+
spaceFilter: spaceFilterSql,
|
|
184
207
|
};
|
|
185
208
|
},
|
|
186
209
|
close: async () => {
|
|
@@ -220,10 +243,11 @@ export async function startTestServer() {
|
|
|
220
243
|
// Import startServer — it creates the HTTP server and returns it
|
|
221
244
|
const { startServer } = await import("./server.js");
|
|
222
245
|
// Start server on port 0 (random available port)
|
|
223
|
-
const resolveViewer = (
|
|
224
|
-
const did =
|
|
246
|
+
const resolveViewer = (request) => {
|
|
247
|
+
const did = request.headers.get('x-test-viewer');
|
|
225
248
|
return typeof did === 'string' ? { did } : null;
|
|
226
249
|
};
|
|
250
|
+
setPrivateCollections(ctx._config.privateCollections);
|
|
227
251
|
const httpServer = startServer(0, ctx._collections, ctx._config.publicDir, ctx._config.oauth, ctx._config.admins, resolveViewer);
|
|
228
252
|
await new Promise((resolve) => httpServer.on('listening', resolve));
|
|
229
253
|
const port = httpServer.address().port;
|
package/dist/views.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// 1. Inline views: defined in the record lexicon with ref: "#main" (e.g., playView)
|
|
4
4
|
// 2. Defs views: defined in a defs lexicon, associated by naming convention (e.g., profileView)
|
|
5
5
|
import { log } from "./logger.js";
|
|
6
|
-
import { getAllLexicons, getLexicon } from "./schema.js";
|
|
6
|
+
import { getAllLexicons, getLexicon } from "./database/schema.js";
|
|
7
7
|
// --- Registry ---
|
|
8
8
|
/** All views keyed by full NSID (e.g., "fm.teal.alpha.feed.play#playView") */
|
|
9
9
|
const views = new Map();
|
package/dist/vite-plugin.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["../src/vite-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["../src/vite-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,KAAK,MAAM,EAA6C,MAAM,MAAM,CAAA;AA8D3G,wBAAgB,IAAI,CAAC,IAAI,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAoOrD"}
|
package/dist/vite-plugin.js
CHANGED
|
@@ -1,87 +1,275 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createRunnableDevEnvironment } from 'vite';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
import { isHatkRoute } from "./adapter.js";
|
|
6
|
+
/** Boot the local PDS if a docker-compose.yml exists. */
|
|
7
|
+
async function ensurePds() {
|
|
8
|
+
if (!existsSync(resolve('docker-compose.yml')))
|
|
9
|
+
return;
|
|
10
|
+
try {
|
|
11
|
+
const res = await fetch('http://localhost:2583/xrpc/_health');
|
|
12
|
+
if (res.ok)
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
catch { }
|
|
16
|
+
console.log('[hatk] Starting PDS...');
|
|
17
|
+
execSync('docker compose up -d', { stdio: 'inherit', cwd: process.cwd() });
|
|
18
|
+
for (let i = 0; i < 30; i++) {
|
|
19
|
+
try {
|
|
20
|
+
const res = await fetch('http://localhost:2583/xrpc/_health');
|
|
21
|
+
if (res.ok) {
|
|
22
|
+
console.log('[hatk] PDS ready');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch { }
|
|
27
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
28
|
+
}
|
|
29
|
+
console.error('[hatk] PDS failed to start');
|
|
30
|
+
}
|
|
31
|
+
/** Run seed file if it exists. */
|
|
32
|
+
function runSeed() {
|
|
33
|
+
const seedFile = resolve('seeds/seed.ts');
|
|
34
|
+
if (!existsSync(seedFile))
|
|
35
|
+
return;
|
|
36
|
+
try {
|
|
37
|
+
execSync(`npx tsx ${seedFile}`, { stdio: 'inherit', cwd: process.cwd() });
|
|
38
|
+
}
|
|
39
|
+
catch { }
|
|
40
|
+
}
|
|
41
|
+
/** Walk all loaded modules in the module graphs to collect CSS URLs for SSR. */
|
|
42
|
+
function collectAllCss(server) {
|
|
43
|
+
const cssUrls = new Set();
|
|
44
|
+
for (const envName of ['hatk', 'client']) {
|
|
45
|
+
const env = server.environments[envName];
|
|
46
|
+
if (!env?.moduleGraph)
|
|
47
|
+
continue;
|
|
48
|
+
// TODO: uses internal Vite module graph API — may break across Vite minor versions
|
|
49
|
+
for (const mod of env.moduleGraph.idToModuleMap?.values?.() ?? []) {
|
|
50
|
+
const url = mod.url || '';
|
|
51
|
+
if (/\.(css|scss|less|styl|stylus|pcss|postcss)(\?|$)/.test(url)) {
|
|
52
|
+
cssUrls.add(url);
|
|
53
|
+
}
|
|
54
|
+
if (url.includes('type=style')) {
|
|
55
|
+
cssUrls.add(url);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (cssUrls.size === 0)
|
|
60
|
+
return '';
|
|
61
|
+
return Array.from(cssUrls)
|
|
62
|
+
.map((url) => `<link rel="stylesheet" href="${url}">`)
|
|
63
|
+
.join('\n');
|
|
64
|
+
}
|
|
4
65
|
export function hatk(opts) {
|
|
5
|
-
const devPort = 3000;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
let
|
|
66
|
+
const devPort = opts?.port ?? 3000;
|
|
67
|
+
let handler = null;
|
|
68
|
+
let ssrRenderPage = null;
|
|
69
|
+
let ssrGetRenderer = null;
|
|
70
|
+
let reloadServer = null;
|
|
71
|
+
let reloadTimer = null;
|
|
9
72
|
return {
|
|
10
73
|
name: 'vite-plugin-hatk',
|
|
74
|
+
// Rewrite $hatk imports in source code so SSR module runners can resolve them.
|
|
75
|
+
// vite-plus's fetchModule bypasses resolve.alias for bare imports.
|
|
76
|
+
transform(code, _id) {
|
|
77
|
+
if (!code.includes('$hatk'))
|
|
78
|
+
return;
|
|
79
|
+
const hatk = resolve('hatk.generated.ts');
|
|
80
|
+
const hatkClient = resolve('hatk.generated.client.ts');
|
|
81
|
+
return code
|
|
82
|
+
.replace(/from\s+['"](\$hatk\/client)['"]/g, `from '${hatkClient}'`)
|
|
83
|
+
.replace(/from\s+['"](\$hatk)['"]/g, `from '${hatk}'`);
|
|
84
|
+
},
|
|
11
85
|
config() {
|
|
12
|
-
const target = `http://127.0.0.1:${backendPort}`;
|
|
13
|
-
// changeOrigin: false preserves the original Host header so DPoP htu matches
|
|
14
|
-
const rule = { target, changeOrigin: false };
|
|
15
86
|
return {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
'/xrpc': rule,
|
|
21
|
-
'/oauth/par': rule,
|
|
22
|
-
'/oauth/token': rule,
|
|
23
|
-
'/oauth/jwks': rule,
|
|
24
|
-
'/oauth/authorize': rule,
|
|
25
|
-
'/oauth/callback': {
|
|
26
|
-
...rule,
|
|
27
|
-
// Only proxy the PDS callback (iss !== our issuer) to the backend.
|
|
28
|
-
// The client-side callback (iss === our issuer) should reach the SPA.
|
|
29
|
-
bypass(req) {
|
|
30
|
-
const url = new URL(req.url, issuer);
|
|
31
|
-
if (url.searchParams.get('iss') === issuer)
|
|
32
|
-
return req.url;
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
'/oauth/client-metadata.json': rule,
|
|
36
|
-
'/oauth-client-metadata.json': rule,
|
|
37
|
-
'/.well-known': rule,
|
|
38
|
-
'/info': rule,
|
|
39
|
-
'/repos': rule,
|
|
40
|
-
'/og': rule,
|
|
41
|
-
'/admin': rule,
|
|
42
|
-
'/_health': rule,
|
|
87
|
+
resolve: {
|
|
88
|
+
alias: {
|
|
89
|
+
'$hatk/client': resolve('hatk.generated.client.ts'),
|
|
90
|
+
$hatk: resolve('hatk.generated.ts'),
|
|
43
91
|
},
|
|
44
92
|
},
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
{
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
93
|
+
environments: {
|
|
94
|
+
hatk: {
|
|
95
|
+
resolve: {
|
|
96
|
+
conditions: ['svelte'],
|
|
97
|
+
noExternal: ['svelte', '@tanstack/svelte-query'],
|
|
98
|
+
external: true,
|
|
99
|
+
},
|
|
100
|
+
dev: {
|
|
101
|
+
createEnvironment(name, config) {
|
|
102
|
+
return createRunnableDevEnvironment(name, config);
|
|
103
|
+
},
|
|
104
|
+
optimizeDeps: {
|
|
105
|
+
exclude: ['better-sqlite3', '@duckdb/node-api'],
|
|
51
106
|
},
|
|
52
107
|
},
|
|
53
|
-
{
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
108
|
+
build: {
|
|
109
|
+
outDir: 'dist/server',
|
|
110
|
+
ssr: true,
|
|
111
|
+
rollupOptions: {
|
|
112
|
+
external: ['better-sqlite3', '@duckdb/node-api'],
|
|
57
113
|
},
|
|
58
114
|
},
|
|
59
|
-
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
server: {
|
|
118
|
+
host: '127.0.0.1',
|
|
119
|
+
port: devPort,
|
|
120
|
+
fs: {
|
|
121
|
+
allow: ['.'],
|
|
122
|
+
},
|
|
123
|
+
watch: {
|
|
124
|
+
ignored: ['**/db/**', '**/data/**'],
|
|
125
|
+
},
|
|
60
126
|
},
|
|
61
127
|
};
|
|
62
128
|
},
|
|
63
|
-
configureServer(server) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
129
|
+
async configureServer(server) {
|
|
130
|
+
// Skip hatk server boot in test mode — tests manage their own context
|
|
131
|
+
if (process.env.VITEST)
|
|
132
|
+
return;
|
|
133
|
+
// Boot PDS and run seeds before starting
|
|
134
|
+
await ensurePds();
|
|
135
|
+
runSeed();
|
|
136
|
+
const env = server.environments.hatk;
|
|
137
|
+
if (!env || !('runner' in env)) {
|
|
138
|
+
console.error('[hatk] hatk environment not available — is Vite 8 with Environment API?');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// Load the hatk boot module through the module runner
|
|
142
|
+
const mainPath = resolve(import.meta.dirname, 'dev-entry.js');
|
|
143
|
+
const mod = await env.runner.import(mainPath);
|
|
144
|
+
handler = mod.handler;
|
|
145
|
+
ssrRenderPage = mod.renderPage;
|
|
146
|
+
ssrGetRenderer = mod.getRenderer;
|
|
147
|
+
reloadServer = mod.reloadServer;
|
|
148
|
+
globalThis.__hatk_callXrpc = mod.callXrpc;
|
|
149
|
+
// Capture cookie parser and name for SSR viewer resolution
|
|
150
|
+
const ssrParseSessionCookie = mod.parseSessionCookie ?? null;
|
|
151
|
+
globalThis.__hatk_parseSessionCookie = ssrParseSessionCookie;
|
|
152
|
+
globalThis.__hatk_sessionCookieName = mod.getSessionCookieName?.() ?? '__hatk_session';
|
|
153
|
+
if (ssrGetRenderer?.()) {
|
|
154
|
+
console.log('[hatk] SSR ready');
|
|
155
|
+
}
|
|
156
|
+
// API routes — must run before Vite's static middleware
|
|
157
|
+
server.middlewares.use(async (req, res, next) => {
|
|
158
|
+
const url = new URL(req.url, `http://localhost:${devPort}`);
|
|
159
|
+
if (!isHatkRoute(url.pathname) || !handler) {
|
|
160
|
+
next();
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
const { toRequest, sendResponse } = await import('./adapter.js');
|
|
165
|
+
const request = toRequest(req, `http://localhost:${devPort}`);
|
|
166
|
+
const response = await handler(request);
|
|
167
|
+
if (response.status === 404) {
|
|
168
|
+
next();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
await sendResponse(res, response);
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
console.error('[hatk]', err.message);
|
|
175
|
+
next(err);
|
|
176
|
+
}
|
|
80
177
|
});
|
|
178
|
+
// SSR middleware — returned function runs after htmlFallback but before indexHtmlMiddleware
|
|
179
|
+
return () => {
|
|
180
|
+
server.middlewares.use(async (req, res, next) => {
|
|
181
|
+
if (!ssrGetRenderer?.()) {
|
|
182
|
+
next();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const accept = req.headers.accept || '';
|
|
186
|
+
const url = req.originalUrl || req.url;
|
|
187
|
+
if (!accept.includes('text/html') || !url) {
|
|
188
|
+
next();
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
const { readFileSync } = await import('node:fs');
|
|
193
|
+
const rawHtml = readFileSync(resolve('index.html'), 'utf-8');
|
|
194
|
+
const template = await server.transformIndexHtml(url, rawHtml);
|
|
195
|
+
const fullUrl = new URL(url, `http://localhost:${devPort}`);
|
|
196
|
+
const headers = {};
|
|
197
|
+
if (req.headers.cookie)
|
|
198
|
+
headers.cookie = req.headers.cookie;
|
|
199
|
+
const request = new Request(fullUrl.href, { headers });
|
|
200
|
+
// Resolve viewer from session cookie for SSR
|
|
201
|
+
// TODO: globalThis.__hatk_viewer is not safe for concurrent SSR requests.
|
|
202
|
+
// Replace with AsyncLocalStorage when callXrpc supports per-request context.
|
|
203
|
+
let viewer = null;
|
|
204
|
+
if (ssrParseSessionCookie) {
|
|
205
|
+
try {
|
|
206
|
+
viewer = await ssrParseSessionCookie(request);
|
|
207
|
+
}
|
|
208
|
+
catch { }
|
|
209
|
+
}
|
|
210
|
+
;
|
|
211
|
+
globalThis.__hatk_viewer = viewer;
|
|
212
|
+
let renderedHtml;
|
|
213
|
+
try {
|
|
214
|
+
renderedHtml = await ssrRenderPage(template, request);
|
|
215
|
+
}
|
|
216
|
+
finally {
|
|
217
|
+
;
|
|
218
|
+
globalThis.__hatk_viewer = null;
|
|
219
|
+
}
|
|
220
|
+
// Inject viewer into HTML so client has it before OAuth initializes
|
|
221
|
+
if (renderedHtml && viewer) {
|
|
222
|
+
const script = `<script>globalThis.__hatk_viewer=${JSON.stringify(viewer)}</script>`;
|
|
223
|
+
renderedHtml = renderedHtml.replace('</head>', `${script}\n</head>`);
|
|
224
|
+
}
|
|
225
|
+
if (!renderedHtml) {
|
|
226
|
+
next();
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
// Collect CSS from all loaded modules to prevent FOUC
|
|
230
|
+
const cssLinks = collectAllCss(server);
|
|
231
|
+
let html = renderedHtml;
|
|
232
|
+
if (cssLinks) {
|
|
233
|
+
html = html.replace('</head>', `${cssLinks}\n</head>`);
|
|
234
|
+
}
|
|
235
|
+
res.setHeader('Content-Type', 'text/html');
|
|
236
|
+
res.end(html);
|
|
237
|
+
}
|
|
238
|
+
catch (err) {
|
|
239
|
+
console.error('[hatk] SSR error:', err.message);
|
|
240
|
+
next(err);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
};
|
|
244
|
+
},
|
|
245
|
+
// Handle HMR for server/ files in the hatk environment
|
|
246
|
+
hotUpdate(options) {
|
|
247
|
+
if (options.file.includes('/server/') && reloadServer) {
|
|
248
|
+
// Debounce: hotUpdate fires once per environment, only reload once
|
|
249
|
+
if (!reloadTimer) {
|
|
250
|
+
reloadTimer = setTimeout(() => {
|
|
251
|
+
reloadTimer = null;
|
|
252
|
+
reloadServer()
|
|
253
|
+
.then(() => {
|
|
254
|
+
console.log('[hatk] Server handlers reloaded');
|
|
255
|
+
})
|
|
256
|
+
.catch((err) => {
|
|
257
|
+
console.error('[hatk] Failed to reload server handlers:', err.message);
|
|
258
|
+
});
|
|
259
|
+
}, 50);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
81
262
|
},
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
263
|
+
// Two-stage production build
|
|
264
|
+
async buildApp(builder) {
|
|
265
|
+
// Stage 1: Build client
|
|
266
|
+
if (builder.environments.client) {
|
|
267
|
+
await builder.build(builder.environments.client);
|
|
268
|
+
}
|
|
269
|
+
// Stage 2: Build hatk server (if environment exists)
|
|
270
|
+
if (builder.environments.hatk) {
|
|
271
|
+
await builder.build(builder.environments.hatk);
|
|
272
|
+
}
|
|
85
273
|
},
|
|
86
274
|
};
|
|
87
275
|
}
|