@oakbun/ws 0.1.1 → 2.0.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/dist/index.d.ts +315 -27
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -64,6 +64,11 @@ type InferInsertFromSchema<S extends SchemaMap> = {
|
|
|
64
64
|
[K in keyof S as IsOptionalOnInsert<S[K]> extends true ? K : never]?: S[K] extends Column<infer T> ? T : never;
|
|
65
65
|
};
|
|
66
66
|
type InferInsert<T> = T extends TableDef<any, infer S, any> ? InferInsertFromSchema<S> : T extends SchemaMap ? InferInsertFromSchema<T> : never;
|
|
67
|
+
type InferUpdate<T> = T extends TableDef<infer R, infer S, any> ? Partial<R> & {
|
|
68
|
+
[K in keyof S as S[K] extends Column<any> ? S[K]['def']['primaryKey'] extends true ? K : never : never]: R[K & keyof R];
|
|
69
|
+
} : T extends SchemaMap ? Partial<{
|
|
70
|
+
[K in keyof T]: T[K] extends Column<infer C> ? C : never;
|
|
71
|
+
}> : never;
|
|
67
72
|
interface TableHookHandlers<T> {
|
|
68
73
|
beforeInsert?: (data: Partial<T>) => Partial<T> | void | Promise<Partial<T> | void>;
|
|
69
74
|
afterInsert?: (result: T, input: Partial<T>) => void | Promise<void>;
|
|
@@ -87,13 +92,66 @@ type InferTableEvents<T, M extends TableEventMap> = (M['afterInsert'] extends st
|
|
|
87
92
|
} : Record<never, never>) & (M['afterDelete'] extends string ? {
|
|
88
93
|
[_ in M['afterDelete']]: T;
|
|
89
94
|
} : Record<never, never>);
|
|
90
|
-
|
|
95
|
+
type RelationKind = 'belongsTo' | 'hasMany' | 'manyToMany';
|
|
96
|
+
/**
|
|
97
|
+
* Metadata for a single declared relation.
|
|
98
|
+
* TForeign captures the foreign table's row type so that WithRelations can
|
|
99
|
+
* produce concrete types (User, Post[]) rather than unknown.
|
|
100
|
+
*
|
|
101
|
+
* `getTable` is a lazy getter to allow circular references between tables.
|
|
102
|
+
*/
|
|
103
|
+
interface RelationMeta<TForeign = unknown> {
|
|
104
|
+
kind: RelationKind;
|
|
105
|
+
name: string;
|
|
106
|
+
getTable: () => TableDef<TForeign, any, any>;
|
|
107
|
+
/** FK column name — on this table for belongsTo, on foreign table for hasMany */
|
|
108
|
+
foreignKey: string;
|
|
109
|
+
/** manyToMany only */
|
|
110
|
+
pivot?: {
|
|
111
|
+
table: TableDef<any, any, any>;
|
|
112
|
+
localKey: string;
|
|
113
|
+
foreignKey: string;
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/** All declared relations on a table, keyed by relation name. */
|
|
117
|
+
type RelationsMap = Record<string, RelationMeta<any>>;
|
|
118
|
+
interface BelongsToRelation<TForeign> extends RelationMeta<TForeign> {
|
|
119
|
+
kind: 'belongsTo';
|
|
120
|
+
}
|
|
121
|
+
interface HasManyRelation<TForeign> extends RelationMeta<TForeign> {
|
|
122
|
+
kind: 'hasMany';
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Derives the result type of a single loaded relation.
|
|
126
|
+
* - belongsTo → TForeign | null
|
|
127
|
+
* - hasMany → TForeign[]
|
|
128
|
+
* - manyToMany → never (not supported in Spec B)
|
|
129
|
+
*/
|
|
130
|
+
type InferRelationResult<R> = R extends BelongsToRelation<infer TForeign> ? TForeign | null : R extends HasManyRelation<infer TForeign> ? TForeign[] : never;
|
|
131
|
+
/**
|
|
132
|
+
* Merges a row type T with the requested relations.
|
|
133
|
+
* Keys must be keys of the table's relations map.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* type PostWithAuthor = WithRelations<Post, typeof postsTable, 'author'>
|
|
137
|
+
* // → Post & { author: User | null }
|
|
138
|
+
*/
|
|
139
|
+
type WithRelations<T, TTable extends {
|
|
140
|
+
relations: RelationsMap;
|
|
141
|
+
}, Keys extends keyof TTable['relations'] & string> = T & {
|
|
142
|
+
[K in Keys]: InferRelationResult<TTable['relations'][K]>;
|
|
143
|
+
};
|
|
144
|
+
interface TableDef<T, S extends SchemaMap = SchemaMap, TEvents extends TableEventMap = TableEventMap, TRelations extends RelationsMap = RelationsMap> {
|
|
91
145
|
readonly name: string;
|
|
92
146
|
readonly schema: S;
|
|
93
147
|
readonly primaryKey: keyof T & string;
|
|
94
148
|
readonly hooks: TableHookHandlers<T>[];
|
|
95
149
|
readonly events: TEvents;
|
|
96
150
|
readonly _eventMap: InferTableEvents<T, TEvents>;
|
|
151
|
+
/** Declared relations — concrete typed map so WithRelations can infer foreign types. */
|
|
152
|
+
readonly relations: TRelations;
|
|
153
|
+
/** The column used for soft delete, or null if not configured. */
|
|
154
|
+
readonly softDeleteColumn: (keyof T & string) | null;
|
|
97
155
|
}
|
|
98
156
|
|
|
99
157
|
interface ModuleHookHandlers<T, TCtx = unknown> {
|
|
@@ -168,6 +226,21 @@ declare class HookExecutor {
|
|
|
168
226
|
private _moduleHandlers;
|
|
169
227
|
}
|
|
170
228
|
|
|
229
|
+
/**
|
|
230
|
+
* Phantom-typed wrapper for a subquery SQL fragment.
|
|
231
|
+
* Col and T are used only at compile time — _phantom is never read at runtime.
|
|
232
|
+
*
|
|
233
|
+
* Produced by SelectBuilder.columns(col).subquery().
|
|
234
|
+
* Accepted by WhereOp IN / NOT IN in place of an array.
|
|
235
|
+
*/
|
|
236
|
+
interface SubqueryResult<Col extends string, T> {
|
|
237
|
+
readonly _sql: string;
|
|
238
|
+
readonly _params: BindingValue[];
|
|
239
|
+
readonly _phantom: {
|
|
240
|
+
col: Col;
|
|
241
|
+
type: T;
|
|
242
|
+
};
|
|
243
|
+
}
|
|
171
244
|
interface JoinClause {
|
|
172
245
|
type: 'INNER' | 'LEFT' | 'RIGHT' | 'FULL';
|
|
173
246
|
table: string;
|
|
@@ -175,6 +248,8 @@ interface JoinClause {
|
|
|
175
248
|
}
|
|
176
249
|
/** SQL dialect — used for ILIKE fallback on non-Postgres adapters. */
|
|
177
250
|
type SqlDialect = 'sqlite' | 'postgres' | 'mysql';
|
|
251
|
+
/** Accepted value for IN / NOT IN — either a plain array or a typed subquery. */
|
|
252
|
+
type InValue<T> = T[] | SubqueryResult<string, T>;
|
|
178
253
|
/** Explicit operator condition for a single column. */
|
|
179
254
|
type WhereOp<T> = {
|
|
180
255
|
op: '=';
|
|
@@ -196,10 +271,10 @@ type WhereOp<T> = {
|
|
|
196
271
|
value: T;
|
|
197
272
|
} | {
|
|
198
273
|
op: 'IN';
|
|
199
|
-
value: T
|
|
274
|
+
value: InValue<T>;
|
|
200
275
|
} | {
|
|
201
276
|
op: 'NOT IN';
|
|
202
|
-
value: T
|
|
277
|
+
value: InValue<T>;
|
|
203
278
|
} | {
|
|
204
279
|
op: 'LIKE';
|
|
205
280
|
value: string;
|
|
@@ -243,6 +318,7 @@ interface SelectOptions {
|
|
|
243
318
|
groupBy?: string[];
|
|
244
319
|
aggregates?: AggregateClause[];
|
|
245
320
|
having?: WhereInput<Record<string, unknown>>;
|
|
321
|
+
distinct?: boolean;
|
|
246
322
|
}
|
|
247
323
|
|
|
248
324
|
interface QueryLog {
|
|
@@ -269,11 +345,12 @@ declare class BoundVelnDB {
|
|
|
269
345
|
private readonly hooks;
|
|
270
346
|
private readonly ctx;
|
|
271
347
|
private readonly queue?;
|
|
348
|
+
private readonly dialect;
|
|
272
349
|
/** Per-request query counter — incremented for every query() and execute() call on this instance. */
|
|
273
350
|
_queryCount: number;
|
|
274
351
|
private readonly adapter;
|
|
275
|
-
constructor(adapter: VelnAdapter, hooks: HookExecutor, ctx: unknown, queue?: RequestEventQueue | undefined, queryLog?: QueryLog);
|
|
276
|
-
from<T, S extends SchemaMap>(table: TableDef<T, S>): SelectBuilder<T, S>;
|
|
352
|
+
constructor(adapter: VelnAdapter, hooks: HookExecutor, ctx: unknown, queue?: RequestEventQueue | undefined, queryLog?: QueryLog, dialect?: SqlDialect);
|
|
353
|
+
from<T, S extends SchemaMap, TRelations extends RelationsMap>(table: TableDef<T, S, any, TRelations>): SelectBuilder<T, S, TRelations>;
|
|
277
354
|
/**
|
|
278
355
|
* Start a JOIN query from the given table name.
|
|
279
356
|
* Returns a JoinBuilder — call .join()/.leftJoin() etc. to add clauses,
|
|
@@ -293,22 +370,36 @@ declare class BoundVelnDB {
|
|
|
293
370
|
* Returns a Map keyed by the foreign-key value; each entry is an array of
|
|
294
371
|
* matching child rows (for one-to-many relations).
|
|
295
372
|
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
373
|
+
* Two call forms:
|
|
374
|
+
*
|
|
375
|
+
* @example — explicit (original)
|
|
298
376
|
* const authorMap = await db.loadRelation(posts, 'authorId', usersTable, 'id')
|
|
299
|
-
*
|
|
300
|
-
*
|
|
377
|
+
*
|
|
378
|
+
* @example — name-based (reads relation metadata declared on the table)
|
|
379
|
+
* const authorMap = await db.loadRelation(posts, 'author', postsTable)
|
|
301
380
|
*/
|
|
302
381
|
loadRelation<TParent extends Record<string, unknown>, TChild, TFk extends keyof TParent & string, TPk extends keyof TChild & string>(parents: TParent[], foreignKey: TFk, childTable: TableDef<TChild>, primaryKey: TPk): Promise<Map<TParent[TFk], TChild[]>>;
|
|
382
|
+
loadRelation<TParent extends Record<string, unknown>>(parents: TParent[], relationName: string, sourceTable: TableDef<any>): Promise<Map<unknown, unknown>>;
|
|
303
383
|
/**
|
|
304
384
|
* Convenience variant of loadRelation for belongs-to (many-to-one) relations.
|
|
305
385
|
* Returns Map<fkValue, TChild> — single child per key instead of an array.
|
|
306
386
|
*
|
|
307
|
-
*
|
|
387
|
+
* Two call forms:
|
|
388
|
+
*
|
|
389
|
+
* @example — explicit (original)
|
|
308
390
|
* const authorMap = await db.loadRelationOne(posts, 'authorId', usersTable, 'id')
|
|
309
|
-
*
|
|
391
|
+
*
|
|
392
|
+
* @example — name-based
|
|
393
|
+
* const authorMap = await db.loadRelationOne(posts, 'author', postsTable)
|
|
310
394
|
*/
|
|
311
395
|
loadRelationOne<TParent extends Record<string, unknown>, TChild, TFk extends keyof TParent & string, TPk extends keyof TChild & string>(parents: TParent[], foreignKey: TFk, childTable: TableDef<TChild>, primaryKey: TPk): Promise<Map<TParent[TFk], TChild>>;
|
|
396
|
+
loadRelationOne<TParent extends Record<string, unknown>>(parents: TParent[], relationName: string, sourceTable: TableDef<any>): Promise<Map<unknown, unknown>>;
|
|
397
|
+
/**
|
|
398
|
+
* Shared implementation for name-based loadRelation / loadRelationOne.
|
|
399
|
+
* Reads RelationMeta from sourceTable.relations, validates the kind,
|
|
400
|
+
* and delegates to the explicit overload.
|
|
401
|
+
*/
|
|
402
|
+
private _loadRelationByName;
|
|
312
403
|
transaction<T>(fn: (db: BoundVelnDB) => Promise<T>): Promise<TransactionResult<T>>;
|
|
313
404
|
/**
|
|
314
405
|
* Execute raw SQL and return typed rows.
|
|
@@ -329,7 +420,7 @@ declare class BoundVelnDB {
|
|
|
329
420
|
parse: (row: unknown) => T;
|
|
330
421
|
}): Promise<T[]>;
|
|
331
422
|
}
|
|
332
|
-
declare class SelectBuilder<T, S extends SchemaMap> {
|
|
423
|
+
declare class SelectBuilder<T, S extends SchemaMap, TRelations extends RelationsMap = RelationsMap> {
|
|
333
424
|
private readonly adapter;
|
|
334
425
|
private readonly hooks;
|
|
335
426
|
private readonly ctx;
|
|
@@ -339,12 +430,39 @@ declare class SelectBuilder<T, S extends SchemaMap> {
|
|
|
339
430
|
private readonly _options;
|
|
340
431
|
private readonly _rawWhere;
|
|
341
432
|
private readonly _dialect;
|
|
342
|
-
|
|
433
|
+
private readonly _withRelations;
|
|
434
|
+
private readonly _includeDeleted;
|
|
435
|
+
constructor(adapter: VelnAdapter, hooks: HookExecutor, ctx: unknown, queue: RequestEventQueue | undefined, table: TableDef<T, S, any, TRelations>, conditions: WhereInput<T>, _options?: SelectOptions, _rawWhere?: {
|
|
343
436
|
sql: string;
|
|
344
437
|
params: BindingValue[];
|
|
345
|
-
}[], _dialect?: SqlDialect);
|
|
438
|
+
}[], _dialect?: SqlDialect, _withRelations?: string[], _includeDeleted?: boolean);
|
|
346
439
|
private _cloneWith;
|
|
347
440
|
private _clone;
|
|
441
|
+
/**
|
|
442
|
+
* Eager-load relations alongside the main query.
|
|
443
|
+
* One additional IN-query per relation — never N+1 regardless of row count.
|
|
444
|
+
*
|
|
445
|
+
* @example
|
|
446
|
+
* const posts = await db.from(postsTable).with({ author: true }).select()
|
|
447
|
+
* posts[0].author // → User | null (fully typed)
|
|
448
|
+
* posts[0].title // → string (original fields preserved)
|
|
449
|
+
*/
|
|
450
|
+
with<Keys extends keyof TRelations & string>(relations: {
|
|
451
|
+
[K in Keys]: true;
|
|
452
|
+
}): SelectBuilder<WithRelations<T, {
|
|
453
|
+
relations: TRelations;
|
|
454
|
+
}, Keys>, S, TRelations>;
|
|
455
|
+
/**
|
|
456
|
+
* Include soft-deleted rows in the query result.
|
|
457
|
+
* By default, tables with `.withSoftDelete()` automatically exclude rows
|
|
458
|
+
* where the soft-delete column is not null.
|
|
459
|
+
*
|
|
460
|
+
* Has no effect on tables without soft delete configured.
|
|
461
|
+
*
|
|
462
|
+
* @example
|
|
463
|
+
* const allUsers = await db.from(usersTable).withDeleted().select()
|
|
464
|
+
*/
|
|
465
|
+
withDeleted(): SelectBuilder<T, S, TRelations>;
|
|
348
466
|
/**
|
|
349
467
|
* Add WHERE conditions. Accepts:
|
|
350
468
|
* - Plain equality map: `.where({ role: 'admin' })`
|
|
@@ -354,7 +472,7 @@ declare class SelectBuilder<T, S extends SchemaMap> {
|
|
|
354
472
|
*
|
|
355
473
|
* Multiple `.where()` calls are combined with AND.
|
|
356
474
|
*/
|
|
357
|
-
where(conditions: WhereInput<T>): SelectBuilder<T, S>;
|
|
475
|
+
where(conditions: WhereInput<T>): SelectBuilder<T, S, TRelations>;
|
|
358
476
|
/**
|
|
359
477
|
* Append a raw SQL WHERE fragment, combined with AND.
|
|
360
478
|
* Use for conditions the builder cannot express.
|
|
@@ -363,31 +481,61 @@ declare class SelectBuilder<T, S extends SchemaMap> {
|
|
|
363
481
|
* .whereRaw('"score" > "threshold"', [])
|
|
364
482
|
* .whereRaw('"created_at" > ?', ['2024-01-01'])
|
|
365
483
|
*/
|
|
366
|
-
whereRaw(sql: string, params: BindingValue[]): SelectBuilder<T, S>;
|
|
484
|
+
whereRaw(sql: string, params: BindingValue[]): SelectBuilder<T, S, TRelations>;
|
|
485
|
+
/**
|
|
486
|
+
* Apply SELECT DISTINCT — deduplicate rows in the result set.
|
|
487
|
+
* Combine with `.columns()` to deduplicate on specific columns.
|
|
488
|
+
*
|
|
489
|
+
* @example
|
|
490
|
+
* await db.from(usersTable).columns('name').distinct().select()
|
|
491
|
+
* // → SELECT DISTINCT "name" FROM "users"
|
|
492
|
+
*/
|
|
493
|
+
distinct(): SelectBuilder<T, S, TRelations>;
|
|
367
494
|
/** Limit the number of rows returned. Bound as a parameter — never interpolated. */
|
|
368
|
-
limit(n: number): SelectBuilder<T, S>;
|
|
495
|
+
limit(n: number): SelectBuilder<T, S, TRelations>;
|
|
369
496
|
/** Skip the first n rows. Bound as a parameter — never interpolated. */
|
|
370
|
-
offset(n: number): SelectBuilder<T, S>;
|
|
497
|
+
offset(n: number): SelectBuilder<T, S, TRelations>;
|
|
371
498
|
/** Add an ORDER BY clause. Multiple calls accumulate in order. */
|
|
372
|
-
orderBy(col: keyof T & string, dir?: 'ASC' | 'DESC'): SelectBuilder<T, S>;
|
|
499
|
+
orderBy(col: keyof T & string, dir?: 'ASC' | 'DESC'): SelectBuilder<T, S, TRelations>;
|
|
373
500
|
/**
|
|
374
501
|
* Convenience helper for cursor-based pagination.
|
|
375
502
|
* page(1, 10) → LIMIT 10 OFFSET 0
|
|
376
503
|
* page(2, 10) → LIMIT 10 OFFSET 10
|
|
377
504
|
*/
|
|
378
|
-
page(page: number, size: number): SelectBuilder<T, S>;
|
|
505
|
+
page(page: number, size: number): SelectBuilder<T, S, TRelations>;
|
|
379
506
|
/**
|
|
380
507
|
* Restrict which columns are returned.
|
|
381
|
-
* SELECT "id", "name" FROM "table" — instead of SELECT *
|
|
382
508
|
*
|
|
383
|
-
*
|
|
509
|
+
* Single-column form returns a ColumnRestrictedBuilder, enabling .subquery():
|
|
510
|
+
* db.from(usersTable).columns('id').subquery() // → SubqueryResult<'id', number>
|
|
511
|
+
*
|
|
512
|
+
* Multi-column form returns a narrowed SelectBuilder:
|
|
513
|
+
* db.from(usersTable).columns('id', 'name') // → SelectBuilder<Pick<User, 'id'|'name'>, ...>
|
|
514
|
+
*/
|
|
515
|
+
columns<K extends keyof T & string>(col: K): ColumnRestrictedBuilder<K, T[K], S, TRelations>;
|
|
516
|
+
columns<K extends keyof T & string>(...cols: K[]): SelectBuilder<Pick<T, K>, S, TRelations>;
|
|
517
|
+
/**
|
|
518
|
+
* Build SELECT SQL + params without executing the query.
|
|
519
|
+
* Used internally by ColumnRestrictedBuilder.subquery().
|
|
384
520
|
*/
|
|
385
|
-
|
|
521
|
+
/** Internal accessor for ColumnRestrictedBuilder / UnionBuilder — returns the adapter. */
|
|
522
|
+
_getAdapter(): VelnAdapter;
|
|
523
|
+
/** Internal accessor for ColumnRestrictedBuilder / UnionBuilder — returns the SQL dialect. */
|
|
524
|
+
_getDialect(): SqlDialect;
|
|
525
|
+
/**
|
|
526
|
+
* Returns the effective WHERE conditions, injecting the soft-delete IS NULL
|
|
527
|
+
* filter when the table has a soft-delete column and .withDeleted() was not called.
|
|
528
|
+
*/
|
|
529
|
+
private _effectiveConditions;
|
|
530
|
+
_buildSelectSQL(): {
|
|
531
|
+
sql: string;
|
|
532
|
+
params: BindingValue[];
|
|
533
|
+
};
|
|
386
534
|
/**
|
|
387
535
|
* Add a GROUP BY clause. Multiple columns are comma-separated.
|
|
388
536
|
* Combine with .aggregate() to get grouped aggregate results.
|
|
389
537
|
*/
|
|
390
|
-
groupBy(...cols: (keyof T & string)[]): SelectBuilder<T, S>;
|
|
538
|
+
groupBy(...cols: (keyof T & string)[]): SelectBuilder<T, S, TRelations>;
|
|
391
539
|
/**
|
|
392
540
|
* Add a HAVING clause — filters aggregate groups.
|
|
393
541
|
* Uses the same WhereInput system as .where() (supports operators, OR/AND).
|
|
@@ -395,7 +543,7 @@ declare class SelectBuilder<T, S extends SchemaMap> {
|
|
|
395
543
|
* @example
|
|
396
544
|
* .groupBy('role').aggregate({ cnt: { fn: 'COUNT' } }).having({ cnt: { op: '>', value: 1 } })
|
|
397
545
|
*/
|
|
398
|
-
having(conditions: WhereInput<Record<string, unknown>>): SelectBuilder<T, S>;
|
|
546
|
+
having(conditions: WhereInput<Record<string, unknown>>): SelectBuilder<T, S, TRelations>;
|
|
399
547
|
/**
|
|
400
548
|
* Execute a GROUP BY + aggregate query.
|
|
401
549
|
* Returns typed rows with group-by columns + aggregate aliases.
|
|
@@ -426,8 +574,133 @@ declare class SelectBuilder<T, S extends SchemaMap> {
|
|
|
426
574
|
private _scalarAggregateRaw;
|
|
427
575
|
select(): Promise<T[]>;
|
|
428
576
|
first(): Promise<T | null>;
|
|
577
|
+
private _executeWith;
|
|
578
|
+
private _attachBelongsTo;
|
|
579
|
+
private _attachHasMany;
|
|
429
580
|
update(patch: Partial<T>): Promise<T>;
|
|
581
|
+
/**
|
|
582
|
+
* Update multiple rows atomically inside a single transaction.
|
|
583
|
+
* Each row must include the primary key. beforeUpdate and afterUpdate hooks
|
|
584
|
+
* run per row. If any row fails, the entire transaction rolls back.
|
|
585
|
+
*
|
|
586
|
+
* @example
|
|
587
|
+
* const updated = await db.from(usersTable).updateMany([
|
|
588
|
+
* { id: 1, name: 'Alice Updated' },
|
|
589
|
+
* { id: 2, role: 'admin' },
|
|
590
|
+
* ])
|
|
591
|
+
*/
|
|
592
|
+
updateMany(rows: InferUpdate<S>[]): Promise<T[]>;
|
|
430
593
|
delete(): Promise<T>;
|
|
594
|
+
/**
|
|
595
|
+
* Soft-delete rows by setting the soft-delete column to the current timestamp.
|
|
596
|
+
* The table must have `.withSoftDelete()` configured — throws otherwise (at execute() time).
|
|
597
|
+
*
|
|
598
|
+
* Does NOT call beforeUpdate/afterUpdate hooks.
|
|
599
|
+
* Without .where(), all rows in the table are soft-deleted.
|
|
600
|
+
*
|
|
601
|
+
* @example
|
|
602
|
+
* await db.from(usersTable).softDelete().where({ id: 1 }).execute()
|
|
603
|
+
*/
|
|
604
|
+
softDelete(): SoftDeleteBuilder<T, S>;
|
|
605
|
+
/**
|
|
606
|
+
* Restore soft-deleted rows by setting the soft-delete column back to null.
|
|
607
|
+
* The table must have `.withSoftDelete()` configured — throws otherwise (at execute() time).
|
|
608
|
+
*
|
|
609
|
+
* @example
|
|
610
|
+
* await db.from(usersTable).restore().where({ id: 1 }).execute()
|
|
611
|
+
*/
|
|
612
|
+
restore(): SoftDeleteBuilder<T, S>;
|
|
613
|
+
}
|
|
614
|
+
declare class ColumnRestrictedBuilder<Col extends string, TCol, S extends SchemaMap, TRelations extends RelationsMap> {
|
|
615
|
+
private readonly _builder;
|
|
616
|
+
private readonly _col;
|
|
617
|
+
constructor(_builder: SelectBuilder<unknown, S, TRelations>, _col: Col);
|
|
618
|
+
where(conditions: WhereInput<Record<string, unknown>>): ColumnRestrictedBuilder<Col, TCol, S, TRelations>;
|
|
619
|
+
limit(n: number): ColumnRestrictedBuilder<Col, TCol, S, TRelations>;
|
|
620
|
+
orderBy(col: Col, dir?: 'ASC' | 'DESC'): ColumnRestrictedBuilder<Col, TCol, S, TRelations>;
|
|
621
|
+
/**
|
|
622
|
+
* Build the SQL for this query as a subquery fragment.
|
|
623
|
+
* The result can be used directly in WHERE IN / NOT IN conditions.
|
|
624
|
+
*
|
|
625
|
+
* @example
|
|
626
|
+
* const activeIds = db.from(usersTable).columns('id').where({ active: true }).subquery()
|
|
627
|
+
* // → SubqueryResult<'id', number>
|
|
628
|
+
*
|
|
629
|
+
* const posts = await db.from(postsTable)
|
|
630
|
+
* .where({ authorId: { op: 'IN', value: activeIds } })
|
|
631
|
+
* .select()
|
|
632
|
+
*/
|
|
633
|
+
subquery(): SubqueryResult<Col, TCol>;
|
|
634
|
+
/** Build raw SELECT SQL + params without parentheses (for UNION). */
|
|
635
|
+
_buildRawSQL(): {
|
|
636
|
+
sql: string;
|
|
637
|
+
params: BindingValue[];
|
|
638
|
+
};
|
|
639
|
+
/**
|
|
640
|
+
* Combine this query with another same-type column query via UNION (deduplicates).
|
|
641
|
+
* Both sides must produce the same column type — enforced at compile time.
|
|
642
|
+
*
|
|
643
|
+
* @example
|
|
644
|
+
* db.from(usersTable).columns('id')
|
|
645
|
+
* .union(db.from(adminsTable).columns('id'))
|
|
646
|
+
* .select()
|
|
647
|
+
*/
|
|
648
|
+
union(other: ColumnRestrictedBuilder<string, TCol, SchemaMap, RelationsMap>): UnionBuilder<TCol>;
|
|
649
|
+
/**
|
|
650
|
+
* Combine via UNION ALL — keeps duplicate rows.
|
|
651
|
+
*/
|
|
652
|
+
unionAll(other: ColumnRestrictedBuilder<string, TCol, SchemaMap, RelationsMap>): UnionBuilder<TCol>;
|
|
653
|
+
}
|
|
654
|
+
declare class SoftDeleteBuilder<T, S extends SchemaMap> {
|
|
655
|
+
private readonly adapter;
|
|
656
|
+
private readonly table;
|
|
657
|
+
private readonly _value;
|
|
658
|
+
private readonly _dialect;
|
|
659
|
+
private _conditions;
|
|
660
|
+
constructor(adapter: VelnAdapter, table: TableDef<T, S>, _value: Date | null, _dialect?: SqlDialect);
|
|
661
|
+
/**
|
|
662
|
+
* Add WHERE conditions to scope which rows are soft-deleted / restored.
|
|
663
|
+
* Multiple calls accumulate with AND.
|
|
664
|
+
* Without .where(), all rows in the table are affected.
|
|
665
|
+
*/
|
|
666
|
+
where(conditions: WhereInput<T>): this;
|
|
667
|
+
/**
|
|
668
|
+
* Execute the soft-delete or restore UPDATE.
|
|
669
|
+
* Throws if the table has no softDeleteColumn configured.
|
|
670
|
+
*/
|
|
671
|
+
execute(): Promise<void>;
|
|
672
|
+
}
|
|
673
|
+
declare class UnionBuilder<T> {
|
|
674
|
+
private readonly _parts;
|
|
675
|
+
private readonly _kind;
|
|
676
|
+
private readonly _adapter;
|
|
677
|
+
private readonly _dialect;
|
|
678
|
+
private _orderBy?;
|
|
679
|
+
private _limit?;
|
|
680
|
+
constructor(_parts: Array<{
|
|
681
|
+
sql: string;
|
|
682
|
+
params: BindingValue[];
|
|
683
|
+
}>, _kind: 'UNION' | 'UNION ALL', _adapter: VelnAdapter, _dialect: SqlDialect);
|
|
684
|
+
/** Append another UNION (deduplicating) leg. */
|
|
685
|
+
union(other: ColumnRestrictedBuilder<string, T, SchemaMap, RelationsMap>): UnionBuilder<T>;
|
|
686
|
+
/** Append another UNION ALL (keep duplicates) leg. */
|
|
687
|
+
unionAll(other: ColumnRestrictedBuilder<string, T, SchemaMap, RelationsMap>): UnionBuilder<T>;
|
|
688
|
+
/** Add ORDER BY to the entire UNION result. */
|
|
689
|
+
orderBy(col: string, dir?: 'ASC' | 'DESC'): UnionBuilder<T>;
|
|
690
|
+
/** Add LIMIT to the entire UNION result. */
|
|
691
|
+
limit(n: number): UnionBuilder<T>;
|
|
692
|
+
/** Execute the UNION query and return typed rows. */
|
|
693
|
+
select(): Promise<Record<string, T>[]>;
|
|
694
|
+
/**
|
|
695
|
+
* Build the UNION as a subquery — wrapped in parentheses.
|
|
696
|
+
* Usable in WHERE IN / NOT IN conditions.
|
|
697
|
+
*
|
|
698
|
+
* @example
|
|
699
|
+
* const adminOrModIds = db.from(usersTable).columns('id').where({ role: 'admin' })
|
|
700
|
+
* .union(db.from(usersTable).columns('id').where({ role: 'mod' }))
|
|
701
|
+
* .subquery()
|
|
702
|
+
*/
|
|
703
|
+
subquery(): SubqueryResult<string, T>;
|
|
431
704
|
}
|
|
432
705
|
declare class InsertBuilder<T, S extends SchemaMap> {
|
|
433
706
|
private readonly adapter;
|
|
@@ -435,9 +708,24 @@ declare class InsertBuilder<T, S extends SchemaMap> {
|
|
|
435
708
|
private readonly ctx;
|
|
436
709
|
private readonly queue;
|
|
437
710
|
private readonly table;
|
|
438
|
-
|
|
711
|
+
private readonly dialect;
|
|
712
|
+
constructor(adapter: VelnAdapter, hooks: HookExecutor, ctx: unknown, queue: RequestEventQueue | undefined, table: TableDef<T, S>, dialect?: SqlDialect);
|
|
439
713
|
insert(data: InferInsert<S>): Promise<T>;
|
|
440
|
-
/**
|
|
714
|
+
/**
|
|
715
|
+
* Insert multiple rows in a single SQL statement.
|
|
716
|
+
* beforeInsert and afterInsert hooks run per row.
|
|
717
|
+
* Defaults (defaultFn / defaultValue) are applied per row.
|
|
718
|
+
*
|
|
719
|
+
* MySQL is not yet supported (no RETURNING *) — throws an informative error.
|
|
720
|
+
*
|
|
721
|
+
* @example
|
|
722
|
+
* const users = await db.into(usersTable).insertMany([
|
|
723
|
+
* { name: 'Alice', email: 'alice@example.com' },
|
|
724
|
+
* { name: 'Bob', email: 'bob@example.com' },
|
|
725
|
+
* ])
|
|
726
|
+
*/
|
|
727
|
+
insertMany(data: InferInsert<S>[]): Promise<T[]>;
|
|
728
|
+
/** Serialize values for storage. Date → ISO string. Drops undefined values. */
|
|
441
729
|
private _serializeForInsert;
|
|
442
730
|
}
|
|
443
731
|
declare class JoinBuilder {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oakbun/ws",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "WebSocket plugin for the OakBun framework.",
|
|
5
5
|
"author": "René (SchildW3rk)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"prepublishOnly": "bun run build"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"oakbun": ">=0.
|
|
44
|
+
"oakbun": ">=0.3.0",
|
|
45
45
|
"zod": "^3.0.0 || ^4.0.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|