@kubun/db-adapter 0.12.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 = [
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 = [
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubun/db-adapter",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "keywords": [],
5
5
  "license": "see LICENSE.md",
6
6
  "sideEffects": false,
@@ -15,7 +15,10 @@
15
15
  "LICENSE.md"
16
16
  ],
17
17
  "dependencies": {
18
- "kysely": "^0.29.4"
18
+ "kysely": "^0.29.5"
19
+ },
20
+ "publishConfig": {
21
+ "access": "public"
19
22
  },
20
23
  "scripts": {
21
24
  "build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",