@hozon/adapter 0.0.0-stage → 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Paul Le Cam
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,17 @@
1
- # Temporary Holding Version
1
+ # @hozon/adapter
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Shared adapter contract and dialect helpers for Hozon's SQLite and PostgreSQL drivers.
4
+
5
+ ```sh
6
+ pnpm add @hozon/adapter
7
+ ```
8
+
9
+ Import `Adapter` when defining a driver contract:
10
+
11
+ ```ts
12
+ import type { Adapter } from '@hozon/adapter'
13
+
14
+ type DriverContract = Adapter
15
+ ```
16
+
17
+ See the [adapter reference](../../docs/reference/adapter.md).
package/lib/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { AbstractPostgresAdapter, type PostgresTypes } from './postgres.js';
2
+ export { AbstractSQLiteAdapter, type SQLiteTypes } from './sqlite.js';
3
+ export type { AbstractAdapter, Adapter, AdapterTypes, ArrayPresenceMode, ColumnTypes, CreatedAtColumn, Functions, NullOrderingTerm, OrderDirection, SearchCapability, SearchEntry, SearchHit, SearchIndex, SearchQuery, UpdatedAtColumn, } from './types.js';
package/lib/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { AbstractPostgresAdapter } from './postgres.js';
2
+ export { AbstractSQLiteAdapter } from './sqlite.js';
@@ -0,0 +1,24 @@
1
+ import type { Expression, RawBuilder } from 'kysely';
2
+ import type { AbstractAdapter, AdapterTypes, ArrayPresenceMode, ColumnTypes, Functions, NullOrderingTerm, OrderDirection, SearchCapability } from './types.js';
3
+ export type PostgresTypes = {
4
+ Binary: Uint8Array;
5
+ JSON: unknown;
6
+ Timestamp: string;
7
+ };
8
+ export declare abstract class AbstractPostgresAdapter<T extends AdapterTypes = PostgresTypes> implements AbstractAdapter<T> {
9
+ search: SearchCapability;
10
+ kind: "postgres";
11
+ functions: Functions;
12
+ types: ColumnTypes;
13
+ encodeBinary(value: Uint8Array): Uint8Array<ArrayBufferLike>;
14
+ encodeJSON(value: unknown): unknown;
15
+ encodeTimestamp(value: Date): string;
16
+ decodeTimestamp(value: string): Date;
17
+ coerceFilterValue(value: unknown): unknown;
18
+ numericCast(expression: Expression<unknown>): Expression<number>;
19
+ nullOrdering(_expression: Expression<unknown>, _direction: OrderDirection): NullOrderingTerm;
20
+ containsPredicate(expression: Expression<unknown>, pattern: string): RawBuilder<boolean>;
21
+ arrayIncludesAllPredicate(expression: Expression<unknown>, values: Array<unknown>): RawBuilder<boolean>;
22
+ arrayIncludesAnyPredicate(expression: Expression<unknown>, values: Array<unknown>): RawBuilder<boolean>;
23
+ arrayPresencePredicate(expression: Expression<unknown>, mode: ArrayPresenceMode): RawBuilder<boolean>;
24
+ }
@@ -0,0 +1,131 @@
1
+ import { sql } from 'kysely';
2
+ import { assertSearchFields, assertSearchIndex } from './search.js';
3
+ export class AbstractPostgresAdapter {
4
+ search = {
5
+ async createIndex (db, params) {
6
+ assertSearchIndex(params.index);
7
+ assertSearchFields(params.fields, params.index);
8
+ await sql`CREATE TABLE IF NOT EXISTS ${sql.table(params.index)} (
9
+ entry_key TEXT PRIMARY KEY REFERENCES ${sql.table(params.table)} (${sql.id(params.key)}) ON DELETE CASCADE,
10
+ search_vector tsvector
11
+ )`.execute(db);
12
+ // Hash the compiled physical name to stay below PostgreSQL's 63-byte limit.
13
+ let hash = 14695981039346656037n;
14
+ for (const char of sql.table(params.index).compile(db).sql){
15
+ hash = BigInt.asUintN(64, (hash ^ BigInt(char.codePointAt(0) ?? 0)) * 1099511628211n);
16
+ }
17
+ const ginIndex = `search_${hash.toString(16)}`;
18
+ await sql`CREATE INDEX IF NOT EXISTS ${sql.id(ginIndex)} ON ${sql.table(params.index)} USING GIN(search_vector)`.execute(db);
19
+ },
20
+ async dropIndex (db, { index }) {
21
+ assertSearchIndex(index);
22
+ await sql`DROP TABLE IF EXISTS ${sql.table(index)} CASCADE`.execute(db);
23
+ },
24
+ async upsertEntry (db, { index, key, fields }) {
25
+ assertSearchIndex(index);
26
+ assertSearchFields(Object.keys(fields), index);
27
+ const vector = sql.join(Object.values(fields).map((value)=>sql`to_tsvector('simple', ${value})`), sql` || `);
28
+ await sql`INSERT INTO ${sql.table(index)} (entry_key, search_vector)
29
+ VALUES (${key}, ${vector})
30
+ ON CONFLICT (entry_key) DO UPDATE SET search_vector = excluded.search_vector
31
+ `.execute(db);
32
+ },
33
+ async removeEntry (db, { index, key }) {
34
+ assertSearchIndex(index);
35
+ await sql`DELETE FROM ${sql.table(index)} WHERE entry_key = ${key}`.execute(db);
36
+ },
37
+ async query (db, { index, query, limit }) {
38
+ assertSearchIndex(index);
39
+ const result = await sql`
40
+ SELECT entry_key AS key, ts_rank(search_vector, plainto_tsquery('simple', ${query})) AS rank
41
+ FROM ${sql.table(index)} WHERE search_vector @@ plainto_tsquery('simple', ${query})
42
+ ORDER BY rank DESC, entry_key LIMIT ${limit}
43
+ `.execute(db);
44
+ return result.rows;
45
+ }
46
+ };
47
+ kind = 'postgres';
48
+ functions = {
49
+ now: 'now()'
50
+ };
51
+ types = {
52
+ bigint: 'bigint',
53
+ boolean: 'boolean',
54
+ double: 'double precision',
55
+ binary: 'bytea',
56
+ json: 'jsonb',
57
+ serial: 'serial',
58
+ text: 'text',
59
+ // Use 3 digits for timestamps to have millisecond precision, same as JS dates
60
+ timestamp: 'timestamptz(3)',
61
+ uuid: 'uuid'
62
+ };
63
+ encodeBinary(value) {
64
+ return value;
65
+ }
66
+ encodeJSON(value) {
67
+ return value;
68
+ }
69
+ encodeTimestamp(value) {
70
+ return value.toISOString();
71
+ }
72
+ decodeTimestamp(value) {
73
+ return new Date(value);
74
+ }
75
+ coerceFilterValue(value) {
76
+ if (typeof value === 'boolean') {
77
+ return value ? 'true' : 'false';
78
+ }
79
+ return value;
80
+ }
81
+ numericCast(expression) {
82
+ return sql`(${expression})::double precision`;
83
+ }
84
+ nullOrdering(_expression, _direction) {
85
+ // Postgres applies SQL-standard null position natively (NULLS LAST on asc, NULLS
86
+ // FIRST on desc) via the dialect's own clause on the field's order-by item.
87
+ return {
88
+ kind: 'native'
89
+ };
90
+ }
91
+ containsPredicate(expression, pattern) {
92
+ return sql`${expression} ILIKE ${pattern} ESCAPE '\\'`;
93
+ }
94
+ arrayIncludesAllPredicate(expression, values) {
95
+ // Native jsonb containment: works for string and numeric elements alike. NULL node
96
+ // coalesces to an empty array, which is a subset only of another empty set.
97
+ // Cast via ::text::jsonb, not a direct ::jsonb: postgres.js infers the parameter's
98
+ // wire type from a trailing ::jsonb cast and re-encodes the JS string as a jsonb
99
+ // *string scalar* instead of sending it as text for the server to parse, so a
100
+ // direct ::jsonb cast silently never matches. The ::text hop forces text encoding.
101
+ // Duplicates are ignored and an empty list is vacuously true (every array, NULL
102
+ // included, contains the empty set) — identical on every backend.
103
+ const distinct = Array.from(new Set(values));
104
+ if (distinct.length === 0) return sql`1 = 1`;
105
+ return sql`COALESCE(${expression}, '[]'::jsonb) @> ${JSON.stringify(distinct)}::text::jsonb`;
106
+ }
107
+ arrayIncludesAnyPredicate(expression, values) {
108
+ // An empty list matches nothing; never emit `IN ()`, which Postgres rejects.
109
+ if (values.length === 0) return sql`1 = 0`;
110
+ // Elements, not `?|`, so numeric arrays are covered too (`?|` only matches string keys).
111
+ 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}`))}))`;
112
+ }
113
+ arrayPresencePredicate(expression, mode) {
114
+ // null/nonNull/empty/nonEmpty must see the true NULL, so the node is never coalesced
115
+ // there — jsonb_array_length(NULL) is NULL, and NULL > 0 is not-true (excludes it).
116
+ switch(mode){
117
+ case 'null':
118
+ return sql`${expression} IS NULL`;
119
+ case 'nonNull':
120
+ return sql`${expression} IS NOT NULL`;
121
+ case 'empty':
122
+ return sql`jsonb_array_length(${expression}) = 0`;
123
+ case 'nonEmpty':
124
+ return sql`jsonb_array_length(${expression}) > 0`;
125
+ case 'nullOrEmpty':
126
+ return sql`COALESCE(jsonb_array_length(${expression}), 0) = 0`;
127
+ default:
128
+ throw new Error(`Invalid array presence mode: ${mode}`);
129
+ }
130
+ }
131
+ }
@@ -0,0 +1,4 @@
1
+ /** Logical names stay within PostgreSQL's identifier limit without truncation. */
2
+ export declare function assertSearchIndex(index: string): void;
3
+ /** FTS5 reserves internal columns and its table name, case-insensitively. */
4
+ export declare function assertSearchFields(fields: Array<string>, index: string): void;
package/lib/search.js ADDED
@@ -0,0 +1,18 @@
1
+ /** Logical names stay within PostgreSQL's identifier limit without truncation. */ export function assertSearchIndex(index) {
2
+ if (!/^[A-Za-z_][A-Za-z0-9_]{0,62}$/.test(index)) {
3
+ throw new Error(`Invalid search index name: ${JSON.stringify(index)}`);
4
+ }
5
+ }
6
+ /** FTS5 reserves internal columns and its table name, case-insensitively. */ export function assertSearchFields(fields, index) {
7
+ if (fields.length === 0 || fields.length > 499) {
8
+ throw new RangeError('Search requires 1 to 499 fields');
9
+ }
10
+ const seen = new Set();
11
+ for (const field of fields){
12
+ const normalised = field.toLowerCase();
13
+ if (!/^[A-Za-z_][A-Za-z0-9_]{0,62}$/.test(field) || /^(entry_key|rank|rowid)$/i.test(field) || normalised === index.toLowerCase() || seen.has(normalised)) {
14
+ throw new Error(`Invalid search field name: ${JSON.stringify(field)}`);
15
+ }
16
+ seen.add(normalised);
17
+ }
18
+ }
@@ -0,0 +1,24 @@
1
+ import type { Expression, RawBuilder } from 'kysely';
2
+ import type { AbstractAdapter, AdapterTypes, ArrayPresenceMode, ColumnTypes, Functions, NullOrderingTerm, OrderDirection, SearchCapability } from './types.js';
3
+ export type SQLiteTypes = {
4
+ Binary: Uint8Array;
5
+ JSON: string;
6
+ Timestamp: number;
7
+ };
8
+ export declare abstract class AbstractSQLiteAdapter<T extends AdapterTypes = SQLiteTypes> implements AbstractAdapter<T> {
9
+ search: SearchCapability | undefined;
10
+ kind: "sqlite";
11
+ functions: Functions;
12
+ types: ColumnTypes;
13
+ encodeBinary(value: Uint8Array): Uint8Array<ArrayBufferLike>;
14
+ encodeJSON(value: unknown): string;
15
+ encodeTimestamp(value: Date): number;
16
+ decodeTimestamp(value: number): Date;
17
+ coerceFilterValue(value: unknown): unknown;
18
+ numericCast(expression: Expression<unknown>): Expression<number>;
19
+ nullOrdering(expression: Expression<unknown>, direction: OrderDirection): NullOrderingTerm;
20
+ containsPredicate(expression: Expression<unknown>, pattern: string): RawBuilder<boolean>;
21
+ arrayIncludesAllPredicate(expression: Expression<unknown>, values: Array<unknown>): RawBuilder<boolean>;
22
+ arrayIncludesAnyPredicate(expression: Expression<unknown>, values: Array<unknown>): RawBuilder<boolean>;
23
+ arrayPresencePredicate(expression: Expression<unknown>, mode: ArrayPresenceMode): RawBuilder<boolean>;
24
+ }
package/lib/sqlite.js ADDED
@@ -0,0 +1,129 @@
1
+ import { sql } from 'kysely';
2
+ import { assertSearchFields, assertSearchIndex } from './search.js';
3
+ export class AbstractSQLiteAdapter {
4
+ search = {
5
+ async createIndex (db, params) {
6
+ assertSearchIndex(params.index);
7
+ assertSearchFields(params.fields, params.index);
8
+ const columns = [
9
+ sql`entry_key UNINDEXED`,
10
+ ...params.fields.map((f)=>sql.id(f))
11
+ ];
12
+ await sql`CREATE VIRTUAL TABLE IF NOT EXISTS ${sql.table(params.index)} USING fts5(${sql.join(columns)})`.execute(db);
13
+ },
14
+ async dropIndex (db, { index }) {
15
+ assertSearchIndex(index);
16
+ await sql`DROP TABLE IF EXISTS ${sql.table(index)}`.execute(db);
17
+ },
18
+ async upsertEntry (db, { index, key, fields }) {
19
+ assertSearchIndex(index);
20
+ assertSearchFields(Object.keys(fields), index);
21
+ // Delete and insert share the caller's transaction for atomic replacement.
22
+ await sql`DELETE FROM ${sql.table(index)} WHERE entry_key = ${key}`.execute(db);
23
+ await sql`INSERT INTO ${sql.table(index)} (${sql.join([
24
+ sql.id('entry_key'),
25
+ ...Object.keys(fields).map((f)=>sql.id(f))
26
+ ])}) VALUES (${sql.join([
27
+ key,
28
+ ...Object.values(fields)
29
+ ])})`.execute(db);
30
+ },
31
+ async removeEntry (db, { index, key }) {
32
+ assertSearchIndex(index);
33
+ await sql`DELETE FROM ${sql.table(index)} WHERE entry_key = ${key}`.execute(db);
34
+ },
35
+ async query (db, { index, query, limit }) {
36
+ assertSearchIndex(index);
37
+ const result = await sql`
38
+ SELECT entry_key AS key, rank FROM ${sql.table(index)}
39
+ WHERE ${sql.table(index)} MATCH ${query}
40
+ ORDER BY rank, entry_key LIMIT ${limit}
41
+ `.execute(db);
42
+ return result.rows;
43
+ }
44
+ };
45
+ kind = 'sqlite';
46
+ functions = {
47
+ now: sql`(unixepoch())`
48
+ };
49
+ types = {
50
+ bigint: 'integer',
51
+ boolean: 'integer',
52
+ double: 'real',
53
+ binary: 'blob',
54
+ json: 'jsonb',
55
+ serial: 'integer',
56
+ text: 'text',
57
+ timestamp: 'integer',
58
+ uuid: 'text'
59
+ };
60
+ encodeBinary(value) {
61
+ return value;
62
+ }
63
+ encodeJSON(value) {
64
+ return JSON.stringify(value);
65
+ }
66
+ encodeTimestamp(value) {
67
+ // SQLite timestamps are stored in seconds
68
+ return Math.floor(value.getTime() / 1000);
69
+ }
70
+ decodeTimestamp(value) {
71
+ // SQLite timestamps are stored in seconds
72
+ return new Date(value * 1000);
73
+ }
74
+ coerceFilterValue(value) {
75
+ if (typeof value === 'boolean') {
76
+ return value ? 1 : 0;
77
+ }
78
+ return value;
79
+ }
80
+ numericCast(expression) {
81
+ return sql`CAST(${expression} AS REAL)`;
82
+ }
83
+ nullOrdering(expression, direction) {
84
+ // SQLite has no NULLS LAST/FIRST clause, so prepend an ascending rank column that
85
+ // buckets nulls last on ascending (rank 1 > 0) and first on descending (rank 0 < 1).
86
+ const nullRank = direction === 'asc' ? 1 : 0;
87
+ const valueRank = direction === 'asc' ? 0 : 1;
88
+ return {
89
+ kind: 'lead',
90
+ expression: sql`CASE WHEN ${expression} IS NULL THEN ${sql.lit(nullRank)} ELSE ${sql.lit(valueRank)} END`
91
+ };
92
+ }
93
+ containsPredicate(expression, pattern) {
94
+ // SQLite's LIKE is already ASCII case-insensitive by default.
95
+ return sql`${expression} LIKE ${pattern} ESCAPE '\\'`;
96
+ }
97
+ arrayIncludesAllPredicate(expression, values) {
98
+ // Duplicates are ignored (the count compares against the distinct set size) and an
99
+ // empty list is vacuously true — identical on every backend.
100
+ const distinct = Array.from(new Set(values));
101
+ if (distinct.length === 0) return sql`1 = 1`;
102
+ // NULL/absent node -> json_each yields zero rows -> COUNT(DISTINCT value) = 0, never
103
+ // equal to a positive set size.
104
+ return sql`(SELECT COUNT(DISTINCT value) FROM json_each(COALESCE(${expression}, '[]')) WHERE value IN (${sql.join(distinct.map((v)=>sql`${v}`))})) = ${distinct.length}`;
105
+ }
106
+ arrayIncludesAnyPredicate(expression, values) {
107
+ // An empty list matches nothing; never emit `IN ()` (portable with Postgres).
108
+ if (values.length === 0) return sql`1 = 0`;
109
+ return sql`EXISTS (SELECT 1 FROM json_each(COALESCE(${expression}, '[]')) WHERE value IN (${sql.join(values.map((v)=>sql`${v}`))}))`;
110
+ }
111
+ arrayPresencePredicate(expression, mode) {
112
+ // null/nonNull/empty/nonEmpty must see the true NULL, so the node is never coalesced
113
+ // there — json_array_length(NULL) is NULL, and NULL > 0 is not-true (excludes it).
114
+ switch(mode){
115
+ case 'null':
116
+ return sql`${expression} IS NULL`;
117
+ case 'nonNull':
118
+ return sql`${expression} IS NOT NULL`;
119
+ case 'empty':
120
+ return sql`json_array_length(${expression}) = 0`;
121
+ case 'nonEmpty':
122
+ return sql`json_array_length(${expression}) > 0`;
123
+ case 'nullOrEmpty':
124
+ return sql`COALESCE(json_array_length(${expression}), 0) = 0`;
125
+ default:
126
+ throw new Error(`Invalid array presence mode: ${mode}`);
127
+ }
128
+ }
129
+ }
package/lib/types.d.ts ADDED
@@ -0,0 +1,89 @@
1
+ import type { ColumnDataType, ColumnType, Dialect, Expression, Kysely, RawBuilder } from 'kysely';
2
+ export type OrderDirection = 'asc' | 'desc';
3
+ export type ArrayPresenceMode = 'null' | 'nonNull' | 'empty' | 'nonEmpty' | 'nullOrEmpty';
4
+ export type NullOrderingTerm = {
5
+ kind: 'lead';
6
+ expression: Expression<number>;
7
+ } | {
8
+ kind: 'native';
9
+ };
10
+ export type CreatedAtColumn = ColumnType<number, number | undefined, never>;
11
+ export type UpdatedAtColumn = ColumnType<number, number | undefined, number | undefined>;
12
+ export type ColumnTypes = {
13
+ bigint: ColumnDataType;
14
+ binary: ColumnDataType;
15
+ boolean: ColumnDataType;
16
+ double: ColumnDataType;
17
+ json: ColumnDataType;
18
+ serial: ColumnDataType;
19
+ text: ColumnDataType;
20
+ timestamp: ColumnDataType;
21
+ uuid: ColumnDataType;
22
+ };
23
+ export type Functions = {
24
+ now: string | RawBuilder<string>;
25
+ };
26
+ export type AdapterTypes = {
27
+ Binary: unknown;
28
+ JSON: unknown;
29
+ Timestamp: unknown;
30
+ };
31
+ /** Keys are text, matching the owning table's referenced column. */
32
+ export type SearchIndex = {
33
+ index: string;
34
+ table: string;
35
+ key: string;
36
+ fields: Array<string>;
37
+ };
38
+ export type SearchEntry = {
39
+ index: string;
40
+ key: string;
41
+ fields: Record<string, string>;
42
+ };
43
+ export type SearchQuery = {
44
+ index: string;
45
+ query: string;
46
+ limit: number;
47
+ };
48
+ export type SearchHit = {
49
+ key: string;
50
+ rank: number;
51
+ };
52
+ /**
53
+ * Logical table names follow the supplied database's prefix rules.
54
+ * Query syntax and rank scales are dialect-specific. No method opens a transaction.
55
+ */
56
+ export type SearchCapability = {
57
+ createIndex<Database>(db: Kysely<Database>, params: SearchIndex): Promise<void>;
58
+ dropIndex<Database>(db: Kysely<Database>, params: {
59
+ index: string;
60
+ }): Promise<void>;
61
+ upsertEntry<Database>(db: Kysely<Database>, params: SearchEntry): Promise<void>;
62
+ removeEntry<Database>(db: Kysely<Database>, params: {
63
+ index: string;
64
+ key: string;
65
+ }): Promise<void>;
66
+ query<Database>(db: Kysely<Database>, params: SearchQuery): Promise<Array<SearchHit>>;
67
+ };
68
+ export type AbstractAdapter<T extends AdapterTypes = AdapterTypes> = {
69
+ kind: 'sqlite' | 'postgres';
70
+ search?: SearchCapability;
71
+ functions: Functions;
72
+ types: ColumnTypes;
73
+ encodeBinary(value: Uint8Array): T['Binary'];
74
+ encodeJSON(value: unknown): T['JSON'];
75
+ encodeTimestamp(value: Date): T['Timestamp'];
76
+ decodeTimestamp(value: T['Timestamp']): Date;
77
+ coerceFilterValue(value: unknown): unknown;
78
+ numericCast(expression: Expression<unknown>): Expression<number>;
79
+ nullOrdering(expression: Expression<unknown>, direction: OrderDirection): NullOrderingTerm;
80
+ containsPredicate(expression: Expression<unknown>, pattern: string): RawBuilder<boolean>;
81
+ arrayIncludesAllPredicate(expression: Expression<unknown>, values: Array<unknown>): RawBuilder<boolean>;
82
+ arrayIncludesAnyPredicate(expression: Expression<unknown>, values: Array<unknown>): RawBuilder<boolean>;
83
+ arrayPresencePredicate(expression: Expression<unknown>, mode: ArrayPresenceMode): RawBuilder<boolean>;
84
+ prepare?(): Promise<void>;
85
+ close?(): Promise<void>;
86
+ };
87
+ export type Adapter<T extends AdapterTypes = AdapterTypes> = AbstractAdapter<T> & {
88
+ dialect: Dialect;
89
+ };
package/lib/types.js ADDED
@@ -0,0 +1 @@
1
+ export { };
package/package.json CHANGED
@@ -1,6 +1,42 @@
1
1
  {
2
2
  "name": "@hozon/adapter",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
4
+ "description": "Database adapter interfaces and dialect behavior for Hozon",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/TairuFramework/hozon.git",
8
+ "directory": "packages/adapter"
9
+ },
10
+ "license": "MIT",
11
+ "sideEffects": false,
12
+ "type": "module",
13
+ "exports": {
14
+ ".": "./lib/index.js"
15
+ },
16
+ "files": [
17
+ "lib/*"
18
+ ],
19
+ "dependencies": {
20
+ "kysely": "^0.29.7"
21
+ },
22
+ "devDependencies": {
23
+ "del-cli": "^8.0.1",
24
+ "vitest": "5.0.5"
25
+ },
26
+ "engines": {
27
+ "node": ">=24"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "scripts": {
33
+ "build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",
34
+ "build:clean": "del-cli lib",
35
+ "build:js": "del-cli \"lib/**/*.js\" && swc src -d ./lib --config-file ../../node_modules/@kigu/dev/swc.json --strip-leading-paths",
36
+ "build:types": "del-cli \"lib/**/*.d.ts\" \"lib/**/*.d.ts.map\" && tsc --emitDeclarationOnly --skipLibCheck",
37
+ "build:types:ci": "tsc --emitDeclarationOnly --skipLibCheck --declarationMap false",
38
+ "test": "pnpm run test:types && pnpm run test:unit",
39
+ "test:types": "tsc --noEmit --skipLibCheck -p tsconfig.test.json",
40
+ "test:unit": "vitest run"
41
+ }
6
42
  }