@confect/server 1.0.0-next.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 (157) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +7 -0
  3. package/dist/ActionCtx.d.ts +12 -0
  4. package/dist/ActionCtx.d.ts.map +1 -0
  5. package/dist/ActionCtx.js +10 -0
  6. package/dist/ActionCtx.js.map +1 -0
  7. package/dist/ActionRunner.d.ts +15 -0
  8. package/dist/ActionRunner.d.ts.map +1 -0
  9. package/dist/ActionRunner.js +23 -0
  10. package/dist/ActionRunner.js.map +1 -0
  11. package/dist/Api.d.ts +27 -0
  12. package/dist/Api.d.ts.map +1 -0
  13. package/dist/Api.js +26 -0
  14. package/dist/Api.js.map +1 -0
  15. package/dist/Auth.d.ts +30 -0
  16. package/dist/Auth.d.ts.map +1 -0
  17. package/dist/Auth.js +24 -0
  18. package/dist/Auth.js.map +1 -0
  19. package/dist/DataModel.d.ts +33 -0
  20. package/dist/DataModel.d.ts.map +1 -0
  21. package/dist/DataModel.js +6 -0
  22. package/dist/DataModel.js.map +1 -0
  23. package/dist/DatabaseReader.d.ts +73 -0
  24. package/dist/DatabaseReader.d.ts.map +1 -0
  25. package/dist/DatabaseReader.js +32 -0
  26. package/dist/DatabaseReader.js.map +1 -0
  27. package/dist/DatabaseSchema.d.ts +139 -0
  28. package/dist/DatabaseSchema.d.ts.map +1 -0
  29. package/dist/DatabaseSchema.js +45 -0
  30. package/dist/DatabaseSchema.js.map +1 -0
  31. package/dist/DatabaseWriter.d.ts +39 -0
  32. package/dist/DatabaseWriter.d.ts.map +1 -0
  33. package/dist/DatabaseWriter.js +43 -0
  34. package/dist/DatabaseWriter.js.map +1 -0
  35. package/dist/Document.d.ts +47 -0
  36. package/dist/Document.d.ts.map +1 -0
  37. package/dist/Document.js +66 -0
  38. package/dist/Document.js.map +1 -0
  39. package/dist/FunctionImpl.d.ts +34 -0
  40. package/dist/FunctionImpl.d.ts.map +1 -0
  41. package/dist/FunctionImpl.js +35 -0
  42. package/dist/FunctionImpl.js.map +1 -0
  43. package/dist/GroupImpl.d.ts +24 -0
  44. package/dist/GroupImpl.d.ts.map +1 -0
  45. package/dist/GroupImpl.js +14 -0
  46. package/dist/GroupImpl.js.map +1 -0
  47. package/dist/Handler.d.ts +31 -0
  48. package/dist/Handler.d.ts.map +1 -0
  49. package/dist/Handler.js +6 -0
  50. package/dist/Handler.js.map +1 -0
  51. package/dist/HttpApi.d.ts +26 -0
  52. package/dist/HttpApi.d.ts.map +1 -0
  53. package/dist/HttpApi.js +74 -0
  54. package/dist/HttpApi.js.map +1 -0
  55. package/dist/Impl.d.ts +24 -0
  56. package/dist/Impl.d.ts.map +1 -0
  57. package/dist/Impl.js +28 -0
  58. package/dist/Impl.js.map +1 -0
  59. package/dist/MutationCtx.d.ts +12 -0
  60. package/dist/MutationCtx.d.ts.map +1 -0
  61. package/dist/MutationCtx.js +10 -0
  62. package/dist/MutationCtx.js.map +1 -0
  63. package/dist/MutationRunner.d.ts +24 -0
  64. package/dist/MutationRunner.d.ts.map +1 -0
  65. package/dist/MutationRunner.js +33 -0
  66. package/dist/MutationRunner.js.map +1 -0
  67. package/dist/OrderedQuery.d.ts +23 -0
  68. package/dist/OrderedQuery.d.ts.map +1 -0
  69. package/dist/OrderedQuery.js +34 -0
  70. package/dist/OrderedQuery.js.map +1 -0
  71. package/dist/QueryCtx.d.ts +12 -0
  72. package/dist/QueryCtx.d.ts.map +1 -0
  73. package/dist/QueryCtx.js +10 -0
  74. package/dist/QueryCtx.js.map +1 -0
  75. package/dist/QueryInitializer.d.ts +49 -0
  76. package/dist/QueryInitializer.d.ts.map +1 -0
  77. package/dist/QueryInitializer.js +83 -0
  78. package/dist/QueryInitializer.js.map +1 -0
  79. package/dist/QueryRunner.d.ts +14 -0
  80. package/dist/QueryRunner.d.ts.map +1 -0
  81. package/dist/QueryRunner.js +23 -0
  82. package/dist/QueryRunner.js.map +1 -0
  83. package/dist/RegisteredFunctions.d.ts +66 -0
  84. package/dist/RegisteredFunctions.d.ts.map +1 -0
  85. package/dist/RegisteredFunctions.js +71 -0
  86. package/dist/RegisteredFunctions.js.map +1 -0
  87. package/dist/Registry.d.ts +15 -0
  88. package/dist/Registry.d.ts.map +1 -0
  89. package/dist/Registry.js +10 -0
  90. package/dist/Registry.js.map +1 -0
  91. package/dist/RegistryItem.d.ts +31 -0
  92. package/dist/RegistryItem.d.ts.map +1 -0
  93. package/dist/RegistryItem.js +20 -0
  94. package/dist/RegistryItem.js.map +1 -0
  95. package/dist/Scheduler.d.ts +23 -0
  96. package/dist/Scheduler.d.ts.map +1 -0
  97. package/dist/Scheduler.js +24 -0
  98. package/dist/Scheduler.js.map +1 -0
  99. package/dist/SchemaToValidator.d.ts +88 -0
  100. package/dist/SchemaToValidator.d.ts.map +1 -0
  101. package/dist/SchemaToValidator.js +155 -0
  102. package/dist/SchemaToValidator.js.map +1 -0
  103. package/dist/Storage.d.ts +69 -0
  104. package/dist/Storage.d.ts.map +1 -0
  105. package/dist/Storage.js +46 -0
  106. package/dist/Storage.js.map +1 -0
  107. package/dist/Table.d.ts +247 -0
  108. package/dist/Table.d.ts.map +1 -0
  109. package/dist/Table.js +97 -0
  110. package/dist/Table.js.map +1 -0
  111. package/dist/TableInfo.d.ts +48 -0
  112. package/dist/TableInfo.d.ts.map +1 -0
  113. package/dist/TableInfo.js +6 -0
  114. package/dist/TableInfo.js.map +1 -0
  115. package/dist/VectorSearch.d.ts +42 -0
  116. package/dist/VectorSearch.d.ts.map +1 -0
  117. package/dist/VectorSearch.js +16 -0
  118. package/dist/VectorSearch.js.map +1 -0
  119. package/dist/_virtual/rolldown_runtime.js +13 -0
  120. package/dist/index.d.ts +30 -0
  121. package/dist/index.js +31 -0
  122. package/dist/internal/utils.d.ts +15 -0
  123. package/dist/internal/utils.d.ts.map +1 -0
  124. package/dist/internal/utils.js +49 -0
  125. package/dist/internal/utils.js.map +1 -0
  126. package/package.json +90 -0
  127. package/src/ActionCtx.ts +9 -0
  128. package/src/ActionRunner.ts +28 -0
  129. package/src/Api.ts +63 -0
  130. package/src/Auth.ts +31 -0
  131. package/src/DataModel.ts +69 -0
  132. package/src/DatabaseReader.ts +75 -0
  133. package/src/DatabaseSchema.ts +134 -0
  134. package/src/DatabaseWriter.ts +166 -0
  135. package/src/Document.ts +200 -0
  136. package/src/FunctionImpl.ts +112 -0
  137. package/src/GroupImpl.ts +60 -0
  138. package/src/Handler.ts +105 -0
  139. package/src/HttpApi.ts +232 -0
  140. package/src/Impl.ts +57 -0
  141. package/src/MutationCtx.ts +11 -0
  142. package/src/MutationRunner.ts +41 -0
  143. package/src/OrderedQuery.ts +109 -0
  144. package/src/QueryCtx.ts +9 -0
  145. package/src/QueryInitializer.ts +308 -0
  146. package/src/QueryRunner.ts +29 -0
  147. package/src/RegisteredFunctions.ts +381 -0
  148. package/src/Registry.ts +13 -0
  149. package/src/RegistryItem.ts +44 -0
  150. package/src/Scheduler.ts +39 -0
  151. package/src/SchemaToValidator.ts +619 -0
  152. package/src/Storage.ts +86 -0
  153. package/src/Table.ts +439 -0
  154. package/src/TableInfo.ts +91 -0
  155. package/src/VectorSearch.ts +46 -0
  156. package/src/index.ts +29 -0
  157. package/src/internal/utils.ts +87 -0
@@ -0,0 +1,619 @@
1
+ import type {
2
+ PropertyValidators,
3
+ Validator,
4
+ VAny,
5
+ VArray,
6
+ VBoolean,
7
+ VBytes,
8
+ VFloat64,
9
+ VId,
10
+ VInt64,
11
+ VLiteral,
12
+ VNull,
13
+ VObject,
14
+ VOptional,
15
+ VRecord,
16
+ VString,
17
+ VUnion,
18
+ } from "convex/values";
19
+ import { v } from "convex/values";
20
+ import {
21
+ Array,
22
+ Cause,
23
+ Data,
24
+ Effect,
25
+ Exit,
26
+ Match,
27
+ Number,
28
+ Option,
29
+ type ParseResult,
30
+ pipe,
31
+ Predicate,
32
+ Schema,
33
+ SchemaAST,
34
+ String,
35
+ } from "effect";
36
+
37
+ import * as GenericId from "@confect/core/GenericId";
38
+ import type {
39
+ DeepMutable,
40
+ IsAny,
41
+ IsOptional,
42
+ IsRecord,
43
+ IsRecursive,
44
+ IsUnion,
45
+ IsValueLiteral,
46
+ TypeError,
47
+ UnionToTuple,
48
+ } from "@confect/core/Types";
49
+
50
+ // Args
51
+
52
+ export const compileArgsSchema = <ConfectValue, ConvexValue>(
53
+ argsSchema: Schema.Schema<ConfectValue, ConvexValue>,
54
+ ): PropertyValidators => {
55
+ const ast = Schema.encodedSchema(argsSchema).ast;
56
+
57
+ return pipe(
58
+ ast,
59
+ Match.value,
60
+ Match.tag("TypeLiteral", (typeLiteralAst) =>
61
+ Array.isEmptyReadonlyArray(typeLiteralAst.indexSignatures)
62
+ ? handlePropertySignatures(typeLiteralAst)
63
+ : Effect.fail(new IndexSignaturesAreNotSupportedError()),
64
+ ),
65
+ Match.orElse(() => Effect.fail(new TopLevelMustBeObjectError())),
66
+ runSyncThrow,
67
+ );
68
+ };
69
+
70
+ // Returns
71
+
72
+ export const compileReturnsSchema = <ConfectValue, ConvexValue>(
73
+ schema: Schema.Schema<ConfectValue, ConvexValue>,
74
+ ): Validator<any, any, any> =>
75
+ runSyncThrow(compileAst(Schema.encodedSchema(schema).ast));
76
+
77
+ // Table
78
+
79
+ /**
80
+ * Convert a table `Schema` to a table `Validator`.
81
+ */
82
+ export type TableSchemaToTableValidator<
83
+ TableSchema extends Schema.Schema.AnyNoContext,
84
+ > =
85
+ ValueToValidator<TableSchema["Encoded"]> extends infer Vd extends
86
+ | VObject<any, any, any, any>
87
+ | VUnion<any, any, any, any>
88
+ ? Vd
89
+ : // TODO: Add type error message
90
+ never;
91
+
92
+ export const compileTableSchema = <
93
+ TableSchema extends Schema.Schema.AnyNoContext,
94
+ >(
95
+ schema: TableSchema,
96
+ ): TableSchemaToTableValidator<TableSchema> => {
97
+ const ast = Schema.encodedSchema(schema).ast;
98
+
99
+ return pipe(
100
+ ast,
101
+ Match.value,
102
+ Match.tag("TypeLiteral", ({ indexSignatures }) =>
103
+ Array.isEmptyReadonlyArray(indexSignatures)
104
+ ? (compileAst(ast) as Effect.Effect<any>)
105
+ : Effect.fail(new IndexSignaturesAreNotSupportedError()),
106
+ ),
107
+ Match.tag("Union", (unionAst) => compileAst(unionAst)),
108
+ Match.orElse(() => Effect.fail(new TopLevelMustBeObjectOrUnionError())),
109
+ runSyncThrow,
110
+ );
111
+ };
112
+
113
+ // Compiler
114
+
115
+ export type ReadonlyValue =
116
+ | string
117
+ | number
118
+ | bigint
119
+ | boolean
120
+ | ArrayBuffer
121
+ | ReadonlyArrayValue
122
+ | ReadonlyRecordValue
123
+ | null;
124
+
125
+ type ReadonlyArrayValue = readonly ReadonlyValue[];
126
+
127
+ export type ReadonlyRecordValue = {
128
+ readonly [key: string]: ReadonlyValue | undefined;
129
+ };
130
+
131
+ export type ValueToValidator<Vl> =
132
+ IsRecursive<Vl> extends true
133
+ ? VAny
134
+ : [Vl] extends [never]
135
+ ? never
136
+ : IsAny<Vl> extends true
137
+ ? VAny
138
+ : [Vl] extends [ReadonlyValue]
139
+ ? Vl extends {
140
+ __tableName: infer TableName extends string;
141
+ }
142
+ ? VId<GenericId.GenericId<TableName>>
143
+ : IsValueLiteral<Vl> extends true
144
+ ? VLiteral<Vl>
145
+ : [Vl] extends [null]
146
+ ? VNull
147
+ : [Vl] extends [boolean]
148
+ ? VBoolean
149
+ : IsUnion<Vl> extends true
150
+ ? UnionValueToValidator<Vl>
151
+ : [Vl] extends [number]
152
+ ? VFloat64
153
+ : [Vl] extends [bigint]
154
+ ? VInt64
155
+ : [Vl] extends [string]
156
+ ? VString
157
+ : [Vl] extends [ArrayBuffer]
158
+ ? VBytes
159
+ : Vl extends ReadonlyArray<ReadonlyValue>
160
+ ? ArrayValueToValidator<Vl>
161
+ : Vl extends ReadonlyRecordValue
162
+ ? RecordValueToValidator<Vl>
163
+ : TypeError<"Unexpected value", Vl>
164
+ : TypeError<"Provided value is not a valid Convex value", Vl>;
165
+
166
+ type ArrayValueToValidator<Vl extends ReadonlyArray<ReadonlyValue>> =
167
+ Vl extends ReadonlyArray<infer El extends ReadonlyValue>
168
+ ? ValueToValidator<El> extends infer Vd extends Validator<any, any, any>
169
+ ? VArray<DeepMutable<El[]>, Vd>
170
+ : never
171
+ : never;
172
+
173
+ type RecordValueToValidator<Vl> = Vl extends ReadonlyRecordValue
174
+ ? {
175
+ -readonly [K in keyof Vl]-?: IsAny<Vl[K]> extends true
176
+ ? IsOptional<Vl, K> extends true
177
+ ? VOptional<VAny>
178
+ : VAny
179
+ : UndefinedOrValueToValidator<Vl[K]>;
180
+ } extends infer VdRecord extends Record<string, any>
181
+ ? {
182
+ -readonly [K in keyof Vl]: undefined extends Vl[K]
183
+ ? DeepMutable<Exclude<Vl[K], undefined>>
184
+ : DeepMutable<Vl[K]>;
185
+ } extends infer VlRecord extends Record<string, any>
186
+ ? IsRecord<VlRecord> extends true
187
+ ? VRecord<VlRecord, VString, VdRecord[keyof VdRecord]>
188
+ : VObject<VlRecord, VdRecord>
189
+ : never
190
+ : never
191
+ : never;
192
+
193
+ export type UndefinedOrValueToValidator<Vl extends ReadonlyValue | undefined> =
194
+ undefined extends Vl
195
+ ? [Vl] extends [(infer Val extends ReadonlyValue) | undefined]
196
+ ? ValueToValidator<Val> extends infer Vd extends Validator<
197
+ any,
198
+ "required",
199
+ any
200
+ >
201
+ ? VOptional<Vd>
202
+ : never
203
+ : never
204
+ : [Vl] extends [ReadonlyValue]
205
+ ? ValueToValidator<Vl>
206
+ : never;
207
+
208
+ type UnionValueToValidator<Vl extends ReadonlyValue> = [Vl] extends [
209
+ ReadonlyValue,
210
+ ]
211
+ ? IsUnion<Vl> extends true
212
+ ? UnionToTuple<Vl> extends infer VlTuple extends
213
+ ReadonlyArray<ReadonlyValue>
214
+ ? ValueTupleToValidatorTuple<VlTuple> extends infer VdTuple extends
215
+ Validator<any, "required", any>[]
216
+ ? VUnion<DeepMutable<Vl>, VdTuple>
217
+ : TypeError<"Failed to convert value tuple to validator tuple">
218
+ : TypeError<"Failed to convert union to tuple">
219
+ : TypeError<"Expected a union of values, but got a single value instead">
220
+ : TypeError<"Provided value is not a valid Convex value">;
221
+
222
+ type ValueTupleToValidatorTuple<VlTuple extends ReadonlyArray<ReadonlyValue>> =
223
+ VlTuple extends
224
+ | [true, false, ...infer VlRest extends ReadonlyArray<ReadonlyValue>]
225
+ | [false, true, ...infer VlRest extends ReadonlyArray<ReadonlyValue>]
226
+ ? ValueTupleToValidatorTuple<VlRest> extends infer VdRest extends Validator<
227
+ any,
228
+ any,
229
+ any
230
+ >[]
231
+ ? [VBoolean<boolean>, ...VdRest]
232
+ : never
233
+ : VlTuple extends [
234
+ infer Vl extends ReadonlyValue,
235
+ ...infer VlRest extends ReadonlyArray<ReadonlyValue>,
236
+ ]
237
+ ? ValueToValidator<Vl> extends infer Vd extends Validator<any, any, any>
238
+ ? ValueTupleToValidatorTuple<VlRest> extends infer VdRest extends
239
+ Validator<any, "required", any>[]
240
+ ? [Vd, ...VdRest]
241
+ : never
242
+ : never
243
+ : [];
244
+
245
+ export const compileSchema = <T, E>(
246
+ schema: Schema.Schema<T, E>,
247
+ // TODO: Can `ValueToValidator` here just accept `E` directly?
248
+ ): ValueToValidator<(typeof schema)["Encoded"]> =>
249
+ runSyncThrow(compileAst(schema.ast)) as any;
250
+
251
+ export const isRecursive = (ast: SchemaAST.AST): boolean =>
252
+ pipe(
253
+ ast,
254
+ Match.value,
255
+ Match.tag(
256
+ "Literal",
257
+ "BooleanKeyword",
258
+ "StringKeyword",
259
+ "NumberKeyword",
260
+ "BigIntKeyword",
261
+ "UnknownKeyword",
262
+ "AnyKeyword",
263
+ "Declaration",
264
+ "UniqueSymbol",
265
+ "SymbolKeyword",
266
+ "UndefinedKeyword",
267
+ "VoidKeyword",
268
+ "NeverKeyword",
269
+ "Enums",
270
+ "TemplateLiteral",
271
+ "ObjectKeyword",
272
+ "Transformation",
273
+ () => false,
274
+ ),
275
+ Match.tag("Union", ({ types }) =>
276
+ Array.some(types, (type) => isRecursive(type)),
277
+ ),
278
+ Match.tag("TypeLiteral", ({ propertySignatures }) =>
279
+ Array.some(propertySignatures, ({ type }) => isRecursive(type)),
280
+ ),
281
+ Match.tag(
282
+ "TupleType",
283
+ ({ elements: optionalElements, rest: elements }) =>
284
+ Array.some(optionalElements, (optionalElement) =>
285
+ isRecursive(optionalElement.type),
286
+ ) || Array.some(elements, (element) => isRecursive(element.type)),
287
+ ),
288
+ Match.tag("Refinement", ({ from }) => isRecursive(from)),
289
+ Match.tag("Suspend", () => true),
290
+ Match.exhaustive,
291
+ );
292
+
293
+ export const compileAst = (
294
+ ast: SchemaAST.AST,
295
+ isOptionalPropertyOfTypeLiteral = false,
296
+ ): Effect.Effect<
297
+ Validator<any, any, any>,
298
+ | UnsupportedSchemaTypeError
299
+ | UnsupportedPropertySignatureKeyTypeError
300
+ | IndexSignaturesAreNotSupportedError
301
+ | MixedIndexAndPropertySignaturesAreNotSupportedError
302
+ | OptionalTupleElementsAreNotSupportedError
303
+ | EmptyTupleIsNotSupportedError
304
+ > =>
305
+ isRecursive(ast)
306
+ ? Effect.succeed(v.any())
307
+ : pipe(
308
+ ast,
309
+ Match.value,
310
+ Match.tag("Literal", ({ literal }) =>
311
+ pipe(
312
+ literal,
313
+ Match.value,
314
+ Match.whenOr(
315
+ Match.string,
316
+ Match.number,
317
+ Match.bigint,
318
+ Match.boolean,
319
+ (l) => v.literal(l),
320
+ ),
321
+ Match.when(Match.null, () => v.null()),
322
+ Match.exhaustive,
323
+ Effect.succeed,
324
+ ),
325
+ ),
326
+ Match.tag("BooleanKeyword", () => Effect.succeed(v.boolean())),
327
+ Match.tag("StringKeyword", (stringAst) =>
328
+ GenericId.tableName(stringAst).pipe(
329
+ Option.match({
330
+ onNone: () => Effect.succeed(v.string()),
331
+ onSome: (tableName) => Effect.succeed(v.id(tableName)),
332
+ }),
333
+ ),
334
+ ),
335
+ Match.tag("NumberKeyword", () => Effect.succeed(v.float64())),
336
+ Match.tag("BigIntKeyword", () => Effect.succeed(v.int64())),
337
+ Match.tag("Union", (unionAst) =>
338
+ handleUnion(unionAst, isOptionalPropertyOfTypeLiteral),
339
+ ),
340
+ Match.tag("TypeLiteral", (typeLiteralAst) =>
341
+ handleTypeLiteral(typeLiteralAst),
342
+ ),
343
+ Match.tag("TupleType", (tupleTypeAst) => handleTupleType(tupleTypeAst)),
344
+ Match.tag("UnknownKeyword", "AnyKeyword", () =>
345
+ Effect.succeed(v.any()),
346
+ ),
347
+ Match.tag("Declaration", (declaration) =>
348
+ Effect.mapBoth(
349
+ declaration.decodeUnknown(...declaration.typeParameters)(
350
+ new ArrayBuffer(0),
351
+ {},
352
+ declaration,
353
+ ) as Effect.Effect<ArrayBuffer, ParseResult.ParseIssue>,
354
+ {
355
+ onSuccess: () => v.bytes(),
356
+ onFailure: () =>
357
+ new UnsupportedSchemaTypeError({
358
+ schemaType: declaration._tag,
359
+ }),
360
+ },
361
+ ),
362
+ ),
363
+ Match.tag("Refinement", ({ from }) => compileAst(from)),
364
+ Match.tag("Suspend", () => Effect.succeed(v.any())),
365
+ Match.tag(
366
+ "UniqueSymbol",
367
+ "SymbolKeyword",
368
+ "UndefinedKeyword",
369
+ "VoidKeyword",
370
+ "NeverKeyword",
371
+ "Enums",
372
+ "TemplateLiteral",
373
+ "ObjectKeyword",
374
+ "Transformation",
375
+ () =>
376
+ new UnsupportedSchemaTypeError({
377
+ schemaType: ast._tag,
378
+ }),
379
+ ),
380
+ Match.exhaustive,
381
+ );
382
+
383
+ const handleUnion = (
384
+ { types: [first, second, ...rest] }: SchemaAST.Union,
385
+ isOptionalPropertyOfTypeLiteral: boolean,
386
+ ) =>
387
+ Effect.gen(function* () {
388
+ const validatorEffects = isOptionalPropertyOfTypeLiteral
389
+ ? Array.filterMap([first, second, ...rest], (type) =>
390
+ Predicate.not(SchemaAST.isUndefinedKeyword)(type)
391
+ ? Option.some(compileAst(type))
392
+ : Option.none(),
393
+ )
394
+ : Array.map([first, second, ...rest], (type) => compileAst(type));
395
+
396
+ const [firstValidator, secondValidator, ...restValidators] =
397
+ yield* Effect.all(validatorEffects);
398
+
399
+ /* v8 ignore start */
400
+ if (firstValidator === undefined) {
401
+ return yield* Effect.dieMessage(
402
+ "First validator of union is undefined; this should be impossible.",
403
+ );
404
+ /* v8 ignore stop */
405
+ } else if (secondValidator === undefined) {
406
+ return firstValidator;
407
+ } else {
408
+ return v.union(firstValidator, secondValidator, ...restValidators);
409
+ }
410
+ });
411
+
412
+ const handleTypeLiteral = (typeLiteralAst: SchemaAST.TypeLiteral) =>
413
+ pipe(
414
+ typeLiteralAst.indexSignatures,
415
+ Array.head,
416
+ Option.match({
417
+ onNone: () =>
418
+ Effect.map(handlePropertySignatures(typeLiteralAst), v.object),
419
+ onSome: ({ parameter, type }) =>
420
+ pipe(
421
+ typeLiteralAst.propertySignatures,
422
+ Array.head,
423
+ Option.match({
424
+ onNone: () =>
425
+ Effect.map(
426
+ Effect.all({
427
+ parameter_: compileAst(parameter),
428
+ type_: compileAst(type),
429
+ }),
430
+ ({ parameter_, type_ }) => v.record(parameter_, type_),
431
+ ),
432
+ onSome: () =>
433
+ Effect.fail(
434
+ new MixedIndexAndPropertySignaturesAreNotSupportedError(),
435
+ ),
436
+ }),
437
+ ),
438
+ }),
439
+ );
440
+
441
+ const handleTupleType = ({ elements, rest }: SchemaAST.TupleType) =>
442
+ Effect.gen(function* () {
443
+ const restValidator = pipe(
444
+ rest,
445
+ Array.head,
446
+ Option.map(({ type }) => compileAst(type)),
447
+ Effect.flatten,
448
+ );
449
+
450
+ const [f, s, ...r] = elements;
451
+
452
+ const elementToValidator = ({ type, isOptional }: SchemaAST.OptionalType) =>
453
+ Effect.if(isOptional, {
454
+ onTrue: () =>
455
+ Effect.fail(new OptionalTupleElementsAreNotSupportedError()),
456
+ onFalse: () => compileAst(type),
457
+ });
458
+
459
+ const arrayItemsValidator = yield* f === undefined
460
+ ? pipe(
461
+ restValidator,
462
+ Effect.catchTag("NoSuchElementException", () =>
463
+ Effect.fail(new EmptyTupleIsNotSupportedError()),
464
+ ),
465
+ )
466
+ : s === undefined
467
+ ? elementToValidator(f)
468
+ : Effect.gen(function* () {
469
+ const firstValidator = yield* elementToValidator(f);
470
+ const secondValidator = yield* elementToValidator(s);
471
+ const restValidators = yield* Effect.forEach(r, elementToValidator);
472
+
473
+ return v.union(firstValidator, secondValidator, ...restValidators);
474
+ });
475
+
476
+ return v.array(arrayItemsValidator);
477
+ });
478
+
479
+ const handlePropertySignatures = (typeLiteralAst: SchemaAST.TypeLiteral) =>
480
+ pipe(
481
+ typeLiteralAst.propertySignatures,
482
+ Effect.forEach(({ type, name, isOptional }) => {
483
+ if (String.isString(name)) {
484
+ // Somehow, somewhere, keys of type number are being coerced to strings…
485
+ return Option.match(Number.parse(name), {
486
+ onNone: () =>
487
+ Effect.gen(function* () {
488
+ const validator = yield* compileAst(type, isOptional);
489
+
490
+ return {
491
+ propertyName: name,
492
+ validator: isOptional ? v.optional(validator) : validator,
493
+ };
494
+ }),
495
+ onSome: (number) =>
496
+ Effect.fail(
497
+ new UnsupportedPropertySignatureKeyTypeError({
498
+ propertyKey: number,
499
+ }),
500
+ ),
501
+ });
502
+ } else {
503
+ return Effect.fail(
504
+ new UnsupportedPropertySignatureKeyTypeError({ propertyKey: name }),
505
+ );
506
+ }
507
+ }),
508
+ Effect.andThen((propertyNamesWithValidators) =>
509
+ pipe(
510
+ propertyNamesWithValidators,
511
+ Array.reduce(
512
+ {} as Record<string, Validator<any, any, any>>,
513
+ (acc, { propertyName, validator }) => ({
514
+ [propertyName]: validator,
515
+ ...acc,
516
+ }),
517
+ ),
518
+ Effect.succeed,
519
+ ),
520
+ ),
521
+ );
522
+
523
+ // Errors
524
+
525
+ const runSyncThrow = <A, E>(effect: Effect.Effect<A, E>) =>
526
+ pipe(
527
+ effect,
528
+ Effect.runSyncExit,
529
+ Exit.match({
530
+ onSuccess: (validator) => validator,
531
+ onFailure: (cause) => {
532
+ throw Cause.squash(cause);
533
+ },
534
+ }),
535
+ );
536
+
537
+ export class TopLevelMustBeObjectError extends Data.TaggedError(
538
+ "TopLevelMustBeObjectError",
539
+ ) {
540
+ /* v8 ignore start */
541
+ override get message() {
542
+ return "Top level schema must be an object";
543
+ }
544
+ /* v8 ignore stop */
545
+ }
546
+
547
+ export class TopLevelMustBeObjectOrUnionError extends Data.TaggedError(
548
+ "TopLevelMustBeObjectOrUnionError",
549
+ ) {
550
+ /* v8 ignore start */
551
+ override get message() {
552
+ return "Top level schema must be an object or a union";
553
+ }
554
+ /* v8 ignore stop */
555
+ }
556
+
557
+ export class UnsupportedPropertySignatureKeyTypeError extends Data.TaggedError(
558
+ "UnsupportedPropertySignatureKeyTypeError",
559
+ )<{
560
+ readonly propertyKey: number | symbol;
561
+ }> {
562
+ /* v8 ignore start */
563
+ override get message() {
564
+ return `Unsupported property signature '${this.propertyKey.toString()}'. Property is of type '${typeof this.propertyKey}' but only 'string' properties are supported.`;
565
+ }
566
+ /* v8 ignore stop */
567
+ }
568
+
569
+ export class EmptyTupleIsNotSupportedError extends Data.TaggedError(
570
+ "EmptyTupleIsNotSupportedError",
571
+ ) {
572
+ /* v8 ignore start */
573
+ override get message() {
574
+ return "Tuple must have at least one element";
575
+ }
576
+ /* v8 ignore stop */
577
+ }
578
+
579
+ export class UnsupportedSchemaTypeError extends Data.TaggedError(
580
+ "UnsupportedSchemaTypeError",
581
+ )<{
582
+ readonly schemaType: SchemaAST.AST["_tag"];
583
+ }> {
584
+ /* v8 ignore start */
585
+ override get message() {
586
+ return `Unsupported schema type '${this.schemaType}'`;
587
+ }
588
+ /* v8 ignore stop */
589
+ }
590
+
591
+ export class IndexSignaturesAreNotSupportedError extends Data.TaggedError(
592
+ "IndexSignaturesAreNotSupportedError",
593
+ ) {
594
+ /* v8 ignore start */
595
+ override get message() {
596
+ return "Index signatures are not supported";
597
+ }
598
+ /* v8 ignore stop */
599
+ }
600
+
601
+ export class MixedIndexAndPropertySignaturesAreNotSupportedError extends Data.TaggedError(
602
+ "MixedIndexAndPropertySignaturesAreNotSupportedError",
603
+ ) {
604
+ /* v8 ignore start */
605
+ override get message() {
606
+ return "Mixed index and property signatures are not supported";
607
+ }
608
+ /* v8 ignore stop */
609
+ }
610
+
611
+ export class OptionalTupleElementsAreNotSupportedError extends Data.TaggedError(
612
+ "OptionalTupleElementsAreNotSupportedError",
613
+ ) {
614
+ /* v8 ignore start */
615
+ override get message() {
616
+ return "Optional tuple elements are not supported";
617
+ }
618
+ /* v8 ignore stop */
619
+ }
package/src/Storage.ts ADDED
@@ -0,0 +1,86 @@
1
+ import type {
2
+ StorageActionWriter as ConvexStorageActionWriter,
3
+ StorageReader as ConvexStorageReader,
4
+ StorageWriter as ConvexStorageWriter,
5
+ } from "convex/server";
6
+ import type { GenericId } from "convex/values";
7
+ import { Effect, flow, Layer, Option, pipe, Schema } from "effect";
8
+
9
+ const makeStorageReader = (storageReader: ConvexStorageReader) => ({
10
+ getUrl: (storageId: GenericId<"_storage">) =>
11
+ Effect.promise(() => storageReader.getUrl(storageId)).pipe(
12
+ Effect.andThen(
13
+ flow(
14
+ Option.fromNullable,
15
+ Option.match({
16
+ onNone: () => Effect.fail(new BlobNotFoundError({ id: storageId })),
17
+ onSome: (doc) => pipe(doc, Schema.decode(Schema.URL), Effect.orDie),
18
+ }),
19
+ ),
20
+ ),
21
+ ),
22
+ });
23
+
24
+ const makeStorageWriter = (storageWriter: ConvexStorageWriter) => ({
25
+ generateUploadUrl: () =>
26
+ Effect.promise(() => storageWriter.generateUploadUrl()).pipe(
27
+ Effect.andThen((url) =>
28
+ pipe(url, Schema.decode(Schema.URL), Effect.orDie),
29
+ ),
30
+ ),
31
+ delete: (storageId: GenericId<"_storage">) =>
32
+ Effect.tryPromise({
33
+ try: () => storageWriter.delete(storageId),
34
+ catch: () => new BlobNotFoundError({ id: storageId }),
35
+ }),
36
+ });
37
+
38
+ const makeStorageActionWriter = (
39
+ storageActionWriter: ConvexStorageActionWriter,
40
+ ) => ({
41
+ get: (storageId: GenericId<"_storage">) =>
42
+ Effect.promise(() => storageActionWriter.get(storageId)).pipe(
43
+ Effect.andThen(
44
+ flow(
45
+ Option.fromNullable,
46
+ Option.match({
47
+ onNone: () => Effect.fail(new BlobNotFoundError({ id: storageId })),
48
+ onSome: Effect.succeed,
49
+ }),
50
+ ),
51
+ ),
52
+ ),
53
+ store: (blob: Blob, options?: { sha256?: string }) =>
54
+ Effect.promise(() => storageActionWriter.store(blob, options)),
55
+ });
56
+
57
+ export class StorageReader extends Effect.Tag(
58
+ "@confect/server/Storage/StorageReader",
59
+ )<StorageReader, ReturnType<typeof makeStorageReader>>() {
60
+ static readonly layer = (storageReader: ConvexStorageReader) =>
61
+ Layer.succeed(this, makeStorageReader(storageReader));
62
+ }
63
+
64
+ export class StorageWriter extends Effect.Tag(
65
+ "@confect/server/Storage/StorageWriter",
66
+ )<StorageWriter, ReturnType<typeof makeStorageWriter>>() {
67
+ static readonly layer = (storageWriter: ConvexStorageWriter) =>
68
+ Layer.succeed(this, makeStorageWriter(storageWriter));
69
+ }
70
+
71
+ export class StorageActionWriter extends Effect.Tag(
72
+ "@confect/server/Storage/StorageActionWriter",
73
+ )<StorageActionWriter, ReturnType<typeof makeStorageActionWriter>>() {
74
+ static readonly layer = (storageActionWriter: ConvexStorageActionWriter) =>
75
+ Layer.succeed(this, makeStorageActionWriter(storageActionWriter));
76
+ }
77
+
78
+ export class BlobNotFoundError extends Schema.TaggedError<BlobNotFoundError>(
79
+ "BlobNotFoundError",
80
+ )("BlobNotFoundError", {
81
+ id: Schema.String,
82
+ }) {
83
+ override get message(): string {
84
+ return `File with ID '${this.id}' not found`;
85
+ }
86
+ }