@apisr/drizzle-model 0.0.4 → 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.
Files changed (42) hide show
  1. package/DISCLAIMER.md +5 -0
  2. package/TODO.md +8 -61
  3. package/package.json +2 -1
  4. package/src/core/dialect.ts +81 -0
  5. package/src/core/index.ts +24 -0
  6. package/src/core/query/error.ts +15 -0
  7. package/src/core/query/joins.ts +596 -0
  8. package/src/core/query/projection.ts +136 -0
  9. package/src/core/query/where.ts +449 -0
  10. package/src/core/result.ts +297 -0
  11. package/src/core/runtime.ts +612 -0
  12. package/src/core/transform.ts +119 -0
  13. package/src/model/builder.ts +40 -6
  14. package/src/model/config.ts +9 -9
  15. package/src/model/format.ts +20 -8
  16. package/src/model/methods/exclude.ts +1 -7
  17. package/src/model/methods/return.ts +11 -11
  18. package/src/model/methods/select.ts +2 -8
  19. package/src/model/model.ts +10 -16
  20. package/src/model/query/error.ts +1 -0
  21. package/src/model/result.ts +134 -21
  22. package/src/types.ts +38 -0
  23. package/tests/base/count.test.ts +47 -0
  24. package/tests/base/delete.test.ts +90 -0
  25. package/tests/base/find.test.ts +209 -0
  26. package/tests/base/insert.test.ts +152 -0
  27. package/tests/base/safe.test.ts +91 -0
  28. package/tests/base/update.test.ts +88 -0
  29. package/tests/base/upsert.test.ts +121 -0
  30. package/tests/base.ts +21 -0
  31. package/src/model/core/joins.ts +0 -364
  32. package/src/model/core/projection.ts +0 -61
  33. package/src/model/core/runtime.ts +0 -334
  34. package/src/model/core/thenable.ts +0 -94
  35. package/src/model/core/transform.ts +0 -65
  36. package/src/model/core/where.ts +0 -249
  37. package/src/model/core/with.ts +0 -28
  38. package/tests/builder-v2-mysql.type-test.ts +0 -51
  39. package/tests/builder-v2.type-test.ts +0 -336
  40. package/tests/builder.test.ts +0 -63
  41. package/tests/find.test.ts +0 -166
  42. package/tests/insert.test.ts +0 -247
@@ -0,0 +1,297 @@
1
+ /** Generic record type. */
2
+ type AnyRecord = Record<string, unknown>;
3
+
4
+ // ---------------------------------------------------------------------------
5
+ // Safe result wrapper
6
+ // ---------------------------------------------------------------------------
7
+
8
+ /**
9
+ * A discriminated union that wraps a result with error handling.
10
+ *
11
+ * When the operation succeeds, `data` contains the value and `error`
12
+ * is `undefined`. When it fails, `error` contains the thrown value
13
+ * and `data` is `undefined`.
14
+ */
15
+ export type SafeResult<T> =
16
+ | { data: T; error: undefined }
17
+ | { data: undefined; error: unknown };
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Query state
21
+ // ---------------------------------------------------------------------------
22
+
23
+ /** Accumulated state for a query operation (findMany / findFirst). */
24
+ export interface QueryState {
25
+ /** Column blacklist for `.exclude()`. */
26
+ exclude?: AnyRecord;
27
+ /** When `true`, formatting is skipped. */
28
+ raw?: boolean;
29
+ /** When `true`, result is wrapped in `{ data, error }`. */
30
+ safe?: boolean;
31
+ /** Column whitelist for `.select()`. */
32
+ select?: AnyRecord;
33
+ /** Compiled where clause. */
34
+ where?: unknown;
35
+ /** Relation inclusions (`.with()` value). */
36
+ with?: unknown;
37
+ }
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Mutate state
41
+ // ---------------------------------------------------------------------------
42
+
43
+ /** Discriminator for mutation kinds. */
44
+ export type MutateKind = "insert" | "update" | "delete" | "upsert";
45
+
46
+ /** Accumulated state for a mutation operation. */
47
+ export interface MutateState {
48
+ /** When `true`, `.return()` or `.returnFirst()` was called. */
49
+ hasReturn?: boolean;
50
+ /** The kind of mutation being performed. */
51
+ kind: MutateKind;
52
+ /** Post-query key exclusion map for `.omit()`. */
53
+ omit?: AnyRecord;
54
+ /** When `true`, only the first element of the returning array is returned. */
55
+ returnFirst?: boolean;
56
+ /** Column selection for `.returning()`. */
57
+ returnSelect?: AnyRecord;
58
+ /** When `true`, result is wrapped in `{ data, error }`. */
59
+ safe?: boolean;
60
+ /** The payload value (insert data, update set, upsert descriptor). */
61
+ value?: unknown;
62
+ /** Compiled where clause (for update / delete). */
63
+ where?: unknown;
64
+ }
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // ThenableResult — base class
68
+ // ---------------------------------------------------------------------------
69
+
70
+ /**
71
+ * A lazy result that implements `PromiseLike` so it can be `await`-ed.
72
+ *
73
+ * Execution is deferred until `.then()` is called, allowing the caller
74
+ * to chain modifiers (`.select()`, `.with()`, …) before the query runs.
75
+ *
76
+ * When `_safe` is `true`, execution errors are caught and the result
77
+ * is wrapped in a {@link SafeResult} discriminated union.
78
+ *
79
+ * @typeParam T - The resolved result type.
80
+ */
81
+ export class ThenableResult<T> implements PromiseLike<T> {
82
+ /** The deferred execution function. */
83
+ protected readonly _execute: () => Promise<T>;
84
+
85
+ /** When `true`, wraps the result in `{ data, error }`. */
86
+ protected readonly _safe: boolean;
87
+
88
+ constructor(execute: () => Promise<T>, safe = false) {
89
+ this._execute = execute;
90
+ this._safe = safe;
91
+ }
92
+
93
+ /**
94
+ * Implements the `PromiseLike` interface.
95
+ *
96
+ * Triggers the deferred execution and forwards to the native
97
+ * `Promise.then()`. When `_safe` is enabled, catches errors and
98
+ * resolves to `{ data, error }` instead of rejecting.
99
+ */
100
+ then<TResult1 = T, TResult2 = never>(
101
+ onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
102
+ onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null
103
+ ): Promise<TResult1 | TResult2> {
104
+ if (!this._safe) {
105
+ return this._execute().then(
106
+ onfulfilled as (value: T) => TResult1 | PromiseLike<TResult1>,
107
+ onrejected as (reason: unknown) => TResult2 | PromiseLike<TResult2>
108
+ );
109
+ }
110
+
111
+ return this._execute()
112
+ .then((data) => ({ data, error: undefined }) as unknown as T)
113
+ .catch((error: unknown) => ({ data: undefined, error }) as unknown as T)
114
+ .then(
115
+ onfulfilled as (value: T) => TResult1 | PromiseLike<TResult1>,
116
+ onrejected as (reason: unknown) => TResult2 | PromiseLike<TResult2>
117
+ );
118
+ }
119
+ }
120
+
121
+ // ---------------------------------------------------------------------------
122
+ // QueryResult
123
+ // ---------------------------------------------------------------------------
124
+
125
+ /**
126
+ * A thenable query result that supports chaining query modifiers.
127
+ *
128
+ * Each modifier returns a **new** `QueryResult` with the updated state,
129
+ * keeping the original immutable.
130
+ *
131
+ * @typeParam T - The resolved result type.
132
+ */
133
+ export class QueryResult<T> extends ThenableResult<T> {
134
+ /** The current accumulated query state. */
135
+ private readonly state: QueryState;
136
+
137
+ /** The runner function that executes the query with the given state. */
138
+ private readonly runner: (state: QueryState) => Promise<T>;
139
+
140
+ constructor(state: QueryState, runner: (state: QueryState) => Promise<T>) {
141
+ super(() => runner(state), state.safe);
142
+ this.state = state;
143
+ this.runner = runner;
144
+ }
145
+
146
+ /**
147
+ * Includes related entities via LEFT JOINs.
148
+ *
149
+ * @param value - A relation selection map (e.g. `{ posts: true }`).
150
+ * @returns A new `QueryResult` with the `.with()` state applied.
151
+ */
152
+ with(value: AnyRecord): QueryResult<T> {
153
+ return new QueryResult({ ...this.state, with: value }, this.runner);
154
+ }
155
+
156
+ /**
157
+ * Whitelists specific fields in the result.
158
+ *
159
+ * @param value - A map of `{ fieldName: true }`.
160
+ * @returns A new `QueryResult` with the `.select()` state applied.
161
+ */
162
+ select(value: AnyRecord): QueryResult<T> {
163
+ return new QueryResult({ ...this.state, select: value }, this.runner);
164
+ }
165
+
166
+ /**
167
+ * Blacklists specific fields from the result.
168
+ *
169
+ * @param value - A map of `{ fieldName: true }`.
170
+ * @returns A new `QueryResult` with the `.exclude()` state applied.
171
+ */
172
+ exclude(value: AnyRecord): QueryResult<T> {
173
+ return new QueryResult({ ...this.state, exclude: value }, this.runner);
174
+ }
175
+
176
+ /**
177
+ * Disables format transformations for this query.
178
+ *
179
+ * @returns A new `QueryResult` with `raw` set to `true`.
180
+ */
181
+ raw(): QueryResult<T> {
182
+ return new QueryResult({ ...this.state, raw: true }, this.runner);
183
+ }
184
+
185
+ /**
186
+ * Wraps the result in a `{ data, error }` discriminated union.
187
+ *
188
+ * When the query succeeds, resolves to `{ data: T, error: undefined }`.
189
+ * When it fails, resolves to `{ data: undefined, error: unknown }`
190
+ * instead of rejecting.
191
+ *
192
+ * @returns A new `QueryResult` with safe error handling enabled.
193
+ */
194
+ safe(): QueryResult<SafeResult<T>> {
195
+ return new QueryResult(
196
+ { ...this.state, safe: true },
197
+ this.runner as (state: QueryState) => Promise<SafeResult<T>>
198
+ );
199
+ }
200
+
201
+ /**
202
+ * Returns the current query state for debugging purposes.
203
+ *
204
+ * @returns The accumulated {@link QueryState}.
205
+ */
206
+ debug(): QueryState {
207
+ return this.state;
208
+ }
209
+ }
210
+
211
+ // ---------------------------------------------------------------------------
212
+ // MutateResult
213
+ // ---------------------------------------------------------------------------
214
+
215
+ /**
216
+ * A thenable mutation result that supports chaining mutation modifiers.
217
+ *
218
+ * Each modifier returns a **new** `MutateResult` with the updated state,
219
+ * keeping the original immutable.
220
+ *
221
+ * @typeParam T - The resolved result type.
222
+ */
223
+ export class MutateResult<T> extends ThenableResult<T> {
224
+ /** The current accumulated mutation state. */
225
+ private readonly state: MutateState;
226
+
227
+ /** The runner function that executes the mutation with the given state. */
228
+ private readonly runner: (state: MutateState) => Promise<T>;
229
+
230
+ constructor(state: MutateState, runner: (state: MutateState) => Promise<T>) {
231
+ super(() => runner(state), state.safe);
232
+ this.state = state;
233
+ this.runner = runner;
234
+ }
235
+
236
+ /**
237
+ * Specifies which columns to return from the mutation (as an array).
238
+ *
239
+ * @param value - Optional column selection map for `.returning()`.
240
+ * @returns A new `MutateResult` with the return selection applied.
241
+ */
242
+ return(value?: AnyRecord): MutateResult<T> {
243
+ return new MutateResult(
244
+ { ...this.state, returnSelect: value, hasReturn: true },
245
+ this.runner
246
+ );
247
+ }
248
+
249
+ /**
250
+ * Specifies which columns to return, resolving to only the **first** row.
251
+ *
252
+ * Behaves like `.return()` but unwraps the array to a single object.
253
+ *
254
+ * @param value - Optional column selection map for `.returning()`.
255
+ * @returns A new `MutateResult` with `returnFirst` enabled.
256
+ */
257
+ returnFirst(value?: AnyRecord): MutateResult<T> {
258
+ return new MutateResult(
259
+ {
260
+ ...this.state,
261
+ returnSelect: value,
262
+ returnFirst: true,
263
+ hasReturn: true,
264
+ },
265
+ this.runner
266
+ );
267
+ }
268
+
269
+ /**
270
+ * Excludes specific fields from the mutation result **after** execution.
271
+ *
272
+ * Unlike `.exclude()` on queries (which affects the SQL projection),
273
+ * `.omit()` removes keys from the result objects in-memory.
274
+ *
275
+ * @param value - A map of `{ fieldName: true }` for fields to remove.
276
+ * @returns A new `MutateResult` with the omit map applied.
277
+ */
278
+ omit(value: AnyRecord): MutateResult<T> {
279
+ return new MutateResult({ ...this.state, omit: value }, this.runner);
280
+ }
281
+
282
+ /**
283
+ * Wraps the result in a `{ data, error }` discriminated union.
284
+ *
285
+ * When the mutation succeeds, resolves to `{ data: T, error: undefined }`.
286
+ * When it fails, resolves to `{ data: undefined, error: unknown }`
287
+ * instead of rejecting.
288
+ *
289
+ * @returns A new `MutateResult` with safe error handling enabled.
290
+ */
291
+ safe(): MutateResult<SafeResult<T>> {
292
+ return new MutateResult(
293
+ { ...this.state, safe: true },
294
+ this.runner as (state: MutateState) => Promise<SafeResult<T>>
295
+ );
296
+ }
297
+ }