@hatk/hatk 0.0.1-alpha.0

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 (109) hide show
  1. package/dist/backfill.d.ts +11 -0
  2. package/dist/backfill.d.ts.map +1 -0
  3. package/dist/backfill.js +328 -0
  4. package/dist/car.d.ts +5 -0
  5. package/dist/car.d.ts.map +1 -0
  6. package/dist/car.js +52 -0
  7. package/dist/cbor.d.ts +7 -0
  8. package/dist/cbor.d.ts.map +1 -0
  9. package/dist/cbor.js +89 -0
  10. package/dist/cid.d.ts +4 -0
  11. package/dist/cid.d.ts.map +1 -0
  12. package/dist/cid.js +39 -0
  13. package/dist/cli.d.ts +3 -0
  14. package/dist/cli.d.ts.map +1 -0
  15. package/dist/cli.js +1663 -0
  16. package/dist/config.d.ts +47 -0
  17. package/dist/config.d.ts.map +1 -0
  18. package/dist/config.js +43 -0
  19. package/dist/db.d.ts +134 -0
  20. package/dist/db.d.ts.map +1 -0
  21. package/dist/db.js +1361 -0
  22. package/dist/feeds.d.ts +95 -0
  23. package/dist/feeds.d.ts.map +1 -0
  24. package/dist/feeds.js +144 -0
  25. package/dist/fts.d.ts +20 -0
  26. package/dist/fts.d.ts.map +1 -0
  27. package/dist/fts.js +762 -0
  28. package/dist/hydrate.d.ts +23 -0
  29. package/dist/hydrate.d.ts.map +1 -0
  30. package/dist/hydrate.js +75 -0
  31. package/dist/indexer.d.ts +14 -0
  32. package/dist/indexer.d.ts.map +1 -0
  33. package/dist/indexer.js +316 -0
  34. package/dist/labels.d.ts +29 -0
  35. package/dist/labels.d.ts.map +1 -0
  36. package/dist/labels.js +111 -0
  37. package/dist/lex-types.d.ts +401 -0
  38. package/dist/lex-types.d.ts.map +1 -0
  39. package/dist/lex-types.js +4 -0
  40. package/dist/lexicon-resolve.d.ts +14 -0
  41. package/dist/lexicon-resolve.d.ts.map +1 -0
  42. package/dist/lexicon-resolve.js +280 -0
  43. package/dist/logger.d.ts +4 -0
  44. package/dist/logger.d.ts.map +1 -0
  45. package/dist/logger.js +23 -0
  46. package/dist/main.d.ts +3 -0
  47. package/dist/main.d.ts.map +1 -0
  48. package/dist/main.js +148 -0
  49. package/dist/mst.d.ts +6 -0
  50. package/dist/mst.d.ts.map +1 -0
  51. package/dist/mst.js +30 -0
  52. package/dist/oauth/client.d.ts +16 -0
  53. package/dist/oauth/client.d.ts.map +1 -0
  54. package/dist/oauth/client.js +54 -0
  55. package/dist/oauth/crypto.d.ts +28 -0
  56. package/dist/oauth/crypto.d.ts.map +1 -0
  57. package/dist/oauth/crypto.js +101 -0
  58. package/dist/oauth/db.d.ts +47 -0
  59. package/dist/oauth/db.d.ts.map +1 -0
  60. package/dist/oauth/db.js +139 -0
  61. package/dist/oauth/discovery.d.ts +22 -0
  62. package/dist/oauth/discovery.d.ts.map +1 -0
  63. package/dist/oauth/discovery.js +50 -0
  64. package/dist/oauth/dpop.d.ts +11 -0
  65. package/dist/oauth/dpop.d.ts.map +1 -0
  66. package/dist/oauth/dpop.js +56 -0
  67. package/dist/oauth/hooks.d.ts +10 -0
  68. package/dist/oauth/hooks.d.ts.map +1 -0
  69. package/dist/oauth/hooks.js +40 -0
  70. package/dist/oauth/server.d.ts +86 -0
  71. package/dist/oauth/server.d.ts.map +1 -0
  72. package/dist/oauth/server.js +572 -0
  73. package/dist/opengraph.d.ts +34 -0
  74. package/dist/opengraph.d.ts.map +1 -0
  75. package/dist/opengraph.js +198 -0
  76. package/dist/schema.d.ts +51 -0
  77. package/dist/schema.d.ts.map +1 -0
  78. package/dist/schema.js +358 -0
  79. package/dist/seed.d.ts +29 -0
  80. package/dist/seed.d.ts.map +1 -0
  81. package/dist/seed.js +86 -0
  82. package/dist/server.d.ts +6 -0
  83. package/dist/server.d.ts.map +1 -0
  84. package/dist/server.js +1024 -0
  85. package/dist/setup.d.ts +8 -0
  86. package/dist/setup.d.ts.map +1 -0
  87. package/dist/setup.js +48 -0
  88. package/dist/test-browser.d.ts +14 -0
  89. package/dist/test-browser.d.ts.map +1 -0
  90. package/dist/test-browser.js +26 -0
  91. package/dist/test.d.ts +47 -0
  92. package/dist/test.d.ts.map +1 -0
  93. package/dist/test.js +256 -0
  94. package/dist/views.d.ts +40 -0
  95. package/dist/views.d.ts.map +1 -0
  96. package/dist/views.js +178 -0
  97. package/dist/vite-plugin.d.ts +5 -0
  98. package/dist/vite-plugin.d.ts.map +1 -0
  99. package/dist/vite-plugin.js +86 -0
  100. package/dist/xrpc-client.d.ts +18 -0
  101. package/dist/xrpc-client.d.ts.map +1 -0
  102. package/dist/xrpc-client.js +54 -0
  103. package/dist/xrpc.d.ts +53 -0
  104. package/dist/xrpc.d.ts.map +1 -0
  105. package/dist/xrpc.js +139 -0
  106. package/fonts/Inter-Regular.woff +0 -0
  107. package/package.json +41 -0
  108. package/public/admin-auth.js +320 -0
  109. package/public/admin.html +2166 -0
@@ -0,0 +1,86 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { resolve } from 'node:path';
3
+ import { existsSync } from 'node:fs';
4
+ 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;
9
+ return {
10
+ name: 'vite-plugin-hatk',
11
+ 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
+ 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,
43
+ },
44
+ },
45
+ test: {
46
+ projects: [
47
+ {
48
+ test: {
49
+ name: 'unit',
50
+ include: ['test/feeds/**/*.test.ts', 'test/xrpc/**/*.test.ts'],
51
+ },
52
+ },
53
+ {
54
+ test: {
55
+ name: 'integration',
56
+ include: ['test/integration/**/*.test.ts'],
57
+ },
58
+ },
59
+ ],
60
+ },
61
+ };
62
+ },
63
+ configureServer(server) {
64
+ const mainPath = resolve(import.meta.dirname, 'main.ts');
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
+ },
75
+ });
76
+ server.httpServer?.on('close', () => {
77
+ serverProcess?.kill();
78
+ serverProcess = null;
79
+ });
80
+ },
81
+ buildEnd() {
82
+ serverProcess?.kill();
83
+ serverProcess = null;
84
+ },
85
+ };
86
+ }
@@ -0,0 +1,18 @@
1
+ type ExtractParams<T> = T extends {
2
+ params: infer P;
3
+ } ? P : Record<string, string>;
4
+ type ExtractOutput<T> = T extends {
5
+ output: infer O;
6
+ } ? O : T;
7
+ interface ClientOptions {
8
+ fetch?: typeof globalThis.fetch;
9
+ }
10
+ export declare function createClient<S>(baseUrl: string, opts?: ClientOptions): {
11
+ query<K extends keyof S & string>(nsid: K, params?: ExtractParams<S[K]> & Record<string, unknown>): Promise<ExtractOutput<S[K]>>;
12
+ call<K extends keyof S & string>(nsid: K, input?: S[K] extends {
13
+ input: infer I;
14
+ } ? I : undefined, params?: ExtractParams<S[K]>): Promise<ExtractOutput<S[K]>>;
15
+ upload<K extends keyof S & string>(nsid: K, data: Blob | ArrayBuffer, contentType: string): Promise<ExtractOutput<S[K]>>;
16
+ };
17
+ export {};
18
+ //# sourceMappingURL=xrpc-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xrpc-client.d.ts","sourceRoot":"","sources":["../src/xrpc-client.ts"],"names":[],"mappings":"AAGA,KAAK,aAAa,CAAC,CAAC,IAAI,CAAC,SAAS;IAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAClF,KAAK,aAAa,CAAC,CAAC,IAAI,CAAC,SAAS;IAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,CAAC,GAAG,CAAC,CAAA;AAE7D,UAAU,aAAa;IACrB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;CAChC;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,aAAkB;UAuBzD,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,QAC9B,CAAC,WACE,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACrD,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SAUpB,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,QAC7B,CAAC,UACC,CAAC,CAAC,CAAC,CAAC,SAAS;QAAE,KAAK,EAAE,MAAM,CAAC,CAAA;KAAE,GAAG,CAAC,GAAG,SAAS,WAC9C,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAC3B,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;WAclB,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,QAC/B,CAAC,QACD,IAAI,GAAG,WAAW,eACX,MAAM,GAClB,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;EAalC"}
@@ -0,0 +1,54 @@
1
+ // xrpc-client.ts — Typed XRPC client
2
+ // Generic over a Schema type (auto-generated from lexicons).
3
+ export function createClient(baseUrl, opts = {}) {
4
+ const fetchFn = opts.fetch || globalThis.fetch.bind(globalThis);
5
+ function buildQs(params) {
6
+ if (!params || typeof params !== 'object')
7
+ return '';
8
+ const obj = params;
9
+ const entries = Object.entries(obj).filter(([, v]) => v !== undefined);
10
+ if (entries.length === 0)
11
+ return '';
12
+ return ('?' +
13
+ new URLSearchParams(entries.reduce((acc, [k, v]) => {
14
+ acc[k] = String(v);
15
+ return acc;
16
+ }, {})));
17
+ }
18
+ return {
19
+ async query(nsid, params) {
20
+ const qs = buildQs(params);
21
+ const res = await fetchFn(`${baseUrl}/xrpc/${nsid}${qs}`);
22
+ if (!res.ok) {
23
+ const err = await res.json().catch(() => ({}));
24
+ throw new Error(err.error || `XRPC ${nsid}: ${res.status}`);
25
+ }
26
+ return res.json();
27
+ },
28
+ async call(nsid, input, params) {
29
+ const qs = buildQs(params);
30
+ const res = await fetchFn(`${baseUrl}/xrpc/${nsid}${qs}`, {
31
+ method: 'POST',
32
+ headers: input ? { 'Content-Type': 'application/json' } : {},
33
+ body: input ? JSON.stringify(input) : undefined,
34
+ });
35
+ if (!res.ok) {
36
+ const err = await res.json().catch(() => ({}));
37
+ throw new Error(err.error || `XRPC ${nsid}: ${res.status}`);
38
+ }
39
+ return res.json();
40
+ },
41
+ async upload(nsid, data, contentType) {
42
+ const res = await fetchFn(`${baseUrl}/xrpc/${nsid}`, {
43
+ method: 'POST',
44
+ headers: { 'Content-Type': contentType },
45
+ body: data,
46
+ });
47
+ if (!res.ok) {
48
+ const err = await res.json().catch(() => ({}));
49
+ throw new Error(err.error || `XRPC ${nsid}: ${res.status}`);
50
+ }
51
+ return res.json();
52
+ },
53
+ };
54
+ }
package/dist/xrpc.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ import type { Row, FlatRow } from './lex-types.ts';
2
+ export type { Row, FlatRow };
3
+ export declare class InvalidRequestError extends Error {
4
+ status: number;
5
+ errorName?: string;
6
+ constructor(message: string, errorName?: string);
7
+ }
8
+ export declare class NotFoundError extends InvalidRequestError {
9
+ status: number;
10
+ constructor(message?: string);
11
+ }
12
+ export interface XrpcContext<P = Record<string, string>, Records extends Record<string, any> = Record<string, any>, I = unknown> {
13
+ db: {
14
+ query: (sql: string, params?: any[]) => Promise<any[]>;
15
+ run: (sql: string, ...params: any[]) => Promise<void>;
16
+ };
17
+ params: P;
18
+ input: I;
19
+ cursor?: string;
20
+ limit: number;
21
+ viewer: {
22
+ did: string;
23
+ } | null;
24
+ packCursor: (primary: string | number, cid: string) => string;
25
+ unpackCursor: (cursor: string) => {
26
+ primary: string;
27
+ cid: string;
28
+ } | null;
29
+ isTakendown: (did: string) => Promise<boolean>;
30
+ filterTakendownDids: (dids: string[]) => Promise<Set<string>>;
31
+ search: <K extends string & keyof Records>(collection: K, q: string, opts?: {
32
+ limit?: number;
33
+ cursor?: string;
34
+ fuzzy?: boolean;
35
+ }) => Promise<{
36
+ records: Row<Records[K]>[];
37
+ cursor?: string;
38
+ }>;
39
+ resolve: <R = unknown>(uris: string[]) => Promise<Row<R>[]>;
40
+ lookup: <R = any>(collection: string, field: string, values: string[]) => Promise<Map<string, Row<R>>>;
41
+ count: (collection: string, field: string, values: string[]) => Promise<Map<string, number>>;
42
+ exists: (collection: string, filters: Record<string, string>) => Promise<boolean>;
43
+ labels: (uris: string[]) => Promise<Map<string, any[]>>;
44
+ blobUrl: (did: string, ref: unknown, preset?: 'avatar' | 'banner' | 'feed_thumbnail' | 'feed_fullsize') => string | undefined;
45
+ }
46
+ export declare function configureRelay(relay: string): void;
47
+ export declare function blobUrl(did: string, ref: unknown, preset?: 'avatar' | 'banner' | 'feed_thumbnail' | 'feed_fullsize'): string | undefined;
48
+ export declare function initXrpc(xrpcDir: string): Promise<void>;
49
+ export declare function executeXrpc(name: string, params: Record<string, string>, cursor: string | undefined, limit: number, viewer?: {
50
+ did: string;
51
+ } | null, input?: unknown): Promise<any | null>;
52
+ export declare function listXrpc(): string[];
53
+ //# sourceMappingURL=xrpc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xrpc.d.ts","sourceRoot":"","sources":["../src/xrpc.ts"],"names":[],"mappings":"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"}
package/dist/xrpc.js ADDED
@@ -0,0 +1,139 @@
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
+ import { resolve, relative } from 'node:path';
10
+ import { readdirSync, statSync } from 'node:fs';
11
+ import { log } from "./logger.js";
12
+ import { querySQL, runSQL, packCursor, unpackCursor, isTakendownDid, filterTakendownDids, searchRecords, findUriByFields, lookupByFieldBatch, countByFieldBatch, queryLabelsForUris, } from "./db.js";
13
+ import { resolveRecords } from "./hydrate.js";
14
+ import { getLexicon } from "./schema.js";
15
+ export class InvalidRequestError extends Error {
16
+ status = 400;
17
+ errorName;
18
+ constructor(message, errorName) {
19
+ super(message);
20
+ this.errorName = errorName;
21
+ }
22
+ }
23
+ export class NotFoundError extends InvalidRequestError {
24
+ status = 404;
25
+ constructor(message = 'Not found') {
26
+ super(message, 'NotFound');
27
+ }
28
+ }
29
+ let _relayUrl = '';
30
+ export function configureRelay(relay) {
31
+ _relayUrl = relay;
32
+ }
33
+ export function blobUrl(did, ref, preset = 'avatar') {
34
+ if (!ref)
35
+ return undefined;
36
+ const p = typeof ref === 'string' ? JSON.parse(ref) : ref;
37
+ if (!p?.ref?.$link)
38
+ return undefined;
39
+ if (_relayUrl.includes('localhost:2583')) {
40
+ return `http://localhost:2583/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${p.ref.$link}`;
41
+ }
42
+ return `https://cdn.bsky.app/img/${preset}/plain/${did}/${p.ref.$link}@jpeg`;
43
+ }
44
+ const handlers = new Map();
45
+ function walkDir(dir) {
46
+ const results = [];
47
+ try {
48
+ for (const entry of readdirSync(dir)) {
49
+ const full = resolve(dir, entry);
50
+ if (statSync(full).isDirectory()) {
51
+ results.push(...walkDir(full));
52
+ }
53
+ else if ((entry.endsWith('.ts') || entry.endsWith('.js')) && !entry.startsWith('_')) {
54
+ results.push(full);
55
+ }
56
+ }
57
+ }
58
+ catch { }
59
+ return results.sort();
60
+ }
61
+ export async function initXrpc(xrpcDir) {
62
+ const files = walkDir(xrpcDir);
63
+ if (files.length === 0)
64
+ return;
65
+ for (const scriptPath of files) {
66
+ const rel = relative(xrpcDir, scriptPath).replace(/\.(ts|js)$/, '');
67
+ const name = rel.replace(/[\\/]/g, '.');
68
+ const mod = await import(__rewriteRelativeImportExtension(scriptPath));
69
+ const handler = mod.default;
70
+ // Extract param schema from lexicon for validation and defaults
71
+ const lexicon = getLexicon(name);
72
+ const paramsDef = lexicon?.defs?.main?.parameters;
73
+ const requiredParams = paramsDef?.required || [];
74
+ const paramProperties = paramsDef?.properties || {};
75
+ handlers.set(name, {
76
+ name,
77
+ execute: async (params, cursor, limit, viewer, input) => {
78
+ // Apply defaults and coerce types from lexicon
79
+ for (const [key, def] of Object.entries(paramProperties)) {
80
+ if (params[key] == null && def.default != null) {
81
+ params[key] = String(def.default);
82
+ }
83
+ if (params[key] != null && def.type === 'integer') {
84
+ params[key] = Number(params[key]);
85
+ }
86
+ }
87
+ for (const param of requiredParams) {
88
+ if (!params[param]) {
89
+ throw new InvalidRequestError(`Missing required parameter: ${param}`, 'InvalidRequest');
90
+ }
91
+ }
92
+ const ctx = {
93
+ db: { query: querySQL, run: runSQL },
94
+ params,
95
+ input: input || {},
96
+ cursor,
97
+ limit,
98
+ viewer,
99
+ packCursor,
100
+ unpackCursor,
101
+ isTakendown: isTakendownDid,
102
+ filterTakendownDids,
103
+ search: searchRecords,
104
+ resolve: resolveRecords,
105
+ lookup: async (collection, field, values) => {
106
+ if (values.length === 0)
107
+ return new Map();
108
+ const unique = [...new Set(values.filter(Boolean))];
109
+ return lookupByFieldBatch(collection, field, unique);
110
+ },
111
+ count: async (collection, field, values) => {
112
+ if (values.length === 0)
113
+ return new Map();
114
+ const unique = [...new Set(values.filter(Boolean))];
115
+ return countByFieldBatch(collection, field, unique);
116
+ },
117
+ exists: async (collection, filters) => {
118
+ const conditions = Object.entries(filters).map(([field, value]) => ({ field, value }));
119
+ const uri = await findUriByFields(collection, conditions);
120
+ return uri !== null;
121
+ },
122
+ labels: queryLabelsForUris,
123
+ blobUrl,
124
+ };
125
+ return handler.handler(ctx);
126
+ },
127
+ });
128
+ log(`[xrpc] discovered: ${name}`);
129
+ }
130
+ }
131
+ export async function executeXrpc(name, params, cursor, limit, viewer, input) {
132
+ const handler = handlers.get(name);
133
+ if (!handler)
134
+ return null;
135
+ return handler.execute(params, cursor, limit, viewer || null, input);
136
+ }
137
+ export function listXrpc() {
138
+ return Array.from(handlers.keys());
139
+ }
Binary file
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@hatk/hatk",
3
+ "version": "0.0.1-alpha.0",
4
+ "bin": {
5
+ "hatk": "dist/cli.js"
6
+ },
7
+ "type": "module",
8
+ "exports": {
9
+ "./feeds": "./dist/feeds.js",
10
+ "./xrpc": "./dist/xrpc.js",
11
+ "./opengraph": "./dist/opengraph.js",
12
+ "./labels": "./dist/labels.js",
13
+ "./lex-types": "./dist/lex-types.js",
14
+ "./xrpc-client": "./dist/xrpc-client.js",
15
+ "./views": "./dist/views.js",
16
+ "./seed": "./dist/seed.js",
17
+ "./setup": "./dist/setup.js",
18
+ "./test": "./dist/test.js",
19
+ "./test/browser": "./dist/test-browser.js",
20
+ "./vite-plugin": "./dist/vite-plugin.js"
21
+ },
22
+ "files": ["dist", "fonts", "public"],
23
+ "scripts": {
24
+ "build": "tsc -p tsconfig.build.json",
25
+ "prepublishOnly": "npm run build"
26
+ },
27
+ "dependencies": {
28
+ "@bigmoves/lexicon": "^0.2.1",
29
+ "@duckdb/node-api": "^1.4.4-r.1",
30
+ "@hatk/oauth-client": "*",
31
+ "@resvg/resvg-js": "^2.6.2",
32
+ "satori": "^0.19.2",
33
+ "vitest": "^4",
34
+ "yaml": "^2.7.0"
35
+ },
36
+ "devDependencies": {
37
+ "@playwright/test": "^1.58.2",
38
+ "@types/react": "^19.2.14",
39
+ "vite": "^6"
40
+ }
41
+ }