@hatk/hatk 0.0.1-alpha.84 → 0.0.1-alpha.86
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.map +1 -1
- package/dist/adapter.js +2 -0
- package/dist/cli.js +5 -1
- package/dist/config.d.ts +47 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +18 -1
- package/dist/database/db.d.ts +11 -0
- package/dist/database/db.d.ts.map +1 -1
- package/dist/database/db.js +356 -160
- package/dist/database/dialect.js +2 -2
- package/dist/database/fts.d.ts.map +1 -1
- package/dist/database/fts.js +6 -2
- package/dist/database/index.d.ts +1 -1
- package/dist/database/index.d.ts.map +1 -1
- package/dist/database/index.js +1 -1
- package/dist/database/schema.d.ts +2 -0
- package/dist/database/schema.d.ts.map +1 -1
- package/dist/database/schema.js +46 -4
- package/dist/dev-entry.d.ts.map +1 -1
- package/dist/dev-entry.js +2 -2
- package/dist/feeds.d.ts +14 -0
- package/dist/feeds.d.ts.map +1 -1
- package/dist/feeds.js +24 -13
- package/dist/hooks.d.ts +17 -0
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +26 -3
- package/dist/hydrate.d.ts +42 -0
- package/dist/hydrate.d.ts.map +1 -1
- package/dist/hydrate.js +18 -5
- package/dist/indexer.d.ts +4 -3
- package/dist/indexer.d.ts.map +1 -1
- package/dist/indexer.js +81 -64
- package/dist/lex-types.d.ts +2 -0
- package/dist/lex-types.d.ts.map +1 -1
- package/dist/main.js +26 -2
- package/dist/oauth/db.d.ts +9 -0
- package/dist/oauth/db.d.ts.map +1 -1
- package/dist/oauth/db.js +12 -0
- package/dist/opengraph.d.ts.map +1 -1
- package/dist/opengraph.js +16 -4
- package/dist/private-collections.d.ts +7 -2
- package/dist/private-collections.d.ts.map +1 -1
- package/dist/private-collections.js +9 -4
- package/dist/server.d.ts +8 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +167 -15
- package/dist/spaces/blob.d.ts +51 -0
- package/dist/spaces/blob.d.ts.map +1 -0
- package/dist/spaces/blob.js +111 -0
- package/dist/spaces/credential.d.ts +62 -0
- package/dist/spaces/credential.d.ts.map +1 -0
- package/dist/spaces/credential.js +184 -0
- package/dist/spaces/engine.d.ts +97 -0
- package/dist/spaces/engine.d.ts.map +1 -0
- package/dist/spaces/engine.js +552 -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 +88 -0
- package/dist/spaces/index.d.ts +55 -0
- package/dist/spaces/index.d.ts.map +1 -0
- package/dist/spaces/index.js +133 -0
- package/dist/spaces/notify.d.ts +51 -0
- package/dist/spaces/notify.d.ts.map +1 -0
- package/dist/spaces/notify.js +121 -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/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 +39 -0
- package/dist/spaces/verify.d.ts.map +1 -0
- package/dist/spaces/verify.js +87 -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 +4 -0
- package/dist/templates/xrpc.tpl +13 -1
- package/dist/test.d.ts +2 -0
- package/dist/test.d.ts.map +1 -1
- package/dist/test.js +8 -5
- package/dist/xrpc.d.ts +19 -3
- package/dist/xrpc.d.ts.map +1 -1
- package/dist/xrpc.js +62 -32
- package/package.json +8 -4
package/dist/database/dialect.js
CHANGED
|
@@ -16,7 +16,7 @@ export const DUCKDB_DIALECT = {
|
|
|
16
16
|
upsertPrefix: 'INSERT OR REPLACE INTO',
|
|
17
17
|
jsonExtractString: (col, path) => `json_extract_string(${col}, '${path}')`,
|
|
18
18
|
jsonArrayStringAgg: (col, path) => `list_string_agg(json_extract_string(${col}, '${path}'))`,
|
|
19
|
-
listTablesQuery: `SELECT table_name FROM information_schema.tables WHERE table_schema = 'main' AND table_name NOT LIKE '\\_%' ESCAPE '
|
|
19
|
+
listTablesQuery: `SELECT table_name FROM information_schema.tables WHERE table_schema = 'main' AND table_name NOT LIKE '\\_%' ESCAPE '\\'`,
|
|
20
20
|
checkpointSQL: 'CHECKPOINT',
|
|
21
21
|
currentTimestamp: 'CURRENT_TIMESTAMP',
|
|
22
22
|
ilike: 'ILIKE',
|
|
@@ -48,7 +48,7 @@ export const SQLITE_DIALECT = {
|
|
|
48
48
|
jsonArrayStringAgg: (col, path) => {
|
|
49
49
|
return `(SELECT group_concat(je.value, ' ') FROM json_each(${col}, '${path}') je)`;
|
|
50
50
|
},
|
|
51
|
-
listTablesQuery: `SELECT name AS table_name FROM sqlite_master WHERE type='table' AND name NOT LIKE '\\_%' ESCAPE '
|
|
51
|
+
listTablesQuery: `SELECT name AS table_name FROM sqlite_master WHERE type='table' AND name NOT LIKE '\\_%' ESCAPE '\\'`,
|
|
52
52
|
checkpointSQL: null,
|
|
53
53
|
currentTimestamp: 'CURRENT_TIMESTAMP',
|
|
54
54
|
ilike: 'LIKE',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fts.d.ts","sourceRoot":"","sources":["../../src/database/fts.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAuE5C,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,IAAI,CAE3D;AAED,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED,wBAAgB,aAAa,IAAI,UAAU,GAAG,IAAI,CAEjD;AAWD,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAE7D;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAElE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEvD;
|
|
1
|
+
{"version":3,"file":"fts.d.ts","sourceRoot":"","sources":["../../src/database/fts.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAuE5C,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,IAAI,CAE3D;AAED,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED,wBAAgB,aAAa,IAAI,UAAU,GAAG,IAAI,CAEjD;AAWD,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAE7D;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAElE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEvD;AAqFD;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBrE;AAED,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAehH;AAED,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAepF;AAED,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYpF;AAokBD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIpD;AAED,wBAAsB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA4B5E"}
|
package/dist/database/fts.js
CHANGED
|
@@ -102,14 +102,18 @@ function computeFtsSchema(collection) {
|
|
|
102
102
|
const selectExprs = ['t.uri', 't.cid', 't.did', 't.indexed_at'];
|
|
103
103
|
const searchColNames = [];
|
|
104
104
|
for (const col of schema.columns) {
|
|
105
|
+
// A blob column holds {ref, mimeType, size} metadata — no searchable text.
|
|
106
|
+
// This has to be decided on isJson, not on sqlType: SQLite's jsonType is
|
|
107
|
+
// TEXT, so a blob column would otherwise fall into the plain-TEXT branch
|
|
108
|
+
// below and be indexed as raw JSON.
|
|
109
|
+
if (col.isJson && record?.properties?.[col.originalName]?.type === 'blob')
|
|
110
|
+
continue;
|
|
105
111
|
if (col.sqlType === 'TEXT') {
|
|
106
112
|
selectExprs.push(`t.${q(col.name)}`);
|
|
107
113
|
searchColNames.push(col.name);
|
|
108
114
|
}
|
|
109
115
|
else if (col.isJson && record?.properties) {
|
|
110
116
|
const prop = record.properties[col.originalName];
|
|
111
|
-
if (prop?.type === 'blob')
|
|
112
|
-
continue; // skip blobs
|
|
113
117
|
if (prop && lexicon) {
|
|
114
118
|
const derived = jsonSearchColumns(`t.${q(col.name)}`, prop, lexicon, dialect);
|
|
115
119
|
if (derived.length > 0) {
|
package/dist/database/index.d.ts
CHANGED
|
@@ -3,5 +3,5 @@ export type { SqlDialect } from './dialect.ts';
|
|
|
3
3
|
export { getDialect, DUCKDB_DIALECT, SQLITE_DIALECT } from './dialect.ts';
|
|
4
4
|
export { createAdapter } from './adapter-factory.ts';
|
|
5
5
|
export { initDatabase, closeDatabase, querySQL, runSQL, insertRecord, deleteRecord, queryRecords, searchRecords, getRecordByUri, getCursor, setCursor, bulkInsertRecords, packCursor, unpackCursor, } from './db.ts';
|
|
6
|
-
export { type TableSchema, type ColumnDef, type ChildTableSchema, loadLexicons, discoverCollections, buildSchemas, generateTableSchema, generateCreateTableSQL, toSnakeCase, getLexicon, getLexiconArray, getAllLexicons, storeLexicons, } from './schema.ts';
|
|
6
|
+
export { type TableSchema, type ColumnDef, type ChildTableSchema, loadLexicons, discoverCollections, buildSchemas, generateTableSchema, generateCreateTableSQL, toSnakeCase, getLexicon, getLexiconArray, getAllLexicons, storeLexicons, columnName, } from './schema.ts';
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/database/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AACjF,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAGpD,OAAO,EACL,YAAY,EACZ,aAAa,EACb,QAAQ,EACR,MAAM,EACN,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,cAAc,EACd,SAAS,EACT,SAAS,EACT,iBAAiB,EACjB,UAAU,EACV,YAAY,GACb,MAAM,SAAS,CAAA;AAGhB,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,YAAY,EACZ,mBAAmB,EACnB,YAAY,EACZ,mBAAmB,EACnB,sBAAsB,EACtB,WAAW,EACX,UAAU,EACV,eAAe,EACf,cAAc,EACd,aAAa,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/database/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AACjF,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAGpD,OAAO,EACL,YAAY,EACZ,aAAa,EACb,QAAQ,EACR,MAAM,EACN,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,cAAc,EACd,SAAS,EACT,SAAS,EACT,iBAAiB,EACjB,UAAU,EACV,YAAY,GACb,MAAM,SAAS,CAAA;AAGhB,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,YAAY,EACZ,mBAAmB,EACnB,YAAY,EACZ,mBAAmB,EACnB,sBAAsB,EACtB,WAAW,EACX,UAAU,EACV,eAAe,EACf,cAAc,EACd,aAAa,EACb,UAAU,GACX,MAAM,aAAa,CAAA"}
|
package/dist/database/index.js
CHANGED
|
@@ -3,4 +3,4 @@ export { createAdapter } from "./adapter-factory.js";
|
|
|
3
3
|
// Re-export commonly used functions from db.ts
|
|
4
4
|
export { initDatabase, closeDatabase, querySQL, runSQL, insertRecord, deleteRecord, queryRecords, searchRecords, getRecordByUri, getCursor, setCursor, bulkInsertRecords, packCursor, unpackCursor, } from "./db.js";
|
|
5
5
|
// Re-export schema utilities
|
|
6
|
-
export { loadLexicons, discoverCollections, buildSchemas, generateTableSchema, generateCreateTableSQL, toSnakeCase, getLexicon, getLexiconArray, getAllLexicons, storeLexicons, } from "./schema.js";
|
|
6
|
+
export { loadLexicons, discoverCollections, buildSchemas, generateTableSchema, generateCreateTableSQL, toSnakeCase, getLexicon, getLexiconArray, getAllLexicons, storeLexicons, columnName, } from "./schema.js";
|
|
@@ -35,8 +35,10 @@ export interface ChildTableSchema {
|
|
|
35
35
|
columns: ColumnDef[];
|
|
36
36
|
}
|
|
37
37
|
export declare function toSnakeCase(str: string): string;
|
|
38
|
+
export declare function columnName(fieldName: string): string;
|
|
38
39
|
export declare function q(name: string): string;
|
|
39
40
|
export declare function loadLexicons(lexiconsDir: string): Map<string, any>;
|
|
41
|
+
export declare function validatableLexicons(lexicons: Map<string, any>): any[];
|
|
40
42
|
/**
|
|
41
43
|
* Discover collections by scanning lexicons for record-type definitions.
|
|
42
44
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/database/schema.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAG9C,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;IACd,MAAM,EAAE,OAAO,CAAA;CAChB;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;AAGD,wBAAgB,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtC;AA+CD,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;
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/database/schema.ts"],"names":[],"mappings":"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"}
|
package/dist/database/schema.js
CHANGED
|
@@ -5,6 +5,18 @@ import { DUCKDB_DIALECT } from "./dialect.js";
|
|
|
5
5
|
export function toSnakeCase(str) {
|
|
6
6
|
return str.replace(/([A-Z])/g, '_$1').toLowerCase();
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* The columns every generated table carries before any lexicon field. A record
|
|
10
|
+
* property with one of these names — `fyi.opensocial.space` has a `uri` — would
|
|
11
|
+
* otherwise produce a CREATE TABLE with a duplicate column and fail at boot.
|
|
12
|
+
* It is stored under `record_<name>` instead; `originalName` still carries the
|
|
13
|
+
* lexicon's spelling, which is what every reshaped row is keyed by.
|
|
14
|
+
*/
|
|
15
|
+
const ENVELOPE_COLUMNS = new Set(['uri', 'cid', 'did', 'space', 'indexed_at', 'handle']);
|
|
16
|
+
export function columnName(fieldName) {
|
|
17
|
+
const snake = toSnakeCase(fieldName);
|
|
18
|
+
return ENVELOPE_COLUMNS.has(snake) ? `record_${snake}` : snake;
|
|
19
|
+
}
|
|
8
20
|
// Quote a column name to avoid conflicts with SQL reserved words
|
|
9
21
|
export function q(name) {
|
|
10
22
|
return `"${name}"`;
|
|
@@ -68,6 +80,20 @@ export function loadLexicons(lexiconsDir) {
|
|
|
68
80
|
}
|
|
69
81
|
return lexicons;
|
|
70
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Lexicons the validator cannot model but hatk legitimately keeps.
|
|
85
|
+
*
|
|
86
|
+
* A space type is how the indexer learns which collections a space holds, and
|
|
87
|
+
* a permission set is what an OAuth scope resolves through; both are real
|
|
88
|
+
* lexicons an app vendors on purpose. `validateLexicons` reports each as an
|
|
89
|
+
* unknown definition type, which would read as a fatal schema error at boot.
|
|
90
|
+
* Only these two are set aside — anything else unknown is still a mistake the
|
|
91
|
+
* validator should refuse.
|
|
92
|
+
*/
|
|
93
|
+
const UNMODELLED_DEF_TYPES = new Set(['space', 'permission-set']);
|
|
94
|
+
export function validatableLexicons(lexicons) {
|
|
95
|
+
return [...lexicons.values()].filter((lex) => !UNMODELLED_DEF_TYPES.has(lex.defs?.main?.type));
|
|
96
|
+
}
|
|
71
97
|
/**
|
|
72
98
|
* Discover collections by scanning lexicons for record-type definitions.
|
|
73
99
|
*/
|
|
@@ -158,7 +184,10 @@ function resolveUnionBranch(ref, collection, fieldName, defs, lexicons, dialect)
|
|
|
158
184
|
let arrayField;
|
|
159
185
|
let wrapperField;
|
|
160
186
|
let propSource = branchDef.properties;
|
|
161
|
-
|
|
187
|
+
// `required` has to travel with propSource: once the columns come from the
|
|
188
|
+
// item (or wrapper target) def, the outer branch's `required` describes the
|
|
189
|
+
// wrapper property, not these columns.
|
|
190
|
+
let branchRequired = new Set(branchDef.required || []);
|
|
162
191
|
// Check for single-property wrapper patterns
|
|
163
192
|
const propEntries = Object.entries(branchDef.properties);
|
|
164
193
|
if (propEntries.length === 1) {
|
|
@@ -171,6 +200,7 @@ function resolveUnionBranch(ref, collection, fieldName, defs, lexicons, dialect)
|
|
|
171
200
|
isArray = true;
|
|
172
201
|
arrayField = onlyField;
|
|
173
202
|
propSource = itemDef.properties;
|
|
203
|
+
branchRequired = new Set(itemDef.required || []);
|
|
174
204
|
}
|
|
175
205
|
}
|
|
176
206
|
else if (onlyProp.type === 'ref' && onlyProp.ref) {
|
|
@@ -179,6 +209,7 @@ function resolveUnionBranch(ref, collection, fieldName, defs, lexicons, dialect)
|
|
|
179
209
|
if (refDef?.type === 'object' && refDef.properties) {
|
|
180
210
|
wrapperField = onlyField;
|
|
181
211
|
propSource = refDef.properties;
|
|
212
|
+
branchRequired = new Set(refDef.required || []);
|
|
182
213
|
}
|
|
183
214
|
}
|
|
184
215
|
}
|
|
@@ -229,7 +260,7 @@ export function generateTableSchema(nsid, lexicon, lexicons, dialect = DUCKDB_DI
|
|
|
229
260
|
}
|
|
230
261
|
// Still add the JSON column for the raw union value
|
|
231
262
|
columns.push({
|
|
232
|
-
name:
|
|
263
|
+
name: columnName(fieldName),
|
|
233
264
|
originalName: fieldName,
|
|
234
265
|
sqlType: dialect.jsonType,
|
|
235
266
|
notNull: required.has(fieldName),
|
|
@@ -287,7 +318,7 @@ export function generateTableSchema(nsid, lexicon, lexicons, dialect = DUCKDB_DI
|
|
|
287
318
|
}
|
|
288
319
|
else {
|
|
289
320
|
columns.push({
|
|
290
|
-
name:
|
|
321
|
+
name: columnName(fieldName),
|
|
291
322
|
originalName: fieldName,
|
|
292
323
|
sqlType,
|
|
293
324
|
notNull: required.has(fieldName),
|
|
@@ -308,10 +339,16 @@ export function generateTableSchema(nsid, lexicon, lexicons, dialect = DUCKDB_DI
|
|
|
308
339
|
}
|
|
309
340
|
// Generate CREATE TABLE SQL from a TableSchema
|
|
310
341
|
export function generateCreateTableSQL(schema, dialect = DUCKDB_DIALECT) {
|
|
342
|
+
// `space` is the permissioned space a row came from, and NULL for everything
|
|
343
|
+
// off the firehose. It is the only per-row visibility marker in the schema:
|
|
344
|
+
// a space record is readable by whoever the space's authority admits, so no
|
|
345
|
+
// read may serve one without knowing who is asking. NULL means "public repo
|
|
346
|
+
// data", which is what every existing row is.
|
|
311
347
|
const lines = [
|
|
312
348
|
' uri TEXT PRIMARY KEY',
|
|
313
349
|
' cid TEXT',
|
|
314
350
|
' did TEXT NOT NULL',
|
|
351
|
+
' space TEXT',
|
|
315
352
|
` indexed_at ${dialect.timestampType} NOT NULL`,
|
|
316
353
|
];
|
|
317
354
|
for (const col of schema.columns) {
|
|
@@ -323,6 +360,9 @@ export function generateCreateTableSQL(schema, dialect = DUCKDB_DIALECT) {
|
|
|
323
360
|
const indexes = [
|
|
324
361
|
`CREATE INDEX IF NOT EXISTS idx_${prefix}_indexed ON ${schema.tableName}(indexed_at DESC);`,
|
|
325
362
|
`CREATE INDEX IF NOT EXISTS idx_${prefix}_author ON ${schema.tableName}(did);`,
|
|
363
|
+
// Every read that serves space rows filters on this column, and every read
|
|
364
|
+
// that does not still tests it for NULL.
|
|
365
|
+
`CREATE INDEX IF NOT EXISTS idx_${prefix}_space ON ${schema.tableName}(space);`,
|
|
326
366
|
];
|
|
327
367
|
// Index ref columns for hydration lookups
|
|
328
368
|
for (const refCol of schema.refColumns) {
|
|
@@ -381,11 +421,13 @@ export function buildSchemas(lexicons, collections, dialect = DUCKDB_DIALECT) {
|
|
|
381
421
|
uri TEXT PRIMARY KEY,
|
|
382
422
|
cid TEXT,
|
|
383
423
|
did TEXT NOT NULL,
|
|
424
|
+
space TEXT,
|
|
384
425
|
indexed_at ${dialect.timestampType} NOT NULL,
|
|
385
426
|
data ${dialect.jsonType}
|
|
386
427
|
);
|
|
387
428
|
CREATE INDEX IF NOT EXISTS idx_${nsid.replace(/\./g, '_')}_indexed ON "${nsid}"(indexed_at DESC);
|
|
388
|
-
CREATE INDEX IF NOT EXISTS idx_${nsid.replace(/\./g, '_')}_author ON "${nsid}"(did)
|
|
429
|
+
CREATE INDEX IF NOT EXISTS idx_${nsid.replace(/\./g, '_')}_author ON "${nsid}"(did);
|
|
430
|
+
CREATE INDEX IF NOT EXISTS idx_${nsid.replace(/\./g, '_')}_space ON "${nsid}"(space);`;
|
|
389
431
|
schemas.push({ collection: nsid, tableName: `"${nsid}"`, columns: [], refColumns: [], children: [], unions: [] });
|
|
390
432
|
ddlStatements.push(genericDDL);
|
|
391
433
|
continue;
|
package/dist/dev-entry.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-entry.d.ts","sourceRoot":"","sources":["../src/dev-entry.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dev-entry.d.ts","sourceRoot":"","sources":["../src/dev-entry.ts"],"names":[],"mappings":"AA+HA,eAAO,MAAM,OAAO,yCAKlB,CAAA;AAEF,yEAAyE;AACzE,wBAAsB,YAAY,kBAEjC;AAED,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AACpC,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA"}
|
package/dist/dev-entry.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Boots hatk infrastructure and exports the fetch handler.
|
|
4
4
|
*/
|
|
5
5
|
import { loadConfig } from "./config.js";
|
|
6
|
-
import { loadLexicons, storeLexicons, discoverCollections, buildSchemas } from "./database/schema.js";
|
|
6
|
+
import { validatableLexicons, loadLexicons, storeLexicons, discoverCollections, buildSchemas, } from "./database/schema.js";
|
|
7
7
|
import { discoverViews } from "./views.js";
|
|
8
8
|
import { initDatabase, migrateSchema, getSchemaDump } from "./database/db.js";
|
|
9
9
|
import { createAdapter } from "./database/adapter-factory.js";
|
|
@@ -32,7 +32,7 @@ configureRelay(config.relay);
|
|
|
32
32
|
configurePlc(config.plc);
|
|
33
33
|
configureCdn(config.cdn);
|
|
34
34
|
const lexicons = loadLexicons(resolve(configDir, 'lexicons'));
|
|
35
|
-
const lexiconErrors = validateLexicons(
|
|
35
|
+
const lexiconErrors = validateLexicons(validatableLexicons(lexicons));
|
|
36
36
|
if (lexiconErrors) {
|
|
37
37
|
for (const [nsid, errors] of Object.entries(lexiconErrors)) {
|
|
38
38
|
for (const err of errors)
|
package/dist/feeds.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export interface PaginateResult<T> {
|
|
|
17
17
|
export interface FeedContext {
|
|
18
18
|
db: {
|
|
19
19
|
query: (sql: string, params?: unknown[]) => Promise<unknown[]>;
|
|
20
|
+
/** Raw SQL with the permissioned-space guard off. See `spaces/guard.ts`. */
|
|
21
|
+
unfiltered: (sql: string, params?: unknown[]) => Promise<unknown[]>;
|
|
20
22
|
};
|
|
21
23
|
params: Record<string, string>;
|
|
22
24
|
cursor?: string;
|
|
@@ -34,6 +36,18 @@ export interface FeedContext {
|
|
|
34
36
|
paginate: <T extends {
|
|
35
37
|
uri: string;
|
|
36
38
|
}>(sql: string, opts?: PaginateOpts) => Promise<PaginateResult<T>>;
|
|
39
|
+
/**
|
|
40
|
+
* The permissioned-space gate, for SQL this feed builds without `paginate`.
|
|
41
|
+
*
|
|
42
|
+
* `paginate` applies it already. This is for a feed that runs its own query
|
|
43
|
+
* — a raw `ctx.db.query` over a record table has to carry it, or it serves
|
|
44
|
+
* space rows to whoever asks. See `spaceFilterSql`.
|
|
45
|
+
*/
|
|
46
|
+
spaceFilter: (alias: string, startIdx: number) => {
|
|
47
|
+
sql: string;
|
|
48
|
+
params: string[];
|
|
49
|
+
nextIdx: number;
|
|
50
|
+
};
|
|
37
51
|
}
|
|
38
52
|
type FeedGenerate = (ctx: FeedContext & {
|
|
39
53
|
ok: (value: FeedResult) => Checked<FeedResult>;
|
package/dist/feeds.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feeds.d.ts","sourceRoot":"","sources":["../src/feeds.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"feeds.d.ts","sourceRoot":"","sources":["../src/feeds.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AACpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAE7C,YAAY,EAAE,WAAW,EAAE,GAAG,EAAE,CAAA;AAEhC,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAA;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,IAAI,EAAE,CAAC,EAAE,CAAA;IACT,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;CAC3B;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE;QACF,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;QAC9D,4EAA4E;QAC5E,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;KACpE,CAAA;IACD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAC9B,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;IAC7D,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IACzE,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAC9C,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAA;IAC7D,QAAQ,EAAE,CAAC,CAAC,SAAS;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAA;IACrG;;;;;;OAMG;IACH,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CACrG;AAkBD,KAAK,YAAY,GAAG,CAClB,GAAG,EAAE,WAAW,GAAG;IAAE,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,CAAA;CAAE,KAClE,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAA;AAEjC,KAAK,QAAQ,GACT;IACE,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,YAAY,CAAA;IACtB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;CACtE,GACD;IACE,UAAU,CAAC,EAAE,KAAK,CAAA;IAClB,IAAI,CAAC,EAAE,KAAK,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,YAAY,CAAA;IACtB,OAAO,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;CACrE,CAAA;AAEL,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,EAAE,EAAE;QAAE,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;KAAE,CAAA;IACtE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;IAC7D,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CAC1E,IACe,CAAC,SAAS;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,KAAK,MAAM,EAAE,OAAO,YAAY,KAAG,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CA4DvG;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ;oBACoB,GAAG;gBApF9C,MAAM;WACX,MAAM;WACN,MAAM;cAEH,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC;;;oBAgFd,GAAG;iBA7E7C,KAAK;WACX,KAAK;WACL,MAAM;aAEJ,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC;;EA0EzE;AAED,4DAA4D;AAC5D,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG,IAAI,CAwCzF;AAID,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA+D/D;AAED,8DAA8D;AAC9D,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAC9B,OAAO,CAAC;IAAE,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAoBzE;AAED,wBAAgB,SAAS,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,CAE7D;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD"}
|
package/dist/feeds.js
CHANGED
|
@@ -9,8 +9,10 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
9
9
|
import { resolve } from 'node:path';
|
|
10
10
|
import { readdirSync } from 'node:fs';
|
|
11
11
|
import { log } from "./logger.js";
|
|
12
|
-
import {
|
|
12
|
+
import { packCursor, unpackCursor, isTakendownDid, filterTakendownDids, resolveHandleToDid, } from "./database/db.js";
|
|
13
13
|
import { resolveRecords, buildBaseContext } from "./hydrate.js";
|
|
14
|
+
import { spaceFilterSql } from "./spaces/visibility.js";
|
|
15
|
+
import { guardedQuerySQL, unfilteredQuerySQL } from "./spaces/guard.js";
|
|
14
16
|
export function createPaginate(deps) {
|
|
15
17
|
return async (sql, opts) => {
|
|
16
18
|
const { db, cursor, limit, packCursor: pack, unpackCursor: unpack } = deps;
|
|
@@ -25,6 +27,16 @@ export function createPaginate(deps) {
|
|
|
25
27
|
const op = order === 'ASC' ? '>' : '<';
|
|
26
28
|
let paramIdx = userParams.length + 1;
|
|
27
29
|
const sqlParams = [...userParams];
|
|
30
|
+
// The permissioned-space gate, applied to whatever table the ordering
|
|
31
|
+
// names — the same table `cid` is read from. Feed SQL is hand-written and
|
|
32
|
+
// nothing can inject a predicate into a string somebody else wrote, so
|
|
33
|
+
// building it here is the only way every feed gets it without every feed
|
|
34
|
+
// author remembering to. Outside a viewer's scope it is `space IS NULL`
|
|
35
|
+
// and binds nothing, which is what it compiles to on an instance that
|
|
36
|
+
// indexes no space.
|
|
37
|
+
const gate = spaceFilterSql(prefix.replace(/\.$/, ''), paramIdx);
|
|
38
|
+
sqlParams.push(...gate.params);
|
|
39
|
+
paramIdx = gate.nextIdx;
|
|
28
40
|
// Build cursor condition
|
|
29
41
|
let cursorCondition = '';
|
|
30
42
|
if (cursor) {
|
|
@@ -35,15 +47,12 @@ export function createPaginate(deps) {
|
|
|
35
47
|
paramIdx += 3;
|
|
36
48
|
}
|
|
37
49
|
}
|
|
38
|
-
// Detect existing WHERE and append cursor condition
|
|
50
|
+
// Detect existing WHERE and append the gate and the cursor condition
|
|
39
51
|
let fullSql = sql;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
fullSql += ` WHERE ${cursorCondition}`;
|
|
46
|
-
}
|
|
52
|
+
const appended = [gate.sql, ...(cursorCondition ? [cursorCondition] : [])];
|
|
53
|
+
if (appended.length) {
|
|
54
|
+
const joiner = /\bWHERE\b/i.test(sql) ? 'AND' : 'WHERE';
|
|
55
|
+
fullSql += ` ${joiner} ${appended.join(' AND ')}`;
|
|
47
56
|
}
|
|
48
57
|
fullSql += ` ORDER BY ${orderBy} ${order}, ${cidCol} ${order} LIMIT $${paramIdx}`;
|
|
49
58
|
sqlParams.push(limit + 1);
|
|
@@ -70,14 +79,14 @@ export function registerFeed(name, generator) {
|
|
|
70
79
|
view: generator.view,
|
|
71
80
|
generate: async (params, cursor, limit, viewer) => {
|
|
72
81
|
const paginateDeps = {
|
|
73
|
-
db: { query:
|
|
82
|
+
db: { query: guardedQuerySQL, unfiltered: unfilteredQuerySQL },
|
|
74
83
|
cursor,
|
|
75
84
|
limit,
|
|
76
85
|
packCursor,
|
|
77
86
|
unpackCursor,
|
|
78
87
|
};
|
|
79
88
|
const ctx = {
|
|
80
|
-
db: { query:
|
|
89
|
+
db: { query: guardedQuerySQL, unfiltered: unfilteredQuerySQL },
|
|
81
90
|
params,
|
|
82
91
|
cursor,
|
|
83
92
|
limit,
|
|
@@ -87,6 +96,7 @@ export function registerFeed(name, generator) {
|
|
|
87
96
|
isTakendown: isTakendownDid,
|
|
88
97
|
filterTakendownDids,
|
|
89
98
|
paginate: createPaginate(paginateDeps),
|
|
99
|
+
spaceFilter: spaceFilterSql,
|
|
90
100
|
};
|
|
91
101
|
const result = await generator.generate(ctx);
|
|
92
102
|
if (Array.isArray(result)) {
|
|
@@ -123,14 +133,14 @@ export async function initFeeds(feedsDir) {
|
|
|
123
133
|
view: generator.view,
|
|
124
134
|
generate: async (params, cursor, limit, viewer) => {
|
|
125
135
|
const paginateDeps = {
|
|
126
|
-
db: { query:
|
|
136
|
+
db: { query: guardedQuerySQL, unfiltered: unfilteredQuerySQL },
|
|
127
137
|
cursor,
|
|
128
138
|
limit,
|
|
129
139
|
packCursor,
|
|
130
140
|
unpackCursor,
|
|
131
141
|
};
|
|
132
142
|
const ctx = {
|
|
133
|
-
db: { query:
|
|
143
|
+
db: { query: guardedQuerySQL, unfiltered: unfilteredQuerySQL },
|
|
134
144
|
params,
|
|
135
145
|
cursor,
|
|
136
146
|
limit,
|
|
@@ -140,6 +150,7 @@ export async function initFeeds(feedsDir) {
|
|
|
140
150
|
isTakendown: isTakendownDid,
|
|
141
151
|
filterTakendownDids,
|
|
142
152
|
paginate: createPaginate(paginateDeps),
|
|
153
|
+
spaceFilter: spaceFilterSql,
|
|
143
154
|
};
|
|
144
155
|
const result = await generator.generate(ctx);
|
|
145
156
|
if (Array.isArray(result)) {
|
package/dist/hooks.d.ts
CHANGED
|
@@ -9,6 +9,13 @@ export type OnLoginCtx = Omit<BaseContext, 'db'> & {
|
|
|
9
9
|
db: {
|
|
10
10
|
query: (sql: string, params?: unknown[]) => Promise<unknown[]>;
|
|
11
11
|
run: (sql: string, params?: unknown[]) => Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Raw SQL with the permissioned-space guard off. A hook is not serving a
|
|
14
|
+
* viewer, and discovering what a space contains — which spaces a
|
|
15
|
+
* community lists, say — is exactly a read over space rows that no scope
|
|
16
|
+
* is open for. See `spaces/guard.ts`.
|
|
17
|
+
*/
|
|
18
|
+
unfiltered: (sql: string, params?: unknown[]) => Promise<unknown[]>;
|
|
12
19
|
};
|
|
13
20
|
/** Trigger a backfill for a DID and wait for it to complete. */
|
|
14
21
|
ensureRepo: (did: string) => Promise<void>;
|
|
@@ -43,6 +50,8 @@ export type OnCommitCtx = {
|
|
|
43
50
|
db: {
|
|
44
51
|
query: (sql: string, params?: unknown[]) => Promise<unknown[]>;
|
|
45
52
|
run: (sql: string, params?: unknown[]) => Promise<void>;
|
|
53
|
+
/** Raw SQL with the permissioned-space guard off. See `spaces/guard.ts`. */
|
|
54
|
+
unfiltered: (sql: string, params?: unknown[]) => Promise<unknown[]>;
|
|
46
55
|
};
|
|
47
56
|
/** Typed record lookup (same as BaseContext). */
|
|
48
57
|
lookup: BaseContext['lookup'];
|
|
@@ -65,6 +74,14 @@ export declare function defineHook(event: 'on-commit', options: {
|
|
|
65
74
|
/**
|
|
66
75
|
* Discover and load the on-login hook from the project's `hooks/` directory.
|
|
67
76
|
* Looks for `on-login.ts` or `on-login.js`. Safe to call if no hook exists.
|
|
77
|
+
*
|
|
78
|
+
* The documented contract is `export default defineHook('on-login', handler)`,
|
|
79
|
+
* which is an object carrying the handler — the same shape the `server/`
|
|
80
|
+
* scanner recognizes by its `__type` tag. A bare async function is still
|
|
81
|
+
* accepted, because that is what the legacy `hooks/` directory used before
|
|
82
|
+
* `defineHook` existed. Anything else is reported rather than installed: a
|
|
83
|
+
* non-callable hook would otherwise fail silently on every login, since
|
|
84
|
+
* `fireOnLoginHook` swallows what the hook throws.
|
|
68
85
|
*/
|
|
69
86
|
export declare function loadOnLoginHook(hooksDir: string): Promise<void>;
|
|
70
87
|
/** Register a hook from a scanned server/ module. */
|
package/dist/hooks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAK9C,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAA;AACjE,OAAO,EAAqC,KAAK,aAAa,EAAE,MAAM,WAAW,CAAA;AAEjF,0EAA0E;AAC1E,MAAM,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG;IACjD,0CAA0C;IAC1C,GAAG,EAAE,MAAM,CAAA;IACX,gDAAgD;IAChD,EAAE,EAAE;QACF,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;QAC9D,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"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"}
|
package/dist/hooks.js
CHANGED
|
@@ -63,6 +63,14 @@ const onCommitHooks = [];
|
|
|
63
63
|
/**
|
|
64
64
|
* Discover and load the on-login hook from the project's `hooks/` directory.
|
|
65
65
|
* Looks for `on-login.ts` or `on-login.js`. Safe to call if no hook exists.
|
|
66
|
+
*
|
|
67
|
+
* The documented contract is `export default defineHook('on-login', handler)`,
|
|
68
|
+
* which is an object carrying the handler — the same shape the `server/`
|
|
69
|
+
* scanner recognizes by its `__type` tag. A bare async function is still
|
|
70
|
+
* accepted, because that is what the legacy `hooks/` directory used before
|
|
71
|
+
* `defineHook` existed. Anything else is reported rather than installed: a
|
|
72
|
+
* non-callable hook would otherwise fail silently on every login, since
|
|
73
|
+
* `fireOnLoginHook` swallows what the hook throws.
|
|
66
74
|
*/
|
|
67
75
|
export async function loadOnLoginHook(hooksDir) {
|
|
68
76
|
const tsPath = resolve(hooksDir, 'on-login.ts');
|
|
@@ -71,9 +79,24 @@ export async function loadOnLoginHook(hooksDir) {
|
|
|
71
79
|
if (!path)
|
|
72
80
|
return;
|
|
73
81
|
const mod = await import(__rewriteRelativeImportExtension(/* @vite-ignore */ `${path}?t=${Date.now()}`));
|
|
74
|
-
|
|
82
|
+
const handler = resolveHookHandler(mod.default);
|
|
83
|
+
if (!handler) {
|
|
84
|
+
emit('hooks', 'on_login_load_error', {
|
|
85
|
+
path,
|
|
86
|
+
error: 'default export is not a function or a defineHook("on-login", ...) result',
|
|
87
|
+
});
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
onLoginHook = handler;
|
|
75
91
|
log('[hooks] on-login hook loaded');
|
|
76
92
|
}
|
|
93
|
+
/** Unwrap a hook module's default export into the callable handler, if it has one. */
|
|
94
|
+
function resolveHookHandler(exported) {
|
|
95
|
+
if (typeof exported === 'function')
|
|
96
|
+
return exported;
|
|
97
|
+
const handler = exported?.handler;
|
|
98
|
+
return typeof handler === 'function' ? handler : null;
|
|
99
|
+
}
|
|
77
100
|
/** Mark a DID as pending, trigger auto-backfill, and wait for completion. */
|
|
78
101
|
async function ensureRepo(did) {
|
|
79
102
|
await setRepoStatus(did, 'pending');
|
|
@@ -102,7 +125,7 @@ export async function fireOnLoginHook(did, oauthConfig) {
|
|
|
102
125
|
const hookPromise = onLoginHook({
|
|
103
126
|
...base,
|
|
104
127
|
did,
|
|
105
|
-
db: { query: base.db.query, run: runSQL },
|
|
128
|
+
db: { query: base.db.query, run: runSQL, unfiltered: base.db.unfiltered },
|
|
106
129
|
ensureRepo,
|
|
107
130
|
createRecord: async (collection, record, opts) => {
|
|
108
131
|
if (!oauthConfig)
|
|
@@ -147,7 +170,7 @@ export function fireOnCommitHooks(items) {
|
|
|
147
170
|
record: item.record,
|
|
148
171
|
repo: item.authorDid,
|
|
149
172
|
uri: item.uri,
|
|
150
|
-
db: { query: base.db.query, run: runSQL },
|
|
173
|
+
db: { query: base.db.query, run: runSQL, unfiltered: base.db.unfiltered },
|
|
151
174
|
lookup: base.lookup,
|
|
152
175
|
push,
|
|
153
176
|
})
|
package/dist/hydrate.d.ts
CHANGED
|
@@ -7,12 +7,54 @@ export interface BaseContext {
|
|
|
7
7
|
} | null;
|
|
8
8
|
db: {
|
|
9
9
|
query: (sql: string, params?: unknown[]) => Promise<unknown[]>;
|
|
10
|
+
/**
|
|
11
|
+
* Raw SQL with the permissioned-space guard off. Named so it can be found
|
|
12
|
+
* with grep: for results never served to a viewer, such as an admin
|
|
13
|
+
* rollup. See `spaces/guard.ts`.
|
|
14
|
+
*/
|
|
15
|
+
unfiltered: (sql: string, params?: unknown[]) => Promise<unknown[]>;
|
|
10
16
|
};
|
|
11
17
|
getRecords: <R = unknown>(collection: string, uris: string[]) => Promise<Map<string, Row<R>>>;
|
|
12
18
|
lookup: <R = unknown>(collection: string, field: string, values: string[]) => Promise<Map<string, Row<R>>>;
|
|
13
19
|
count: (collection: string, field: string, values: string[]) => Promise<Map<string, number>>;
|
|
14
20
|
labels: (uris: string[]) => Promise<Map<string, unknown[]>>;
|
|
15
21
|
blobUrl: (did: string, ref: unknown, preset?: string) => string | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* The permissioned-space gate, for hand-written SQL.
|
|
24
|
+
*
|
|
25
|
+
* Every helper on this context applies it already. Raw SQL through
|
|
26
|
+
* `ctx.db.query` cannot — nothing can inject a predicate into a string
|
|
27
|
+
* somebody else wrote — so a query that selects from a collection a space
|
|
28
|
+
* writes into has to apply it itself:
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* const gate = ctx.spaceFilter('t', 2)
|
|
32
|
+
* ctx.db.query(
|
|
33
|
+
* `SELECT t.* FROM "app.example.post" t WHERE t.did = $1 AND ${gate.sql}`,
|
|
34
|
+
* ['did:plc:someone', ...gate.params],
|
|
35
|
+
* )
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* Outside a viewer's scope this is `t.space IS NULL` and binds nothing, so on
|
|
39
|
+
* an instance that indexes no space it costs one predicate on an indexed
|
|
40
|
+
* column and changes no result.
|
|
41
|
+
*/
|
|
42
|
+
spaceFilter: (alias: string, startIdx: number) => {
|
|
43
|
+
sql: string;
|
|
44
|
+
params: string[];
|
|
45
|
+
nextIdx: number;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Every record of a collection in a space the viewer may read. Empty for a
|
|
49
|
+
* space outside their scope; see `requireSpace` for the explicit refusal.
|
|
50
|
+
*/
|
|
51
|
+
spaceRecords: <R = unknown>(collection: string, space: string) => Promise<Row<R>[]>;
|
|
52
|
+
/** Every record where `field` is one of `values` — the many-rows counterpart of `lookup`. */
|
|
53
|
+
records: <R = unknown>(collection: string, field: string, values: string[]) => Promise<Row<R>[]>;
|
|
54
|
+
/** Throw `NotAuthorized` unless the viewer may read this space. */
|
|
55
|
+
requireSpace: (space: string) => void;
|
|
56
|
+
/** The URL this appview serves a space blob at, for the viewer asking. */
|
|
57
|
+
spaceBlobUrl: (space: string, repo: string, cid: string) => string;
|
|
16
58
|
}
|
|
17
59
|
/** Fetch records for URIs, reshape them, and filter out taken-down DIDs. */
|
|
18
60
|
export declare function resolveRecords(uris: string[]): Promise<Row<unknown>[]>;
|
package/dist/hydrate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hydrate.d.ts","sourceRoot":"","sources":["../src/hydrate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hydrate.d.ts","sourceRoot":"","sources":["../src/hydrate.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAA;AAEzC,YAAY,EAAE,GAAG,EAAE,CAAA;AAInB,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAC/C,EAAE,EAAE;QACF,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;QAC9D;;;;WAIG;QACH,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;KACpE,CAAA;IACD,UAAU,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC7F,MAAM,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC1G,KAAK,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAC5F,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,CAAA;IAC3D,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;IAC3E;;;;;;;;;;;;;;;;;;;OAmBG;IACH,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;IACpG;;;OAGG;IACH,YAAY,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IACnF,6FAA6F;IAC7F,OAAO,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IAChG,mEAAmE;IACnE,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACrC,0EAA0E;IAC1E,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;CACnE;AAID,4EAA4E;AAC5E,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAsC5E;AAID,yCAAyC;AACzC,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAAG,WAAW,CAuB7F"}
|
package/dist/hydrate.js
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
|
-
import { getRecordsMap, countByFieldBatch, lookupByFieldBatch,
|
|
1
|
+
import { getRecordsMap, countByFieldBatch, lookupByFieldBatch, queryLabelsForUris, filterTakendownDids, getRecordsByUris, reshapeRow, } from "./database/db.js";
|
|
2
2
|
import { blobUrl } from "./xrpc.js";
|
|
3
|
+
import { collectionFromRecordUri } from "./spaces/uri.js";
|
|
4
|
+
import { spaceFilterSql } from "./spaces/visibility.js";
|
|
5
|
+
import { guardedQuerySQL, unfilteredQuerySQL } from "./spaces/guard.js";
|
|
6
|
+
import { records, requireSpace, spaceRecords } from "./spaces/records.js";
|
|
7
|
+
import { spaceBlobUrl } from "./spaces/blob.js";
|
|
3
8
|
// --- Record Resolution ---
|
|
4
9
|
/** Fetch records for URIs, reshape them, and filter out taken-down DIDs. */
|
|
5
10
|
export async function resolveRecords(uris) {
|
|
6
11
|
if (uris.length === 0)
|
|
7
12
|
return [];
|
|
8
|
-
// Group URIs by collection for batch fetching
|
|
13
|
+
// Group URIs by collection for batch fetching. A space record's collection
|
|
14
|
+
// sits in a different segment than a repo record's, so the position is read
|
|
15
|
+
// by a parser that knows both shapes rather than inline.
|
|
9
16
|
const byCollection = new Map();
|
|
10
17
|
for (const uri of uris) {
|
|
11
|
-
const
|
|
12
|
-
|
|
18
|
+
const col = collectionFromRecordUri(uri);
|
|
19
|
+
if (!col)
|
|
20
|
+
continue;
|
|
13
21
|
if (!byCollection.has(col))
|
|
14
22
|
byCollection.set(col, []);
|
|
15
23
|
byCollection.get(col).push(uri);
|
|
@@ -43,7 +51,7 @@ export async function resolveRecords(uris) {
|
|
|
43
51
|
export function buildBaseContext(viewer) {
|
|
44
52
|
return {
|
|
45
53
|
viewer,
|
|
46
|
-
db: { query:
|
|
54
|
+
db: { query: guardedQuerySQL, unfiltered: unfilteredQuerySQL },
|
|
47
55
|
getRecords: getRecordsMap,
|
|
48
56
|
lookup: async (collection, field, values) => {
|
|
49
57
|
if (values.length === 0)
|
|
@@ -59,5 +67,10 @@ export function buildBaseContext(viewer) {
|
|
|
59
67
|
},
|
|
60
68
|
labels: queryLabelsForUris,
|
|
61
69
|
blobUrl,
|
|
70
|
+
spaceFilter: spaceFilterSql,
|
|
71
|
+
spaceRecords,
|
|
72
|
+
records,
|
|
73
|
+
requireSpace,
|
|
74
|
+
spaceBlobUrl,
|
|
62
75
|
};
|
|
63
76
|
}
|