@kubun/db-adapter 0.11.0 → 0.13.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 +5 -1
- package/lib/postgres.js +36 -0
- package/lib/sqlite.d.ts +5 -1
- package/lib/sqlite.js +32 -0
- package/lib/types.d.ts +5 -0
- package/package.json +12 -9
package/lib/postgres.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Expression, Kysely, RawBuilder } from 'kysely';
|
|
2
|
-
import type { AbstractAdapter, AdapterTypes, ColumnTypes, Functions, NullOrderingTerm, OrderDirection, SearchHit, SearchIndexConfig, SearchQuery } from './types.js';
|
|
2
|
+
import type { AbstractAdapter, AdapterTypes, ArrayPresenceMode, ColumnTypes, Functions, NullOrderingTerm, OrderDirection, SearchHit, SearchIndexConfig, SearchQuery } from './types.js';
|
|
3
3
|
export type PostgresTypes = {
|
|
4
4
|
Binary: Uint8Array;
|
|
5
5
|
JSON: unknown;
|
|
@@ -15,6 +15,10 @@ export declare class AbstractPostgresAdapter<T extends AdapterTypes = PostgresTy
|
|
|
15
15
|
coerceFilterValue(value: unknown): unknown;
|
|
16
16
|
numericCast(expression: Expression<unknown>): Expression<number>;
|
|
17
17
|
nullOrdering(_expression: Expression<unknown>, _direction: OrderDirection): NullOrderingTerm;
|
|
18
|
+
containsPredicate(expression: Expression<unknown>, pattern: string): RawBuilder<boolean>;
|
|
19
|
+
arrayIncludesAllPredicate(expression: Expression<unknown>, values: Array<unknown>): RawBuilder<boolean>;
|
|
20
|
+
arrayIncludesAnyPredicate(expression: Expression<unknown>, values: Array<unknown>): RawBuilder<boolean>;
|
|
21
|
+
arrayPresencePredicate(expression: Expression<unknown>, mode: ArrayPresenceMode): RawBuilder<boolean>;
|
|
18
22
|
readAccessPredicate(params: {
|
|
19
23
|
viewerDID: string;
|
|
20
24
|
groupIDs: Array<string>;
|
package/lib/postgres.js
CHANGED
|
@@ -41,6 +41,40 @@ export class AbstractPostgresAdapter {
|
|
|
41
41
|
kind: 'native'
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
+
containsPredicate(expression, pattern) {
|
|
45
|
+
return sql`${expression} ILIKE ${pattern} ESCAPE '\\'`;
|
|
46
|
+
}
|
|
47
|
+
arrayIncludesAllPredicate(expression, values) {
|
|
48
|
+
// Native jsonb containment: works for string and numeric elements alike. NULL node
|
|
49
|
+
// coalesces to an empty array, which is a subset only of another empty set.
|
|
50
|
+
// Cast via ::text::jsonb, not a direct ::jsonb: postgres.js infers the parameter's
|
|
51
|
+
// wire type from a trailing ::jsonb cast and re-encodes the JS string as a jsonb
|
|
52
|
+
// *string scalar* instead of sending it as text for the server to parse, so a
|
|
53
|
+
// direct ::jsonb cast silently never matches. The ::text hop forces text encoding.
|
|
54
|
+
return sql`COALESCE(${expression}, '[]'::jsonb) @> ${JSON.stringify(values)}::text::jsonb`;
|
|
55
|
+
}
|
|
56
|
+
arrayIncludesAnyPredicate(expression, values) {
|
|
57
|
+
// Elements, not `?|`, so numeric arrays are covered too (`?|` only matches string keys).
|
|
58
|
+
return sql`EXISTS (SELECT 1 FROM jsonb_array_elements_text(COALESCE(${expression}, '[]'::jsonb)) AS _t WHERE _t IN (${sql.join(values.map((v)=>sql`${v}`))}))`;
|
|
59
|
+
}
|
|
60
|
+
arrayPresencePredicate(expression, mode) {
|
|
61
|
+
// null/nonNull/empty/nonEmpty must see the true NULL, so the node is never coalesced
|
|
62
|
+
// there — jsonb_array_length(NULL) is NULL, and NULL > 0 is not-true (excludes it).
|
|
63
|
+
switch(mode){
|
|
64
|
+
case 'null':
|
|
65
|
+
return sql`${expression} IS NULL`;
|
|
66
|
+
case 'nonNull':
|
|
67
|
+
return sql`${expression} IS NOT NULL`;
|
|
68
|
+
case 'empty':
|
|
69
|
+
return sql`jsonb_array_length(${expression}) = 0`;
|
|
70
|
+
case 'nonEmpty':
|
|
71
|
+
return sql`jsonb_array_length(${expression}) > 0`;
|
|
72
|
+
case 'nullOrEmpty':
|
|
73
|
+
return sql`COALESCE(jsonb_array_length(${expression}), 0) = 0`;
|
|
74
|
+
default:
|
|
75
|
+
throw new Error(`Invalid array presence mode: ${mode}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
44
78
|
readAccessPredicate(params, modelID) {
|
|
45
79
|
const { viewerDID, groupIDs, circleIDs, serverDefault } = params;
|
|
46
80
|
const docRestrictedParts = [
|
|
@@ -72,6 +106,7 @@ export class AbstractPostgresAdapter {
|
|
|
72
106
|
OR ((data -> 'accessPermissions' -> 'read' ->> 'level') IS NULL AND EXISTS (
|
|
73
107
|
SELECT 1 FROM kubun_graph_user_model_access_defaults AS _mad
|
|
74
108
|
WHERE _mad.owner_did = owner AND _mad.model_id = ${modelID} AND _mad.permission_type = 'read'
|
|
109
|
+
AND (_mad.removed IS NULL OR _mad.removed = 0)
|
|
75
110
|
AND (
|
|
76
111
|
_mad.access_level = 'anyone'
|
|
77
112
|
OR (_mad.access_level = 'only_owner' AND owner = ${viewerDID})
|
|
@@ -81,6 +116,7 @@ export class AbstractPostgresAdapter {
|
|
|
81
116
|
OR ((data -> 'accessPermissions' -> 'read' ->> 'level') IS NULL AND NOT EXISTS (
|
|
82
117
|
SELECT 1 FROM kubun_graph_user_model_access_defaults AS _mad2
|
|
83
118
|
WHERE _mad2.owner_did = owner AND _mad2.model_id = ${modelID} AND _mad2.permission_type = 'read'
|
|
119
|
+
AND (_mad2.removed IS NULL OR _mad2.removed = 0)
|
|
84
120
|
) AND (${serverDefaultCond}))
|
|
85
121
|
)`;
|
|
86
122
|
}
|
package/lib/sqlite.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Expression, Kysely, RawBuilder } from 'kysely';
|
|
2
|
-
import type { AbstractAdapter, AdapterTypes, ColumnTypes, Functions, NullOrderingTerm, OrderDirection, SearchHit, SearchIndexConfig, SearchQuery } from './types.js';
|
|
2
|
+
import type { AbstractAdapter, AdapterTypes, ArrayPresenceMode, ColumnTypes, Functions, NullOrderingTerm, OrderDirection, SearchHit, SearchIndexConfig, SearchQuery } from './types.js';
|
|
3
3
|
export type SQLiteTypes = {
|
|
4
4
|
Binary: Uint8Array;
|
|
5
5
|
JSON: string;
|
|
@@ -15,6 +15,10 @@ export declare class AbstractSQLiteAdapter<T extends AdapterTypes = SQLiteTypes>
|
|
|
15
15
|
coerceFilterValue(value: unknown): unknown;
|
|
16
16
|
numericCast(expression: Expression<unknown>): Expression<number>;
|
|
17
17
|
nullOrdering(expression: Expression<unknown>, direction: OrderDirection): NullOrderingTerm;
|
|
18
|
+
containsPredicate(expression: Expression<unknown>, pattern: string): RawBuilder<boolean>;
|
|
19
|
+
arrayIncludesAllPredicate(expression: Expression<unknown>, values: Array<unknown>): RawBuilder<boolean>;
|
|
20
|
+
arrayIncludesAnyPredicate(expression: Expression<unknown>, values: Array<unknown>): RawBuilder<boolean>;
|
|
21
|
+
arrayPresencePredicate(expression: Expression<unknown>, mode: ArrayPresenceMode): RawBuilder<boolean>;
|
|
18
22
|
readAccessPredicate(params: {
|
|
19
23
|
viewerDID: string;
|
|
20
24
|
groupIDs: Array<string>;
|
package/lib/sqlite.js
CHANGED
|
@@ -45,6 +45,36 @@ export class AbstractSQLiteAdapter {
|
|
|
45
45
|
expression: sql`CASE WHEN ${expression} IS NULL THEN ${sql.lit(nullRank)} ELSE ${sql.lit(valueRank)} END`
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
|
+
containsPredicate(expression, pattern) {
|
|
49
|
+
// SQLite's LIKE is already ASCII case-insensitive by default.
|
|
50
|
+
return sql`${expression} LIKE ${pattern} ESCAPE '\\'`;
|
|
51
|
+
}
|
|
52
|
+
arrayIncludesAllPredicate(expression, values) {
|
|
53
|
+
// NULL/absent node -> json_each yields zero rows -> COUNT(DISTINCT value) = 0, never
|
|
54
|
+
// equal to a positive set size.
|
|
55
|
+
return sql`(SELECT COUNT(DISTINCT value) FROM json_each(COALESCE(${expression}, '[]')) WHERE value IN (${sql.join(values.map((v)=>sql`${v}`))})) = ${values.length}`;
|
|
56
|
+
}
|
|
57
|
+
arrayIncludesAnyPredicate(expression, values) {
|
|
58
|
+
return sql`EXISTS (SELECT 1 FROM json_each(COALESCE(${expression}, '[]')) WHERE value IN (${sql.join(values.map((v)=>sql`${v}`))}))`;
|
|
59
|
+
}
|
|
60
|
+
arrayPresencePredicate(expression, mode) {
|
|
61
|
+
// null/nonNull/empty/nonEmpty must see the true NULL, so the node is never coalesced
|
|
62
|
+
// there — json_array_length(NULL) is NULL, and NULL > 0 is not-true (excludes it).
|
|
63
|
+
switch(mode){
|
|
64
|
+
case 'null':
|
|
65
|
+
return sql`${expression} IS NULL`;
|
|
66
|
+
case 'nonNull':
|
|
67
|
+
return sql`${expression} IS NOT NULL`;
|
|
68
|
+
case 'empty':
|
|
69
|
+
return sql`json_array_length(${expression}) = 0`;
|
|
70
|
+
case 'nonEmpty':
|
|
71
|
+
return sql`json_array_length(${expression}) > 0`;
|
|
72
|
+
case 'nullOrEmpty':
|
|
73
|
+
return sql`COALESCE(json_array_length(${expression}), 0) = 0`;
|
|
74
|
+
default:
|
|
75
|
+
throw new Error(`Invalid array presence mode: ${mode}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
48
78
|
readAccessPredicate(params, modelID) {
|
|
49
79
|
const { viewerDID, groupIDs, circleIDs, serverDefault } = params;
|
|
50
80
|
const docRestrictedParts = [
|
|
@@ -76,6 +106,7 @@ export class AbstractSQLiteAdapter {
|
|
|
76
106
|
OR (json_extract(data, '$.accessPermissions.read.level') IS NULL AND EXISTS (
|
|
77
107
|
SELECT 1 FROM kubun_graph_user_model_access_defaults AS _mad
|
|
78
108
|
WHERE _mad.owner_did = owner AND _mad.model_id = ${modelID} AND _mad.permission_type = 'read'
|
|
109
|
+
AND (_mad.removed IS NULL OR _mad.removed = 0)
|
|
79
110
|
AND (
|
|
80
111
|
_mad.access_level = 'anyone'
|
|
81
112
|
OR (_mad.access_level = 'only_owner' AND owner = ${viewerDID})
|
|
@@ -85,6 +116,7 @@ export class AbstractSQLiteAdapter {
|
|
|
85
116
|
OR (json_extract(data, '$.accessPermissions.read.level') IS NULL AND NOT EXISTS (
|
|
86
117
|
SELECT 1 FROM kubun_graph_user_model_access_defaults AS _mad2
|
|
87
118
|
WHERE _mad2.owner_did = owner AND _mad2.model_id = ${modelID} AND _mad2.permission_type = 'read'
|
|
119
|
+
AND (_mad2.removed IS NULL OR _mad2.removed = 0)
|
|
88
120
|
) AND (${serverDefaultCond}))
|
|
89
121
|
)`;
|
|
90
122
|
}
|
package/lib/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ColumnDataType, ColumnType, Dialect, Expression, Kysely, RawBuilder } from 'kysely';
|
|
2
2
|
export type OrderDirection = 'asc' | 'desc';
|
|
3
|
+
export type ArrayPresenceMode = 'null' | 'nonNull' | 'empty' | 'nonEmpty' | 'nullOrEmpty';
|
|
3
4
|
export type NullOrderingTerm = {
|
|
4
5
|
kind: 'lead';
|
|
5
6
|
expression: Expression<number>;
|
|
@@ -53,6 +54,10 @@ export type AbstractAdapter<T extends AdapterTypes = AdapterTypes> = {
|
|
|
53
54
|
coerceFilterValue(value: unknown): unknown;
|
|
54
55
|
numericCast(expression: Expression<unknown>): Expression<number>;
|
|
55
56
|
nullOrdering(expression: Expression<unknown>, direction: OrderDirection): NullOrderingTerm;
|
|
57
|
+
containsPredicate(expression: Expression<unknown>, pattern: string): RawBuilder<boolean>;
|
|
58
|
+
arrayIncludesAllPredicate(expression: Expression<unknown>, values: Array<unknown>): RawBuilder<boolean>;
|
|
59
|
+
arrayIncludesAnyPredicate(expression: Expression<unknown>, values: Array<unknown>): RawBuilder<boolean>;
|
|
60
|
+
arrayPresencePredicate(expression: Expression<unknown>, mode: ArrayPresenceMode): RawBuilder<boolean>;
|
|
56
61
|
readAccessPredicate(params: {
|
|
57
62
|
viewerDID: string;
|
|
58
63
|
groupIDs: Array<string>;
|
package/package.json
CHANGED
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubun/db-adapter",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"license": "see LICENSE.md",
|
|
3
|
+
"version": "0.13.0",
|
|
5
4
|
"keywords": [],
|
|
5
|
+
"license": "see LICENSE.md",
|
|
6
|
+
"sideEffects": false,
|
|
6
7
|
"type": "module",
|
|
7
|
-
"main": "lib/index.js",
|
|
8
|
-
"types": "lib/index.d.ts",
|
|
9
8
|
"exports": {
|
|
10
9
|
".": "./lib/index.js"
|
|
11
10
|
},
|
|
11
|
+
"main": "lib/index.js",
|
|
12
|
+
"types": "lib/index.d.ts",
|
|
12
13
|
"files": [
|
|
13
14
|
"lib/*",
|
|
14
15
|
"LICENSE.md"
|
|
15
16
|
],
|
|
16
|
-
"sideEffects": false,
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"kysely": "^0.29.
|
|
18
|
+
"kysely": "^0.29.5"
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
19
22
|
},
|
|
20
23
|
"scripts": {
|
|
24
|
+
"build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",
|
|
21
25
|
"build:clean": "del lib",
|
|
22
26
|
"build:js": "swc src -d ./lib --config-file ../../node_modules/@kigu/dev/swc.json --strip-leading-paths",
|
|
23
27
|
"build:types": "tsc --emitDeclarationOnly --skipLibCheck",
|
|
24
28
|
"build:types:ci": "tsc --emitDeclarationOnly --declarationMap false",
|
|
25
|
-
"
|
|
29
|
+
"test": "pnpm run test:types && pnpm run test:unit",
|
|
26
30
|
"test:types": "tsc --noEmit",
|
|
27
|
-
"test:unit": "vitest run"
|
|
28
|
-
"test": "pnpm run test:types && pnpm run test:unit"
|
|
31
|
+
"test:unit": "vitest run"
|
|
29
32
|
}
|
|
30
33
|
}
|