@hatk/hatk 0.0.1-alpha.41 → 0.0.1-alpha.42
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/cli.js +16 -553
- package/dist/database/adapters/sqlite.d.ts.map +1 -1
- package/dist/database/adapters/sqlite.js +2 -1
- package/dist/database/db.d.ts +23 -0
- package/dist/database/db.d.ts.map +1 -1
- package/dist/database/db.js +81 -4
- package/dist/labels.d.ts +2 -0
- package/dist/labels.d.ts.map +1 -1
- package/dist/labels.js +5 -0
- 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 +24 -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 +31 -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/oauth/server.d.ts.map +1 -1
- package/dist/oauth/server.js +2 -1
- package/dist/pds-proxy.d.ts.map +1 -1
- package/dist/pds-proxy.js +15 -0
- package/dist/server-init.d.ts.map +1 -1
- package/dist/server-init.js +3 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +91 -13
- package/dist/templates/feed.tpl +14 -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 +41 -0
- package/package.json +3 -2
- package/public/admin.html +133 -0
- package/dist/cloudflare/container.d.ts +0 -73
- package/dist/cloudflare/container.d.ts.map +0 -1
- package/dist/cloudflare/container.js +0 -232
- package/dist/cloudflare/hooks.d.ts +0 -33
- package/dist/cloudflare/hooks.d.ts.map +0 -1
- package/dist/cloudflare/hooks.js +0 -40
- package/dist/cloudflare/init.d.ts +0 -27
- package/dist/cloudflare/init.d.ts.map +0 -1
- package/dist/cloudflare/init.js +0 -103
- package/dist/cloudflare/worker.d.ts +0 -27
- package/dist/cloudflare/worker.d.ts.map +0 -1
- package/dist/cloudflare/worker.js +0 -54
- package/dist/database/adapters/d1.d.ts +0 -56
- package/dist/database/adapters/d1.d.ts.map +0 -1
- package/dist/database/adapters/d1.js +0 -108
- package/dist/db.d.ts +0 -134
- package/dist/db.d.ts.map +0 -1
- package/dist/db.js +0 -1327
- package/dist/fts.d.ts +0 -20
- package/dist/fts.d.ts.map +0 -1
- package/dist/fts.js +0 -767
- 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 -59
- package/dist/schema.d.ts.map +0 -1
- package/dist/schema.js +0 -387
- 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/oauth/hooks.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/** onLogin hook: called after each successful OAuth login. */
|
|
2
|
-
export type OnLoginCtx = {
|
|
3
|
-
did: string;
|
|
4
|
-
ensureRepo: (did: string) => Promise<void>;
|
|
5
|
-
};
|
|
6
|
-
/** Load on-login hook from the exercise's hooks/ directory. */
|
|
7
|
-
export declare function loadOnLoginHook(hooksDir: string): Promise<void>;
|
|
8
|
-
/** Fire the onLogin hook if loaded. Errors are logged but don't block login. */
|
|
9
|
-
export declare function fireOnLoginHook(did: string): Promise<void>;
|
|
10
|
-
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/oauth/hooks.ts"],"names":[],"mappings":"AAMA,8DAA8D;AAC9D,MAAM,MAAM,UAAU,GAAG;IACvB,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3C,CAAA;AAMD,+DAA+D;AAC/D,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQrE;AAOD,gFAAgF;AAChF,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOhE"}
|
package/dist/oauth/hooks.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
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 { existsSync } from 'node:fs';
|
|
10
|
-
import { resolve } from 'node:path';
|
|
11
|
-
import { log } from "../logger.js";
|
|
12
|
-
import { setRepoStatus } from "../db.js";
|
|
13
|
-
import { triggerAutoBackfill } from "../indexer.js";
|
|
14
|
-
let onLoginHook = null;
|
|
15
|
-
/** Load on-login hook from the exercise's hooks/ directory. */
|
|
16
|
-
export async function loadOnLoginHook(hooksDir) {
|
|
17
|
-
const tsPath = resolve(hooksDir, 'on-login.ts');
|
|
18
|
-
const jsPath = resolve(hooksDir, 'on-login.js');
|
|
19
|
-
const path = existsSync(tsPath) ? tsPath : existsSync(jsPath) ? jsPath : null;
|
|
20
|
-
if (!path)
|
|
21
|
-
return;
|
|
22
|
-
const mod = await import(__rewriteRelativeImportExtension(path));
|
|
23
|
-
onLoginHook = mod.default;
|
|
24
|
-
log('[oauth] on-login hook loaded');
|
|
25
|
-
}
|
|
26
|
-
async function ensureRepo(did) {
|
|
27
|
-
await setRepoStatus(did, 'pending');
|
|
28
|
-
triggerAutoBackfill(did);
|
|
29
|
-
}
|
|
30
|
-
/** Fire the onLogin hook if loaded. Errors are logged but don't block login. */
|
|
31
|
-
export async function fireOnLoginHook(did) {
|
|
32
|
-
if (!onLoginHook)
|
|
33
|
-
return;
|
|
34
|
-
try {
|
|
35
|
-
await onLoginHook({ did, ensureRepo });
|
|
36
|
-
}
|
|
37
|
-
catch (err) {
|
|
38
|
-
console.error('[oauth] onLogin hook error:', err.message);
|
|
39
|
-
}
|
|
40
|
-
}
|
package/dist/schema.d.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
export interface ColumnDef {
|
|
2
|
-
name: string;
|
|
3
|
-
originalName: string;
|
|
4
|
-
duckdbType: string;
|
|
5
|
-
notNull: boolean;
|
|
6
|
-
isRef: boolean;
|
|
7
|
-
}
|
|
8
|
-
export interface UnionBranchSchema {
|
|
9
|
-
type: string;
|
|
10
|
-
branchName: string;
|
|
11
|
-
tableName: string;
|
|
12
|
-
columns: ColumnDef[];
|
|
13
|
-
isArray: boolean;
|
|
14
|
-
arrayField?: string;
|
|
15
|
-
wrapperField?: string;
|
|
16
|
-
}
|
|
17
|
-
export interface UnionFieldSchema {
|
|
18
|
-
fieldName: string;
|
|
19
|
-
branches: UnionBranchSchema[];
|
|
20
|
-
}
|
|
21
|
-
export interface TableSchema {
|
|
22
|
-
collection: string;
|
|
23
|
-
tableName: string;
|
|
24
|
-
columns: ColumnDef[];
|
|
25
|
-
refColumns: string[];
|
|
26
|
-
children: ChildTableSchema[];
|
|
27
|
-
unions: UnionFieldSchema[];
|
|
28
|
-
}
|
|
29
|
-
export interface ChildTableSchema {
|
|
30
|
-
parentCollection: string;
|
|
31
|
-
fieldName: string;
|
|
32
|
-
tableName: string;
|
|
33
|
-
columns: ColumnDef[];
|
|
34
|
-
}
|
|
35
|
-
export declare function toSnakeCase(str: string): string;
|
|
36
|
-
export declare function loadLexicons(lexiconsDir: string): Map<string, any>;
|
|
37
|
-
/**
|
|
38
|
-
* Discover collections by scanning lexicons for record-type definitions.
|
|
39
|
-
*/
|
|
40
|
-
export declare function discoverCollections(lexicons: Map<string, any>): string[];
|
|
41
|
-
export declare function storeLexicons(lexicons: Map<string, any>): void;
|
|
42
|
-
export declare function getLexicon(nsid: string): any | undefined;
|
|
43
|
-
export declare function getAllLexicons(): Array<{
|
|
44
|
-
nsid: string;
|
|
45
|
-
lexicon: any;
|
|
46
|
-
}>;
|
|
47
|
-
/** Get all stored lexicons as a flat array (for @bigmoves/lexicon validators) */
|
|
48
|
-
export declare function getLexiconArray(): any[];
|
|
49
|
-
export declare function generateTableSchema(nsid: string, lexicon: any, lexicons?: Map<string, any>): TableSchema;
|
|
50
|
-
export declare function generateCreateTableSQL(schema: TableSchema): string;
|
|
51
|
-
/**
|
|
52
|
-
* Build table schemas and DDL from lexicons and collections.
|
|
53
|
-
* Shared by main.ts (server boot) and cli.ts (hatk schema command).
|
|
54
|
-
*/
|
|
55
|
-
export declare function buildSchemas(lexicons: Map<string, any>, collections: string[]): {
|
|
56
|
-
schemas: TableSchema[];
|
|
57
|
-
ddlStatements: string[];
|
|
58
|
-
};
|
|
59
|
-
//# sourceMappingURL=schema.d.ts.map
|
package/dist/schema.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,SAAS,EAAE,CAAA;IACpB,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,iBAAiB,EAAE,CAAA;CAC9B;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,SAAS,EAAE,CAAA;IACpB,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,QAAQ,EAAE,gBAAgB,EAAE,CAAA;IAC5B,MAAM,EAAE,gBAAgB,EAAE,CAAA;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,gBAAgB,EAAE,MAAM,CAAA;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,SAAS,EAAE,CAAA;CACrB;AAGD,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE/C;AA8CD,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CASlE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,EAAE,CASxE;AAID,wBAAgB,aAAa,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAI9D;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS,CAExD;AAED,wBAAgB,cAAc,IAAI,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,CAEtE;AAED,iFAAiF;AACjF,wBAAgB,eAAe,IAAI,GAAG,EAAE,CAEvC;AAuHD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,WAAW,CA0GxG;AAGD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAoElE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,EAC1B,WAAW,EAAE,MAAM,EAAE,GACpB;IAAE,OAAO,EAAE,WAAW,EAAE,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAA;CAAE,CA2BrD"}
|
package/dist/schema.js
DELETED
|
@@ -1,387 +0,0 @@
|
|
|
1
|
-
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
// Convert camelCase to snake_case
|
|
4
|
-
export function toSnakeCase(str) {
|
|
5
|
-
return str.replace(/([A-Z])/g, '_$1').toLowerCase();
|
|
6
|
-
}
|
|
7
|
-
function mapType(prop) {
|
|
8
|
-
if (prop.type === 'string') {
|
|
9
|
-
if (prop.format === 'datetime')
|
|
10
|
-
return { duckdbType: 'TIMESTAMP', isRef: false };
|
|
11
|
-
if (prop.format === 'at-uri')
|
|
12
|
-
return { duckdbType: 'TEXT', isRef: true };
|
|
13
|
-
return { duckdbType: 'TEXT', isRef: false };
|
|
14
|
-
}
|
|
15
|
-
if (prop.type === 'integer')
|
|
16
|
-
return { duckdbType: 'INTEGER', isRef: false };
|
|
17
|
-
if (prop.type === 'boolean')
|
|
18
|
-
return { duckdbType: 'BOOLEAN', isRef: false };
|
|
19
|
-
if (prop.type === 'bytes')
|
|
20
|
-
return { duckdbType: 'BLOB', isRef: false };
|
|
21
|
-
if (prop.type === 'cid-link')
|
|
22
|
-
return { duckdbType: 'TEXT', isRef: false };
|
|
23
|
-
if (prop.type === 'array')
|
|
24
|
-
return { duckdbType: 'JSON', isRef: false };
|
|
25
|
-
if (prop.type === 'blob')
|
|
26
|
-
return { duckdbType: 'JSON', isRef: false };
|
|
27
|
-
if (prop.type === 'union')
|
|
28
|
-
return { duckdbType: 'JSON', isRef: false };
|
|
29
|
-
if (prop.type === 'unknown')
|
|
30
|
-
return { duckdbType: 'JSON', isRef: false };
|
|
31
|
-
if (prop.type === 'object')
|
|
32
|
-
return { duckdbType: 'JSON', isRef: false };
|
|
33
|
-
if (prop.type === 'ref') {
|
|
34
|
-
// strongRef contains { uri, cid } — handled specially in generateTableSchema
|
|
35
|
-
if (prop.ref === 'com.atproto.repo.strongRef')
|
|
36
|
-
return { duckdbType: 'STRONG_REF', isRef: true };
|
|
37
|
-
return { duckdbType: 'JSON', isRef: false };
|
|
38
|
-
}
|
|
39
|
-
return { duckdbType: 'TEXT', isRef: false };
|
|
40
|
-
}
|
|
41
|
-
// Recursively find all .json files in a directory
|
|
42
|
-
function findJsonFiles(dir) {
|
|
43
|
-
const results = [];
|
|
44
|
-
for (const entry of readdirSync(dir)) {
|
|
45
|
-
const full = join(dir, entry);
|
|
46
|
-
if (statSync(full).isDirectory()) {
|
|
47
|
-
results.push(...findJsonFiles(full));
|
|
48
|
-
}
|
|
49
|
-
else if (entry.endsWith('.json')) {
|
|
50
|
-
results.push(full);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return results;
|
|
54
|
-
}
|
|
55
|
-
// Load all lexicon files and index by NSID
|
|
56
|
-
export function loadLexicons(lexiconsDir) {
|
|
57
|
-
const lexicons = new Map();
|
|
58
|
-
for (const file of findJsonFiles(lexiconsDir)) {
|
|
59
|
-
const content = JSON.parse(readFileSync(file, 'utf-8'));
|
|
60
|
-
if (content.lexicon === 1 && content.id) {
|
|
61
|
-
lexicons.set(content.id, content);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return lexicons;
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Discover collections by scanning lexicons for record-type definitions.
|
|
68
|
-
*/
|
|
69
|
-
export function discoverCollections(lexicons) {
|
|
70
|
-
const collections = [];
|
|
71
|
-
for (const [nsid, lexicon] of lexicons) {
|
|
72
|
-
const mainDef = lexicon.defs?.main;
|
|
73
|
-
if (mainDef?.type === 'record') {
|
|
74
|
-
collections.push(nsid);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return collections.sort();
|
|
78
|
-
}
|
|
79
|
-
const storedLexicons = new Map();
|
|
80
|
-
export function storeLexicons(lexicons) {
|
|
81
|
-
for (const [nsid, lex] of lexicons) {
|
|
82
|
-
storedLexicons.set(nsid, lex);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
export function getLexicon(nsid) {
|
|
86
|
-
return storedLexicons.get(nsid);
|
|
87
|
-
}
|
|
88
|
-
export function getAllLexicons() {
|
|
89
|
-
return [...storedLexicons.entries()].map(([nsid, lexicon]) => ({ nsid, lexicon }));
|
|
90
|
-
}
|
|
91
|
-
/** Get all stored lexicons as a flat array (for @bigmoves/lexicon validators) */
|
|
92
|
-
export function getLexiconArray() {
|
|
93
|
-
return [...storedLexicons.values()];
|
|
94
|
-
}
|
|
95
|
-
function resolveArrayItemProperties(items, defs) {
|
|
96
|
-
if (!items)
|
|
97
|
-
return null;
|
|
98
|
-
// Inline object with properties
|
|
99
|
-
if (items.type === 'object' && items.properties) {
|
|
100
|
-
return items.properties;
|
|
101
|
-
}
|
|
102
|
-
// Ref to a named def (e.g., "#artist")
|
|
103
|
-
if (items.type === 'ref' && items.ref?.startsWith('#')) {
|
|
104
|
-
const defName = items.ref.slice(1);
|
|
105
|
-
const def = defs?.[defName];
|
|
106
|
-
if (def?.type === 'object' && def.properties) {
|
|
107
|
-
return def.properties;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return null;
|
|
111
|
-
}
|
|
112
|
-
/** Resolve a ref string to its definition object */
|
|
113
|
-
function resolveRefDef(ref, defs, lexicons) {
|
|
114
|
-
if (ref.startsWith('#')) {
|
|
115
|
-
return defs?.[ref.slice(1)] || null;
|
|
116
|
-
}
|
|
117
|
-
if (ref.includes('#')) {
|
|
118
|
-
const [nsid, defName] = ref.split('#');
|
|
119
|
-
return lexicons?.get(nsid)?.defs?.[defName] || null;
|
|
120
|
-
}
|
|
121
|
-
return lexicons?.get(ref)?.defs?.main || null;
|
|
122
|
-
}
|
|
123
|
-
/** Resolve a single union ref to a branch schema */
|
|
124
|
-
function resolveUnionBranch(ref, collection, fieldName, defs, lexicons) {
|
|
125
|
-
let branchDef = null;
|
|
126
|
-
let branchName;
|
|
127
|
-
let fullType;
|
|
128
|
-
let branchDefs = defs; // defs context for resolving inner refs
|
|
129
|
-
if (ref.startsWith('#')) {
|
|
130
|
-
const defName = ref.slice(1);
|
|
131
|
-
branchDef = defs?.[defName];
|
|
132
|
-
branchName = toSnakeCase(defName);
|
|
133
|
-
fullType = `${collection}#${defName}`;
|
|
134
|
-
}
|
|
135
|
-
else if (ref.includes('#')) {
|
|
136
|
-
const [nsid, defName] = ref.split('#');
|
|
137
|
-
const lex = lexicons?.get(nsid);
|
|
138
|
-
branchDef = lex?.defs?.[defName];
|
|
139
|
-
branchName = toSnakeCase(defName);
|
|
140
|
-
fullType = ref;
|
|
141
|
-
branchDefs = lex?.defs || defs;
|
|
142
|
-
}
|
|
143
|
-
else {
|
|
144
|
-
const lex = lexicons?.get(ref);
|
|
145
|
-
branchDef = lex?.defs?.main;
|
|
146
|
-
branchName = ref.split('.').pop();
|
|
147
|
-
fullType = ref;
|
|
148
|
-
branchDefs = lex?.defs || defs;
|
|
149
|
-
}
|
|
150
|
-
if (!branchDef || branchDef.type !== 'object' || !branchDef.properties)
|
|
151
|
-
return null;
|
|
152
|
-
let isArray = false;
|
|
153
|
-
let arrayField;
|
|
154
|
-
let wrapperField;
|
|
155
|
-
let propSource = branchDef.properties;
|
|
156
|
-
const branchRequired = new Set(branchDef.required || []);
|
|
157
|
-
// Check for single-property wrapper patterns
|
|
158
|
-
const propEntries = Object.entries(branchDef.properties);
|
|
159
|
-
if (propEntries.length === 1) {
|
|
160
|
-
const [onlyField, onlyProp] = propEntries[0];
|
|
161
|
-
if (onlyProp.type === 'array' && onlyProp.items) {
|
|
162
|
-
// Single array property (like embed.images wrapping images[])
|
|
163
|
-
const items = onlyProp.items;
|
|
164
|
-
const itemDef = items.type === 'ref' && items.ref ? resolveRefDef(items.ref, branchDefs, lexicons) : items;
|
|
165
|
-
if (itemDef?.type === 'object' && itemDef.properties) {
|
|
166
|
-
isArray = true;
|
|
167
|
-
arrayField = onlyField;
|
|
168
|
-
propSource = itemDef.properties;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
else if (onlyProp.type === 'ref' && onlyProp.ref) {
|
|
172
|
-
// Single ref property (like embed.external wrapping external{})
|
|
173
|
-
const refDef = resolveRefDef(onlyProp.ref, branchDefs, lexicons);
|
|
174
|
-
if (refDef?.type === 'object' && refDef.properties) {
|
|
175
|
-
wrapperField = onlyField;
|
|
176
|
-
propSource = refDef.properties;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
const snakeField = toSnakeCase(fieldName);
|
|
181
|
-
const tableName = `"${collection}__${snakeField}_${branchName}"`;
|
|
182
|
-
const columns = [];
|
|
183
|
-
for (const [propName, prop] of Object.entries(propSource)) {
|
|
184
|
-
const { duckdbType, isRef } = mapType(prop);
|
|
185
|
-
// Skip STRONG_REF expansion in branch tables — treat as JSON
|
|
186
|
-
const finalType = duckdbType === 'STRONG_REF' ? 'JSON' : duckdbType;
|
|
187
|
-
columns.push({
|
|
188
|
-
name: toSnakeCase(propName),
|
|
189
|
-
originalName: propName,
|
|
190
|
-
duckdbType: finalType,
|
|
191
|
-
notNull: branchRequired.has(propName),
|
|
192
|
-
isRef: finalType !== 'JSON' && isRef,
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
return { type: fullType, branchName, tableName, columns, isArray, arrayField, wrapperField };
|
|
196
|
-
}
|
|
197
|
-
// Generate a TableSchema from a lexicon record definition
|
|
198
|
-
export function generateTableSchema(nsid, lexicon, lexicons) {
|
|
199
|
-
const mainDef = lexicon.defs?.main;
|
|
200
|
-
if (!mainDef || mainDef.type !== 'record') {
|
|
201
|
-
throw new Error(`Lexicon ${nsid} does not define a record type`);
|
|
202
|
-
}
|
|
203
|
-
const record = mainDef.record;
|
|
204
|
-
if (!record || record.type !== 'object') {
|
|
205
|
-
throw new Error(`Lexicon ${nsid} record is not an object type`);
|
|
206
|
-
}
|
|
207
|
-
const required = new Set(record.required || []);
|
|
208
|
-
const columns = [];
|
|
209
|
-
const children = [];
|
|
210
|
-
const unions = [];
|
|
211
|
-
for (const [fieldName, prop] of Object.entries(record.properties || {})) {
|
|
212
|
-
const p = prop;
|
|
213
|
-
// Check for union fields — decompose into branch child tables
|
|
214
|
-
if (p.type === 'union' && p.refs) {
|
|
215
|
-
const branches = [];
|
|
216
|
-
for (const ref of p.refs) {
|
|
217
|
-
const branch = resolveUnionBranch(ref, nsid, fieldName, lexicon.defs, lexicons);
|
|
218
|
-
if (branch)
|
|
219
|
-
branches.push(branch);
|
|
220
|
-
}
|
|
221
|
-
if (branches.length > 0) {
|
|
222
|
-
unions.push({ fieldName, branches });
|
|
223
|
-
}
|
|
224
|
-
// Still add the JSON column for the raw union value
|
|
225
|
-
columns.push({
|
|
226
|
-
name: toSnakeCase(fieldName),
|
|
227
|
-
originalName: fieldName,
|
|
228
|
-
duckdbType: 'JSON',
|
|
229
|
-
notNull: required.has(fieldName),
|
|
230
|
-
isRef: false,
|
|
231
|
-
});
|
|
232
|
-
continue;
|
|
233
|
-
}
|
|
234
|
-
// Check if this is a decomposable array (array of structured objects)
|
|
235
|
-
if (p.type === 'array') {
|
|
236
|
-
const itemProps = resolveArrayItemProperties(p.items, lexicon.defs);
|
|
237
|
-
if (itemProps) {
|
|
238
|
-
const childColumns = [];
|
|
239
|
-
const itemRequired = new Set(p.items?.required || lexicon.defs?.[p.items?.ref?.slice(1)]?.required || []);
|
|
240
|
-
for (const [itemField, itemProp] of Object.entries(itemProps)) {
|
|
241
|
-
const { duckdbType, isRef } = mapType(itemProp);
|
|
242
|
-
childColumns.push({
|
|
243
|
-
name: toSnakeCase(itemField),
|
|
244
|
-
originalName: itemField,
|
|
245
|
-
duckdbType,
|
|
246
|
-
notNull: itemRequired.has(itemField),
|
|
247
|
-
isRef,
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
const snakeField = toSnakeCase(fieldName);
|
|
251
|
-
children.push({
|
|
252
|
-
parentCollection: nsid,
|
|
253
|
-
fieldName,
|
|
254
|
-
tableName: `"${nsid}__${snakeField}"`,
|
|
255
|
-
columns: childColumns,
|
|
256
|
-
});
|
|
257
|
-
continue;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
const { duckdbType, isRef } = mapType(p);
|
|
261
|
-
if (duckdbType === 'STRONG_REF') {
|
|
262
|
-
// Expand strongRef into two columns: {name}_uri and {name}_cid
|
|
263
|
-
columns.push({
|
|
264
|
-
name: toSnakeCase(fieldName) + '_uri',
|
|
265
|
-
originalName: fieldName,
|
|
266
|
-
duckdbType: 'TEXT',
|
|
267
|
-
notNull: required.has(fieldName),
|
|
268
|
-
isRef: true,
|
|
269
|
-
});
|
|
270
|
-
columns.push({
|
|
271
|
-
name: toSnakeCase(fieldName) + '_cid',
|
|
272
|
-
originalName: fieldName + '__cid',
|
|
273
|
-
duckdbType: 'TEXT',
|
|
274
|
-
notNull: required.has(fieldName),
|
|
275
|
-
isRef: false,
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
else {
|
|
279
|
-
columns.push({
|
|
280
|
-
name: toSnakeCase(fieldName),
|
|
281
|
-
originalName: fieldName,
|
|
282
|
-
duckdbType,
|
|
283
|
-
notNull: required.has(fieldName),
|
|
284
|
-
isRef,
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
const refColumns = columns.filter((c) => c.isRef).map((c) => c.name);
|
|
289
|
-
return {
|
|
290
|
-
collection: nsid,
|
|
291
|
-
tableName: `"${nsid}"`,
|
|
292
|
-
columns,
|
|
293
|
-
refColumns,
|
|
294
|
-
children,
|
|
295
|
-
unions,
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
// Generate CREATE TABLE SQL from a TableSchema
|
|
299
|
-
export function generateCreateTableSQL(schema) {
|
|
300
|
-
const lines = [
|
|
301
|
-
' uri TEXT PRIMARY KEY',
|
|
302
|
-
' cid TEXT',
|
|
303
|
-
' did TEXT NOT NULL',
|
|
304
|
-
' indexed_at TIMESTAMP NOT NULL',
|
|
305
|
-
];
|
|
306
|
-
for (const col of schema.columns) {
|
|
307
|
-
const nullable = col.notNull ? ' NOT NULL' : '';
|
|
308
|
-
lines.push(` ${col.name} ${col.duckdbType}${nullable}`);
|
|
309
|
-
}
|
|
310
|
-
const createTable = `CREATE TABLE IF NOT EXISTS ${schema.tableName} (\n${lines.join(',\n')}\n);`;
|
|
311
|
-
const prefix = schema.collection.replace(/\./g, '_');
|
|
312
|
-
const indexes = [
|
|
313
|
-
`CREATE INDEX IF NOT EXISTS idx_${prefix}_indexed ON ${schema.tableName}(indexed_at DESC);`,
|
|
314
|
-
`CREATE INDEX IF NOT EXISTS idx_${prefix}_author ON ${schema.tableName}(did);`,
|
|
315
|
-
];
|
|
316
|
-
// Index ref columns for hydration lookups
|
|
317
|
-
for (const refCol of schema.refColumns) {
|
|
318
|
-
indexes.push(`CREATE INDEX IF NOT EXISTS idx_${prefix}_${refCol} ON ${schema.tableName}(${refCol});`);
|
|
319
|
-
}
|
|
320
|
-
// Child table DDL
|
|
321
|
-
const childDDL = [];
|
|
322
|
-
for (const child of schema.children) {
|
|
323
|
-
const childLines = [' parent_uri TEXT NOT NULL', ' parent_did TEXT NOT NULL'];
|
|
324
|
-
for (const col of child.columns) {
|
|
325
|
-
const nullable = col.notNull ? ' NOT NULL' : '';
|
|
326
|
-
childLines.push(` ${col.name} ${col.duckdbType}${nullable}`);
|
|
327
|
-
}
|
|
328
|
-
childDDL.push(`CREATE TABLE IF NOT EXISTS ${child.tableName} (\n${childLines.join(',\n')}\n);`);
|
|
329
|
-
const childPrefix = `${prefix}__${toSnakeCase(child.fieldName)}`;
|
|
330
|
-
childDDL.push(`CREATE INDEX IF NOT EXISTS idx_${childPrefix}_parent ON ${child.tableName}(parent_uri);`);
|
|
331
|
-
childDDL.push(`CREATE INDEX IF NOT EXISTS idx_${childPrefix}_did ON ${child.tableName}(parent_did);`);
|
|
332
|
-
for (const col of child.columns) {
|
|
333
|
-
if (col.duckdbType === 'JSON' || col.duckdbType === 'BLOB')
|
|
334
|
-
continue;
|
|
335
|
-
childDDL.push(`CREATE INDEX IF NOT EXISTS idx_${childPrefix}_${col.name} ON ${child.tableName}(${col.name});`);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
// Union branch table DDL
|
|
339
|
-
for (const union of schema.unions) {
|
|
340
|
-
for (const branch of union.branches) {
|
|
341
|
-
const branchLines = [' parent_uri TEXT NOT NULL', ' parent_did TEXT NOT NULL'];
|
|
342
|
-
for (const col of branch.columns) {
|
|
343
|
-
const nullable = col.notNull ? ' NOT NULL' : '';
|
|
344
|
-
branchLines.push(` ${col.name} ${col.duckdbType}${nullable}`);
|
|
345
|
-
}
|
|
346
|
-
childDDL.push(`CREATE TABLE IF NOT EXISTS ${branch.tableName} (\n${branchLines.join(',\n')}\n);`);
|
|
347
|
-
const branchPrefix = branch.tableName.replace(/"/g, '').replace(/\./g, '_');
|
|
348
|
-
childDDL.push(`CREATE INDEX IF NOT EXISTS idx_${branchPrefix}_parent ON ${branch.tableName}(parent_uri);`);
|
|
349
|
-
childDDL.push(`CREATE INDEX IF NOT EXISTS idx_${branchPrefix}_did ON ${branch.tableName}(parent_did);`);
|
|
350
|
-
for (const col of branch.columns) {
|
|
351
|
-
if (col.duckdbType === 'JSON' || col.duckdbType === 'BLOB')
|
|
352
|
-
continue;
|
|
353
|
-
childDDL.push(`CREATE INDEX IF NOT EXISTS idx_${branchPrefix}_${col.name} ON ${branch.tableName}(${col.name});`);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
return [createTable, ...indexes, ...childDDL].join('\n');
|
|
358
|
-
}
|
|
359
|
-
/**
|
|
360
|
-
* Build table schemas and DDL from lexicons and collections.
|
|
361
|
-
* Shared by main.ts (server boot) and cli.ts (hatk schema command).
|
|
362
|
-
*/
|
|
363
|
-
export function buildSchemas(lexicons, collections) {
|
|
364
|
-
const schemas = [];
|
|
365
|
-
const ddlStatements = [];
|
|
366
|
-
for (const nsid of collections) {
|
|
367
|
-
const lexicon = lexicons.get(nsid);
|
|
368
|
-
if (!lexicon) {
|
|
369
|
-
const genericDDL = `CREATE TABLE IF NOT EXISTS "${nsid}" (
|
|
370
|
-
uri TEXT PRIMARY KEY,
|
|
371
|
-
cid TEXT,
|
|
372
|
-
did TEXT NOT NULL,
|
|
373
|
-
indexed_at TIMESTAMP NOT NULL,
|
|
374
|
-
data JSON
|
|
375
|
-
);
|
|
376
|
-
CREATE INDEX IF NOT EXISTS idx_${nsid.replace(/\./g, '_')}_indexed ON "${nsid}"(indexed_at DESC);
|
|
377
|
-
CREATE INDEX IF NOT EXISTS idx_${nsid.replace(/\./g, '_')}_author ON "${nsid}"(did);`;
|
|
378
|
-
schemas.push({ collection: nsid, tableName: `"${nsid}"`, columns: [], refColumns: [], children: [], unions: [] });
|
|
379
|
-
ddlStatements.push(genericDDL);
|
|
380
|
-
continue;
|
|
381
|
-
}
|
|
382
|
-
const schema = generateTableSchema(nsid, lexicon, lexicons);
|
|
383
|
-
schemas.push(schema);
|
|
384
|
-
ddlStatements.push(generateCreateTableSQL(schema));
|
|
385
|
-
}
|
|
386
|
-
return { schemas, ddlStatements };
|
|
387
|
-
}
|
package/dist/test-browser.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { expect, type Page } from '@playwright/test';
|
|
2
|
-
import type { TestServer } from './test.ts';
|
|
3
|
-
type WorkerFixtures = {
|
|
4
|
-
server: TestServer;
|
|
5
|
-
};
|
|
6
|
-
/** Inject __TEST_AUTH__ into a page so isLoggedIn() and viewerDid() work. */
|
|
7
|
-
export declare function loginAs(page: Page, did: string): Promise<void>;
|
|
8
|
-
/**
|
|
9
|
-
* Extended Playwright test with an auto-started hatk server fixture.
|
|
10
|
-
* The server starts once per test file (worker scope) and is shared across tests.
|
|
11
|
-
*/
|
|
12
|
-
export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions & WorkerFixtures>;
|
|
13
|
-
export { expect };
|
|
14
|
-
//# sourceMappingURL=test-browser.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"test-browser.d.ts","sourceRoot":"","sources":["../src/test-browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,MAAM,EAAE,KAAK,IAAI,EAAE,MAAM,kBAAkB,CAAA;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAG3C,KAAK,cAAc,GAAG;IACpB,MAAM,EAAE,UAAU,CAAA;CACnB,CAAA;AAED,6EAA6E;AAC7E,wBAAsB,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIpE;AAED;;;GAGG;AACH,eAAO,MAAM,IAAI,8PAWf,CAAA;AAEF,OAAO,EAAE,MAAM,EAAE,CAAA"}
|
package/dist/test-browser.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { test as base, expect } from '@playwright/test';
|
|
2
|
-
import { startTestServer } from "./test.js";
|
|
3
|
-
/** Inject __TEST_AUTH__ into a page so isLoggedIn() and viewerDid() work. */
|
|
4
|
-
export async function loginAs(page, did) {
|
|
5
|
-
await page.addInitScript((d) => {
|
|
6
|
-
;
|
|
7
|
-
window.__TEST_AUTH__ = { did: d };
|
|
8
|
-
}, did);
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Extended Playwright test with an auto-started hatk server fixture.
|
|
12
|
-
* The server starts once per test file (worker scope) and is shared across tests.
|
|
13
|
-
*/
|
|
14
|
-
export const test = base.extend({
|
|
15
|
-
// eslint-disable-next-line no-empty-pattern -- Playwright fixture API requires the deps arg
|
|
16
|
-
server: [
|
|
17
|
-
async (_deps, use) => {
|
|
18
|
-
const server = await startTestServer();
|
|
19
|
-
await server.loadFixtures();
|
|
20
|
-
await use(server);
|
|
21
|
-
await server.close();
|
|
22
|
-
},
|
|
23
|
-
{ scope: 'worker' },
|
|
24
|
-
],
|
|
25
|
-
});
|
|
26
|
-
export { expect };
|