bun-query-builder 0.1.58 → 0.1.60

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.
@@ -32,7 +32,7 @@ declare interface InferableModelDefinition {
32
32
  readonly useAuth?: boolean | object
33
33
  readonly billable?: boolean | object
34
34
  }
35
- readonly belongsTo?: readonly string[] | Readonly<Record<string, string>>
35
+ readonly belongsTo?: readonly (string | object)[] | Readonly<Record<string, string | object>>
36
36
  readonly hasMany?: readonly string[] | Readonly<Record<string, string>>
37
37
  readonly hasOne?: readonly string[] | Readonly<Record<string, string>>
38
38
  readonly belongsToMany?: readonly (string | object)[] | Readonly<Record<string, string | object>>
@@ -72,6 +72,41 @@ declare type ResolveDefinition<TModel> = TModel extends { getDefinition: () => i
72
72
  never;
73
73
  /** Extract user-defined attribute keys from a model definition */
74
74
  declare type DefinitionAttributeKeys<TDef extends InferableModelDefinition> = keyof TDef['attributes'] & string;
75
+ /** The model's primary-key column name, defaulting to `id`. */
76
+ declare type PrimaryKeyOf<TDef> = TDef extends { primaryKey: infer PK extends string } ? PK : 'id';
77
+ /**
78
+ * A single `belongsTo` entry reduced to the FK column it puts on this table.
79
+ * Mirrors `normalizeRelationEntry` in relation-utils.ts and `BelongsToFkOf` in
80
+ * orm.ts: an explicit `foreignKey` wins, otherwise the name is derived from
81
+ * the model name.
82
+ */
83
+ declare type BelongsToFkOf<E> = E extends string
84
+ ? `${SnakeCase<Uncapitalize<E>>}_id`
85
+ : E extends { foreignKey: infer F extends string }
86
+ ? F
87
+ : E extends { model: infer M extends string }
88
+ ? `${SnakeCase<Uncapitalize<M>>}_id`
89
+ : never;
90
+ /**
91
+ * The foreign-key columns a `belongsTo` puts on THIS model's table — they are
92
+ * emitted by the migration generator, so they are as real as any declared
93
+ * attribute. The array case MUST be checked first: a tuple also structurally
94
+ * matches `Record`, and the record branch would otherwise read its numeric
95
+ * indices as entries.
96
+ */
97
+ declare type BelongsToKeys<TDef> = TDef extends { belongsTo: infer R }
98
+ ? R extends readonly (infer E)[]
99
+ ? BelongsToFkOf<E>
100
+ : R extends Readonly<Record<string, infer W>>
101
+ ? BelongsToFkOf<W>
102
+ : never
103
+ : never;
104
+ /** camelCase -> snake_case, matching the column names migrations emit. */
105
+ declare type SnakeCase<S extends string> = S extends `${infer C}${infer Rest}`
106
+ ? C extends Lowercase<C>
107
+ ? `${C}${SnakeCase<Rest>}`
108
+ : `_${Lowercase<C>}${SnakeCase<Rest>}`
109
+ : S;
75
110
  // ============================================================================
76
111
  // Single attribute type inference
77
112
  // ============================================================================
@@ -93,14 +128,23 @@ export type InferAttributes<TModel> = ResolveDefinition<TModel> extends infer TD
93
128
  [K in DefinitionAttributeKeys<TDef>]: TDef['attributes'][K] extends { nullable: true }
94
129
  ? InferSingleAttributeType<TDef['attributes'][K]> | null
95
130
  : InferSingleAttributeType<TDef['attributes'][K]>
96
- } & { [K in TDef extends { primaryKey: infer PK extends string } ? PK : 'id']: number }
97
- & (TDef['traits'] extends { useUuid: true } ? { uuid: string } : {})
98
- & (TDef['traits'] extends { useTimestamps: true } ? { created_at: string; updated_at: string | null } : {})
99
- & (TDef['traits'] extends { timestampable: true | object } ? { created_at: string; updated_at: string | null } : {})
100
- & (TDef['traits'] extends { useSoftDeletes: true } ? { deleted_at: string | null } : {})
101
- & (TDef['traits'] extends { softDeletable: true | object } ? { deleted_at: string | null } : {})
102
- & (TDef['traits'] extends { useAuth: true | object } ? { two_factor_secret: string | null; public_key: string | null } : {})
103
- & (TDef['traits'] extends { billable: true | object } ? { stripe_id: string | null } : {})
131
+ }
132
+ // A column the model spells out is the authority on its own type; the
133
+ // trait / FK default only fills the gap. Without the Omit, a model that
134
+ // declares `created_at: { type: 'date' }` alongside `useTimestamps`
135
+ // intersected the two into `Date & string` — an uninhabited type.
136
+ & Omit<
137
+ { [K in PrimaryKeyOf<TDef>]: number }
138
+ & (TDef['traits'] extends { useUuid: true } ? { uuid: string } : {})
139
+ & (TDef['traits'] extends { useTimestamps: true } ? { created_at: string; updated_at: string | null } : {})
140
+ & (TDef['traits'] extends { timestampable: true | object } ? { created_at: string; updated_at: string | null } : {})
141
+ & (TDef['traits'] extends { useSoftDeletes: true } ? { deleted_at: string | null } : {})
142
+ & (TDef['traits'] extends { softDeletable: true | object } ? { deleted_at: string | null } : {})
143
+ & (TDef['traits'] extends { useAuth: true | object } ? { two_factor_secret: string | null; public_key: string | null } : {})
144
+ & (TDef['traits'] extends { billable: true | object } ? { stripe_id: string | null } : {})
145
+ & { [K in BelongsToKeys<TDef>]: number },
146
+ DefinitionAttributeKeys<TDef>
147
+ >
104
148
  : never;
105
149
  /**
106
150
  * The full row type for a model, including an index signature for trait-added
@@ -144,6 +188,11 @@ export type ModelCreateDataLoose<TModel> = InferFillableAttributes<TModel> & { [
144
188
  * Infer only the fillable fields from a model definition or wrapped model.
145
189
  * This is the type accepted by `create()`, `update()`, and `fill()`.
146
190
  *
191
+ * Mirrors `InferAttributes`: a column declared `nullable: true` admits `null`.
192
+ * Columns that are `nullable` or carry a `default` are also OPTIONAL — the
193
+ * database supplies a value when the caller omits one, so requiring them at
194
+ * the call site rejected perfectly valid inserts.
195
+ *
147
196
  * @example
148
197
  * ```ts
149
198
  * type UserFillable = InferFillableAttributes<typeof UserModel>
@@ -151,11 +200,25 @@ export type ModelCreateDataLoose<TModel> = InferFillableAttributes<TModel> & { [
151
200
  * ```
152
201
  */
153
202
  export type InferFillableAttributes<TModel> = ResolveDefinition<TModel> extends infer TDef extends InferableModelDefinition
154
- ? {
155
- [K in DefinitionAttributeKeys<TDef> as TDef['attributes'][K] extends { fillable: true } ? K : never]:
156
- InferSingleAttributeType<TDef['attributes'][K]>
157
- }
203
+ ? FillableRequired<TDef> & FillableOptional<TDef>
158
204
  : never;
205
+ /** Fillable keys the caller MUST supply — no `nullable`, no `default`. */
206
+ declare type FillableRequired<TDef extends InferableModelDefinition> = {
207
+ [K in DefinitionAttributeKeys<TDef> as TDef['attributes'][K] extends { fillable: true }
208
+ ? TDef['attributes'][K] extends { nullable: true } | { default: unknown } ? never : K
209
+ : never]:
210
+ InferFillableType<TDef['attributes'][K]>
211
+ }
212
+ /** Fillable keys the database can fill in — `nullable` or `default`. */
213
+ declare type FillableOptional<TDef extends InferableModelDefinition> = {
214
+ [K in DefinitionAttributeKeys<TDef> as TDef['attributes'][K] extends { fillable: true }
215
+ ? TDef['attributes'][K] extends { nullable: true } | { default: unknown } ? K : never
216
+ : never]?:
217
+ InferFillableType<TDef['attributes'][K]>
218
+ }
219
+ declare type InferFillableType<TAttr> = TAttr extends { nullable: true }
220
+ ? InferSingleAttributeType<TAttr> | null
221
+ : InferSingleAttributeType<TAttr>;
159
222
  /**
160
223
  * Infer the primary key type from a model definition or wrapped model.
161
224
  * Returns the literal string type of the primary key column name.
@@ -183,7 +246,9 @@ export type InferTableName<TModel> = ResolveDefinition<TModel> extends infer TDe
183
246
  : never;
184
247
  /**
185
248
  * Infer all valid relation names from a model definition or wrapped model.
186
- * Combines belongsTo, hasMany, hasOne, belongsToMany, hasOneThrough, and hasManyThrough.
249
+ * Covers every relation kind `RelationCardinality` knows about: belongsTo,
250
+ * hasMany, hasOne, belongsToMany, hasOneThrough, hasManyThrough, and the
251
+ * polymorphic morphOne / morphMany / morphToMany / morphedByMany.
187
252
  *
188
253
  * @example
189
254
  * ```ts
@@ -198,6 +263,10 @@ export type InferRelationNames<TModel> = ResolveDefinition<TModel> extends infer
198
263
  | InferBelongsToManyNames<TDef>
199
264
  | InferHasOneThroughNames<TDef>
200
265
  | InferHasManyThroughNames<TDef>
266
+ | InferMorphOneNames<TDef>
267
+ | InferMorphManyNames<TDef>
268
+ | InferMorphToManyNames<TDef>
269
+ | InferMorphedByManyNames<TDef>
201
270
  : never;
202
271
  /**
203
272
  * Infer column names that have numeric types from a model definition or wrapped model.
@@ -223,10 +292,18 @@ export type InferNumericColumns<TModel> = ResolveDefinition<TModel> extends infe
223
292
  * type UserCols = InferColumnNames<typeof UserModel>
224
293
  * // 'name' | 'email' | 'age' | 'role' | 'id' | 'uuid' | 'created_at' | 'updated_at'
225
294
  * ```
295
+ *
296
+ * Kept in parity with the ORM layer's `ColumnName`: the primary key honors a
297
+ * custom `primaryKey` (a custom-pk model exposes THAT column, not a phantom
298
+ * `id`), belongsTo-implied foreign keys are included because the migration
299
+ * generator emits them, and every declared attribute is also valid in its
300
+ * snake_case column form.
226
301
  */
227
302
  export type InferColumnNames<TModel> = ResolveDefinition<TModel> extends infer TDef extends InferableModelDefinition
228
303
  ? DefinitionAttributeKeys<TDef>
229
- | 'id'
304
+ | SnakeCase<DefinitionAttributeKeys<TDef>>
305
+ | PrimaryKeyOf<TDef>
306
+ | BelongsToKeys<TDef>
230
307
  | (TDef['traits'] extends { useUuid: true } ? 'uuid' : never)
231
308
  | (TDef['traits'] extends { useTimestamps: true } ? 'created_at' | 'updated_at' : never)
232
309
  | (TDef['traits'] extends { timestampable: true | object } ? 'created_at' | 'updated_at' : never)
@@ -303,25 +380,35 @@ export type InferPivotColumns<TModel, R extends string> = ResolveDefinition<TMod
303
380
  : Record<string, unknown>
304
381
  : Record<string, unknown>;
305
382
  /**
306
- * Relation names of one relation declaration. Array form lowercases the
307
- * (unwrapped) model name; record form uses the keys. The array case MUST be
308
- * checked first: a tuple also structurally matches `Readonly<Record<...>>`
383
+ * Relation names of one relation declaration. A bare string (`morphOne:
384
+ * 'Image'`) lowercases the model name; array form lowercases the (unwrapped)
385
+ * model name; record form uses the keys.
386
+ *
387
+ * Order matters. The bare-string case must precede the record case (a string
388
+ * would otherwise fall through to `never`), and the array case MUST precede
389
+ * the record case: a tuple also structurally matches `Readonly<Record<...>>`
309
390
  * and would otherwise leak its own keys ('length', indices, ...) into the
310
391
  * relation-name union.
311
392
  */
312
- declare type RelationKeyOf<V> = V extends readonly (infer E)[]
313
- ? E extends string ? Lowercase<E>
314
- : E extends { model: infer M extends string } ? Lowercase<M>
315
- : never
316
- : V extends Readonly<Record<infer K, unknown>>
317
- ? K & string
318
- : never;
393
+ declare type RelationKeyOf<V> = V extends string
394
+ ? Lowercase<V>
395
+ : V extends readonly (infer E)[]
396
+ ? E extends string ? Lowercase<E>
397
+ : E extends { model: infer M extends string } ? Lowercase<M>
398
+ : never
399
+ : V extends Readonly<Record<infer K, unknown>>
400
+ ? K & string
401
+ : never;
319
402
  declare type InferBelongsToNames<TDef> = TDef extends { belongsTo: infer V } ? RelationKeyOf<V> : never;
320
403
  declare type InferHasManyNames<TDef> = TDef extends { hasMany: infer V } ? RelationKeyOf<V> : never;
321
404
  declare type InferHasOneNames<TDef> = TDef extends { hasOne: infer V } ? RelationKeyOf<V> : never;
322
405
  declare type InferBelongsToManyNames<TDef> = TDef extends { belongsToMany: infer V } ? RelationKeyOf<V> : never;
323
406
  declare type InferHasOneThroughNames<TDef> = TDef extends { hasOneThrough: infer V } ? RelationKeyOf<V> : never;
324
407
  declare type InferHasManyThroughNames<TDef> = TDef extends { hasManyThrough: infer V } ? RelationKeyOf<V> : never;
408
+ declare type InferMorphOneNames<TDef> = TDef extends { morphOne: infer V } ? RelationKeyOf<V> : never;
409
+ declare type InferMorphManyNames<TDef> = TDef extends { morphMany: infer V } ? RelationKeyOf<V> : never;
410
+ declare type InferMorphToManyNames<TDef> = TDef extends { morphToMany: infer V } ? RelationKeyOf<V> : never;
411
+ declare type InferMorphedByManyNames<TDef> = TDef extends { morphedByMany: infer V } ? RelationKeyOf<V> : never;
325
412
  /**
326
413
  * Determine the cardinality of a relation on a model.
327
414
  * hasMany / belongsToMany / hasManyThrough / morphMany / morphToMany /
package/dist/types.d.ts CHANGED
@@ -90,7 +90,7 @@ export declare interface AliasingConfig {
90
90
  */
91
91
  export declare interface RelationsConfig {
92
92
  foreignKeyFormat: 'singularParent_id' | 'parentId'
93
- singularizeStrategy?: 'stripTrailingS' | 'none'
93
+ singularizeStrategy?: 'stripTrailingS' | 'none' | 'inflect'
94
94
  maxDepth?: number
95
95
  maxEagerLoad?: number
96
96
  detectCycles?: boolean
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bun-query-builder",
3
3
  "type": "module",
4
- "version": "0.1.58",
4
+ "version": "0.1.60",
5
5
  "description": "A simple yet performant query builder for TypeScript. Built with Bun.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",
@@ -80,7 +80,7 @@
80
80
  "@stacksjs/clapp": "^0.2.0",
81
81
  "@stacksjs/ts-faker": "^0.1.8",
82
82
  "@stacksjs/ts-validation": "^0.5.0",
83
- "dynamodb-tooling": "^0.3.2"
83
+ "dynamodb-tooling": "^0.3.5"
84
84
  },
85
85
  "devDependencies": {
86
86
  "bunfig": "^0.15.6"