@gaganref/confect-server 10.3.1-fork.5 → 10.3.1-fork.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ActionCall.d.ts +30 -0
- package/dist/ActionCall.d.ts.map +1 -0
- package/dist/ActionCall.js +41 -0
- package/dist/ActionCall.js.map +1 -0
- package/dist/ActionRunner.d.ts +1 -1
- package/dist/ActionRunner.d.ts.map +1 -1
- package/dist/ActionRunner.js +3 -3
- package/dist/ActionRunner.js.map +1 -1
- package/dist/AiGatewayDecisionClient.js +1 -1
- package/dist/AiGatewayLanguageClient.js +1 -1
- package/dist/Auth.d.ts +13 -1
- package/dist/Auth.d.ts.map +1 -1
- package/dist/Auth.js +13 -3
- package/dist/Auth.js.map +1 -1
- package/dist/Callback.js +4 -4
- package/dist/ConvexConfigProvider.js +2 -2
- package/dist/ConvexFunctionDefinitions.d.ts.map +1 -1
- package/dist/ConvexFunctionDefinitions.js +4 -4
- package/dist/ConvexFunctionDefinitions.js.map +1 -1
- package/dist/ConvexLogger.js +1 -1
- package/dist/CronJobs.js +6 -6
- package/dist/DatabaseSchema.js +2 -2
- package/dist/DatabaseWriter.d.ts +36 -2
- package/dist/DatabaseWriter.d.ts.map +1 -1
- package/dist/DatabaseWriter.js +55 -8
- package/dist/DatabaseWriter.js.map +1 -1
- package/dist/ExecutionMetadata.js +1 -1
- package/dist/FunctionImpl.js +2 -2
- package/dist/FunctionRegistryItem.d.ts +1 -0
- package/dist/FunctionRegistryItem.d.ts.map +1 -1
- package/dist/FunctionRegistryItem.js +2 -1
- package/dist/FunctionRegistryItem.js.map +1 -1
- package/dist/GroupImpl.d.ts +21 -2
- package/dist/GroupImpl.d.ts.map +1 -1
- package/dist/GroupImpl.js +21 -22
- package/dist/GroupImpl.js.map +1 -1
- package/dist/GroupRegistry.d.ts +1 -0
- package/dist/GroupRegistry.d.ts.map +1 -1
- package/dist/GroupRegistry.js +10 -3
- package/dist/GroupRegistry.js.map +1 -1
- package/dist/Handler.d.ts +8 -4
- package/dist/Handler.d.ts.map +1 -1
- package/dist/Handler.js.map +1 -1
- package/dist/HttpRouter.d.ts +2 -0
- package/dist/HttpRouter.d.ts.map +1 -1
- package/dist/HttpRouter.js +17 -8
- package/dist/HttpRouter.js.map +1 -1
- package/dist/MiddlewareImpl.js +1 -1
- package/dist/MutationRunner.d.ts +1 -1
- package/dist/MutationRunner.d.ts.map +1 -1
- package/dist/MutationRunner.js +3 -3
- package/dist/MutationRunner.js.map +1 -1
- package/dist/MutationTransactionContext.d.ts.map +1 -1
- package/dist/MutationTransactionContext.js +4 -3
- package/dist/MutationTransactionContext.js.map +1 -1
- package/dist/OfficialComponent.d.ts +60 -0
- package/dist/OfficialComponent.d.ts.map +1 -0
- package/dist/OfficialComponent.js +71 -0
- package/dist/OfficialComponent.js.map +1 -0
- package/dist/QueryInitializer.js +2 -2
- package/dist/QueryRunner.js +2 -2
- package/dist/QueryStream.d.ts +120 -12
- package/dist/QueryStream.d.ts.map +1 -1
- package/dist/QueryStream.js +143 -41
- package/dist/QueryStream.js.map +1 -1
- package/dist/QueryStreamCursor.js +1 -1
- package/dist/QueryStreamIndexPrefix.js +1 -1
- package/dist/QueryStreamIndexRange.js +2 -2
- package/dist/QueryStreamKey.js +1 -1
- package/dist/QueryStreamKeyBounds.js +2 -2
- package/dist/QueryStreamKeyLayout.js +2 -2
- package/dist/QueryStreamKeyValues.js +2 -2
- package/dist/QueryStreamPagination.js +1 -1
- package/dist/QueryStreamReadBudget.d.ts +3 -2
- package/dist/QueryStreamReadBudget.d.ts.map +1 -1
- package/dist/QueryStreamReadBudget.js +8 -5
- package/dist/QueryStreamReadBudget.js.map +1 -1
- package/dist/QueryTransactionContext.js +2 -2
- package/dist/RegisteredConvexFunction.d.ts +10 -5
- package/dist/RegisteredConvexFunction.d.ts.map +1 -1
- package/dist/RegisteredConvexFunction.js +45 -29
- package/dist/RegisteredConvexFunction.js.map +1 -1
- package/dist/RegisteredFunction.d.ts +39 -22
- package/dist/RegisteredFunction.d.ts.map +1 -1
- package/dist/RegisteredFunction.js +155 -42
- package/dist/RegisteredFunction.js.map +1 -1
- package/dist/RegisteredNodeFunction.d.ts +1 -1
- package/dist/RegisteredNodeFunction.d.ts.map +1 -1
- package/dist/RegisteredNodeFunction.js +10 -6
- package/dist/RegisteredNodeFunction.js.map +1 -1
- package/dist/RegistryItems.d.ts +7 -0
- package/dist/RegistryItems.d.ts.map +1 -1
- package/dist/RegistryItems.js +8 -3
- package/dist/RegistryItems.js.map +1 -1
- package/dist/RequestMetadata.js +1 -1
- package/dist/Scheduler.d.ts.map +1 -1
- package/dist/Scheduler.js +11 -10
- package/dist/Scheduler.js.map +1 -1
- package/dist/Storage.js +1 -1
- package/dist/StorageActionWriter.js +1 -1
- package/dist/StorageReader.js +1 -1
- package/dist/StorageWriter.js +1 -1
- package/dist/TransactionMetadata.js +1 -1
- package/dist/VectorSearch.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -18
- package/dist/internal/AiGatewayDecisionClient.js +1 -1
- package/dist/internal/AiGatewayLanguageClient.js +1 -1
- package/dist/internal/AiGatewayServiceToken.js +1 -1
- package/dist/internal/DocumentPatch.d.ts +16 -0
- package/dist/internal/DocumentPatch.d.ts.map +1 -0
- package/dist/internal/DocumentPatch.js +36 -0
- package/dist/internal/DocumentPatch.js.map +1 -0
- package/dist/internal/observability.d.ts +5 -0
- package/dist/internal/observability.d.ts.map +1 -0
- package/dist/internal/observability.js +21 -0
- package/dist/internal/observability.js.map +1 -0
- package/dist/internal/reservedArgument.d.ts +8 -0
- package/dist/internal/reservedArgument.d.ts.map +1 -0
- package/dist/internal/reservedArgument.js +30 -0
- package/dist/internal/reservedArgument.js.map +1 -0
- package/package.json +5 -5
- package/src/ActionCall.ts +83 -0
- package/src/ActionRunner.ts +3 -2
- package/src/Auth.ts +23 -1
- package/src/ConvexFunctionDefinitions.ts +2 -1
- package/src/DatabaseWriter.ts +183 -25
- package/src/FunctionRegistryItem.ts +4 -0
- package/src/GroupImpl.ts +61 -8
- package/src/GroupRegistry.ts +7 -3
- package/src/Handler.ts +21 -5
- package/src/HttpRouter.ts +54 -4
- package/src/MutationRunner.ts +3 -2
- package/src/MutationTransactionContext.ts +2 -1
- package/src/OfficialComponent.ts +167 -0
- package/src/QueryStream.ts +391 -61
- package/src/QueryStreamReadBudget.ts +19 -6
- package/src/RegisteredConvexFunction.ts +35 -2
- package/src/RegisteredFunction.ts +251 -45
- package/src/RegisteredNodeFunction.ts +11 -1
- package/src/RegistryItems.ts +31 -0
- package/src/Scheduler.ts +13 -16
- package/src/index.ts +2 -0
- package/src/internal/DocumentPatch.ts +87 -0
- package/src/internal/observability.ts +27 -0
- package/src/internal/reservedArgument.ts +56 -0
package/src/QueryStream.ts
CHANGED
|
@@ -70,7 +70,16 @@ import * as QueryStreamPagination from "./QueryStreamPagination.js";
|
|
|
70
70
|
import * as QueryStreamReadBudget from "./QueryStreamReadBudget.js";
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
|
-
*
|
|
73
|
+
* Default physical row budget for `paginate` and `collect`. Supply an explicit
|
|
74
|
+
* `maximumRowsRead` to override it. Plain Effect Stream consumers use their
|
|
75
|
+
* enclosing budget and remain unlimited when none is provided.
|
|
76
|
+
*
|
|
77
|
+
* @experimental
|
|
78
|
+
*/
|
|
79
|
+
export const DEFAULT_MAXIMUM_ROWS_READ = 2_000;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* A query stream exhausted its read budget before its consumer could finish.
|
|
74
83
|
*
|
|
75
84
|
* @experimental
|
|
76
85
|
*/
|
|
@@ -79,7 +88,7 @@ export class ReadBudgetExceededError extends Schema.TaggedError<ReadBudgetExceed
|
|
|
79
88
|
{ rowsRead: Schema.Natural, bytesRead: Schema.Natural },
|
|
80
89
|
) {
|
|
81
90
|
override get message() {
|
|
82
|
-
return "The read budget was exhausted before
|
|
91
|
+
return "The read budget was exhausted before the query stream completed; increase the budget or simplify the query";
|
|
83
92
|
}
|
|
84
93
|
}
|
|
85
94
|
|
|
@@ -441,6 +450,89 @@ export const empty =
|
|
|
441
450
|
return make(orderDirection);
|
|
442
451
|
};
|
|
443
452
|
|
|
453
|
+
/**
|
|
454
|
+
* A deferred stream's resolved direction or layout differs from its
|
|
455
|
+
* declaration. The check runs on consumption, before the resolved source is
|
|
456
|
+
* pulled.
|
|
457
|
+
*
|
|
458
|
+
* @experimental
|
|
459
|
+
*/
|
|
460
|
+
export class DeferredStreamMismatchError extends Data.TaggedError(
|
|
461
|
+
"DeferredStreamMismatchError",
|
|
462
|
+
)<{
|
|
463
|
+
readonly expectedOrderDirection: QueryStreamOrderDirection.QueryStreamOrderDirection;
|
|
464
|
+
readonly actualOrderDirection: QueryStreamOrderDirection.QueryStreamOrderDirection;
|
|
465
|
+
readonly expectedKeyLayout: QueryStreamKeyLayout.QueryStreamKeyLayout;
|
|
466
|
+
readonly actualKeyLayout: QueryStreamKeyLayout.QueryStreamKeyLayout;
|
|
467
|
+
}> {}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Defer an effect that checks access or constructs a query stream until it is
|
|
471
|
+
* pulled. Supply its layout and direction without reading documents. This lets
|
|
472
|
+
* `flatMap` build and narrow an inner stream before running its checks.
|
|
473
|
+
*
|
|
474
|
+
* The effect runs afresh on each consumption, seek, or reversal. Failures and
|
|
475
|
+
* service requirements are preserved. Its stream must match the declared layout
|
|
476
|
+
* and direction, or consumption dies with `DeferredStreamMismatchError`.
|
|
477
|
+
*
|
|
478
|
+
* @experimental
|
|
479
|
+
*/
|
|
480
|
+
export const defer = <
|
|
481
|
+
Doc,
|
|
482
|
+
KeyLabels extends QueryStreamKeyLabels.QueryStreamKeyLabels,
|
|
483
|
+
OrderDirection extends QueryStreamOrderDirection.QueryStreamOrderDirection,
|
|
484
|
+
E = never,
|
|
485
|
+
R = never,
|
|
486
|
+
E2 = never,
|
|
487
|
+
R2 = never,
|
|
488
|
+
>(
|
|
489
|
+
options: {
|
|
490
|
+
readonly keyLayout: QueryStreamKeyLayout.QueryStreamKeyLayout<KeyLabels>;
|
|
491
|
+
readonly orderDirection: OrderDirection;
|
|
492
|
+
},
|
|
493
|
+
make: Effect.Effect<
|
|
494
|
+
QueryStream<Doc, KeyLabels, NoInfer<OrderDirection>, E, R>,
|
|
495
|
+
E2,
|
|
496
|
+
R2
|
|
497
|
+
>,
|
|
498
|
+
): QueryStream<Doc, KeyLabels, OrderDirection, E | E2, R | R2> => {
|
|
499
|
+
const checked = Effect.map(make, (stream) => {
|
|
500
|
+
if (
|
|
501
|
+
stream.orderDirection !== options.orderDirection ||
|
|
502
|
+
!QueryStreamKeyLayout.Equivalence(stream.keyLayout, options.keyLayout)
|
|
503
|
+
) {
|
|
504
|
+
throw new DeferredStreamMismatchError({
|
|
505
|
+
expectedOrderDirection: options.orderDirection,
|
|
506
|
+
actualOrderDirection: stream.orderDirection,
|
|
507
|
+
expectedKeyLayout: options.keyLayout,
|
|
508
|
+
actualKeyLayout: stream.keyLayout,
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
return stream;
|
|
512
|
+
});
|
|
513
|
+
return new QueryStream(
|
|
514
|
+
options.orderDirection,
|
|
515
|
+
options.keyLayout,
|
|
516
|
+
Stream.unwrap(Effect.map(checked, (stream) => stream.annotated)),
|
|
517
|
+
undefined,
|
|
518
|
+
(bounds) =>
|
|
519
|
+
defer(
|
|
520
|
+
options,
|
|
521
|
+
Effect.map(checked, (stream) => narrowByParsedBounds(stream, bounds)),
|
|
522
|
+
),
|
|
523
|
+
() =>
|
|
524
|
+
defer(
|
|
525
|
+
{
|
|
526
|
+
...options,
|
|
527
|
+
orderDirection: QueryStreamOrderDirection.flip(
|
|
528
|
+
options.orderDirection,
|
|
529
|
+
),
|
|
530
|
+
},
|
|
531
|
+
Effect.map(checked, reverse),
|
|
532
|
+
),
|
|
533
|
+
);
|
|
534
|
+
};
|
|
535
|
+
|
|
444
536
|
// -----------------------------------------------------------------------------
|
|
445
537
|
// Constructors
|
|
446
538
|
// -----------------------------------------------------------------------------
|
|
@@ -742,6 +834,21 @@ const fillMergeSource = <Doc, E>(
|
|
|
742
834
|
BudgetLimited: () => Effect.succeed(source),
|
|
743
835
|
});
|
|
744
836
|
|
|
837
|
+
const consumeMergeHead = <Doc, E>(
|
|
838
|
+
source: MergeSource<Doc, E>,
|
|
839
|
+
): MergeSource<Doc, E> =>
|
|
840
|
+
source._tag === "Buffered"
|
|
841
|
+
? Option.match(Chunk.head(source.tail), {
|
|
842
|
+
onNone: () => MergeSource.NeedsPull({ pull: source.pull }),
|
|
843
|
+
onSome: (head) =>
|
|
844
|
+
MergeSource.Buffered({
|
|
845
|
+
pull: source.pull,
|
|
846
|
+
head,
|
|
847
|
+
tail: Chunk.drop(source.tail, 1),
|
|
848
|
+
}),
|
|
849
|
+
})
|
|
850
|
+
: source;
|
|
851
|
+
|
|
745
852
|
/**
|
|
746
853
|
* One step of the k-way merge as a pure unfold: fill every source, emit the
|
|
747
854
|
* earliest head (ties go to the earliest source, keeping the merge stable), and
|
|
@@ -749,7 +856,10 @@ const fillMergeSource = <Doc, E>(
|
|
|
749
856
|
* exhausted or an input stopped before its next key was known.
|
|
750
857
|
*/
|
|
751
858
|
const mergeStep =
|
|
752
|
-
<Doc, E>(
|
|
859
|
+
<Doc, E>(
|
|
860
|
+
keyOrder: Order.Order<QueryStreamKeyValues.QueryStreamKeyValues>,
|
|
861
|
+
dedupe: boolean,
|
|
862
|
+
) =>
|
|
753
863
|
(
|
|
754
864
|
sources: ReadonlyArray<MergeSource<Doc, E>>,
|
|
755
865
|
): Effect.Effect<
|
|
@@ -791,36 +901,73 @@ const mergeStep =
|
|
|
791
901
|
),
|
|
792
902
|
);
|
|
793
903
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
onNone: () => MergeSource.NeedsPull({ pull: source.pull }),
|
|
802
|
-
onSome: (head) =>
|
|
803
|
-
MergeSource.Buffered({
|
|
804
|
-
pull: source.pull,
|
|
805
|
-
head,
|
|
806
|
-
tail: Chunk.drop(source.tail, 1),
|
|
807
|
-
}),
|
|
808
|
-
})
|
|
809
|
-
: source,
|
|
810
|
-
),
|
|
904
|
+
if (Option.isNone(earliest)) return undefined;
|
|
905
|
+
if (!dedupe) {
|
|
906
|
+
const { index, element } = earliest.value;
|
|
907
|
+
return Tuple.make(
|
|
908
|
+
element,
|
|
909
|
+
Array.map(filled, (source, sourceIndex) =>
|
|
910
|
+
sourceIndex === index ? consumeMergeHead(source) : source,
|
|
811
911
|
),
|
|
812
|
-
)
|
|
813
|
-
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
const keyValues = earliest.value.element.keyValues;
|
|
915
|
+
const matches = (source: MergeSource<Doc, E>) =>
|
|
916
|
+
source._tag === "Buffered" &&
|
|
917
|
+
keyOrder(source.head.keyValues, keyValues) === 0;
|
|
918
|
+
let representative = earliest.value;
|
|
919
|
+
let remaining = filled;
|
|
920
|
+
// Resolve the entire equal-key group before publishing its boundary.
|
|
921
|
+
// A budget stop leaves it for the next page, including any visible copy.
|
|
922
|
+
for (;;) {
|
|
923
|
+
representative = Array.reduce(
|
|
924
|
+
remaining,
|
|
925
|
+
representative,
|
|
926
|
+
(best, source, index) =>
|
|
927
|
+
source._tag === "Buffered" &&
|
|
928
|
+
matches(source) &&
|
|
929
|
+
Option.isSome(source.head.doc) &&
|
|
930
|
+
(Option.isNone(best.element.doc) || index < best.index)
|
|
931
|
+
? new MergeCandidate({ index, element: source.head })
|
|
932
|
+
: best,
|
|
933
|
+
);
|
|
934
|
+
remaining = yield* Effect.forEach(
|
|
935
|
+
Array.map(remaining, (source) =>
|
|
936
|
+
matches(source) ? consumeMergeHead(source) : source,
|
|
937
|
+
),
|
|
938
|
+
fillMergeSource,
|
|
939
|
+
{ concurrency: (yield* readBudget.isUnlimited) ? "unbounded" : 1 },
|
|
940
|
+
);
|
|
941
|
+
if (
|
|
942
|
+
remaining.some((source) => MergeSource.$is("BudgetLimited")(source))
|
|
943
|
+
)
|
|
944
|
+
return undefined;
|
|
945
|
+
if (!remaining.some(matches))
|
|
946
|
+
return Tuple.make(representative.element, remaining);
|
|
947
|
+
}
|
|
814
948
|
});
|
|
815
949
|
|
|
950
|
+
/**
|
|
951
|
+
* Optional merge behavior. With `dedupe`, equal complete stored keys, including
|
|
952
|
+
* ID tiebreakers, emit once. The first visible copy in source order wins; a
|
|
953
|
+
* filtered copy cannot suppress a visible one. Different keys remain distinct.
|
|
954
|
+
*
|
|
955
|
+
* @experimental
|
|
956
|
+
*/
|
|
957
|
+
export interface MergeOptions {
|
|
958
|
+
readonly dedupe?: boolean;
|
|
959
|
+
}
|
|
960
|
+
|
|
816
961
|
/**
|
|
817
962
|
* Combine streams sharing a key layout into one stream in key order. Use this
|
|
818
963
|
* to query several index ranges at once, such as notes by multiple roles.
|
|
819
964
|
*
|
|
820
965
|
* The merge emits the smallest next key when ascending and the largest when
|
|
821
|
-
* descending. Unlike `Stream.merge`, it does not interleave by arrival time.
|
|
822
|
-
*
|
|
823
|
-
*
|
|
966
|
+
* descending. Unlike `Stream.merge`, it does not interleave by arrival time. By
|
|
967
|
+
* default, a document matched twice appears twice. Pass `{ dedupe: true }` to
|
|
968
|
+
* coalesce equal complete keys, including ID tiebreakers. Resolving a group can
|
|
969
|
+
* prefetch the next key from each input; all such reads count against the
|
|
970
|
+
* shared budget. Supply at least one input; use `empty` for an empty list.
|
|
824
971
|
*
|
|
825
972
|
* Inputs need compatible document types and identical key layouts and
|
|
826
973
|
* directions. TypeScript rejects known mismatches; runtime mismatches throw
|
|
@@ -842,6 +989,7 @@ export const merge = <
|
|
|
842
989
|
QueryStream<Doc, KeyLabels, NoInfer<OrderDirection>, E, R>
|
|
843
990
|
>,
|
|
844
991
|
],
|
|
992
|
+
options?: MergeOptions,
|
|
845
993
|
): QueryStream<Doc, KeyLabels, OrderDirection, E, R> => {
|
|
846
994
|
const head = Array.headNonEmpty(streams);
|
|
847
995
|
const incompatible = Array.findFirst(
|
|
@@ -858,7 +1006,7 @@ export const merge = <
|
|
|
858
1006
|
actualKeyLayout: incompatible.value.keyLayout,
|
|
859
1007
|
});
|
|
860
1008
|
}
|
|
861
|
-
return mergeUnchecked(streams);
|
|
1009
|
+
return mergeUnchecked(streams, options);
|
|
862
1010
|
};
|
|
863
1011
|
|
|
864
1012
|
/**
|
|
@@ -877,6 +1025,7 @@ const mergeUnchecked = <
|
|
|
877
1025
|
QueryStream<Doc, KeyLabels, OrderDirection, E, R>,
|
|
878
1026
|
...ReadonlyArray<QueryStream<Doc, KeyLabels, OrderDirection, E, R>>,
|
|
879
1027
|
],
|
|
1028
|
+
options?: MergeOptions,
|
|
880
1029
|
): QueryStream<Doc, KeyLabels, OrderDirection, E, R> => {
|
|
881
1030
|
const head = Array.headNonEmpty(streams);
|
|
882
1031
|
const annotated: Stream.Stream<
|
|
@@ -889,7 +1038,10 @@ const mergeUnchecked = <
|
|
|
889
1038
|
(pulls) =>
|
|
890
1039
|
Stream.unfold(
|
|
891
1040
|
Array.map(pulls, (pull) => MergeSource.NeedsPull({ pull })),
|
|
892
|
-
mergeStep<Doc, E>(
|
|
1041
|
+
mergeStep<Doc, E>(
|
|
1042
|
+
QueryStreamKeyValues.Order(head.orderDirection),
|
|
1043
|
+
options?.dedupe === true,
|
|
1044
|
+
),
|
|
893
1045
|
),
|
|
894
1046
|
),
|
|
895
1047
|
);
|
|
@@ -903,17 +1055,25 @@ const mergeUnchecked = <
|
|
|
903
1055
|
// key space, so each branch converts them to its own index-key
|
|
904
1056
|
// space itself.
|
|
905
1057
|
(parsedBounds) =>
|
|
906
|
-
mergeUnchecked(
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1058
|
+
mergeUnchecked(
|
|
1059
|
+
[
|
|
1060
|
+
narrowByParsedBounds(head, parsedBounds),
|
|
1061
|
+
...Array.map(Array.tailNonEmpty(streams), (stream) =>
|
|
1062
|
+
narrowByParsedBounds(stream, parsedBounds),
|
|
1063
|
+
),
|
|
1064
|
+
],
|
|
1065
|
+
options,
|
|
1066
|
+
),
|
|
912
1067
|
() =>
|
|
913
|
-
mergeUnchecked(
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1068
|
+
mergeUnchecked(
|
|
1069
|
+
[
|
|
1070
|
+
reverse(head),
|
|
1071
|
+
...Array.map(Array.tailNonEmpty(streams), (stream) =>
|
|
1072
|
+
reverse(stream),
|
|
1073
|
+
),
|
|
1074
|
+
],
|
|
1075
|
+
options,
|
|
1076
|
+
),
|
|
917
1077
|
);
|
|
918
1078
|
};
|
|
919
1079
|
|
|
@@ -2140,6 +2300,98 @@ const narrowInMemory = <
|
|
|
2140
2300
|
// Sinks
|
|
2141
2301
|
// -----------------------------------------------------------------------------
|
|
2142
2302
|
|
|
2303
|
+
/**
|
|
2304
|
+
* Read limits for `collect`. These count underlying index reads, including
|
|
2305
|
+
* filtered rows and join inputs, rather than just emitted values. At either
|
|
2306
|
+
* limit, one extra row may be read to distinguish completion from an overrun;
|
|
2307
|
+
* it counts toward reported reads but is never emitted. This also applies at
|
|
2308
|
+
* zero, so an empty stream completes under a zero budget.
|
|
2309
|
+
*
|
|
2310
|
+
* @experimental
|
|
2311
|
+
*/
|
|
2312
|
+
export interface CollectOptions {
|
|
2313
|
+
readonly maximumRowsRead?: number;
|
|
2314
|
+
readonly maximumBytesRead?: number;
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
/**
|
|
2318
|
+
* Collect the whole query stream or fail with `ReadBudgetExceededError` when a
|
|
2319
|
+
* read budget stops it. No partial array reaches the caller. Arbitrary I/O in
|
|
2320
|
+
* callbacks is not counted. Use `paginate` for data that can grow without
|
|
2321
|
+
* bound.
|
|
2322
|
+
*
|
|
2323
|
+
* With explicit options, each run gets a fresh budget. Without options, use the
|
|
2324
|
+
* enclosing budget if present, or the default row budget.
|
|
2325
|
+
*
|
|
2326
|
+
* @experimental
|
|
2327
|
+
*/
|
|
2328
|
+
export const collect: {
|
|
2329
|
+
(
|
|
2330
|
+
options?: CollectOptions,
|
|
2331
|
+
): <Doc, KeyLabels extends QueryStreamKeyLabels.QueryStreamKeyLabels, E, R>(
|
|
2332
|
+
self: QueryStream<
|
|
2333
|
+
Doc,
|
|
2334
|
+
KeyLabels,
|
|
2335
|
+
QueryStreamOrderDirection.QueryStreamOrderDirection,
|
|
2336
|
+
E,
|
|
2337
|
+
R
|
|
2338
|
+
>,
|
|
2339
|
+
) => Effect.Effect<Array<Doc>, E | ReadBudgetExceededError, R>;
|
|
2340
|
+
<Doc, KeyLabels extends QueryStreamKeyLabels.QueryStreamKeyLabels, E, R>(
|
|
2341
|
+
self: QueryStream<
|
|
2342
|
+
Doc,
|
|
2343
|
+
KeyLabels,
|
|
2344
|
+
QueryStreamOrderDirection.QueryStreamOrderDirection,
|
|
2345
|
+
E,
|
|
2346
|
+
R
|
|
2347
|
+
>,
|
|
2348
|
+
options?: CollectOptions,
|
|
2349
|
+
): Effect.Effect<Array<Doc>, E | ReadBudgetExceededError, R>;
|
|
2350
|
+
} = dual(
|
|
2351
|
+
(args) => isQueryStream(args[0]),
|
|
2352
|
+
Effect.fn("QueryStream.collect")(function* <
|
|
2353
|
+
Doc,
|
|
2354
|
+
KeyLabels extends QueryStreamKeyLabels.QueryStreamKeyLabels,
|
|
2355
|
+
E,
|
|
2356
|
+
R,
|
|
2357
|
+
>(
|
|
2358
|
+
self: QueryStream<
|
|
2359
|
+
Doc,
|
|
2360
|
+
KeyLabels,
|
|
2361
|
+
QueryStreamOrderDirection.QueryStreamOrderDirection,
|
|
2362
|
+
E,
|
|
2363
|
+
R
|
|
2364
|
+
>,
|
|
2365
|
+
options?: CollectOptions,
|
|
2366
|
+
) {
|
|
2367
|
+
const makeBudget = Schema.decodeEffect(QueryStreamReadBudget.Limits)({
|
|
2368
|
+
maximumRowsRead: DEFAULT_MAXIMUM_ROWS_READ,
|
|
2369
|
+
...options,
|
|
2370
|
+
}).pipe(
|
|
2371
|
+
Effect.catchTag("SchemaError", (cause) =>
|
|
2372
|
+
Effect.die(new QueryStreamReadBudget.InvalidReadLimitError({ cause })),
|
|
2373
|
+
),
|
|
2374
|
+
Effect.flatMap(QueryStreamReadBudget.make),
|
|
2375
|
+
);
|
|
2376
|
+
const budget = yield* options === undefined
|
|
2377
|
+
? Effect.serviceOption(QueryStreamReadBudget.QueryStreamReadBudget).pipe(
|
|
2378
|
+
Effect.flatMap(
|
|
2379
|
+
Option.match({ onNone: () => makeBudget, onSome: Effect.succeed }),
|
|
2380
|
+
),
|
|
2381
|
+
)
|
|
2382
|
+
: makeBudget;
|
|
2383
|
+
const rows = yield* Stream.runCollect(self.toStream()).pipe(
|
|
2384
|
+
Effect.provideService(
|
|
2385
|
+
QueryStreamReadBudget.QueryStreamReadBudget,
|
|
2386
|
+
budget,
|
|
2387
|
+
),
|
|
2388
|
+
);
|
|
2389
|
+
if (yield* budget.isStopped)
|
|
2390
|
+
return yield* new ReadBudgetExceededError(yield* budget.getReadCounts);
|
|
2391
|
+
return rows;
|
|
2392
|
+
}),
|
|
2393
|
+
);
|
|
2394
|
+
|
|
2143
2395
|
/**
|
|
2144
2396
|
* `unique` found at least two emitted values. Narrow the query if at most one
|
|
2145
2397
|
* value was expected, or use another consumer to handle multiple results.
|
|
@@ -2189,6 +2441,27 @@ export const unique = Effect.fn("QueryStream.unique")(
|
|
|
2189
2441
|
// Pagination
|
|
2190
2442
|
// -----------------------------------------------------------------------------
|
|
2191
2443
|
|
|
2444
|
+
/**
|
|
2445
|
+
* Caller-owned envelope for every pagination cursor, including end sentinels.
|
|
2446
|
+
* `open` validates a received cursor and returns `None` on a mismatch; paginate
|
|
2447
|
+
* then dies with `ConvexError({ paginationError: "InvalidCursor" })` before
|
|
2448
|
+
* decoding or reading. `seal` wraps every outgoing continuation and split.
|
|
2449
|
+
* `seal` must return the same string for equal inputs so cursor equality stays
|
|
2450
|
+
* stable.
|
|
2451
|
+
*
|
|
2452
|
+
* Use this to bind cursors to query semantics or sign them. Both callbacks may
|
|
2453
|
+
* use Effect services. Confect owns no keys, fingerprints, or encryption
|
|
2454
|
+
* policy.
|
|
2455
|
+
*
|
|
2456
|
+
* @experimental
|
|
2457
|
+
*/
|
|
2458
|
+
export interface CursorEnvelope<R = never> {
|
|
2459
|
+
readonly open: (
|
|
2460
|
+
cursor: string,
|
|
2461
|
+
) => Effect.Effect<Option.Option<string>, never, R>;
|
|
2462
|
+
readonly seal: (cursor: string) => Effect.Effect<string, never, R>;
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2192
2465
|
/**
|
|
2193
2466
|
* Convex pagination options interpreted over a query stream's stored keys. Pass
|
|
2194
2467
|
* a paginated query handler's `paginationOpts` directly to `paginate`.
|
|
@@ -2200,17 +2473,27 @@ export const unique = Effect.fn("QueryStream.unique")(
|
|
|
2200
2473
|
* - `endCursor`: optional inclusive boundary returned by a previous page. Pins
|
|
2201
2474
|
* the page to a key range regardless of how many values it holds.
|
|
2202
2475
|
* - `maximumRowsRead`: optional budget for physical document reads from
|
|
2203
|
-
* underlying query-stream indexes, including filtered documents.
|
|
2476
|
+
* underlying query-stream indexes, including filtered documents. Defaults to
|
|
2477
|
+
* 2,000; an explicit limit overrides the default.
|
|
2204
2478
|
* - `maximumBytesRead`: optional budget charging estimated document size on every
|
|
2205
2479
|
* read, not Convex's exact billed bytes. The final document can take the
|
|
2206
2480
|
* total over the limit because its size is known only after reading it.
|
|
2481
|
+
* - `cursorEnvelope`: optional caller-owned validation and sealing. Both input
|
|
2482
|
+
* cursors are opened before decoding, including an end sentinel. Every output
|
|
2483
|
+
* cursor is sealed. A refused envelope ends in `InvalidCursor`.
|
|
2484
|
+
*
|
|
2485
|
+
* At either read limit, including zero, one extra row may be read to
|
|
2486
|
+
* distinguish completion from an overrun. It is counted but never emitted. An
|
|
2487
|
+
* empty stream completes under a zero budget.
|
|
2207
2488
|
*
|
|
2208
2489
|
* With `numItems: 0`, an existing cursor is returned unchanged with an empty
|
|
2209
2490
|
* page and `isDone: false`; a null cursor dies with `EmptyInitialPageError`.
|
|
2210
2491
|
*
|
|
2211
2492
|
* @experimental
|
|
2212
2493
|
*/
|
|
2213
|
-
export type PaginateOptions = ConvexPaginationOptions
|
|
2494
|
+
export type PaginateOptions<R = never> = ConvexPaginationOptions & {
|
|
2495
|
+
readonly cursorEnvelope?: CursorEnvelope<R>;
|
|
2496
|
+
};
|
|
2214
2497
|
|
|
2215
2498
|
/**
|
|
2216
2499
|
* One page in Convex's pagination result shape. Return the whole result to
|
|
@@ -2264,9 +2547,9 @@ export type PaginationResult<Doc> = ConvexPaginationResult<Doc>;
|
|
|
2264
2547
|
* query semantics change, even if an old cursor is still accepted.
|
|
2265
2548
|
*
|
|
2266
2549
|
* Cursors expose boundary field names and key values, including document IDs.
|
|
2267
|
-
*
|
|
2268
|
-
*
|
|
2269
|
-
*
|
|
2550
|
+
* Without a `cursorEnvelope`, clients can read or craft them. Do not publicly
|
|
2551
|
+
* paginate over sensitive indexed fields unless pinned with `eq`. Never
|
|
2552
|
+
* construct cursors from documents or decode them into `narrow` bounds.
|
|
2270
2553
|
*
|
|
2271
2554
|
* Return the whole result to reactive clients. React's
|
|
2272
2555
|
* `useStreamPaginatedQuery` handles pinning, splitting, and invalid-cursor
|
|
@@ -2276,8 +2559,8 @@ export type PaginationResult<Doc> = ConvexPaginationResult<Doc>;
|
|
|
2276
2559
|
* @experimental
|
|
2277
2560
|
*/
|
|
2278
2561
|
export const paginate: {
|
|
2279
|
-
(
|
|
2280
|
-
options: PaginateOptions
|
|
2562
|
+
<R2 = never>(
|
|
2563
|
+
options: PaginateOptions<R2>,
|
|
2281
2564
|
): <Doc, KeyLabels extends QueryStreamKeyLabels.QueryStreamKeyLabels, E, R>(
|
|
2282
2565
|
self: QueryStream<
|
|
2283
2566
|
Doc,
|
|
@@ -2286,8 +2569,18 @@ export const paginate: {
|
|
|
2286
2569
|
E,
|
|
2287
2570
|
R
|
|
2288
2571
|
>,
|
|
2289
|
-
) => Effect.Effect<
|
|
2290
|
-
|
|
2572
|
+
) => Effect.Effect<
|
|
2573
|
+
PaginationResult<Doc>,
|
|
2574
|
+
E | ReadBudgetExceededError,
|
|
2575
|
+
R | R2
|
|
2576
|
+
>;
|
|
2577
|
+
<
|
|
2578
|
+
Doc,
|
|
2579
|
+
KeyLabels extends QueryStreamKeyLabels.QueryStreamKeyLabels,
|
|
2580
|
+
E,
|
|
2581
|
+
R,
|
|
2582
|
+
R2 = never,
|
|
2583
|
+
>(
|
|
2291
2584
|
self: QueryStream<
|
|
2292
2585
|
Doc,
|
|
2293
2586
|
KeyLabels,
|
|
@@ -2295,8 +2588,8 @@ export const paginate: {
|
|
|
2295
2588
|
E,
|
|
2296
2589
|
R
|
|
2297
2590
|
>,
|
|
2298
|
-
options: PaginateOptions
|
|
2299
|
-
): Effect.Effect<PaginationResult<Doc>, E | ReadBudgetExceededError, R>;
|
|
2591
|
+
options: PaginateOptions<R2>,
|
|
2592
|
+
): Effect.Effect<PaginationResult<Doc>, E | ReadBudgetExceededError, R | R2>;
|
|
2300
2593
|
} = dual(
|
|
2301
2594
|
2,
|
|
2302
2595
|
Effect.fn("QueryStream.paginate")(
|
|
@@ -2307,10 +2600,37 @@ export const paginate: {
|
|
|
2307
2600
|
R,
|
|
2308
2601
|
OrderDirection extends
|
|
2309
2602
|
QueryStreamOrderDirection.QueryStreamOrderDirection,
|
|
2603
|
+
R2 = never,
|
|
2310
2604
|
>(
|
|
2311
2605
|
self: QueryStream<Doc, KeyLabels, OrderDirection, E, R>,
|
|
2312
|
-
options: PaginateOptions
|
|
2606
|
+
options: PaginateOptions<R2>,
|
|
2313
2607
|
) {
|
|
2608
|
+
const seal = (cursor: string): Effect.Effect<string, never, R2> =>
|
|
2609
|
+
options.cursorEnvelope === undefined
|
|
2610
|
+
? Effect.succeed(cursor)
|
|
2611
|
+
: options.cursorEnvelope.seal(cursor);
|
|
2612
|
+
const open = (cursor: string): Effect.Effect<string, never, R2> =>
|
|
2613
|
+
options.cursorEnvelope === undefined
|
|
2614
|
+
? Effect.succeed(cursor)
|
|
2615
|
+
: options.cursorEnvelope.open(cursor).pipe(
|
|
2616
|
+
Effect.flatMap(
|
|
2617
|
+
Option.match({
|
|
2618
|
+
onNone: () =>
|
|
2619
|
+
Effect.die(
|
|
2620
|
+
new ConvexError({ paginationError: "InvalidCursor" }),
|
|
2621
|
+
),
|
|
2622
|
+
onSome: Effect.succeed,
|
|
2623
|
+
}),
|
|
2624
|
+
),
|
|
2625
|
+
);
|
|
2626
|
+
const opened = yield* Effect.all({
|
|
2627
|
+
start:
|
|
2628
|
+
options.cursor === null ? Effect.succeed(null) : open(options.cursor),
|
|
2629
|
+
end:
|
|
2630
|
+
options.endCursor == null
|
|
2631
|
+
? Effect.succeed(null)
|
|
2632
|
+
: open(options.endCursor),
|
|
2633
|
+
});
|
|
2314
2634
|
const cursorSchema = QueryStreamCursor.fromKeyLayout(self.keyLayout);
|
|
2315
2635
|
const encodeCursor = Schema.encodeEffect(cursorSchema);
|
|
2316
2636
|
const decodeCursor = Schema.decodeEffect(cursorSchema);
|
|
@@ -2320,7 +2640,7 @@ export const paginate: {
|
|
|
2320
2640
|
identity,
|
|
2321
2641
|
);
|
|
2322
2642
|
const decoded = yield* Effect.all({
|
|
2323
|
-
start: Option.match(Option.fromNullOr(
|
|
2643
|
+
start: Option.match(Option.fromNullOr(opened.start), {
|
|
2324
2644
|
onNone: () => Effect.succeed(QueryStreamPagination.Start.Beginning()),
|
|
2325
2645
|
onSome: (cursor) =>
|
|
2326
2646
|
Effect.map(decodeCursor(cursor), (key) =>
|
|
@@ -2330,7 +2650,7 @@ export const paginate: {
|
|
|
2330
2650
|
}),
|
|
2331
2651
|
),
|
|
2332
2652
|
}),
|
|
2333
|
-
range: Option.match(Option.fromNullishOr(
|
|
2653
|
+
range: Option.match(Option.fromNullishOr(opened.end), {
|
|
2334
2654
|
onNone: () => Effect.succeed(QueryStreamPagination.Range.Unpinned()),
|
|
2335
2655
|
onSome: (cursor) =>
|
|
2336
2656
|
cursor === QueryStreamCursor.END_CURSOR
|
|
@@ -2361,7 +2681,7 @@ export const paginate: {
|
|
|
2361
2681
|
return {
|
|
2362
2682
|
page: [],
|
|
2363
2683
|
isDone: false,
|
|
2364
|
-
continueCursor: request.cursor,
|
|
2684
|
+
continueCursor: yield* seal(request.cursor),
|
|
2365
2685
|
} satisfies PaginationResult<Doc>;
|
|
2366
2686
|
}
|
|
2367
2687
|
const start = Option.map(request.after, (key) => ({
|
|
@@ -2396,7 +2716,7 @@ export const paginate: {
|
|
|
2396
2716
|
() => QueryStreamPagination.initial<Doc>(),
|
|
2397
2717
|
(state) => state._tag === "Reading",
|
|
2398
2718
|
(state, { doc, keyValues }: Element<Doc>) =>
|
|
2399
|
-
Effect.map(readBudget.
|
|
2719
|
+
Effect.map(readBudget.isStopped, (hitLimit) =>
|
|
2400
2720
|
QueryStreamPagination.record(
|
|
2401
2721
|
request,
|
|
2402
2722
|
state,
|
|
@@ -2414,7 +2734,10 @@ export const paginate: {
|
|
|
2414
2734
|
return yield* new ReadBudgetExceededError(counts);
|
|
2415
2735
|
}
|
|
2416
2736
|
const encode = (key: QueryStreamKey.Complete) =>
|
|
2417
|
-
encodeCursor(key).pipe(
|
|
2737
|
+
encodeCursor(key).pipe(
|
|
2738
|
+
Effect.catchTag("SchemaError", Effect.die),
|
|
2739
|
+
Effect.flatMap(seal),
|
|
2740
|
+
);
|
|
2418
2741
|
const encodeSplit = (
|
|
2419
2742
|
result: Extract<
|
|
2420
2743
|
QueryStreamPagination.Outcome<Doc>,
|
|
@@ -2424,7 +2747,7 @@ export const paginate: {
|
|
|
2424
2747
|
Effect.gen(function* () {
|
|
2425
2748
|
const continueCursor = yield* Match.value(result.continuation).pipe(
|
|
2426
2749
|
Match.tagsExhaustive({
|
|
2427
|
-
End: () =>
|
|
2750
|
+
End: () => seal(QueryStreamCursor.END_CURSOR),
|
|
2428
2751
|
Key: ({ key }) => encode(key),
|
|
2429
2752
|
}),
|
|
2430
2753
|
);
|
|
@@ -2439,11 +2762,15 @@ export const paginate: {
|
|
|
2439
2762
|
return yield* Match.value(outcome.success).pipe(
|
|
2440
2763
|
Match.tagsExhaustive({
|
|
2441
2764
|
Done: ({ page }) =>
|
|
2442
|
-
Effect.
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2765
|
+
Effect.map(
|
|
2766
|
+
seal(QueryStreamCursor.END_CURSOR),
|
|
2767
|
+
(continueCursor) =>
|
|
2768
|
+
({
|
|
2769
|
+
page: Array.fromIterable(page),
|
|
2770
|
+
isDone: true,
|
|
2771
|
+
continueCursor,
|
|
2772
|
+
}) satisfies PaginationResult<Doc>,
|
|
2773
|
+
),
|
|
2447
2774
|
Continue: ({ page, key }) =>
|
|
2448
2775
|
Effect.map(
|
|
2449
2776
|
encode(key),
|
|
@@ -2463,7 +2790,10 @@ export const paginate: {
|
|
|
2463
2790
|
Effect.provideServiceEffect(
|
|
2464
2791
|
effect,
|
|
2465
2792
|
QueryStreamReadBudget.QueryStreamReadBudget,
|
|
2466
|
-
Schema.decodeEffect(QueryStreamReadBudget.Limits)(
|
|
2793
|
+
Schema.decodeEffect(QueryStreamReadBudget.Limits)({
|
|
2794
|
+
maximumRowsRead: DEFAULT_MAXIMUM_ROWS_READ,
|
|
2795
|
+
...options,
|
|
2796
|
+
}).pipe(
|
|
2467
2797
|
Effect.catchTag("SchemaError", (cause) =>
|
|
2468
2798
|
Effect.die(
|
|
2469
2799
|
new QueryStreamReadBudget.InvalidReadLimitError({ cause }),
|
|
@@ -22,8 +22,9 @@ export interface QueryStreamReadBudget {
|
|
|
22
22
|
readonly getReadCounts: Effect.Effect<ReadCounts>;
|
|
23
23
|
/**
|
|
24
24
|
* Returns a stream that records reads against this budget and stops
|
|
25
|
-
* requesting further batches once exhausted
|
|
26
|
-
*
|
|
25
|
+
* requesting further batches once exhausted, except for one limit-boundary
|
|
26
|
+
* lookahead to detect completion. Apply directly to the document source
|
|
27
|
+
* before buffering, filtering, or combining its output.
|
|
27
28
|
*/
|
|
28
29
|
readonly accountFor: (
|
|
29
30
|
encodedDocuments: Stream.Stream<unknown>,
|
|
@@ -133,10 +134,22 @@ export const make = Effect.fnUntraced(function* (
|
|
|
133
134
|
SynchronizedRef.modifyEffect(stateRef, (state) =>
|
|
134
135
|
Effect.gen(function* () {
|
|
135
136
|
if (isExhausted(state, limits)) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
const canProbeEnd =
|
|
138
|
+
State.$is("Active")(state) &&
|
|
139
|
+
!Option.exists(
|
|
140
|
+
limits.maximumRowsRead,
|
|
141
|
+
(limit) => state.rowsRead > limit,
|
|
142
|
+
) &&
|
|
143
|
+
!Option.exists(
|
|
144
|
+
limits.maximumBytesRead,
|
|
145
|
+
(limit) => state.bytesRead > limit,
|
|
146
|
+
);
|
|
147
|
+
// Iterators expose completion only through a pull. Count a
|
|
148
|
+
// lookahead row, but never publish it beyond either limit.
|
|
149
|
+
const counts = canProbeEnd
|
|
150
|
+
? record(state, yield* pull)
|
|
151
|
+
: readCounts(state);
|
|
152
|
+
return Tuple.make(Option.none(), State.Stopped(counts));
|
|
140
153
|
}
|
|
141
154
|
const documents = yield* pull;
|
|
142
155
|
return Tuple.make(
|