@kubun/db-adapter 0.16.0 → 0.18.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/chunk.d.ts +2 -0
- package/lib/chunk.js +15 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/postgres.d.ts +4 -0
- package/lib/postgres.js +27 -7
- package/lib/sqlite.d.ts +4 -0
- package/lib/sqlite.js +19 -0
- package/lib/types.d.ts +4 -0
- package/package.json +4 -4
package/lib/chunk.d.ts
ADDED
package/lib/chunk.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// The most values one `IN (...)` list binds; longer lists are read in chunks,
|
|
2
|
+
// well inside every adapter's bind-parameter limit.
|
|
3
|
+
export const IN_LIST_CHUNK = 500;
|
|
4
|
+
export function chunked(values, size = IN_LIST_CHUNK) {
|
|
5
|
+
// Deduplicated first, as a single `IN` would be: a value repeated across
|
|
6
|
+
// two chunks would otherwise return its rows twice.
|
|
7
|
+
const unique = [
|
|
8
|
+
...new Set(values)
|
|
9
|
+
];
|
|
10
|
+
const chunks = [];
|
|
11
|
+
for(let start = 0; start < unique.length; start += size){
|
|
12
|
+
chunks.push(unique.slice(start, start + size));
|
|
13
|
+
}
|
|
14
|
+
return chunks;
|
|
15
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { chunked, IN_LIST_CHUNK } from './chunk.js';
|
|
1
2
|
export { AbstractPostgresAdapter, type PostgresTypes } from './postgres.js';
|
|
2
3
|
export { AbstractSQLiteAdapter, type SQLiteTypes } from './sqlite.js';
|
|
3
4
|
export type { AbstractAdapter, Adapter, AdapterTypes, ColumnTypes, CreatedAtColumn, Functions, SearchHit, SearchIndexConfig, SearchQuery, UpdatedAtColumn, } from './types.js';
|
package/lib/index.js
CHANGED
package/lib/postgres.d.ts
CHANGED
|
@@ -19,6 +19,10 @@ export declare class AbstractPostgresAdapter<T extends AdapterTypes = PostgresTy
|
|
|
19
19
|
arrayIncludesAllPredicate(expression: Expression<unknown>, values: Array<unknown>): RawBuilder<boolean>;
|
|
20
20
|
arrayIncludesAnyPredicate(expression: Expression<unknown>, values: Array<unknown>): RawBuilder<boolean>;
|
|
21
21
|
arrayPresencePredicate(expression: Expression<unknown>, mode: ArrayPresenceMode): RawBuilder<boolean>;
|
|
22
|
+
groupReadPredicate(fieldValues: Expression<unknown>, params: {
|
|
23
|
+
groupID: string;
|
|
24
|
+
circleIDs: Array<string>;
|
|
25
|
+
}): RawBuilder<boolean>;
|
|
22
26
|
readAccessPredicate(params: {
|
|
23
27
|
viewerDID: string;
|
|
24
28
|
groupIDs: Array<string>;
|
package/lib/postgres.js
CHANGED
|
@@ -55,7 +55,7 @@ export class AbstractPostgresAdapter {
|
|
|
55
55
|
}
|
|
56
56
|
arrayIncludesAnyPredicate(expression, values) {
|
|
57
57
|
// Elements, not `?|`, so numeric arrays are covered too (`?|` only matches string keys).
|
|
58
|
-
return sql`EXISTS (SELECT 1 FROM jsonb_array_elements_text(
|
|
58
|
+
return sql`EXISTS (SELECT 1 FROM jsonb_array_elements_text(case when jsonb_typeof(${expression}) = 'array' then ${expression} else '[]'::jsonb end) AS _t WHERE _t IN (${sql.join(values.map((v)=>sql`${v}`))}))`;
|
|
59
59
|
}
|
|
60
60
|
arrayPresencePredicate(expression, mode) {
|
|
61
61
|
// null/nonNull/empty/nonEmpty must see the true NULL, so the node is never coalesced
|
|
@@ -75,26 +75,43 @@ export class AbstractPostgresAdapter {
|
|
|
75
75
|
throw new Error(`Invalid array presence mode: ${mode}`);
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
+
groupReadPredicate(fieldValues, params) {
|
|
79
|
+
const read = sql`${fieldValues} -> '$readRule'`;
|
|
80
|
+
const level = sql`${read} ->> 'level'`;
|
|
81
|
+
const grants = (field, ids)=>{
|
|
82
|
+
const node = sql`${read} -> ${sql.lit(field)}`;
|
|
83
|
+
const array = sql`case when jsonb_typeof(${node}) = 'array' then ${node} else '[]'::jsonb end`;
|
|
84
|
+
return sql`(not exists (select 1 from jsonb_array_elements(${array}) as entry(value) where jsonb_typeof(value) != 'string')
|
|
85
|
+
and exists (select 1 from jsonb_array_elements_text(${array}) as entry(value) where value in (select jsonb_array_elements_text(case when jsonb_typeof(${JSON.stringify(ids)}::text::jsonb) = 'array' then ${JSON.stringify(ids)}::text::jsonb else '[]'::jsonb end))))`;
|
|
86
|
+
};
|
|
87
|
+
const restricted = [
|
|
88
|
+
grants('allowedGroups', [
|
|
89
|
+
params.groupID
|
|
90
|
+
])
|
|
91
|
+
];
|
|
92
|
+
if (params.circleIDs.length > 0) restricted.push(grants('allowedCircles', params.circleIDs));
|
|
93
|
+
return sql`(${level} = 'anyone' or (${level} = 'restricted' and (${sql.join(restricted, sql` or `)})))`;
|
|
94
|
+
}
|
|
78
95
|
readAccessPredicate(params, modelID) {
|
|
79
96
|
const { viewerDID, groupIDs, circleIDs, serverDefault } = params;
|
|
80
97
|
const docRestrictedParts = [
|
|
81
|
-
sql`EXISTS (SELECT 1 FROM jsonb_array_elements_text(
|
|
98
|
+
sql`EXISTS (SELECT 1 FROM jsonb_array_elements_text(case when jsonb_typeof(data -> 'accessPermissions' -> 'read' -> 'allowedDIDs') = 'array' then data -> 'accessPermissions' -> 'read' -> 'allowedDIDs' else '[]'::jsonb end) AS _t WHERE _t = ${viewerDID})`
|
|
82
99
|
];
|
|
83
100
|
if (groupIDs.length > 0) {
|
|
84
|
-
docRestrictedParts.push(sql`EXISTS (SELECT 1 FROM jsonb_array_elements_text(
|
|
101
|
+
docRestrictedParts.push(sql`EXISTS (SELECT 1 FROM jsonb_array_elements_text(case when jsonb_typeof(data -> 'accessPermissions' -> 'read' -> 'allowedGroups') = 'array' then data -> 'accessPermissions' -> 'read' -> 'allowedGroups' else '[]'::jsonb end) AS _t WHERE _t IN (${sql.join(groupIDs.map((id)=>sql`${id}`))}))`);
|
|
85
102
|
}
|
|
86
103
|
if (circleIDs.length > 0) {
|
|
87
|
-
docRestrictedParts.push(sql`EXISTS (SELECT 1 FROM jsonb_array_elements_text(
|
|
104
|
+
docRestrictedParts.push(sql`EXISTS (SELECT 1 FROM jsonb_array_elements_text(case when jsonb_typeof(data -> 'accessPermissions' -> 'read' -> 'allowedCircles') = 'array' then data -> 'accessPermissions' -> 'read' -> 'allowedCircles' else '[]'::jsonb end) AS _t WHERE _t IN (${sql.join(circleIDs.map((id)=>sql`${id}`))}))`);
|
|
88
105
|
}
|
|
89
106
|
const docRestricted = docRestrictedParts.reduce((a, b)=>sql`${a} OR ${b}`);
|
|
90
107
|
const madRestrictedParts = [
|
|
91
|
-
sql`EXISTS (SELECT 1 FROM jsonb_array_elements_text(
|
|
108
|
+
sql`EXISTS (SELECT 1 FROM jsonb_array_elements_text(case when jsonb_typeof(_mad.allowed_dids) = 'array' then _mad.allowed_dids else '[]'::jsonb end) AS _t WHERE _t = ${viewerDID})`
|
|
92
109
|
];
|
|
93
110
|
if (groupIDs.length > 0) {
|
|
94
|
-
madRestrictedParts.push(sql`EXISTS (SELECT 1 FROM jsonb_array_elements_text(
|
|
111
|
+
madRestrictedParts.push(sql`EXISTS (SELECT 1 FROM jsonb_array_elements_text(case when jsonb_typeof(_mad.allowed_groups) = 'array' then _mad.allowed_groups else '[]'::jsonb end) AS _t WHERE _t IN (${sql.join(groupIDs.map((id)=>sql`${id}`))}))`);
|
|
95
112
|
}
|
|
96
113
|
if (circleIDs.length > 0) {
|
|
97
|
-
madRestrictedParts.push(sql`EXISTS (SELECT 1 FROM jsonb_array_elements_text(
|
|
114
|
+
madRestrictedParts.push(sql`EXISTS (SELECT 1 FROM jsonb_array_elements_text(case when jsonb_typeof(_mad.allowed_circles) = 'array' then _mad.allowed_circles else '[]'::jsonb end) AS _t WHERE _t IN (${sql.join(circleIDs.map((id)=>sql`${id}`))}))`);
|
|
98
115
|
}
|
|
99
116
|
const madRestricted = madRestrictedParts.reduce((a, b)=>sql`${a} OR ${b}`);
|
|
100
117
|
const serverDefaultCond = serverDefault === 'anyone' ? sql`true` : serverDefault === 'only_owner' ? sql`owner = ${viewerDID}` : sql`false`;
|
|
@@ -117,6 +134,9 @@ export class AbstractPostgresAdapter {
|
|
|
117
134
|
SELECT 1 FROM kubun_graph_user_model_access_defaults AS _mad2
|
|
118
135
|
WHERE _mad2.owner_did = owner AND _mad2.model_id = ${modelID} AND _mad2.permission_type = 'read'
|
|
119
136
|
AND (_mad2.removed IS NULL OR _mad2.removed = 0)
|
|
137
|
+
) AND NOT EXISTS (
|
|
138
|
+
SELECT 1 FROM kubun_graph_access_default_records AS _adr
|
|
139
|
+
WHERE _adr.type = 'access.default' AND _adr.owner = ${sql.ref(`k_${modelID}.owner`)}
|
|
120
140
|
) AND (${serverDefaultCond}))
|
|
121
141
|
)`;
|
|
122
142
|
}
|
package/lib/sqlite.d.ts
CHANGED
|
@@ -19,6 +19,10 @@ export declare class AbstractSQLiteAdapter<T extends AdapterTypes = SQLiteTypes>
|
|
|
19
19
|
arrayIncludesAllPredicate(expression: Expression<unknown>, values: Array<unknown>): RawBuilder<boolean>;
|
|
20
20
|
arrayIncludesAnyPredicate(expression: Expression<unknown>, values: Array<unknown>): RawBuilder<boolean>;
|
|
21
21
|
arrayPresencePredicate(expression: Expression<unknown>, mode: ArrayPresenceMode): RawBuilder<boolean>;
|
|
22
|
+
groupReadPredicate(fieldValues: Expression<unknown>, params: {
|
|
23
|
+
groupID: string;
|
|
24
|
+
circleIDs: Array<string>;
|
|
25
|
+
}): RawBuilder<boolean>;
|
|
22
26
|
readAccessPredicate(params: {
|
|
23
27
|
viewerDID: string;
|
|
24
28
|
groupIDs: Array<string>;
|
package/lib/sqlite.js
CHANGED
|
@@ -83,6 +83,22 @@ export class AbstractSQLiteAdapter {
|
|
|
83
83
|
throw new Error(`Invalid array presence mode: ${mode}`);
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
+
groupReadPredicate(fieldValues, params) {
|
|
87
|
+
const level = sql`json_extract(${fieldValues}, '$."$readRule".level')`;
|
|
88
|
+
const grants = (field, ids)=>{
|
|
89
|
+
const path = `$."$readRule".${field}`;
|
|
90
|
+
return sql`(json_type(${fieldValues}, ${path}) = 'array'
|
|
91
|
+
and not exists (select 1 from json_each(json_extract(${fieldValues}, ${path})) where type != 'text')
|
|
92
|
+
and exists (select 1 from json_each(json_extract(${fieldValues}, ${path})) where value in (select value from json_each(${JSON.stringify(ids)}))))`;
|
|
93
|
+
};
|
|
94
|
+
const restricted = [
|
|
95
|
+
grants('allowedGroups', [
|
|
96
|
+
params.groupID
|
|
97
|
+
])
|
|
98
|
+
];
|
|
99
|
+
if (params.circleIDs.length > 0) restricted.push(grants('allowedCircles', params.circleIDs));
|
|
100
|
+
return sql`(${level} = 'anyone' or (${level} = 'restricted' and (${sql.join(restricted, sql` or `)})))`;
|
|
101
|
+
}
|
|
86
102
|
readAccessPredicate(params, modelID) {
|
|
87
103
|
const { viewerDID, groupIDs, circleIDs, serverDefault } = params;
|
|
88
104
|
const docRestrictedParts = [
|
|
@@ -125,6 +141,9 @@ export class AbstractSQLiteAdapter {
|
|
|
125
141
|
SELECT 1 FROM kubun_graph_user_model_access_defaults AS _mad2
|
|
126
142
|
WHERE _mad2.owner_did = owner AND _mad2.model_id = ${modelID} AND _mad2.permission_type = 'read'
|
|
127
143
|
AND (_mad2.removed IS NULL OR _mad2.removed = 0)
|
|
144
|
+
) AND NOT EXISTS (
|
|
145
|
+
SELECT 1 FROM kubun_graph_access_default_records AS _adr
|
|
146
|
+
WHERE _adr.type = 'access.default' AND _adr.owner = ${sql.ref(`k_${modelID}.owner`)}
|
|
128
147
|
) AND (${serverDefaultCond}))
|
|
129
148
|
)`;
|
|
130
149
|
}
|
package/lib/types.d.ts
CHANGED
|
@@ -58,6 +58,10 @@ export type AbstractAdapter<T extends AdapterTypes = AdapterTypes> = {
|
|
|
58
58
|
arrayIncludesAllPredicate(expression: Expression<unknown>, values: Array<unknown>): RawBuilder<boolean>;
|
|
59
59
|
arrayIncludesAnyPredicate(expression: Expression<unknown>, values: Array<unknown>): RawBuilder<boolean>;
|
|
60
60
|
arrayPresencePredicate(expression: Expression<unknown>, mode: ArrayPresenceMode): RawBuilder<boolean>;
|
|
61
|
+
groupReadPredicate(fieldValues: Expression<unknown>, params: {
|
|
62
|
+
groupID: string;
|
|
63
|
+
circleIDs: Array<string>;
|
|
64
|
+
}): RawBuilder<boolean>;
|
|
61
65
|
readAccessPredicate(params: {
|
|
62
66
|
viewerDID: string;
|
|
63
67
|
groupIDs: Array<string>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubun/db-adapter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"keywords": [],
|
|
5
5
|
"license": "see LICENSE.md",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"LICENSE.md"
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"kysely": "^0.29.
|
|
18
|
+
"kysely": "^0.29.7"
|
|
19
19
|
},
|
|
20
20
|
"publishConfig": {
|
|
21
21
|
"access": "public"
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"scripts": {
|
|
24
24
|
"build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",
|
|
25
25
|
"build:clean": "del lib",
|
|
26
|
-
"build:js": "swc src -d ./lib --config-file ../../node_modules/@kigu/dev/swc.json --strip-leading-paths",
|
|
27
|
-
"build:types": "tsc --emitDeclarationOnly --skipLibCheck",
|
|
26
|
+
"build:js": "del 'lib/**/*.js' && swc src -d ./lib --config-file ../../node_modules/@kigu/dev/swc.json --strip-leading-paths",
|
|
27
|
+
"build:types": "del 'lib/**/*.d.ts' 'lib/**/*.d.ts.map' && tsc --emitDeclarationOnly --skipLibCheck",
|
|
28
28
|
"build:types:ci": "tsc --emitDeclarationOnly --declarationMap false",
|
|
29
29
|
"test": "pnpm run test:types && pnpm run test:unit",
|
|
30
30
|
"test:types": "tsc --noEmit",
|