@hatk/hatk 0.0.1-alpha.4 → 0.0.1-alpha.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/dist/adapter.d.ts +19 -0
  2. package/dist/adapter.d.ts.map +1 -0
  3. package/dist/adapter.js +107 -0
  4. package/dist/backfill.d.ts +60 -1
  5. package/dist/backfill.d.ts.map +1 -1
  6. package/dist/backfill.js +167 -33
  7. package/dist/car.d.ts +59 -1
  8. package/dist/car.d.ts.map +1 -1
  9. package/dist/car.js +179 -7
  10. package/dist/cbor.d.ts +37 -0
  11. package/dist/cbor.d.ts.map +1 -1
  12. package/dist/cbor.js +36 -3
  13. package/dist/cid.d.ts +37 -0
  14. package/dist/cid.d.ts.map +1 -1
  15. package/dist/cid.js +38 -3
  16. package/dist/cli.js +417 -133
  17. package/dist/cloudflare/container.d.ts +73 -0
  18. package/dist/cloudflare/container.d.ts.map +1 -0
  19. package/dist/cloudflare/container.js +232 -0
  20. package/dist/cloudflare/hooks.d.ts +33 -0
  21. package/dist/cloudflare/hooks.d.ts.map +1 -0
  22. package/dist/cloudflare/hooks.js +40 -0
  23. package/dist/cloudflare/init.d.ts +27 -0
  24. package/dist/cloudflare/init.d.ts.map +1 -0
  25. package/dist/cloudflare/init.js +103 -0
  26. package/dist/cloudflare/worker.d.ts +27 -0
  27. package/dist/cloudflare/worker.d.ts.map +1 -0
  28. package/dist/cloudflare/worker.js +54 -0
  29. package/dist/config.d.ts +12 -1
  30. package/dist/config.d.ts.map +1 -1
  31. package/dist/config.js +36 -9
  32. package/dist/database/adapter-factory.d.ts +6 -0
  33. package/dist/database/adapter-factory.d.ts.map +1 -0
  34. package/dist/database/adapter-factory.js +20 -0
  35. package/dist/database/adapters/d1.d.ts +56 -0
  36. package/dist/database/adapters/d1.d.ts.map +1 -0
  37. package/dist/database/adapters/d1.js +108 -0
  38. package/dist/database/adapters/duckdb-search.d.ts +12 -0
  39. package/dist/database/adapters/duckdb-search.d.ts.map +1 -0
  40. package/dist/database/adapters/duckdb-search.js +27 -0
  41. package/dist/database/adapters/duckdb.d.ts +25 -0
  42. package/dist/database/adapters/duckdb.d.ts.map +1 -0
  43. package/dist/database/adapters/duckdb.js +161 -0
  44. package/dist/database/adapters/sqlite-search.d.ts +23 -0
  45. package/dist/database/adapters/sqlite-search.d.ts.map +1 -0
  46. package/dist/database/adapters/sqlite-search.js +74 -0
  47. package/dist/database/adapters/sqlite.d.ts +18 -0
  48. package/dist/database/adapters/sqlite.d.ts.map +1 -0
  49. package/dist/database/adapters/sqlite.js +87 -0
  50. package/dist/database/db.d.ts +159 -0
  51. package/dist/database/db.d.ts.map +1 -0
  52. package/dist/database/db.js +1445 -0
  53. package/dist/database/dialect.d.ts +45 -0
  54. package/dist/database/dialect.d.ts.map +1 -0
  55. package/dist/database/dialect.js +72 -0
  56. package/dist/database/fts.d.ts +27 -0
  57. package/dist/database/fts.d.ts.map +1 -0
  58. package/dist/database/fts.js +846 -0
  59. package/dist/database/index.d.ts +7 -0
  60. package/dist/database/index.d.ts.map +1 -0
  61. package/dist/database/index.js +6 -0
  62. package/dist/database/ports.d.ts +50 -0
  63. package/dist/database/ports.d.ts.map +1 -0
  64. package/dist/database/ports.js +1 -0
  65. package/dist/database/schema.d.ts +61 -0
  66. package/dist/database/schema.d.ts.map +1 -0
  67. package/dist/database/schema.js +394 -0
  68. package/dist/db.d.ts +1 -1
  69. package/dist/db.d.ts.map +1 -1
  70. package/dist/db.js +4 -38
  71. package/dist/dev-entry.d.ts +8 -0
  72. package/dist/dev-entry.d.ts.map +1 -0
  73. package/dist/dev-entry.js +110 -0
  74. package/dist/feeds.d.ts +12 -8
  75. package/dist/feeds.d.ts.map +1 -1
  76. package/dist/feeds.js +45 -6
  77. package/dist/fts.d.ts.map +1 -1
  78. package/dist/fts.js +5 -0
  79. package/dist/hooks.d.ts +22 -0
  80. package/dist/hooks.d.ts.map +1 -0
  81. package/dist/hooks.js +75 -0
  82. package/dist/hydrate.d.ts +6 -5
  83. package/dist/hydrate.d.ts.map +1 -1
  84. package/dist/hydrate.js +4 -16
  85. package/dist/indexer.d.ts +20 -0
  86. package/dist/indexer.d.ts.map +1 -1
  87. package/dist/indexer.js +53 -7
  88. package/dist/labels.d.ts +34 -0
  89. package/dist/labels.d.ts.map +1 -1
  90. package/dist/labels.js +66 -6
  91. package/dist/logger.d.ts +29 -0
  92. package/dist/logger.d.ts.map +1 -1
  93. package/dist/logger.js +29 -0
  94. package/dist/main.js +134 -67
  95. package/dist/mst.d.ts +18 -1
  96. package/dist/mst.d.ts.map +1 -1
  97. package/dist/mst.js +19 -8
  98. package/dist/oauth/db.d.ts.map +1 -1
  99. package/dist/oauth/db.js +43 -17
  100. package/dist/oauth/server.d.ts +2 -0
  101. package/dist/oauth/server.d.ts.map +1 -1
  102. package/dist/oauth/server.js +102 -7
  103. package/dist/oauth/session.d.ts +11 -0
  104. package/dist/oauth/session.d.ts.map +1 -0
  105. package/dist/oauth/session.js +65 -0
  106. package/dist/opengraph.d.ts +10 -0
  107. package/dist/opengraph.d.ts.map +1 -1
  108. package/dist/opengraph.js +73 -39
  109. package/dist/pds-proxy.d.ts +42 -0
  110. package/dist/pds-proxy.d.ts.map +1 -0
  111. package/dist/pds-proxy.js +189 -0
  112. package/dist/renderer.d.ts +27 -0
  113. package/dist/renderer.d.ts.map +1 -0
  114. package/dist/renderer.js +46 -0
  115. package/dist/resolve-hatk.d.ts +6 -0
  116. package/dist/resolve-hatk.d.ts.map +1 -0
  117. package/dist/resolve-hatk.js +20 -0
  118. package/dist/response.d.ts +16 -0
  119. package/dist/response.d.ts.map +1 -0
  120. package/dist/response.js +69 -0
  121. package/dist/scanner.d.ts +21 -0
  122. package/dist/scanner.d.ts.map +1 -0
  123. package/dist/scanner.js +88 -0
  124. package/dist/schema.d.ts +8 -0
  125. package/dist/schema.d.ts.map +1 -1
  126. package/dist/schema.js +29 -0
  127. package/dist/seed.d.ts +19 -0
  128. package/dist/seed.d.ts.map +1 -1
  129. package/dist/seed.js +43 -4
  130. package/dist/server-init.d.ts +8 -0
  131. package/dist/server-init.d.ts.map +1 -0
  132. package/dist/server-init.js +61 -0
  133. package/dist/server.d.ts +26 -3
  134. package/dist/server.d.ts.map +1 -1
  135. package/dist/server.js +528 -635
  136. package/dist/setup.d.ts +28 -1
  137. package/dist/setup.d.ts.map +1 -1
  138. package/dist/setup.js +50 -3
  139. package/dist/test.d.ts +1 -1
  140. package/dist/test.d.ts.map +1 -1
  141. package/dist/test.js +38 -32
  142. package/dist/views.js +1 -1
  143. package/dist/vite-plugin.d.ts +1 -1
  144. package/dist/vite-plugin.d.ts.map +1 -1
  145. package/dist/vite-plugin.js +254 -66
  146. package/dist/xrpc.d.ts +46 -10
  147. package/dist/xrpc.d.ts.map +1 -1
  148. package/dist/xrpc.js +128 -39
  149. package/package.json +13 -6
  150. package/public/admin.html +0 -54
package/dist/setup.d.ts CHANGED
@@ -1,8 +1,35 @@
1
+ import type { BulkInserter } from './database/ports.ts';
2
+ /** Context passed to each setup script's handler function. */
1
3
  export interface SetupContext {
2
4
  db: {
3
5
  query: (sql: string, params?: any[]) => Promise<any[]>;
4
- run: (sql: string, ...params: any[]) => Promise<void>;
6
+ run: (sql: string, params?: any[]) => Promise<void>;
7
+ runBatch: (operations: Array<{
8
+ sql: string;
9
+ params: any[];
10
+ }>) => Promise<void>;
11
+ createBulkInserter: (table: string, columns: string[], options?: {
12
+ onConflict?: 'ignore' | 'replace';
13
+ batchSize?: number;
14
+ }) => Promise<BulkInserter>;
5
15
  };
6
16
  }
17
+ export type SetupHandler = (ctx: SetupContext) => Promise<void>;
18
+ export declare function defineSetup(handler: SetupHandler): {
19
+ __type: "setup";
20
+ handler: SetupHandler;
21
+ };
22
+ /**
23
+ * Run all setup scripts in the given directory on server boot.
24
+ *
25
+ * Each script should export a default handler function (or `{ handler }`) that
26
+ * receives a {@link SetupContext} with database access. Scripts run in sorted
27
+ * filename order — prefix with numbers (e.g. `01-create-tables.ts`) to control
28
+ * execution order. Files starting with `_` are ignored.
29
+ *
30
+ * @param setupDir - Absolute path to the `setup/` directory
31
+ */
7
32
  export declare function initSetup(setupDir: string): Promise<void>;
33
+ /** Run a single setup handler with a SetupContext. */
34
+ export declare function runSetupHandler(name: string, handler: SetupHandler): Promise<void>;
8
35
  //# sourceMappingURL=setup.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE;QACF,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;QACtD,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;KACtD,CAAA;CACF;AAiBD,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB/D"}
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAEvD,8DAA8D;AAC9D,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE;QACF,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;QACtD,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;QACnD,QAAQ,EAAE,CAAC,UAAU,EAAE,KAAK,CAAC;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,GAAG,EAAE,CAAA;SAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;QAC9E,kBAAkB,EAAE,CAClB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,CAAC,EAAE;YAAE,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;YAAC,SAAS,CAAC,EAAE,MAAM,CAAA;SAAE,KAChE,OAAO,CAAC,YAAY,CAAC,CAAA;KAC3B,CAAA;CACF;AAED,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAE/D,wBAAgB,WAAW,CAAC,OAAO,EAAE,YAAY;;;EAEhD;AAkBD;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB/D;AAED,sDAAsD;AACtD,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAOxF"}
package/dist/setup.js CHANGED
@@ -6,10 +6,38 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
6
6
  }
7
7
  return path;
8
8
  };
9
+ /**
10
+ * Setup scripts that run once on server boot for initializing custom tables,
11
+ * views, or other database state.
12
+ *
13
+ * Place scripts in the `setup/` directory. Each module default-exports a handler
14
+ * function (or `{ handler }`) that receives a {@link SetupContext} with database
15
+ * access. Scripts run in sorted filename order — prefix with numbers to control
16
+ * execution order. Files starting with `_` are ignored.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * // setup/01-leaderboard.ts
21
+ * import type { SetupContext } from '@hatk/hatk/setup'
22
+ *
23
+ * export default async function (ctx: SetupContext) {
24
+ * await ctx.db.run(`
25
+ * CREATE TABLE IF NOT EXISTS leaderboard (
26
+ * did TEXT PRIMARY KEY,
27
+ * score INTEGER DEFAULT 0
28
+ * )
29
+ * `)
30
+ * }
31
+ * ```
32
+ */
9
33
  import { resolve, relative } from 'node:path';
10
34
  import { readdirSync, statSync } from 'node:fs';
11
35
  import { log } from "./logger.js";
12
- import { querySQL, runSQL } from "./db.js";
36
+ import { querySQL, runSQL, runBatch, createBulkInserterSQL } from "./database/db.js";
37
+ export function defineSetup(handler) {
38
+ return { __type: 'setup', handler };
39
+ }
40
+ /** Recursively collect .ts/.js files in a directory, skipping files prefixed with `_`. */
13
41
  function walkDir(dir) {
14
42
  const results = [];
15
43
  try {
@@ -26,23 +54,42 @@ function walkDir(dir) {
26
54
  catch { }
27
55
  return results.sort();
28
56
  }
57
+ /**
58
+ * Run all setup scripts in the given directory on server boot.
59
+ *
60
+ * Each script should export a default handler function (or `{ handler }`) that
61
+ * receives a {@link SetupContext} with database access. Scripts run in sorted
62
+ * filename order — prefix with numbers (e.g. `01-create-tables.ts`) to control
63
+ * execution order. Files starting with `_` are ignored.
64
+ *
65
+ * @param setupDir - Absolute path to the `setup/` directory
66
+ */
29
67
  export async function initSetup(setupDir) {
30
68
  const files = walkDir(setupDir);
31
69
  if (files.length === 0)
32
70
  return;
33
71
  for (const scriptPath of files) {
34
72
  const name = relative(setupDir, scriptPath).replace(/\.(ts|js)$/, '');
35
- const mod = await import(__rewriteRelativeImportExtension(scriptPath));
73
+ const mod = await import(__rewriteRelativeImportExtension(/* @vite-ignore */ `${scriptPath}?t=${Date.now()}`));
36
74
  const handler = mod.default?.handler || mod.default;
37
75
  if (typeof handler !== 'function') {
38
76
  console.warn(`[setup] ${name}: no handler function found, skipping`);
39
77
  continue;
40
78
  }
41
79
  const ctx = {
42
- db: { query: querySQL, run: runSQL },
80
+ db: { query: querySQL, run: runSQL, runBatch, createBulkInserter: createBulkInserterSQL },
43
81
  };
44
82
  log(`[setup] running: ${name}`);
45
83
  await handler(ctx);
46
84
  log(`[setup] done: ${name}`);
47
85
  }
48
86
  }
87
+ /** Run a single setup handler with a SetupContext. */
88
+ export async function runSetupHandler(name, handler) {
89
+ const ctx = {
90
+ db: { query: querySQL, run: runSQL, runBatch, createBulkInserter: createBulkInserterSQL },
91
+ };
92
+ log(`[setup] running: ${name}`);
93
+ await handler(ctx);
94
+ log(`[setup] done: ${name}`);
95
+ }
package/dist/test.d.ts CHANGED
@@ -4,7 +4,7 @@ import type { FeedContext } from './feeds.ts';
4
4
  export interface TestContext {
5
5
  db: {
6
6
  query: (sql: string, params?: any[]) => Promise<any[]>;
7
- run: (sql: string, ...params: any[]) => Promise<void>;
7
+ run: (sql: string, params?: any[]) => Promise<void>;
8
8
  };
9
9
  loadFixtures: (dir?: string) => Promise<void>;
10
10
  loadFeed: (name: string) => {
@@ -1 +1 @@
1
- {"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../src/test.ts"],"names":[],"mappings":"AAIA,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAiBzD,OAAO,EAAE,IAAI,IAAI,iBAAiB,EAAE,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAA;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE7C,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE;QACF,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;QACtD,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;KACtD,CAAA;IACD,YAAY,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7C,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;KAAE,CAAA;IAC5E,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;KAAE,CAAA;IACnE,WAAW,EAAE,CAAC,IAAI,CAAC,EAAE;QACnB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,MAAM,CAAC,EAAE;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAA;QAC/B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAChC,KAAK,WAAW,CAAA;IACjB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1B,gBAAgB,CAAC,OAAO,EAAE,UAAU,CAAA;IACpC,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,CAAA;CACxC;AAED,MAAM,WAAW,UAAW,SAAQ,WAAW;IAC7C,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC9D,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC7E,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,QAAQ,KAAK,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAA;IAC/D,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAClE;AAYD;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,WAAW,CAAC,CAiL9D;AA8BD,wBAAsB,eAAe,IAAI,OAAO,CAAC,UAAU,CAAC,CAgD3D"}
1
+ {"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../src/test.ts"],"names":[],"mappings":"AAIA,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAkBzD,OAAO,EAAE,IAAI,IAAI,iBAAiB,EAAE,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAA;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE7C,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE;QACF,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;QACtD,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;KACpD,CAAA;IACD,YAAY,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7C,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;KAAE,CAAA;IAC5E,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;KAAE,CAAA;IACnE,WAAW,EAAE,CAAC,IAAI,CAAC,EAAE;QACnB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,MAAM,CAAC,EAAE;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAA;QAC/B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAChC,KAAK,WAAW,CAAA;IACjB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1B,gBAAgB,CAAC,OAAO,EAAE,UAAU,CAAA;IACpC,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,CAAA;CACxC;AAED,MAAM,WAAW,UAAW,SAAQ,WAAW;IAC7C,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC9D,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC7E,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,QAAQ,KAAK,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAA;IAC/D,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAClE;AAYD;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,WAAW,CAAC,CA0K9D;AA8BD,wBAAsB,eAAe,IAAI,OAAO,CAAC,UAAU,CAAC,CAgD3D"}
package/dist/test.js CHANGED
@@ -1,27 +1,29 @@
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 { initFeeds, executeFeed, listFeeds, createPaginate } from "./feeds.js";
8
- import { initXrpc, executeXrpc, listXrpc, configureRelay } from "./xrpc.js";
9
- import { initOpengraph } from "./opengraph.js";
10
- import { initLabels } from "./labels.js";
6
+ import { 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 { setSearchPort } from "./database/fts.js";
11
+ import { executeFeed, listFeeds, createPaginate } from "./feeds.js";
12
+ import { executeXrpc, listXrpc, configureRelay } 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";
16
18
  /**
17
- * Find the project's config.yaml by walking up from cwd.
18
- * Returns the resolved config path, or falls back to 'config.yaml'.
19
+ * Find the project's hatk.config.ts by walking up from cwd.
20
+ * Returns the resolved config path, or falls back to 'hatk.config.ts'.
19
21
  */
20
22
  function findConfigPath() {
21
23
  const explicit = process.env.APPVIEW_CONFIG;
22
24
  if (explicit)
23
25
  return resolve(explicit);
24
- return resolve('config.yaml');
26
+ return resolve('hatk.config.ts');
25
27
  }
26
28
  /**
27
29
  * Boot an in-memory hatk context for unit tests.
@@ -33,8 +35,9 @@ function findConfigPath() {
33
35
  * but it will NOT work with --pool=threads (multiple tests sharing a process).
34
36
  */
35
37
  export async function createTestContext() {
38
+ registerHatkResolveHook();
36
39
  const configPath = findConfigPath();
37
- const config = loadConfig(configPath);
40
+ const config = await loadConfig(configPath);
38
41
  const configDir = dirname(resolve(configPath));
39
42
  configureRelay(config.relay);
40
43
  // Load and validate lexicons
@@ -56,23 +59,16 @@ export async function createTestContext() {
56
59
  continue;
57
60
  const schema = generateTableSchema(nsid, lexicon, lexicons);
58
61
  schemas.push(schema);
59
- ddlStatements.push(generateCreateTableSQL(schema));
62
+ ddlStatements.push(generateCreateTableSQL(schema, SQLITE_DIALECT));
60
63
  }
61
- // In-memory DuckDB
62
- await initDatabase(':memory:', schemas, ddlStatements);
63
- // Discover views + hooks
64
+ // In-memory SQLite — faster startup, no native module issues in Vite's module runner
65
+ const { adapter, searchPort } = await createAdapter('sqlite');
66
+ setSearchPort(searchPort);
67
+ await initDatabase(adapter, ':memory:', schemas, ddlStatements);
68
+ // Discover views
64
69
  discoverViews();
65
- try {
66
- await loadOnLoginHook(resolve(configDir, 'hooks'));
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'));
70
+ // Discover feeds, xrpc, labels, hooks, og from server/ directory (skip setup scripts in tests)
71
+ await initServer(resolve(configDir, 'server'), { skipSetup: true });
76
72
  return {
77
73
  db: { query: querySQL, run: runSQL },
78
74
  _config: config,
@@ -94,7 +90,12 @@ export async function createTestContext() {
94
90
  if (Array.isArray(records)) {
95
91
  for (const rec of records) {
96
92
  const row = interpolateHelpers(rec);
97
- await runSQL(`INSERT OR IGNORE INTO _repos (did, status, handle, backfilled_at) VALUES ($1, $2, $3, $4)`, row.did, row.status || 'active', row.handle || row.did.split(':').pop() + '.test', new Date().toISOString());
93
+ await runSQL(`INSERT OR IGNORE INTO _repos (did, status, handle, backfilled_at) VALUES ($1, $2, $3, $4)`, [
94
+ row.did,
95
+ row.status || 'active',
96
+ row.handle || row.did.split(':').pop() + '.test',
97
+ new Date().toISOString(),
98
+ ]);
98
99
  }
99
100
  }
100
101
  }
@@ -119,7 +120,7 @@ export async function createTestContext() {
119
120
  const row = interpolateHelpers(rec);
120
121
  const vals = keys.map((k) => row[k]);
121
122
  const placeholders = keys.map((_, i) => `$${i + 1}`).join(', ');
122
- await runSQL(`INSERT INTO "${tableName}" (${keys.map((k) => `"${k}"`).join(', ')}) VALUES (${placeholders})`, ...vals);
123
+ await runSQL(`INSERT INTO "${tableName}" (${keys.map((k) => `"${k}"`).join(', ')}) VALUES (${placeholders})`, vals);
123
124
  }
124
125
  continue;
125
126
  }
@@ -133,7 +134,12 @@ export async function createTestContext() {
133
134
  // Auto-register DID in _repos if not already present
134
135
  if (!seenDids.has(did)) {
135
136
  seenDids.add(did);
136
- await runSQL(`INSERT OR IGNORE INTO _repos (did, status, handle, backfilled_at) VALUES ($1, $2, $3, $4)`, did, 'active', did.split(':').pop() + '.test', new Date().toISOString());
137
+ await runSQL(`INSERT OR IGNORE INTO _repos (did, status, handle, backfilled_at) VALUES ($1, $2, $3, $4)`, [
138
+ did,
139
+ 'active',
140
+ did.split(':').pop() + '.test',
141
+ new Date().toISOString(),
142
+ ]);
137
143
  }
138
144
  await insertRecord(tableName, uri, cid, did, fields);
139
145
  }
@@ -220,8 +226,8 @@ export async function startTestServer() {
220
226
  // Import startServer — it creates the HTTP server and returns it
221
227
  const { startServer } = await import("./server.js");
222
228
  // Start server on port 0 (random available port)
223
- const resolveViewer = (req) => {
224
- const did = req.headers['x-test-viewer'];
229
+ const resolveViewer = (request) => {
230
+ const did = request.headers.get('x-test-viewer');
225
231
  return typeof did === 'string' ? { did } : null;
226
232
  };
227
233
  const httpServer = startServer(0, ctx._collections, ctx._config.publicDir, ctx._config.oauth, ctx._config.admins, resolveViewer);
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();
@@ -1,4 +1,4 @@
1
- import type { Plugin } from 'vite';
1
+ import { type Plugin } from 'vite';
2
2
  export declare function hatk(opts?: {
3
3
  port?: number;
4
4
  }): Plugin;
@@ -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,CAuFrD"}
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"}
@@ -1,87 +1,275 @@
1
- import { spawn } from 'node:child_process';
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
- const backendPort = opts?.port ?? devPort + 1;
7
- const issuer = `http://127.0.0.1:${devPort}`;
8
- let serverProcess = null;
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
- server: {
17
- host: '127.0.0.1',
18
- port: devPort,
19
- proxy: {
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
- test: {
46
- projects: [
47
- {
48
- test: {
49
- name: 'unit',
50
- include: ['test/feeds/**/*.test.ts', 'test/xrpc/**/*.test.ts'],
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
- test: {
55
- name: 'integration',
56
- include: ['test/integration/**/*.test.ts'],
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
- const mainPath = resolve(import.meta.dirname, 'main.js');
65
- const watchDirs = ['xrpc', 'feeds', 'labels', 'jobs', 'setup', 'lexicons'].filter((d) => existsSync(d));
66
- const watchArgs = watchDirs.flatMap((d) => ['--watch-path', d]);
67
- serverProcess = spawn('npx', ['tsx', 'watch', ...watchArgs, mainPath, 'config.yaml'], {
68
- stdio: 'inherit',
69
- cwd: process.cwd(),
70
- env: {
71
- ...process.env,
72
- PORT: String(backendPort),
73
- OAUTH_ISSUER: process.env.OAUTH_ISSUER || issuer,
74
- DEV_MODE: '1',
75
- },
76
- });
77
- server.httpServer?.on('close', () => {
78
- serverProcess?.kill();
79
- serverProcess = null;
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
- buildEnd() {
83
- serverProcess?.kill();
84
- serverProcess = null;
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
  }