@effect-app/infra 2.0.1 → 2.1.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/CHANGELOG.md +12 -0
- package/_cjs/api/internal/RequestContextMiddleware.cjs +2 -3
- package/_cjs/api/internal/RequestContextMiddleware.cjs.map +1 -1
- package/_cjs/api/internal/events.cjs +2 -2
- package/_cjs/api/internal/events.cjs.map +1 -1
- package/_cjs/api/setupRequest.cjs +1 -1
- package/_cjs/api/setupRequest.cjs.map +1 -1
- package/_cjs/fileUtil.cjs +48 -0
- package/_cjs/fileUtil.cjs.map +1 -0
- package/_cjs/logger/shared.cjs +2 -2
- package/_cjs/logger/shared.cjs.map +1 -1
- package/_cjs/services/CUPS.cjs +118 -0
- package/_cjs/services/CUPS.cjs.map +1 -0
- package/_cjs/services/QueueMaker/SQLQueue.cjs +1 -1
- package/_cjs/services/QueueMaker/SQLQueue.cjs.map +1 -1
- package/_cjs/services/QueueMaker/memQueue.cjs +1 -1
- package/_cjs/services/QueueMaker/memQueue.cjs.map +1 -1
- package/_cjs/services/QueueMaker/sbqueue.cjs +1 -1
- package/_cjs/services/QueueMaker/sbqueue.cjs.map +1 -1
- package/_cjs/services/Store/Cosmos.cjs +1 -1
- package/_cjs/services/Store/Cosmos.cjs.map +1 -1
- package/_cjs/services/Store/Disk.cjs +1 -1
- package/_cjs/services/adapters/SQL/Model.cjs +500 -0
- package/_cjs/services/adapters/SQL/Model.cjs.map +1 -0
- package/_cjs/services/adapters/SQL.cjs +11 -0
- package/_cjs/services/adapters/SQL.cjs.map +1 -0
- package/_cjs/services/adapters/ServiceBus.cjs +76 -0
- package/_cjs/services/adapters/ServiceBus.cjs.map +1 -0
- package/_cjs/services/adapters/cosmos-client.cjs +18 -0
- package/_cjs/services/adapters/cosmos-client.cjs.map +1 -0
- package/_cjs/services/adapters/index.cjs +6 -0
- package/_cjs/services/adapters/index.cjs.map +1 -0
- package/_cjs/services/adapters/logger.cjs +9 -0
- package/_cjs/services/adapters/logger.cjs.map +1 -0
- package/_cjs/services/adapters/memQueue.cjs +31 -0
- package/_cjs/services/adapters/memQueue.cjs.map +1 -0
- package/_cjs/services/adapters/mongo-client.cjs +20 -0
- package/_cjs/services/adapters/mongo-client.cjs.map +1 -0
- package/_cjs/services/adapters/redis-client.cjs +83 -0
- package/_cjs/services/adapters/redis-client.cjs.map +1 -0
- package/dist/api/internal/RequestContextMiddleware.d.ts.map +1 -1
- package/dist/api/internal/RequestContextMiddleware.js +3 -4
- package/dist/api/internal/events.d.ts.map +1 -1
- package/dist/api/internal/events.js +3 -3
- package/dist/api/setupRequest.d.ts +1 -2
- package/dist/api/setupRequest.d.ts.map +1 -1
- package/dist/api/setupRequest.js +3 -3
- package/dist/fileUtil.d.ts +23 -0
- package/dist/fileUtil.d.ts.map +1 -0
- package/dist/fileUtil.js +41 -0
- package/dist/logger/shared.d.ts.map +1 -1
- package/dist/logger/shared.js +2 -2
- package/dist/services/CUPS.d.ts +26 -0
- package/dist/services/CUPS.d.ts.map +1 -0
- package/dist/services/CUPS.js +111 -0
- package/dist/services/QueueMaker/SQLQueue.d.ts.map +1 -1
- package/dist/services/QueueMaker/SQLQueue.js +2 -2
- package/dist/services/QueueMaker/memQueue.d.ts +1 -1
- package/dist/services/QueueMaker/memQueue.d.ts.map +1 -1
- package/dist/services/QueueMaker/memQueue.js +2 -2
- package/dist/services/QueueMaker/sbqueue.d.ts +3 -3
- package/dist/services/QueueMaker/sbqueue.d.ts.map +1 -1
- package/dist/services/QueueMaker/sbqueue.js +2 -2
- package/dist/services/Store/Cosmos.d.ts.map +1 -1
- package/dist/services/Store/Cosmos.js +2 -2
- package/dist/services/Store/Disk.js +2 -2
- package/dist/services/adapters/SQL/Model.d.ts +538 -0
- package/dist/services/adapters/SQL/Model.d.ts.map +1 -0
- package/dist/services/adapters/SQL/Model.js +508 -0
- package/dist/services/adapters/SQL.d.ts +2 -0
- package/dist/services/adapters/SQL.d.ts.map +1 -0
- package/dist/services/adapters/SQL.js +2 -0
- package/dist/services/adapters/ServiceBus.d.ts +50 -0
- package/dist/services/adapters/ServiceBus.d.ts.map +1 -0
- package/dist/services/adapters/ServiceBus.js +73 -0
- package/dist/services/adapters/cosmos-client.d.ts +10 -0
- package/dist/services/adapters/cosmos-client.d.ts.map +1 -0
- package/dist/services/adapters/cosmos-client.js +8 -0
- package/dist/services/adapters/index.d.ts +2 -0
- package/dist/services/adapters/index.d.ts.map +1 -0
- package/dist/services/adapters/index.js +2 -0
- package/dist/services/adapters/logger.d.ts +8 -0
- package/dist/services/adapters/logger.d.ts.map +1 -0
- package/dist/services/adapters/logger.js +3 -0
- package/dist/services/adapters/memQueue.d.ts +34 -0
- package/dist/services/adapters/memQueue.d.ts.map +1 -0
- package/dist/services/adapters/memQueue.js +24 -0
- package/dist/services/adapters/mongo-client.d.ts +10 -0
- package/dist/services/adapters/mongo-client.d.ts.map +1 -0
- package/dist/services/adapters/mongo-client.js +12 -0
- package/dist/services/adapters/redis-client.d.ts +29 -0
- package/dist/services/adapters/redis-client.d.ts.map +1 -0
- package/dist/services/adapters/redis-client.js +93 -0
- package/package.json +128 -12
- package/src/api/internal/RequestContextMiddleware.ts +2 -3
- package/src/api/internal/events.ts +2 -2
- package/src/api/setupRequest.ts +2 -3
- package/src/fileUtil.ts +85 -0
- package/src/logger/shared.ts +2 -3
- package/src/services/CUPS.ts +151 -0
- package/src/services/QueueMaker/SQLQueue.ts +1 -1
- package/src/services/QueueMaker/memQueue.ts +1 -1
- package/src/services/QueueMaker/sbqueue.ts +7 -7
- package/src/services/Store/Cosmos.ts +1 -1
- package/src/services/Store/Disk.ts +1 -1
- package/src/services/adapters/SQL/Model.ts +939 -0
- package/src/services/adapters/SQL.ts +1 -0
- package/src/services/adapters/ServiceBus.ts +140 -0
- package/src/services/adapters/cosmos-client.ts +16 -0
- package/src/services/adapters/index.ts +0 -0
- package/src/services/adapters/logger.ts +3 -0
- package/src/services/adapters/memQueue.ts +26 -0
- package/src/services/adapters/mongo-client.ts +23 -0
- package/src/services/adapters/redis-client.ts +123 -0
- package/tsconfig.src.json +0 -3
- package/src/services/Store/Redis.ts.bak +0 -88
- package/src/services/simpledb/cosmosdb.ts.bak +0 -149
- package/src/services/simpledb/diskdb.ts.bak +0 -165
- package/src/services/simpledb/index.ts.bak +0 -6
- package/src/services/simpledb/memdb.ts.bak +0 -78
- package/src/services/simpledb/mongodb.ts.bak +0 -107
- package/src/services/simpledb/redisdb.ts.bak +0 -202
- package/src/services/simpledb/shared.ts.bak +0 -117
- package/src/services/simpledb/simpledb.ts.bak +0 -121
|
@@ -0,0 +1,939 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
6
|
+
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
7
|
+
/**
|
|
8
|
+
* @since 1.0.0
|
|
9
|
+
*/
|
|
10
|
+
import * as RRX from "@effect/experimental/RequestResolver"
|
|
11
|
+
import * as VariantSchema from "@effect/experimental/VariantSchema"
|
|
12
|
+
import { SqlClient } from "@effect/sql/SqlClient"
|
|
13
|
+
import * as SqlResolver from "@effect/sql/SqlResolver"
|
|
14
|
+
import * as SqlSchema from "@effect/sql/SqlSchema"
|
|
15
|
+
import { randomUUID } from "crypto" // TODO
|
|
16
|
+
import type { Brand } from "effect/Brand"
|
|
17
|
+
import * as DateTime from "effect/DateTime"
|
|
18
|
+
import type { DurationInput } from "effect/Duration"
|
|
19
|
+
import * as Effect from "effect/Effect"
|
|
20
|
+
import * as Option from "effect/Option"
|
|
21
|
+
import * as ParseResult from "effect/ParseResult"
|
|
22
|
+
import * as Schema from "effect/Schema"
|
|
23
|
+
import type { Scope } from "effect/Scope"
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
Class,
|
|
27
|
+
Field,
|
|
28
|
+
FieldExcept,
|
|
29
|
+
FieldOnly,
|
|
30
|
+
Struct,
|
|
31
|
+
Union,
|
|
32
|
+
extract,
|
|
33
|
+
fieldEvolve,
|
|
34
|
+
fieldFromKey
|
|
35
|
+
} = VariantSchema.make({
|
|
36
|
+
variants: ["select", "insert", "update", "json", "jsonCreate", "jsonUpdate"],
|
|
37
|
+
defaultVariant: "select"
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @since 1.0.0
|
|
42
|
+
* @category models
|
|
43
|
+
*/
|
|
44
|
+
export type Any = Schema.Schema.Any & {
|
|
45
|
+
readonly fields: Schema.Struct.Fields
|
|
46
|
+
readonly insert: Schema.Schema.Any
|
|
47
|
+
readonly update: Schema.Schema.Any
|
|
48
|
+
readonly json: Schema.Schema.Any
|
|
49
|
+
readonly jsonCreate: Schema.Schema.Any
|
|
50
|
+
readonly jsonUpdate: Schema.Schema.Any
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @since 1.0.0
|
|
55
|
+
* @category models
|
|
56
|
+
*/
|
|
57
|
+
export type AnyNoContext = Schema.Schema.AnyNoContext & {
|
|
58
|
+
readonly fields: Schema.Struct.Fields
|
|
59
|
+
readonly insert: Schema.Schema.AnyNoContext
|
|
60
|
+
readonly update: Schema.Schema.AnyNoContext
|
|
61
|
+
readonly json: Schema.Schema.AnyNoContext
|
|
62
|
+
readonly jsonCreate: Schema.Schema.AnyNoContext
|
|
63
|
+
readonly jsonUpdate: Schema.Schema.AnyNoContext
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @since 1.0.0
|
|
68
|
+
* @category models
|
|
69
|
+
*/
|
|
70
|
+
export type VariantsDatabase = "select" | "insert" | "update"
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @since 1.0.0
|
|
74
|
+
* @category models
|
|
75
|
+
*/
|
|
76
|
+
export type VariantsJson = "json" | "jsonCreate" | "jsonUpdate"
|
|
77
|
+
|
|
78
|
+
export {
|
|
79
|
+
/**
|
|
80
|
+
* A base class used for creating domain model schemas.
|
|
81
|
+
*
|
|
82
|
+
* It supports common variants for database and JSON apis.
|
|
83
|
+
*
|
|
84
|
+
* @since 1.0.0
|
|
85
|
+
* @category constructors
|
|
86
|
+
* @example
|
|
87
|
+
* import { Schema } from "effect/Schema"
|
|
88
|
+
* import { Model } from "@effect/sql"
|
|
89
|
+
*
|
|
90
|
+
* export const GroupId = Schema.Number.pipe(Schema.brand("GroupId"))
|
|
91
|
+
*
|
|
92
|
+
* export class Group extends Model.Class<Group>("Group")({
|
|
93
|
+
* id: Model.Generated(GroupId),
|
|
94
|
+
* name: Schema.NonEmptyTrimmedString,
|
|
95
|
+
* createdAt: Model.DateTimeInsertFromDate,
|
|
96
|
+
* updatedAt: Model.DateTimeUpdateFromDate
|
|
97
|
+
* }) {}
|
|
98
|
+
*
|
|
99
|
+
* // schema used for selects
|
|
100
|
+
* Group
|
|
101
|
+
*
|
|
102
|
+
* // schema used for inserts
|
|
103
|
+
* Group.insert
|
|
104
|
+
*
|
|
105
|
+
* // schema used for updates
|
|
106
|
+
* Group.update
|
|
107
|
+
*
|
|
108
|
+
* // schema used for json api
|
|
109
|
+
* Group.json
|
|
110
|
+
* Group.jsonCreate
|
|
111
|
+
* Group.jsonUpdate
|
|
112
|
+
*
|
|
113
|
+
* // you can also turn them into classes
|
|
114
|
+
* class GroupJson extends Schema.Class<GroupJson>("GroupJson")(Group.json) {
|
|
115
|
+
* get upperName() {
|
|
116
|
+
* return this.name.toUpperCase()
|
|
117
|
+
* }
|
|
118
|
+
* }
|
|
119
|
+
*/
|
|
120
|
+
Class,
|
|
121
|
+
/**
|
|
122
|
+
* @since 1.0.0
|
|
123
|
+
* @category extraction
|
|
124
|
+
*/
|
|
125
|
+
extract,
|
|
126
|
+
/**
|
|
127
|
+
* @since 1.0.0
|
|
128
|
+
* @category fields
|
|
129
|
+
*/
|
|
130
|
+
Field,
|
|
131
|
+
/**
|
|
132
|
+
* @since 1.0.0
|
|
133
|
+
* @category fields
|
|
134
|
+
*/
|
|
135
|
+
fieldEvolve,
|
|
136
|
+
/**
|
|
137
|
+
* @since 1.0.0
|
|
138
|
+
* @category fields
|
|
139
|
+
*/
|
|
140
|
+
FieldExcept,
|
|
141
|
+
/**
|
|
142
|
+
* @since 1.0.0
|
|
143
|
+
* @category fields
|
|
144
|
+
*/
|
|
145
|
+
fieldFromKey,
|
|
146
|
+
/**
|
|
147
|
+
* @since 1.0.0
|
|
148
|
+
* @category fields
|
|
149
|
+
*/
|
|
150
|
+
FieldOnly,
|
|
151
|
+
/**
|
|
152
|
+
* @since 1.0.0
|
|
153
|
+
* @category constructors
|
|
154
|
+
*/
|
|
155
|
+
Struct,
|
|
156
|
+
/**
|
|
157
|
+
* @since 1.0.0
|
|
158
|
+
* @category constructors
|
|
159
|
+
*/
|
|
160
|
+
Union
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* @since 1.0.0
|
|
165
|
+
* @category fields
|
|
166
|
+
*/
|
|
167
|
+
export const fields: <A extends VariantSchema.Struct<any>>(self: A) => A[VariantSchema.TypeId] = VariantSchema.fields
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* @since 1.0.0
|
|
171
|
+
* @category overrideable
|
|
172
|
+
*/
|
|
173
|
+
export const Override: <A>(value: A) => A & Brand<"Override"> = VariantSchema.Override
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* @since 1.0.0
|
|
177
|
+
* @category generated
|
|
178
|
+
*/
|
|
179
|
+
export interface Generated<S extends Schema.Schema.All | Schema.PropertySignature.All> extends
|
|
180
|
+
VariantSchema.Field<{
|
|
181
|
+
readonly select: S
|
|
182
|
+
readonly update: S
|
|
183
|
+
readonly json: S
|
|
184
|
+
}>
|
|
185
|
+
{}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* A field that represents a column that is generated by the database.
|
|
189
|
+
*
|
|
190
|
+
* It is available for selection and update, but not for insertion.
|
|
191
|
+
*
|
|
192
|
+
* @since 1.0.0
|
|
193
|
+
* @category generated
|
|
194
|
+
*/
|
|
195
|
+
export const Generated = <S extends Schema.Schema.All | Schema.PropertySignature.All>(
|
|
196
|
+
schema: S
|
|
197
|
+
): Generated<S> =>
|
|
198
|
+
Field({
|
|
199
|
+
select: schema,
|
|
200
|
+
update: schema,
|
|
201
|
+
json: schema
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* @since 1.0.0
|
|
206
|
+
* @category generated
|
|
207
|
+
*/
|
|
208
|
+
export interface GeneratedByApp<S extends Schema.Schema.All | Schema.PropertySignature.All>
|
|
209
|
+
extends
|
|
210
|
+
VariantSchema.Field<{
|
|
211
|
+
readonly select: S
|
|
212
|
+
readonly insert: S
|
|
213
|
+
readonly update: S
|
|
214
|
+
readonly json: S
|
|
215
|
+
}>
|
|
216
|
+
{}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* A field that represents a column that is generated by the application.
|
|
220
|
+
*
|
|
221
|
+
* It is required by the database, but not by the JSON variants.
|
|
222
|
+
*
|
|
223
|
+
* @since 1.0.0
|
|
224
|
+
* @category generated
|
|
225
|
+
*/
|
|
226
|
+
export const GeneratedByApp = <S extends Schema.Schema.All | Schema.PropertySignature.All>(
|
|
227
|
+
schema: S
|
|
228
|
+
): GeneratedByApp<S> =>
|
|
229
|
+
Field({
|
|
230
|
+
select: schema,
|
|
231
|
+
insert: schema,
|
|
232
|
+
update: schema,
|
|
233
|
+
json: schema
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* @since 1.0.0
|
|
238
|
+
* @category sensitive
|
|
239
|
+
*/
|
|
240
|
+
export interface Sensitive<S extends Schema.Schema.All | Schema.PropertySignature.All> extends
|
|
241
|
+
VariantSchema.Field<{
|
|
242
|
+
readonly select: S
|
|
243
|
+
readonly insert: S
|
|
244
|
+
readonly update: S
|
|
245
|
+
}>
|
|
246
|
+
{}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* A field that represents a sensitive value that should not be exposed in the
|
|
250
|
+
* JSON variants.
|
|
251
|
+
*
|
|
252
|
+
* @since 1.0.0
|
|
253
|
+
* @category sensitive
|
|
254
|
+
*/
|
|
255
|
+
export const Sensitive = <S extends Schema.Schema.All | Schema.PropertySignature.All>(
|
|
256
|
+
schema: S
|
|
257
|
+
): Sensitive<S> =>
|
|
258
|
+
Field({
|
|
259
|
+
select: schema,
|
|
260
|
+
insert: schema,
|
|
261
|
+
update: schema
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Convert a field to one that is optional for all variants.
|
|
266
|
+
*
|
|
267
|
+
* For the database variants, it will accept `null`able values.
|
|
268
|
+
* For the JSON variants, it will also accept missing keys.
|
|
269
|
+
*
|
|
270
|
+
* @since 1.0.0
|
|
271
|
+
* @category optional
|
|
272
|
+
*/
|
|
273
|
+
export interface FieldOption<S extends Schema.Schema.Any> extends
|
|
274
|
+
VariantSchema.Field<{
|
|
275
|
+
readonly select: Schema.OptionFromNullOr<S>
|
|
276
|
+
readonly insert: Schema.OptionFromNullOr<S>
|
|
277
|
+
readonly update: Schema.OptionFromNullOr<S>
|
|
278
|
+
readonly json: Schema.optionalWith<S, { as: "Option" }>
|
|
279
|
+
readonly jsonCreate: Schema.optionalWith<S, { as: "Option"; nullable: true }>
|
|
280
|
+
readonly jsonUpdate: Schema.optionalWith<S, { as: "Option"; nullable: true }>
|
|
281
|
+
}>
|
|
282
|
+
{}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Convert a field to one that is optional for all variants.
|
|
286
|
+
*
|
|
287
|
+
* For the database variants, it will accept `null`able values.
|
|
288
|
+
* For the JSON variants, it will also accept missing keys.
|
|
289
|
+
*
|
|
290
|
+
* @since 1.0.0
|
|
291
|
+
* @category optional
|
|
292
|
+
*/
|
|
293
|
+
export const FieldOption: <Field extends VariantSchema.Field<any> | Schema.Schema.Any>(
|
|
294
|
+
self: Field
|
|
295
|
+
) => Field extends Schema.Schema.Any ? FieldOption<Field>
|
|
296
|
+
: Field extends VariantSchema.Field<infer S> ? VariantSchema.Field<
|
|
297
|
+
{
|
|
298
|
+
readonly [K in keyof S]: S[K] extends Schema.Schema.Any
|
|
299
|
+
? K extends VariantsDatabase ? Schema.OptionFromNullOr<S[K]>
|
|
300
|
+
: Schema.optionalWith<S[K], { as: "Option"; nullable: true }>
|
|
301
|
+
: never
|
|
302
|
+
}
|
|
303
|
+
>
|
|
304
|
+
: never = fieldEvolve({
|
|
305
|
+
select: Schema.OptionFromNullOr,
|
|
306
|
+
insert: Schema.OptionFromNullOr,
|
|
307
|
+
update: Schema.OptionFromNullOr,
|
|
308
|
+
json: Schema.optionalWith({ as: "Option" }),
|
|
309
|
+
jsonCreate: Schema.optionalWith({ as: "Option", nullable: true }),
|
|
310
|
+
jsonUpdate: Schema.optionalWith({ as: "Option", nullable: true })
|
|
311
|
+
}) as any
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* @since 1.0.0
|
|
315
|
+
* @category date & time
|
|
316
|
+
*/
|
|
317
|
+
export interface DateTimeFromDate extends
|
|
318
|
+
Schema.transform<
|
|
319
|
+
typeof Schema.ValidDateFromSelf,
|
|
320
|
+
typeof Schema.DateTimeUtcFromSelf
|
|
321
|
+
>
|
|
322
|
+
{}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* @since 1.0.0
|
|
326
|
+
* @category date & time
|
|
327
|
+
*/
|
|
328
|
+
export const DateTimeFromDate: DateTimeFromDate = Schema.transform(
|
|
329
|
+
Schema.ValidDateFromSelf,
|
|
330
|
+
Schema.DateTimeUtcFromSelf,
|
|
331
|
+
{
|
|
332
|
+
decode: DateTime.unsafeFromDate,
|
|
333
|
+
encode: DateTime.toDateUtc
|
|
334
|
+
}
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* @since 1.0.0
|
|
339
|
+
* @category date & time
|
|
340
|
+
*/
|
|
341
|
+
export interface Date extends Schema.transformOrFail<typeof Schema.String, typeof Schema.DateTimeUtcFromSelf> {}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* A schema for a `DateTime.Utc` that is serialized as a date string in the
|
|
345
|
+
* format `YYYY-MM-DD`.
|
|
346
|
+
*
|
|
347
|
+
* @since 1.0.0
|
|
348
|
+
* @category date & time
|
|
349
|
+
*/
|
|
350
|
+
export const Date: Date = Schema.transformOrFail(
|
|
351
|
+
Schema.String,
|
|
352
|
+
Schema.DateTimeUtcFromSelf,
|
|
353
|
+
{
|
|
354
|
+
decode: (s, _, ast) =>
|
|
355
|
+
DateTime.make(s).pipe(
|
|
356
|
+
Option.map(DateTime.removeTime),
|
|
357
|
+
Option.match({
|
|
358
|
+
onNone: () => ParseResult.fail(new ParseResult.Type(ast, s)),
|
|
359
|
+
onSome: (dt) => ParseResult.succeed(dt)
|
|
360
|
+
})
|
|
361
|
+
),
|
|
362
|
+
encode: (dt) => ParseResult.succeed(DateTime.formatIsoDate(dt))
|
|
363
|
+
}
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* @since 1.0.0
|
|
368
|
+
* @category date & time
|
|
369
|
+
*/
|
|
370
|
+
export const DateWithNow = VariantSchema.Overrideable(Date, Schema.DateTimeUtcFromSelf, {
|
|
371
|
+
generate: Option.match({
|
|
372
|
+
onNone: () => Effect.map(DateTime.now, DateTime.removeTime),
|
|
373
|
+
onSome: (dt) => Effect.succeed(DateTime.removeTime(dt))
|
|
374
|
+
})
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* @since 1.0.0
|
|
379
|
+
* @category date & time
|
|
380
|
+
*/
|
|
381
|
+
export const DateTimeWithNow = VariantSchema.Overrideable(Schema.String, Schema.DateTimeUtcFromSelf, {
|
|
382
|
+
generate: Option.match({
|
|
383
|
+
onNone: () => Effect.map(DateTime.now, DateTime.formatIso),
|
|
384
|
+
onSome: (dt) => Effect.succeed(DateTime.formatIso(dt))
|
|
385
|
+
})
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* @since 1.0.0
|
|
390
|
+
* @category date & time
|
|
391
|
+
*/
|
|
392
|
+
export const DateTimeFromDateWithNow = VariantSchema.Overrideable(Schema.DateFromSelf, Schema.DateTimeUtcFromSelf, {
|
|
393
|
+
generate: Option.match({
|
|
394
|
+
onNone: () => Effect.map(DateTime.now, DateTime.toDateUtc),
|
|
395
|
+
onSome: (dt) => Effect.succeed(DateTime.toDateUtc(dt))
|
|
396
|
+
})
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* @since 1.0.0
|
|
401
|
+
* @category date & time
|
|
402
|
+
*/
|
|
403
|
+
export const DateTimeFromNumberWithNow = VariantSchema.Overrideable(Schema.Number, Schema.DateTimeUtcFromSelf, {
|
|
404
|
+
generate: Option.match({
|
|
405
|
+
onNone: () => Effect.map(DateTime.now, DateTime.toEpochMillis),
|
|
406
|
+
onSome: (dt) => Effect.succeed(DateTime.toEpochMillis(dt))
|
|
407
|
+
})
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* @since 1.0.0
|
|
412
|
+
* @category date & time
|
|
413
|
+
*/
|
|
414
|
+
export interface DateTimeInsert extends
|
|
415
|
+
VariantSchema.Field<{
|
|
416
|
+
readonly select: typeof Schema.DateTimeUtc
|
|
417
|
+
readonly insert: VariantSchema.Overrideable<DateTime.Utc, string>
|
|
418
|
+
readonly json: typeof Schema.DateTimeUtc
|
|
419
|
+
}>
|
|
420
|
+
{}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* A field that represents a date-time value that is inserted as the current
|
|
424
|
+
* `DateTime.Utc`. It is serialized as a string for the database.
|
|
425
|
+
*
|
|
426
|
+
* It is omitted from updates and is available for selection.
|
|
427
|
+
*
|
|
428
|
+
* @since 1.0.0
|
|
429
|
+
* @category date & time
|
|
430
|
+
*/
|
|
431
|
+
export const DateTimeInsert: DateTimeInsert = Field({
|
|
432
|
+
select: Schema.DateTimeUtc,
|
|
433
|
+
insert: DateTimeWithNow,
|
|
434
|
+
json: Schema.DateTimeUtc
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* @since 1.0.0
|
|
439
|
+
* @category date & time
|
|
440
|
+
*/
|
|
441
|
+
export interface DateTimeInsertFromDate extends
|
|
442
|
+
VariantSchema.Field<{
|
|
443
|
+
readonly select: DateTimeFromDate
|
|
444
|
+
readonly insert: VariantSchema.Overrideable<DateTime.Utc, globalThis.Date>
|
|
445
|
+
readonly json: typeof Schema.DateTimeUtc
|
|
446
|
+
}>
|
|
447
|
+
{}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* A field that represents a date-time value that is inserted as the current
|
|
451
|
+
* `DateTime.Utc`. It is serialized as a `Date` for the database.
|
|
452
|
+
*
|
|
453
|
+
* It is omitted from updates and is available for selection.
|
|
454
|
+
*
|
|
455
|
+
* @since 1.0.0
|
|
456
|
+
* @category date & time
|
|
457
|
+
*/
|
|
458
|
+
export const DateTimeInsertFromDate: DateTimeInsertFromDate = Field({
|
|
459
|
+
select: DateTimeFromDate,
|
|
460
|
+
insert: DateTimeFromDateWithNow,
|
|
461
|
+
json: Schema.DateTimeUtc
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* @since 1.0.0
|
|
466
|
+
* @category date & time
|
|
467
|
+
*/
|
|
468
|
+
export interface DateTimeInsertFromNumber extends
|
|
469
|
+
VariantSchema.Field<{
|
|
470
|
+
readonly select: typeof Schema.DateTimeUtcFromNumber
|
|
471
|
+
readonly insert: VariantSchema.Overrideable<DateTime.Utc, number>
|
|
472
|
+
readonly json: typeof Schema.DateTimeUtcFromNumber
|
|
473
|
+
}>
|
|
474
|
+
{}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* A field that represents a date-time value that is inserted as the current
|
|
478
|
+
* `DateTime.Utc`. It is serialized as a `number`.
|
|
479
|
+
*
|
|
480
|
+
* It is omitted from updates and is available for selection.
|
|
481
|
+
*
|
|
482
|
+
* @since 1.0.0
|
|
483
|
+
* @category date & time
|
|
484
|
+
*/
|
|
485
|
+
export const DateTimeInsertFromNumber: DateTimeInsertFromNumber = Field({
|
|
486
|
+
select: Schema.DateTimeUtcFromNumber,
|
|
487
|
+
insert: DateTimeFromNumberWithNow,
|
|
488
|
+
json: Schema.DateTimeUtcFromNumber
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* @since 1.0.0
|
|
493
|
+
* @category date & time
|
|
494
|
+
*/
|
|
495
|
+
export interface DateTimeUpdate extends
|
|
496
|
+
VariantSchema.Field<{
|
|
497
|
+
readonly select: typeof Schema.DateTimeUtc
|
|
498
|
+
readonly insert: VariantSchema.Overrideable<DateTime.Utc, string>
|
|
499
|
+
readonly update: VariantSchema.Overrideable<DateTime.Utc, string>
|
|
500
|
+
readonly json: typeof Schema.DateTimeUtc
|
|
501
|
+
}>
|
|
502
|
+
{}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* A field that represents a date-time value that is updated as the current
|
|
506
|
+
* `DateTime.Utc`. It is serialized as a string for the database.
|
|
507
|
+
*
|
|
508
|
+
* It is set to the current `DateTime.Utc` on updates and inserts and is
|
|
509
|
+
* available for selection.
|
|
510
|
+
*
|
|
511
|
+
* @since 1.0.0
|
|
512
|
+
* @category date & time
|
|
513
|
+
*/
|
|
514
|
+
export const DateTimeUpdate: DateTimeUpdate = Field({
|
|
515
|
+
select: Schema.DateTimeUtc,
|
|
516
|
+
insert: DateTimeWithNow,
|
|
517
|
+
update: DateTimeWithNow,
|
|
518
|
+
json: Schema.DateTimeUtc
|
|
519
|
+
})
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* @since 1.0.0
|
|
523
|
+
* @category date & time
|
|
524
|
+
*/
|
|
525
|
+
export interface DateTimeUpdateFromDate extends
|
|
526
|
+
VariantSchema.Field<{
|
|
527
|
+
readonly select: DateTimeFromDate
|
|
528
|
+
readonly insert: VariantSchema.Overrideable<DateTime.Utc, globalThis.Date>
|
|
529
|
+
readonly update: VariantSchema.Overrideable<DateTime.Utc, globalThis.Date>
|
|
530
|
+
readonly json: typeof Schema.DateTimeUtc
|
|
531
|
+
}>
|
|
532
|
+
{}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* A field that represents a date-time value that is updated as the current
|
|
536
|
+
* `DateTime.Utc`. It is serialized as a `Date` for the database.
|
|
537
|
+
*
|
|
538
|
+
* It is set to the current `DateTime.Utc` on updates and inserts and is
|
|
539
|
+
* available for selection.
|
|
540
|
+
*
|
|
541
|
+
* @since 1.0.0
|
|
542
|
+
* @category date & time
|
|
543
|
+
*/
|
|
544
|
+
export const DateTimeUpdateFromDate: DateTimeUpdateFromDate = Field({
|
|
545
|
+
select: DateTimeFromDate,
|
|
546
|
+
insert: DateTimeFromDateWithNow,
|
|
547
|
+
update: DateTimeFromDateWithNow,
|
|
548
|
+
json: Schema.DateTimeUtc
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* @since 1.0.0
|
|
553
|
+
* @category date & time
|
|
554
|
+
*/
|
|
555
|
+
export interface DateTimeUpdateFromNumber extends
|
|
556
|
+
VariantSchema.Field<{
|
|
557
|
+
readonly select: typeof Schema.DateTimeUtcFromNumber
|
|
558
|
+
readonly insert: VariantSchema.Overrideable<DateTime.Utc, number>
|
|
559
|
+
readonly update: VariantSchema.Overrideable<DateTime.Utc, number>
|
|
560
|
+
readonly json: typeof Schema.DateTimeUtcFromNumber
|
|
561
|
+
}>
|
|
562
|
+
{}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* A field that represents a date-time value that is updated as the current
|
|
566
|
+
* `DateTime.Utc`. It is serialized as a `number`.
|
|
567
|
+
*
|
|
568
|
+
* It is set to the current `DateTime.Utc` on updates and inserts and is
|
|
569
|
+
* available for selection.
|
|
570
|
+
*
|
|
571
|
+
* @since 1.0.0
|
|
572
|
+
* @category date & time
|
|
573
|
+
*/
|
|
574
|
+
export const DateTimeUpdateFromNumber: DateTimeUpdateFromNumber = Field({
|
|
575
|
+
select: Schema.DateTimeUtcFromNumber,
|
|
576
|
+
insert: DateTimeFromNumberWithNow,
|
|
577
|
+
update: DateTimeFromNumberWithNow,
|
|
578
|
+
json: Schema.DateTimeUtcFromNumber
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* @since 1.0.0
|
|
583
|
+
* @category json
|
|
584
|
+
*/
|
|
585
|
+
export interface JsonFromString<S extends Schema.Schema.All | Schema.PropertySignature.All>
|
|
586
|
+
extends
|
|
587
|
+
VariantSchema.Field<{
|
|
588
|
+
readonly select: Schema.Schema<Schema.Schema.Type<S>, string, Schema.Schema.Context<S>>
|
|
589
|
+
readonly insert: Schema.Schema<Schema.Schema.Type<S>, string, Schema.Schema.Context<S>>
|
|
590
|
+
readonly update: Schema.Schema<Schema.Schema.Type<S>, string, Schema.Schema.Context<S>>
|
|
591
|
+
readonly json: S
|
|
592
|
+
readonly jsonCreate: S
|
|
593
|
+
readonly jsonUpdate: S
|
|
594
|
+
}>
|
|
595
|
+
{}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* A field that represents a JSON value stored as text in the database.
|
|
599
|
+
*
|
|
600
|
+
* The "json" variants will use the object schema directly.
|
|
601
|
+
*
|
|
602
|
+
* @since 1.0.0
|
|
603
|
+
* @category json
|
|
604
|
+
*/
|
|
605
|
+
export const JsonFromString = <S extends Schema.Schema.All | Schema.PropertySignature.All>(
|
|
606
|
+
schema: S
|
|
607
|
+
): JsonFromString<S> => {
|
|
608
|
+
const parsed = Schema.parseJson(schema as any)
|
|
609
|
+
return Field({
|
|
610
|
+
select: parsed,
|
|
611
|
+
insert: parsed,
|
|
612
|
+
update: parsed,
|
|
613
|
+
json: schema,
|
|
614
|
+
jsonCreate: schema,
|
|
615
|
+
jsonUpdate: schema
|
|
616
|
+
}) as any
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Create a simple CRUD repository from a model.
|
|
621
|
+
*
|
|
622
|
+
* @since 1.0.0
|
|
623
|
+
* @category repository
|
|
624
|
+
*/
|
|
625
|
+
export const makeRepository = <
|
|
626
|
+
S extends Any,
|
|
627
|
+
Id extends (keyof S["Type"]) & (keyof S["update"]["Type"]) & (keyof S["fields"])
|
|
628
|
+
>(Model: S, options: {
|
|
629
|
+
readonly tableName: string
|
|
630
|
+
readonly spanPrefix: string
|
|
631
|
+
readonly idColumn: Id
|
|
632
|
+
readonly versionColumn?: string | undefined
|
|
633
|
+
}): Effect.Effect<
|
|
634
|
+
{
|
|
635
|
+
readonly insert: (
|
|
636
|
+
insert: S["insert"]["Type"]
|
|
637
|
+
) => Effect.Effect<S["Type"], never, S["Context"] | S["insert"]["Context"]>
|
|
638
|
+
readonly insertVoid: (
|
|
639
|
+
insert: S["insert"]["Type"]
|
|
640
|
+
) => Effect.Effect<void, never, S["Context"] | S["insert"]["Context"]>
|
|
641
|
+
readonly update: (
|
|
642
|
+
update: S["update"]["Type"]
|
|
643
|
+
) => Effect.Effect<S["Type"], never, S["Context"] | S["update"]["Context"]>
|
|
644
|
+
readonly updateVoid: (
|
|
645
|
+
update: S["update"]["Type"]
|
|
646
|
+
) => Effect.Effect<void, never, S["Context"] | S["update"]["Context"]>
|
|
647
|
+
readonly findById: (
|
|
648
|
+
id: Schema.Schema.Type<S["fields"][Id]>
|
|
649
|
+
) => Effect.Effect<Option.Option<S["Type"]>, never, S["Context"] | Schema.Schema.Context<S["fields"][Id]>>
|
|
650
|
+
readonly delete: (
|
|
651
|
+
id: Schema.Schema.Type<S["fields"][Id]>
|
|
652
|
+
) => Effect.Effect<void, never, Schema.Schema.Context<S["fields"][Id]>>
|
|
653
|
+
},
|
|
654
|
+
never,
|
|
655
|
+
SqlClient
|
|
656
|
+
> =>
|
|
657
|
+
Effect.gen(function*() {
|
|
658
|
+
const sql = yield* SqlClient
|
|
659
|
+
const idSchema = Model.fields[options.idColumn] as Schema.Schema.Any
|
|
660
|
+
const idColumn = options.idColumn as string
|
|
661
|
+
const versionColumn = options.versionColumn
|
|
662
|
+
|
|
663
|
+
// TODO: insert version automatically...
|
|
664
|
+
// I guess we should hide the versionColumn and insert it in the schema instead
|
|
665
|
+
const insertSchema = SqlSchema.single({
|
|
666
|
+
Request: Model.insert,
|
|
667
|
+
Result: Model,
|
|
668
|
+
execute: (request) =>
|
|
669
|
+
sql.onDialectOrElse({
|
|
670
|
+
mysql: () =>
|
|
671
|
+
sql`insert into ${sql(options.tableName)} ${sql.insert(request)};
|
|
672
|
+
select * from ${sql(options.tableName)} where ${sql(idColumn)} = LAST_INSERT_ID();`
|
|
673
|
+
.unprepared
|
|
674
|
+
.pipe(
|
|
675
|
+
Effect.map(([, results]) => results as any)
|
|
676
|
+
),
|
|
677
|
+
orElse: () => sql`insert into ${sql(options.tableName)} ${sql.insert(request).returning("*")}`
|
|
678
|
+
})
|
|
679
|
+
})
|
|
680
|
+
const insert = (
|
|
681
|
+
insert: S["insert"]["Type"]
|
|
682
|
+
): Effect.Effect<S["Type"], never, S["Context"] | S["insert"]["Context"]> =>
|
|
683
|
+
insertSchema(insert).pipe(
|
|
684
|
+
Effect.orDie,
|
|
685
|
+
Effect.withSpan(`${options.spanPrefix}.insert`, {
|
|
686
|
+
captureStackTrace: false,
|
|
687
|
+
attributes: { insert }
|
|
688
|
+
})
|
|
689
|
+
) as any
|
|
690
|
+
|
|
691
|
+
const insertVoidSchema = SqlSchema.void({
|
|
692
|
+
Request: Model.insert,
|
|
693
|
+
execute: (request) => sql`insert into ${sql(options.tableName)} ${sql.insert(request)}`
|
|
694
|
+
})
|
|
695
|
+
const insertVoid = (
|
|
696
|
+
insert: S["insert"]["Type"]
|
|
697
|
+
): Effect.Effect<void, never, S["Context"] | S["insert"]["Context"]> =>
|
|
698
|
+
insertVoidSchema(insert).pipe(
|
|
699
|
+
Effect.orDie,
|
|
700
|
+
Effect.withSpan(`${options.spanPrefix}.insertVoid`, {
|
|
701
|
+
captureStackTrace: false,
|
|
702
|
+
attributes: { insert }
|
|
703
|
+
})
|
|
704
|
+
) as any
|
|
705
|
+
|
|
706
|
+
const updateSchema = SqlSchema.single({
|
|
707
|
+
Request: Model.update,
|
|
708
|
+
Result: Model,
|
|
709
|
+
execute: versionColumn
|
|
710
|
+
? (request) =>
|
|
711
|
+
sql.onDialectOrElse({
|
|
712
|
+
mysql: () =>
|
|
713
|
+
sql`update ${sql(options.tableName)} set ${
|
|
714
|
+
sql.update({ ...request, [versionColumn]: randomUUID() }, [idColumn])
|
|
715
|
+
} where ${sql(idColumn)} = ${request[idColumn]} and ${sql(versionColumn)} = ${request[versionColumn]};
|
|
716
|
+
select * from ${sql(options.tableName)} where ${sql(idColumn)} = ${request[idColumn]};`
|
|
717
|
+
.unprepared
|
|
718
|
+
.pipe(
|
|
719
|
+
Effect.map(([, results]) => results as any)
|
|
720
|
+
),
|
|
721
|
+
orElse: () =>
|
|
722
|
+
sql`update ${sql(options.tableName)} set ${
|
|
723
|
+
sql.update({ ...request, [versionColumn]: randomUUID() }, [idColumn])
|
|
724
|
+
} where ${sql(idColumn)} = ${request[idColumn]} and ${sql(versionColumn)} = ${
|
|
725
|
+
request[versionColumn]
|
|
726
|
+
} returning *`
|
|
727
|
+
})
|
|
728
|
+
: (request) =>
|
|
729
|
+
sql.onDialectOrElse({
|
|
730
|
+
mysql: () =>
|
|
731
|
+
sql`update ${sql(options.tableName)} set ${sql.update(request, [idColumn])} where ${sql(idColumn)} = ${
|
|
732
|
+
request[idColumn]
|
|
733
|
+
};
|
|
734
|
+
select * from ${sql(options.tableName)} where ${sql(idColumn)} = ${request[idColumn]};`
|
|
735
|
+
.unprepared
|
|
736
|
+
.pipe(
|
|
737
|
+
Effect.map(([, results]) => results as any)
|
|
738
|
+
),
|
|
739
|
+
orElse: () =>
|
|
740
|
+
sql`update ${sql(options.tableName)} set ${sql.update(request, [idColumn])} where ${sql(idColumn)} = ${
|
|
741
|
+
request[idColumn]
|
|
742
|
+
} returning *`
|
|
743
|
+
})
|
|
744
|
+
})
|
|
745
|
+
const update = (
|
|
746
|
+
update: S["update"]["Type"]
|
|
747
|
+
): Effect.Effect<S["Type"], never, S["Context"] | S["update"]["Context"]> =>
|
|
748
|
+
updateSchema(update).pipe(
|
|
749
|
+
Effect.orDie,
|
|
750
|
+
Effect.withSpan(`${options.spanPrefix}.update`, {
|
|
751
|
+
captureStackTrace: false,
|
|
752
|
+
attributes: { update }
|
|
753
|
+
})
|
|
754
|
+
) as any
|
|
755
|
+
|
|
756
|
+
const updateVoidSchema = SqlSchema.void({
|
|
757
|
+
Request: Model.update,
|
|
758
|
+
execute: versionColumn
|
|
759
|
+
? (request) =>
|
|
760
|
+
sql`update ${sql(options.tableName)} set ${
|
|
761
|
+
sql.update({ ...request, [versionColumn]: randomUUID() }, [idColumn])
|
|
762
|
+
} where ${sql(idColumn)} = ${request[idColumn]} and ${sql(versionColumn)} = ${request[versionColumn]}`
|
|
763
|
+
: (request) =>
|
|
764
|
+
sql`update ${sql(options.tableName)} set ${sql.update(request, [idColumn])} where ${sql(idColumn)} = ${
|
|
765
|
+
request[idColumn]
|
|
766
|
+
}`
|
|
767
|
+
})
|
|
768
|
+
const updateVoid = (
|
|
769
|
+
update: S["update"]["Type"]
|
|
770
|
+
): Effect.Effect<void, never, S["Context"] | S["update"]["Context"]> =>
|
|
771
|
+
updateVoidSchema(update).pipe(
|
|
772
|
+
Effect.orDie,
|
|
773
|
+
Effect.withSpan(`${options.spanPrefix}.updateVoid`, {
|
|
774
|
+
captureStackTrace: false,
|
|
775
|
+
attributes: { update }
|
|
776
|
+
})
|
|
777
|
+
) as any
|
|
778
|
+
|
|
779
|
+
const findByIdSchema = SqlSchema.findOne({
|
|
780
|
+
Request: idSchema,
|
|
781
|
+
Result: Model,
|
|
782
|
+
execute: (id) => sql`select * from ${sql(options.tableName)} where ${sql(idColumn)} = ${id}`
|
|
783
|
+
})
|
|
784
|
+
const findById = (
|
|
785
|
+
id: Schema.Schema.Type<S["fields"][Id]>
|
|
786
|
+
): Effect.Effect<Option.Option<S["Type"]>, never, S["Context"] | Schema.Schema.Context<S["fields"][Id]>> =>
|
|
787
|
+
findByIdSchema(id).pipe(
|
|
788
|
+
Effect.orDie,
|
|
789
|
+
Effect.withSpan(`${options.spanPrefix}.findById`, {
|
|
790
|
+
captureStackTrace: false,
|
|
791
|
+
attributes: { id }
|
|
792
|
+
})
|
|
793
|
+
) as any
|
|
794
|
+
|
|
795
|
+
const deleteSchema = SqlSchema.void({
|
|
796
|
+
Request: idSchema,
|
|
797
|
+
execute: (id) => sql`delete from ${sql(options.tableName)} where ${sql(idColumn)} = ${id}`
|
|
798
|
+
})
|
|
799
|
+
const delete_ = (
|
|
800
|
+
id: Schema.Schema.Type<S["fields"][Id]>
|
|
801
|
+
): Effect.Effect<void, never, Schema.Schema.Context<S["fields"][Id]>> =>
|
|
802
|
+
deleteSchema(id).pipe(
|
|
803
|
+
Effect.orDie,
|
|
804
|
+
Effect.withSpan(`${options.spanPrefix}.delete`, {
|
|
805
|
+
captureStackTrace: false,
|
|
806
|
+
attributes: { id }
|
|
807
|
+
})
|
|
808
|
+
) as any
|
|
809
|
+
|
|
810
|
+
return { insert, insertVoid, update, updateVoid, findById, delete: delete_ } as const
|
|
811
|
+
})
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* Create some simple data loaders from a model.
|
|
815
|
+
*
|
|
816
|
+
* @since 1.0.0
|
|
817
|
+
* @category repository
|
|
818
|
+
*/
|
|
819
|
+
export const makeDataLoaders = <
|
|
820
|
+
S extends AnyNoContext,
|
|
821
|
+
Id extends (keyof S["Type"]) & (keyof S["update"]["Type"]) & (keyof S["fields"])
|
|
822
|
+
>(
|
|
823
|
+
Model: S,
|
|
824
|
+
options: {
|
|
825
|
+
readonly tableName: string
|
|
826
|
+
readonly spanPrefix: string
|
|
827
|
+
readonly idColumn: Id
|
|
828
|
+
readonly window: DurationInput
|
|
829
|
+
readonly maxBatchSize?: number | undefined
|
|
830
|
+
}
|
|
831
|
+
): Effect.Effect<
|
|
832
|
+
{
|
|
833
|
+
readonly insert: (insert: S["insert"]["Type"]) => Effect.Effect<S["Type"]>
|
|
834
|
+
readonly insertVoid: (insert: S["insert"]["Type"]) => Effect.Effect<void>
|
|
835
|
+
readonly findById: (id: Schema.Schema.Type<S["fields"][Id]>) => Effect.Effect<Option.Option<S["Type"]>>
|
|
836
|
+
readonly delete: (id: Schema.Schema.Type<S["fields"][Id]>) => Effect.Effect<void>
|
|
837
|
+
},
|
|
838
|
+
never,
|
|
839
|
+
SqlClient | Scope
|
|
840
|
+
> =>
|
|
841
|
+
Effect.gen(function*() {
|
|
842
|
+
const sql = yield* SqlClient
|
|
843
|
+
const idSchema = Model.fields[options.idColumn] as Schema.Schema.Any
|
|
844
|
+
const idColumn = options.idColumn as string
|
|
845
|
+
|
|
846
|
+
const insertResolver = yield* SqlResolver.ordered(`${options.spanPrefix}/insert`, {
|
|
847
|
+
Request: Model.insert,
|
|
848
|
+
Result: Model,
|
|
849
|
+
execute: (request) =>
|
|
850
|
+
sql.onDialectOrElse({
|
|
851
|
+
mysql: () =>
|
|
852
|
+
Effect.forEach(request, (request) =>
|
|
853
|
+
sql`insert into ${sql(options.tableName)} ${sql.insert(request)};
|
|
854
|
+
select * from ${sql(options.tableName)} where ${sql(idColumn)} = LAST_INSERT_ID();`
|
|
855
|
+
.unprepared
|
|
856
|
+
.pipe(
|
|
857
|
+
Effect.map(([, results]) => results as any)
|
|
858
|
+
), { concurrency: 10 }),
|
|
859
|
+
orElse: () => sql`insert into ${sql(options.tableName)} ${sql.insert(request).returning("*")}`
|
|
860
|
+
})
|
|
861
|
+
})
|
|
862
|
+
const insertLoader = yield* RRX.dataLoader(insertResolver, {
|
|
863
|
+
window: options.window,
|
|
864
|
+
maxBatchSize: options.maxBatchSize!
|
|
865
|
+
})
|
|
866
|
+
const insertExecute = insertResolver.makeExecute(insertLoader)
|
|
867
|
+
const insert = (
|
|
868
|
+
insert: S["insert"]["Type"]
|
|
869
|
+
): Effect.Effect<S["Type"], never, S["Context"] | S["insert"]["Context"]> =>
|
|
870
|
+
insertExecute(insert).pipe(
|
|
871
|
+
Effect.orDie,
|
|
872
|
+
Effect.withSpan(`${options.spanPrefix}.insert`, {
|
|
873
|
+
captureStackTrace: false,
|
|
874
|
+
attributes: { insert }
|
|
875
|
+
})
|
|
876
|
+
)
|
|
877
|
+
|
|
878
|
+
const insertVoidResolver = yield* SqlResolver.void(`${options.spanPrefix}/insertVoid`, {
|
|
879
|
+
Request: Model.insert,
|
|
880
|
+
execute: (request) => sql`insert into ${sql(options.tableName)} ${sql.insert(request)}`
|
|
881
|
+
})
|
|
882
|
+
const insertVoidLoader = yield* RRX.dataLoader(insertVoidResolver, {
|
|
883
|
+
window: options.window,
|
|
884
|
+
maxBatchSize: options.maxBatchSize!
|
|
885
|
+
})
|
|
886
|
+
const insertVoidExecute = insertVoidResolver.makeExecute(insertVoidLoader)
|
|
887
|
+
const insertVoid = (
|
|
888
|
+
insert: S["insert"]["Type"]
|
|
889
|
+
): Effect.Effect<void, never, S["Context"] | S["insert"]["Context"]> =>
|
|
890
|
+
insertVoidExecute(insert).pipe(
|
|
891
|
+
Effect.orDie,
|
|
892
|
+
Effect.withSpan(`${options.spanPrefix}.insertVoid`, {
|
|
893
|
+
captureStackTrace: false,
|
|
894
|
+
attributes: { insert }
|
|
895
|
+
})
|
|
896
|
+
)
|
|
897
|
+
|
|
898
|
+
const findByIdResolver = yield* SqlResolver.findById(`${options.spanPrefix}/findById`, {
|
|
899
|
+
Id: idSchema,
|
|
900
|
+
Result: Model,
|
|
901
|
+
ResultId(request) {
|
|
902
|
+
return request[idColumn]
|
|
903
|
+
},
|
|
904
|
+
execute: (ids) => sql`select * from ${sql(options.tableName)} where ${sql.in(idColumn, ids)}`
|
|
905
|
+
})
|
|
906
|
+
const findByIdLoader = yield* RRX.dataLoader(findByIdResolver, {
|
|
907
|
+
window: options.window,
|
|
908
|
+
maxBatchSize: options.maxBatchSize!
|
|
909
|
+
})
|
|
910
|
+
const findByIdExecute = findByIdResolver.makeExecute(findByIdLoader)
|
|
911
|
+
const findById = (id: Schema.Schema.Type<S["fields"][Id]>): Effect.Effect<Option.Option<S["Type"]>> =>
|
|
912
|
+
findByIdExecute(id).pipe(
|
|
913
|
+
Effect.orDie,
|
|
914
|
+
Effect.withSpan(`${options.spanPrefix}.findById`, {
|
|
915
|
+
captureStackTrace: false,
|
|
916
|
+
attributes: { id }
|
|
917
|
+
})
|
|
918
|
+
) as any
|
|
919
|
+
|
|
920
|
+
const deleteResolver = yield* SqlResolver.void(`${options.spanPrefix}/delete`, {
|
|
921
|
+
Request: idSchema,
|
|
922
|
+
execute: (ids) => sql`delete from ${sql(options.tableName)} where ${sql.in(idColumn, ids)}`
|
|
923
|
+
})
|
|
924
|
+
const deleteLoader = yield* RRX.dataLoader(deleteResolver, {
|
|
925
|
+
window: options.window,
|
|
926
|
+
maxBatchSize: options.maxBatchSize!
|
|
927
|
+
})
|
|
928
|
+
const deleteExecute = deleteResolver.makeExecute(deleteLoader)
|
|
929
|
+
const delete_ = (id: Schema.Schema.Type<S["fields"][Id]>): Effect.Effect<void> =>
|
|
930
|
+
deleteExecute(id).pipe(
|
|
931
|
+
Effect.orDie,
|
|
932
|
+
Effect.withSpan(`${options.spanPrefix}.delete`, {
|
|
933
|
+
captureStackTrace: false,
|
|
934
|
+
attributes: { id }
|
|
935
|
+
})
|
|
936
|
+
) as any
|
|
937
|
+
|
|
938
|
+
return { insert, insertVoid, findById, delete: delete_ } as const
|
|
939
|
+
})
|