@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 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.11.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.2"
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
- "build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",
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
  }