@kubun/db-adapter 0.9.0 → 0.10.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.
- package/lib/postgres.d.ts +8 -1
- package/lib/postgres.js +32 -14
- package/lib/sqlite.d.ts +8 -1
- package/lib/sqlite.js +23 -5
- package/lib/types.d.ts +8 -1
- package/package.json +2 -2
package/lib/postgres.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Kysely } from 'kysely';
|
|
1
|
+
import type { Expression, Kysely, RawBuilder } from 'kysely';
|
|
2
2
|
import type { AbstractAdapter, AdapterTypes, ColumnTypes, Functions, SearchHit, SearchIndexConfig, SearchQuery } from './types.js';
|
|
3
3
|
export type PostgresTypes = {
|
|
4
4
|
Binary: Uint8Array;
|
|
@@ -13,6 +13,13 @@ export declare class AbstractPostgresAdapter<T extends AdapterTypes = PostgresTy
|
|
|
13
13
|
encodeTimestamp(value: Date): string;
|
|
14
14
|
decodeTimestamp(value: string): Date;
|
|
15
15
|
coerceFilterValue(value: unknown): unknown;
|
|
16
|
+
numericCast(expression: Expression<unknown>): Expression<number>;
|
|
17
|
+
readAccessPredicate(params: {
|
|
18
|
+
viewerDID: string;
|
|
19
|
+
groupIDs: Array<string>;
|
|
20
|
+
circleIDs: Array<string>;
|
|
21
|
+
serverDefault: string;
|
|
22
|
+
}, modelID: string): RawBuilder<boolean>;
|
|
16
23
|
createSearchIndex(db: Kysely<unknown>, config: SearchIndexConfig): Promise<void>;
|
|
17
24
|
dropSearchIndex(db: Kysely<unknown>, modelID: string): Promise<void>;
|
|
18
25
|
updateSearchEntry(db: Kysely<unknown>, modelID: string, documentID: string, fields: Record<string, string>, fieldNames: Array<string>): Promise<void>;
|
package/lib/postgres.js
CHANGED
|
@@ -1,19 +1,37 @@
|
|
|
1
|
-
import{sql as e}from"kysely";export class AbstractPostgresAdapter{functions={now:"now()"};types={binary:"bytea",json:"jsonb",serial:"serial",text:"text",timestamp:"timestamptz(3)",uuid:"uuid"};encodeBinary(e){return e}encodeJSON(e){return e}encodeTimestamp(e){return e.toISOString()}decodeTimestamp(e){return new Date(e)}coerceFilterValue(e){return"boolean"==typeof e?e?"true":"false":e}
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import{sql as e}from"kysely";export class AbstractPostgresAdapter{functions={now:"now()"};types={binary:"bytea",json:"jsonb",serial:"serial",text:"text",timestamp:"timestamptz(3)",uuid:"uuid"};encodeBinary(e){return e}encodeJSON(e){return e}encodeTimestamp(e){return e.toISOString()}decodeTimestamp(e){return new Date(e)}coerceFilterValue(e){return"boolean"==typeof e?e?"true":"false":e}numericCast(t){return e`(${t})::numeric`}readAccessPredicate(t,a){let{viewerDID:r,groupIDs:s,circleIDs:n,serverDefault:o}=t,_=[e`EXISTS (SELECT 1 FROM jsonb_array_elements_text(COALESCE(data -> 'accessPermissions' -> 'read' -> 'allowedDIDs', '[]'::jsonb)) AS _t WHERE _t = ${r})`];s.length>0&&_.push(e`EXISTS (SELECT 1 FROM jsonb_array_elements_text(COALESCE(data -> 'accessPermissions' -> 'read' -> 'allowedGroups', '[]'::jsonb)) AS _t WHERE _t IN (${e.join(s.map(t=>e`${t}`))}))`),n.length>0&&_.push(e`EXISTS (SELECT 1 FROM jsonb_array_elements_text(COALESCE(data -> 'accessPermissions' -> 'read' -> 'allowedCircles', '[]'::jsonb)) AS _t WHERE _t IN (${e.join(n.map(t=>e`${t}`))}))`);let d=_.reduce((t,a)=>e`${t} OR ${a}`),E=[e`EXISTS (SELECT 1 FROM jsonb_array_elements_text(COALESCE(_mad.allowed_dids, '[]'::jsonb)) AS _t WHERE _t = ${r})`];s.length>0&&E.push(e`EXISTS (SELECT 1 FROM jsonb_array_elements_text(COALESCE(_mad.allowed_groups, '[]'::jsonb)) AS _t WHERE _t IN (${e.join(s.map(t=>e`${t}`))}))`),n.length>0&&E.push(e`EXISTS (SELECT 1 FROM jsonb_array_elements_text(COALESCE(_mad.allowed_circles, '[]'::jsonb)) AS _t WHERE _t IN (${e.join(n.map(t=>e`${t}`))}))`);let c=E.reduce((t,a)=>e`${t} OR ${a}`),l="anyone"===o?e`true`:"only_owner"===o?e`owner = ${r}`:e`false`;return e`(
|
|
2
|
+
owner = ${r}
|
|
3
|
+
OR (data -> 'accessPermissions' -> 'read' ->> 'level') = 'anyone'
|
|
4
|
+
OR ((data -> 'accessPermissions' -> 'read' ->> 'level') = 'only_owner' AND owner = ${r})
|
|
5
|
+
OR ((data -> 'accessPermissions' -> 'read' ->> 'level') = 'restricted' AND (${d}))
|
|
6
|
+
OR ((data -> 'accessPermissions' -> 'read' ->> 'level') IS NULL AND EXISTS (
|
|
7
|
+
SELECT 1 FROM kubun_graph_user_model_access_defaults AS _mad
|
|
8
|
+
WHERE _mad.owner_did = owner AND _mad.model_id = ${a} AND _mad.permission_type = 'read'
|
|
9
|
+
AND (
|
|
10
|
+
_mad.access_level = 'anyone'
|
|
11
|
+
OR (_mad.access_level = 'only_owner' AND owner = ${r})
|
|
12
|
+
OR (_mad.access_level = 'restricted' AND (${c}))
|
|
13
|
+
)
|
|
14
|
+
))
|
|
15
|
+
OR ((data -> 'accessPermissions' -> 'read' ->> 'level') IS NULL AND NOT EXISTS (
|
|
16
|
+
SELECT 1 FROM kubun_graph_user_model_access_defaults AS _mad2
|
|
17
|
+
WHERE _mad2.owner_did = owner AND _mad2.model_id = ${a} AND _mad2.permission_type = 'read'
|
|
18
|
+
) AND (${l}))
|
|
19
|
+
)`}async createSearchIndex(t,a){let r=`fts_${a.modelID}`,s=`k_${a.modelID}`;await e`
|
|
20
|
+
CREATE TABLE IF NOT EXISTS ${e.ref(r)} (
|
|
21
|
+
document_id TEXT PRIMARY KEY REFERENCES ${e.ref(s)}(id) ON DELETE CASCADE,
|
|
4
22
|
search_vector tsvector
|
|
5
23
|
)
|
|
6
24
|
`.execute(t),await e`
|
|
7
|
-
CREATE INDEX IF NOT EXISTS ${e.ref(`ftsi_${
|
|
8
|
-
ON ${e.ref(
|
|
9
|
-
`.execute(t)}async dropSearchIndex(t,
|
|
10
|
-
INSERT INTO ${e.ref(
|
|
11
|
-
VALUES (${
|
|
12
|
-
ON CONFLICT (document_id) DO UPDATE SET search_vector = ${
|
|
13
|
-
`.execute(t)}async removeSearchEntry(t,r
|
|
14
|
-
SELECT document_id, ts_rank(search_vector, plainto_tsquery('simple', ${
|
|
15
|
-
FROM ${e.ref(
|
|
16
|
-
WHERE search_vector @@ plainto_tsquery('simple', ${
|
|
25
|
+
CREATE INDEX IF NOT EXISTS ${e.ref(`ftsi_${a.modelID}`)}
|
|
26
|
+
ON ${e.ref(r)} USING GIN(search_vector)
|
|
27
|
+
`.execute(t)}async dropSearchIndex(t,a){let r=`fts_${a}`;await e`DROP TABLE IF EXISTS ${e.ref(r)} CASCADE`.execute(t)}async updateSearchEntry(t,a,r,s,n){let o=`fts_${a}`,_=n.map(t=>{let a=s[t]??"";return e`to_tsvector('simple', ${a})`}).reduce((t,a)=>e`${t} || ${a}`);await e`
|
|
28
|
+
INSERT INTO ${e.ref(o)} (document_id, search_vector)
|
|
29
|
+
VALUES (${r}, ${_})
|
|
30
|
+
ON CONFLICT (document_id) DO UPDATE SET search_vector = ${_}
|
|
31
|
+
`.execute(t)}async removeSearchEntry(t,a,r){let s=`fts_${a}`;await e`DELETE FROM ${e.ref(s)} WHERE document_id = ${r}`.execute(t)}async searchIndex(t,a){let r=`fts_${a.modelID}`;return(await e`
|
|
32
|
+
SELECT document_id, ts_rank(search_vector, plainto_tsquery('simple', ${a.query})) AS rank
|
|
33
|
+
FROM ${e.ref(r)}
|
|
34
|
+
WHERE search_vector @@ plainto_tsquery('simple', ${a.query})
|
|
17
35
|
ORDER BY rank DESC
|
|
18
|
-
LIMIT ${
|
|
36
|
+
LIMIT ${a.limit}
|
|
19
37
|
`.execute(t)).rows.map(e=>({documentID:e.document_id,rank:e.rank}))}}
|
package/lib/sqlite.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Kysely } from 'kysely';
|
|
1
|
+
import type { Expression, Kysely, RawBuilder } from 'kysely';
|
|
2
2
|
import type { AbstractAdapter, AdapterTypes, ColumnTypes, Functions, SearchHit, SearchIndexConfig, SearchQuery } from './types.js';
|
|
3
3
|
export type SQLiteTypes = {
|
|
4
4
|
Binary: Uint8Array;
|
|
@@ -13,6 +13,13 @@ export declare class AbstractSQLiteAdapter<T extends AdapterTypes = SQLiteTypes>
|
|
|
13
13
|
encodeTimestamp(value: Date): number;
|
|
14
14
|
decodeTimestamp(value: number): Date;
|
|
15
15
|
coerceFilterValue(value: unknown): unknown;
|
|
16
|
+
numericCast(expression: Expression<unknown>): Expression<number>;
|
|
17
|
+
readAccessPredicate(params: {
|
|
18
|
+
viewerDID: string;
|
|
19
|
+
groupIDs: Array<string>;
|
|
20
|
+
circleIDs: Array<string>;
|
|
21
|
+
serverDefault: string;
|
|
22
|
+
}, modelID: string): RawBuilder<boolean>;
|
|
16
23
|
createSearchIndex(db: Kysely<unknown>, config: SearchIndexConfig): Promise<void>;
|
|
17
24
|
dropSearchIndex(db: Kysely<unknown>, modelID: string): Promise<void>;
|
|
18
25
|
updateSearchEntry(db: Kysely<unknown>, modelID: string, documentID: string, fields: Record<string, string>, fieldNames: Array<string>): Promise<void>;
|
package/lib/sqlite.js
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
|
-
import{sql as e}from"kysely";export class AbstractSQLiteAdapter{functions={now:e`(unixepoch())`};types={binary:"blob",json:"jsonb",serial:"integer",text:"text",timestamp:"integer",uuid:"text"};encodeBinary(e){return e}encodeJSON(e){return JSON.stringify(e)}encodeTimestamp(e){return Math.floor(e.getTime()/1e3)}decodeTimestamp(e){return new Date(1e3*e)}coerceFilterValue(e){return"boolean"==typeof e?+!!e:e}
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import{sql as e}from"kysely";export class AbstractSQLiteAdapter{functions={now:e`(unixepoch())`};types={binary:"blob",json:"jsonb",serial:"integer",text:"text",timestamp:"integer",uuid:"text"};encodeBinary(e){return e}encodeJSON(e){return JSON.stringify(e)}encodeTimestamp(e){return Math.floor(e.getTime()/1e3)}decodeTimestamp(e){return new Date(1e3*e)}coerceFilterValue(e){return"boolean"==typeof e?+!!e:e}numericCast(a){return e`CAST(${a} AS REAL)`}readAccessPredicate(a,s){let{viewerDID:t,groupIDs:r,circleIDs:n,serverDefault:o}=a,d=[e`EXISTS (SELECT 1 FROM json_each(COALESCE(json_extract(data, '$.accessPermissions.read.allowedDIDs'), '[]')) WHERE value = ${t})`];r.length>0&&d.push(e`EXISTS (SELECT 1 FROM json_each(COALESCE(json_extract(data, '$.accessPermissions.read.allowedGroups'), '[]')) WHERE value IN (${e.join(r.map(a=>e`${a}`))}))`),n.length>0&&d.push(e`EXISTS (SELECT 1 FROM json_each(COALESCE(json_extract(data, '$.accessPermissions.read.allowedCircles'), '[]')) WHERE value IN (${e.join(n.map(a=>e`${a}`))}))`);let c=d.reduce((a,s)=>e`${a} OR ${s}`),E=[e`EXISTS (SELECT 1 FROM json_each(COALESCE(_mad.allowed_dids, '[]')) WHERE value = ${t})`];r.length>0&&E.push(e`EXISTS (SELECT 1 FROM json_each(COALESCE(_mad.allowed_groups, '[]')) WHERE value IN (${e.join(r.map(a=>e`${a}`))}))`),n.length>0&&E.push(e`EXISTS (SELECT 1 FROM json_each(COALESCE(_mad.allowed_circles, '[]')) WHERE value IN (${e.join(n.map(a=>e`${a}`))}))`);let i=E.reduce((a,s)=>e`${a} OR ${s}`),l="anyone"===o?e`1 = 1`:"only_owner"===o?e`owner = ${t}`:e`1 = 0`;return e`(
|
|
2
|
+
owner = ${t}
|
|
3
|
+
OR json_extract(data, '$.accessPermissions.read.level') = 'anyone'
|
|
4
|
+
OR (json_extract(data, '$.accessPermissions.read.level') = 'only_owner' AND owner = ${t})
|
|
5
|
+
OR (json_extract(data, '$.accessPermissions.read.level') = 'restricted' AND (${c}))
|
|
6
|
+
OR (json_extract(data, '$.accessPermissions.read.level') IS NULL AND EXISTS (
|
|
7
|
+
SELECT 1 FROM kubun_graph_user_model_access_defaults AS _mad
|
|
8
|
+
WHERE _mad.owner_did = owner AND _mad.model_id = ${s} AND _mad.permission_type = 'read'
|
|
9
|
+
AND (
|
|
10
|
+
_mad.access_level = 'anyone'
|
|
11
|
+
OR (_mad.access_level = 'only_owner' AND owner = ${t})
|
|
12
|
+
OR (_mad.access_level = 'restricted' AND (${i}))
|
|
13
|
+
)
|
|
14
|
+
))
|
|
15
|
+
OR (json_extract(data, '$.accessPermissions.read.level') IS NULL AND NOT EXISTS (
|
|
16
|
+
SELECT 1 FROM kubun_graph_user_model_access_defaults AS _mad2
|
|
17
|
+
WHERE _mad2.owner_did = owner AND _mad2.model_id = ${s} AND _mad2.permission_type = 'read'
|
|
18
|
+
) AND (${l}))
|
|
19
|
+
)`}async createSearchIndex(a,s){let t=`fts_${s.modelID}`,r=["document_id UNINDEXED",...s.fields].join(", ");await e`CREATE VIRTUAL TABLE IF NOT EXISTS ${e.ref(t)} USING fts5(${e.raw(r)})`.execute(a)}async dropSearchIndex(a,s){let t=`fts_${s}`;await e`DROP TABLE IF EXISTS ${e.ref(t)}`.execute(a)}async updateSearchEntry(a,s,t,r,n){let o=`fts_${s}`;await e`DELETE FROM ${e.ref(o)} WHERE document_id = ${t}`.execute(a);let d=["document_id",...n].join(", "),c=[t,...n.map(e=>r[e]??"")].map(a=>e.lit(a));await e`INSERT INTO ${e.ref(o)} (${e.raw(d)}) VALUES (${e.join(c)})`.execute(a)}async removeSearchEntry(a,s,t){let r=`fts_${s}`;await e`DELETE FROM ${e.ref(r)} WHERE document_id = ${t}`.execute(a)}async searchIndex(a,s){let t=`fts_${s.modelID}`;return(await e`
|
|
20
|
+
SELECT document_id, rank FROM ${e.ref(t)}
|
|
21
|
+
WHERE ${e.ref(t)} MATCH ${s.query}
|
|
4
22
|
ORDER BY rank
|
|
5
|
-
LIMIT ${
|
|
6
|
-
`.execute(
|
|
23
|
+
LIMIT ${s.limit}
|
|
24
|
+
`.execute(a)).rows.map(e=>({documentID:e.document_id,rank:e.rank}))}}
|
package/lib/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ColumnDataType, ColumnType, Dialect, Kysely, RawBuilder } from 'kysely';
|
|
1
|
+
import type { ColumnDataType, ColumnType, Dialect, Expression, Kysely, RawBuilder } from 'kysely';
|
|
2
2
|
export type CreatedAtColumn = ColumnType<number, number | undefined, never>;
|
|
3
3
|
export type UpdatedAtColumn = ColumnType<number, number | undefined, number | undefined>;
|
|
4
4
|
export type ColumnTypes = {
|
|
@@ -43,6 +43,13 @@ export type AbstractAdapter<T extends AdapterTypes = AdapterTypes> = {
|
|
|
43
43
|
removeSearchEntry(db: Kysely<unknown>, modelID: string, documentID: string): Promise<void>;
|
|
44
44
|
searchIndex(db: Kysely<unknown>, params: SearchQuery): Promise<Array<SearchHit>>;
|
|
45
45
|
coerceFilterValue(value: unknown): unknown;
|
|
46
|
+
numericCast(expression: Expression<unknown>): Expression<number>;
|
|
47
|
+
readAccessPredicate(params: {
|
|
48
|
+
viewerDID: string;
|
|
49
|
+
groupIDs: Array<string>;
|
|
50
|
+
circleIDs: Array<string>;
|
|
51
|
+
serverDefault: string;
|
|
52
|
+
}, modelID: string): RawBuilder<boolean>;
|
|
46
53
|
};
|
|
47
54
|
export type Adapter<T extends AdapterTypes = AdapterTypes> = AbstractAdapter<T> & {
|
|
48
55
|
dialect: Dialect;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubun/db-adapter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"license": "see LICENSE.md",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"type": "module",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
],
|
|
16
16
|
"sideEffects": false,
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"kysely": "^0.
|
|
18
|
+
"kysely": "^0.29.2"
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
21
|
"build:clean": "del lib",
|