@fragno-dev/db 0.1.14 → 0.1.15
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/.turbo/turbo-build.log +179 -139
- package/CHANGELOG.md +24 -0
- package/dist/adapters/adapters.d.ts +15 -1
- package/dist/adapters/adapters.d.ts.map +1 -1
- package/dist/adapters/adapters.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-adapter.d.ts +3 -1
- package/dist/adapters/drizzle/drizzle-adapter.d.ts.map +1 -1
- package/dist/adapters/drizzle/drizzle-adapter.js +9 -2
- package/dist/adapters/drizzle/drizzle-adapter.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-query.js +2 -2
- package/dist/adapters/drizzle/drizzle-query.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-compiler.js +27 -8
- package/dist/adapters/drizzle/drizzle-uow-compiler.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-decoder.js +22 -15
- package/dist/adapters/drizzle/drizzle-uow-decoder.js.map +1 -1
- package/dist/adapters/drizzle/drizzle-uow-executor.js +18 -7
- package/dist/adapters/drizzle/drizzle-uow-executor.js.map +1 -1
- package/dist/adapters/drizzle/generate.d.ts +4 -1
- package/dist/adapters/drizzle/generate.d.ts.map +1 -1
- package/dist/adapters/drizzle/generate.js +11 -18
- package/dist/adapters/drizzle/generate.js.map +1 -1
- package/dist/adapters/kysely/kysely-adapter.d.ts +3 -1
- package/dist/adapters/kysely/kysely-adapter.d.ts.map +1 -1
- package/dist/adapters/kysely/kysely-adapter.js +7 -1
- package/dist/adapters/kysely/kysely-adapter.js.map +1 -1
- package/dist/adapters/kysely/kysely-query-builder.js +1 -1
- package/dist/adapters/kysely/kysely-query-compiler.js +3 -2
- package/dist/adapters/kysely/kysely-query-compiler.js.map +1 -1
- package/dist/adapters/kysely/kysely-query.d.ts +1 -0
- package/dist/adapters/kysely/kysely-query.d.ts.map +1 -1
- package/dist/adapters/kysely/kysely-query.js +25 -18
- package/dist/adapters/kysely/kysely-query.js.map +1 -1
- package/dist/adapters/kysely/kysely-shared.d.ts +3 -0
- package/dist/adapters/kysely/kysely-shared.d.ts.map +1 -1
- package/dist/adapters/kysely/kysely-shared.js +16 -1
- package/dist/adapters/kysely/kysely-shared.js.map +1 -1
- package/dist/adapters/kysely/kysely-uow-compiler.js +34 -11
- package/dist/adapters/kysely/kysely-uow-compiler.js.map +1 -1
- package/dist/adapters/kysely/kysely-uow-executor.js +8 -4
- package/dist/adapters/kysely/kysely-uow-executor.js.map +1 -1
- package/dist/adapters/kysely/migration/execute-base.js +1 -1
- package/dist/adapters/kysely/migration/execute-base.js.map +1 -1
- package/dist/db-fragment-definition-builder.d.ts +152 -0
- package/dist/db-fragment-definition-builder.d.ts.map +1 -0
- package/dist/db-fragment-definition-builder.js +137 -0
- package/dist/db-fragment-definition-builder.js.map +1 -0
- package/dist/fragments/internal-fragment.d.ts +19 -0
- package/dist/fragments/internal-fragment.d.ts.map +1 -0
- package/dist/fragments/internal-fragment.js +39 -0
- package/dist/fragments/internal-fragment.js.map +1 -0
- package/dist/migration-engine/generation-engine.d.ts.map +1 -1
- package/dist/migration-engine/generation-engine.js +35 -15
- package/dist/migration-engine/generation-engine.js.map +1 -1
- package/dist/mod.d.ts +8 -20
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +7 -35
- package/dist/mod.js.map +1 -1
- package/dist/node_modules/.pnpm/rou3@0.7.8/node_modules/rou3/dist/index.js +165 -0
- package/dist/node_modules/.pnpm/rou3@0.7.8/node_modules/rou3/dist/index.js.map +1 -0
- package/dist/packages/fragno/dist/api/bind-services.js +20 -0
- package/dist/packages/fragno/dist/api/bind-services.js.map +1 -0
- package/dist/packages/fragno/dist/api/error.js +48 -0
- package/dist/packages/fragno/dist/api/error.js.map +1 -0
- package/dist/packages/fragno/dist/api/fragment-definition-builder.js +320 -0
- package/dist/packages/fragno/dist/api/fragment-definition-builder.js.map +1 -0
- package/dist/packages/fragno/dist/api/fragment-instantiator.js +487 -0
- package/dist/packages/fragno/dist/api/fragment-instantiator.js.map +1 -0
- package/dist/packages/fragno/dist/api/fragno-response.js +73 -0
- package/dist/packages/fragno/dist/api/fragno-response.js.map +1 -0
- package/dist/packages/fragno/dist/api/internal/response-stream.js +81 -0
- package/dist/packages/fragno/dist/api/internal/response-stream.js.map +1 -0
- package/dist/packages/fragno/dist/api/internal/route.js +10 -0
- package/dist/packages/fragno/dist/api/internal/route.js.map +1 -0
- package/dist/packages/fragno/dist/api/mutable-request-state.js +97 -0
- package/dist/packages/fragno/dist/api/mutable-request-state.js.map +1 -0
- package/dist/packages/fragno/dist/api/request-context-storage.js +43 -0
- package/dist/packages/fragno/dist/api/request-context-storage.js.map +1 -0
- package/dist/packages/fragno/dist/api/request-input-context.js +118 -0
- package/dist/packages/fragno/dist/api/request-input-context.js.map +1 -0
- package/dist/packages/fragno/dist/api/request-middleware.js +83 -0
- package/dist/packages/fragno/dist/api/request-middleware.js.map +1 -0
- package/dist/packages/fragno/dist/api/request-output-context.js +119 -0
- package/dist/packages/fragno/dist/api/request-output-context.js.map +1 -0
- package/dist/packages/fragno/dist/api/route.js +17 -0
- package/dist/packages/fragno/dist/api/route.js.map +1 -0
- package/dist/packages/fragno/dist/internal/symbols.js +10 -0
- package/dist/packages/fragno/dist/internal/symbols.js.map +1 -0
- package/dist/query/cursor.d.ts +10 -2
- package/dist/query/cursor.d.ts.map +1 -1
- package/dist/query/cursor.js +11 -4
- package/dist/query/cursor.js.map +1 -1
- package/dist/query/execute-unit-of-work.d.ts +123 -0
- package/dist/query/execute-unit-of-work.d.ts.map +1 -0
- package/dist/query/execute-unit-of-work.js +184 -0
- package/dist/query/execute-unit-of-work.js.map +1 -0
- package/dist/query/query.d.ts +2 -2
- package/dist/query/query.d.ts.map +1 -1
- package/dist/query/result-transform.js +4 -2
- package/dist/query/result-transform.js.map +1 -1
- package/dist/query/retry-policy.d.ts +88 -0
- package/dist/query/retry-policy.d.ts.map +1 -0
- package/dist/query/retry-policy.js +61 -0
- package/dist/query/retry-policy.js.map +1 -0
- package/dist/query/unit-of-work.d.ts +104 -50
- package/dist/query/unit-of-work.d.ts.map +1 -1
- package/dist/query/unit-of-work.js +384 -194
- package/dist/query/unit-of-work.js.map +1 -1
- package/dist/schema/serialize.js +12 -7
- package/dist/schema/serialize.js.map +1 -1
- package/dist/with-database.d.ts +28 -0
- package/dist/with-database.d.ts.map +1 -0
- package/dist/with-database.js +34 -0
- package/dist/with-database.js.map +1 -0
- package/package.json +9 -2
- package/src/adapters/adapters.ts +16 -0
- package/src/adapters/drizzle/drizzle-adapter-pglite.test.ts +80 -16
- package/src/adapters/drizzle/drizzle-adapter-sqlite.test.ts +158 -2
- package/src/adapters/drizzle/drizzle-adapter.test.ts +3 -51
- package/src/adapters/drizzle/drizzle-adapter.ts +20 -7
- package/src/adapters/drizzle/drizzle-query.ts +1 -2
- package/src/adapters/drizzle/drizzle-uow-compiler-mysql.test.ts +1442 -0
- package/src/adapters/drizzle/drizzle-uow-compiler-sqlite.test.ts +1414 -0
- package/src/adapters/drizzle/drizzle-uow-compiler.test.ts +21 -4
- package/src/adapters/drizzle/drizzle-uow-compiler.ts +44 -3
- package/src/adapters/drizzle/drizzle-uow-decoder.ts +32 -22
- package/src/adapters/drizzle/drizzle-uow-executor.ts +41 -8
- package/src/adapters/drizzle/generate.test.ts +102 -269
- package/src/adapters/drizzle/generate.ts +12 -30
- package/src/adapters/drizzle/test-utils.ts +36 -5
- package/src/adapters/kysely/kysely-adapter-pglite.test.ts +64 -20
- package/src/adapters/kysely/kysely-adapter-sqlite.test.ts +156 -0
- package/src/adapters/kysely/kysely-adapter.ts +9 -1
- package/src/adapters/kysely/kysely-query-compiler.ts +3 -8
- package/src/adapters/kysely/kysely-query.ts +34 -25
- package/src/adapters/kysely/kysely-shared.ts +34 -0
- package/src/adapters/kysely/kysely-uow-compiler.test.ts +61 -73
- package/src/adapters/kysely/kysely-uow-compiler.ts +44 -12
- package/src/adapters/kysely/kysely-uow-executor.ts +26 -7
- package/src/adapters/kysely/kysely-uow-joins.test.ts +31 -48
- package/src/adapters/kysely/migration/execute-base.ts +1 -1
- package/src/db-fragment-definition-builder.test.ts +887 -0
- package/src/db-fragment-definition-builder.ts +506 -0
- package/src/db-fragment-instantiator.test.ts +467 -0
- package/src/db-fragment-integration.test.ts +408 -0
- package/src/fragments/internal-fragment.test.ts +160 -0
- package/src/fragments/internal-fragment.ts +85 -0
- package/src/migration-engine/generation-engine.test.ts +58 -15
- package/src/migration-engine/generation-engine.ts +78 -25
- package/src/mod.ts +25 -52
- package/src/query/cursor.test.ts +119 -0
- package/src/query/cursor.ts +17 -4
- package/src/query/execute-unit-of-work.test.ts +1310 -0
- package/src/query/execute-unit-of-work.ts +463 -0
- package/src/query/query.ts +2 -2
- package/src/query/result-transform.test.ts +129 -0
- package/src/query/result-transform.ts +4 -1
- package/src/query/retry-policy.test.ts +217 -0
- package/src/query/retry-policy.ts +141 -0
- package/src/query/unit-of-work-coordinator.test.ts +833 -0
- package/src/query/unit-of-work-types.test.ts +2 -2
- package/src/query/unit-of-work.test.ts +873 -191
- package/src/query/unit-of-work.ts +602 -409
- package/src/schema/serialize.ts +22 -11
- package/src/with-database.ts +140 -0
- package/tsdown.config.ts +1 -0
- package/dist/bind-services.d.ts +0 -7
- package/dist/bind-services.d.ts.map +0 -1
- package/dist/bind-services.js +0 -14
- package/dist/bind-services.js.map +0 -1
- package/dist/fragment.d.ts +0 -173
- package/dist/fragment.d.ts.map +0 -1
- package/dist/fragment.js +0 -191
- package/dist/fragment.js.map +0 -1
- package/dist/shared/settings-schema.js +0 -36
- package/dist/shared/settings-schema.js.map +0 -1
- package/src/bind-services.test.ts +0 -214
- package/src/bind-services.ts +0 -37
- package/src/db-fragment.test.ts +0 -800
- package/src/fragment.ts +0 -727
- package/src/query/unit-of-work-multi-schema.test.ts +0 -64
- package/src/shared/settings-schema.ts +0 -61
- package/src/uow-context-integration.test.ts +0 -102
- package/src/uow-context.test.ts +0 -182
|
@@ -157,6 +157,7 @@ export type RetrievalOperation<
|
|
|
157
157
|
indexName: string;
|
|
158
158
|
options: FindOptions<TTable, SelectClause<TTable>>;
|
|
159
159
|
withCursor?: boolean;
|
|
160
|
+
withSingleResult?: boolean;
|
|
160
161
|
}
|
|
161
162
|
| {
|
|
162
163
|
type: "count";
|
|
@@ -198,6 +199,13 @@ export type MutationOperation<
|
|
|
198
199
|
table: TTable["name"];
|
|
199
200
|
id: FragnoId | string;
|
|
200
201
|
checkVersion: boolean;
|
|
202
|
+
}
|
|
203
|
+
| {
|
|
204
|
+
type: "check";
|
|
205
|
+
schema: TSchema;
|
|
206
|
+
namespace?: string;
|
|
207
|
+
table: TTable["name"];
|
|
208
|
+
id: FragnoId;
|
|
201
209
|
};
|
|
202
210
|
|
|
203
211
|
/**
|
|
@@ -211,6 +219,12 @@ export interface CompiledMutation<TOutput> {
|
|
|
211
219
|
* null means don't check affected rows (e.g., for create operations).
|
|
212
220
|
*/
|
|
213
221
|
expectedAffectedRows: number | null;
|
|
222
|
+
/**
|
|
223
|
+
* Number of rows this SELECT query must return for the transaction to succeed.
|
|
224
|
+
* Used for check operations to verify version without modifying data.
|
|
225
|
+
* null means this is not a SELECT query that needs row count validation.
|
|
226
|
+
*/
|
|
227
|
+
expectedReturnedRows: number | null;
|
|
214
228
|
}
|
|
215
229
|
|
|
216
230
|
/**
|
|
@@ -862,13 +876,14 @@ export function buildJoinIndexed<TTable extends AnyTable, TJoinOut>(
|
|
|
862
876
|
}
|
|
863
877
|
|
|
864
878
|
/**
|
|
865
|
-
*
|
|
866
|
-
* This allows UOW instances to be passed between
|
|
879
|
+
* Full Unit of Work interface with all operations including execution.
|
|
880
|
+
* This allows UOW instances to be passed between different contexts that use different schemas.
|
|
867
881
|
*/
|
|
868
|
-
export interface
|
|
882
|
+
export interface IUnitOfWork {
|
|
869
883
|
// Getters (schema-agnostic)
|
|
870
884
|
readonly state: UOWState;
|
|
871
885
|
readonly name: string | undefined;
|
|
886
|
+
readonly nonce: string;
|
|
872
887
|
readonly retrievalPhase: Promise<unknown[]>;
|
|
873
888
|
readonly mutationPhase: Promise<void>;
|
|
874
889
|
|
|
@@ -881,30 +896,238 @@ export interface IUnitOfWorkBase {
|
|
|
881
896
|
getMutationOperations(): ReadonlyArray<MutationOperation<AnySchema>>;
|
|
882
897
|
getCreatedIds(): FragnoId[];
|
|
883
898
|
|
|
899
|
+
// Parent-child relationships
|
|
900
|
+
restrict(): IUnitOfWork;
|
|
901
|
+
|
|
902
|
+
// Reset for retry support
|
|
903
|
+
reset(): void;
|
|
904
|
+
|
|
884
905
|
// Schema-specific view (for cross-schema operations)
|
|
885
906
|
forSchema<TOtherSchema extends AnySchema>(
|
|
886
907
|
schema: TOtherSchema,
|
|
887
908
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
888
|
-
):
|
|
909
|
+
): TypedUnitOfWork<TOtherSchema, [], any>;
|
|
889
910
|
}
|
|
890
911
|
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
912
|
+
/**
|
|
913
|
+
* Restricted UOW interface without execute methods.
|
|
914
|
+
* Useful when you want to allow building operations but not executing them,
|
|
915
|
+
* to prevent deadlocks or enforce execution control at a higher level.
|
|
916
|
+
*
|
|
917
|
+
* Note: This is just a marker interface. Restriction is enforced by the UnitOfWork class itself.
|
|
918
|
+
*/
|
|
919
|
+
export interface IUnitOfWorkRestricted
|
|
920
|
+
extends Omit<IUnitOfWork, "executeRetrieve" | "executeMutations"> {}
|
|
921
|
+
|
|
922
|
+
export function createUnitOfWork(
|
|
897
923
|
compiler: UOWCompiler<unknown>,
|
|
898
|
-
executor: UOWExecutor<unknown,
|
|
899
|
-
decoder: UOWDecoder<
|
|
924
|
+
executor: UOWExecutor<unknown, unknown>,
|
|
925
|
+
decoder: UOWDecoder<unknown>,
|
|
926
|
+
schemaNamespaceMap?: WeakMap<AnySchema, string>,
|
|
900
927
|
name?: string,
|
|
901
|
-
): UnitOfWork
|
|
902
|
-
return new UnitOfWork(
|
|
928
|
+
): UnitOfWork {
|
|
929
|
+
return new UnitOfWork(compiler, executor, decoder, name, undefined, schemaNamespaceMap);
|
|
903
930
|
}
|
|
904
931
|
|
|
905
932
|
export interface UnitOfWorkConfig {
|
|
906
933
|
dryRun?: boolean;
|
|
907
934
|
onQuery?: (query: unknown) => void;
|
|
935
|
+
nonce?: string;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* Encapsulates a promise with its resolver/rejecter functions.
|
|
940
|
+
* Simplifies management of deferred promises with built-in error handling.
|
|
941
|
+
*/
|
|
942
|
+
class DeferredPromise<T> {
|
|
943
|
+
#resolve?: (value: T) => void;
|
|
944
|
+
#reject?: (error: Error) => void;
|
|
945
|
+
#promise: Promise<T>;
|
|
946
|
+
|
|
947
|
+
constructor() {
|
|
948
|
+
const { promise, resolve, reject } = Promise.withResolvers<T>();
|
|
949
|
+
this.#promise = promise;
|
|
950
|
+
this.#resolve = resolve;
|
|
951
|
+
this.#reject = reject;
|
|
952
|
+
// Attach no-op error handler to prevent unhandled rejection warnings
|
|
953
|
+
this.#promise.catch(() => {});
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
get promise(): Promise<T> {
|
|
957
|
+
return this.#promise;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
resolve(value: T): void {
|
|
961
|
+
this.#resolve?.(value);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
reject(error: Error): void {
|
|
965
|
+
this.#reject?.(error);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* Reset to a new promise
|
|
970
|
+
*/
|
|
971
|
+
reset(): void {
|
|
972
|
+
const { promise, resolve, reject } = Promise.withResolvers<T>();
|
|
973
|
+
this.#promise = promise;
|
|
974
|
+
this.#resolve = resolve;
|
|
975
|
+
this.#reject = reject;
|
|
976
|
+
// Attach no-op error handler to prevent unhandled rejection warnings
|
|
977
|
+
this.#promise.catch(() => {});
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* Tracks readiness signals from a group of children.
|
|
983
|
+
* Maintains a promise that resolves when all registered children have signaled.
|
|
984
|
+
*/
|
|
985
|
+
class ReadinessTracker {
|
|
986
|
+
#expectedCount = 0;
|
|
987
|
+
#signalCount = 0;
|
|
988
|
+
#resolve?: () => void;
|
|
989
|
+
#promise: Promise<void> = Promise.resolve();
|
|
990
|
+
|
|
991
|
+
get promise(): Promise<void> {
|
|
992
|
+
return this.#promise;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* Register that we're expecting a signal from a child
|
|
997
|
+
*/
|
|
998
|
+
registerChild(): void {
|
|
999
|
+
if (this.#expectedCount === 0) {
|
|
1000
|
+
// First child - create new promise
|
|
1001
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
1002
|
+
this.#promise = promise;
|
|
1003
|
+
this.#resolve = resolve;
|
|
1004
|
+
}
|
|
1005
|
+
this.#expectedCount++;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* Signal that one child is ready
|
|
1010
|
+
*/
|
|
1011
|
+
signal(): void {
|
|
1012
|
+
this.#signalCount++;
|
|
1013
|
+
if (this.#signalCount >= this.#expectedCount && this.#resolve) {
|
|
1014
|
+
this.#resolve();
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
/**
|
|
1019
|
+
* Reset to initial state
|
|
1020
|
+
*/
|
|
1021
|
+
reset(): void {
|
|
1022
|
+
this.#expectedCount = 0;
|
|
1023
|
+
this.#signalCount = 0;
|
|
1024
|
+
this.#resolve = undefined;
|
|
1025
|
+
this.#promise = Promise.resolve();
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
/**
|
|
1030
|
+
* Manages parent-child relationships and readiness coordination for Unit of Work instances.
|
|
1031
|
+
* This allows parent UOWs to wait for all child UOWs to signal readiness before executing phases.
|
|
1032
|
+
*/
|
|
1033
|
+
class UOWChildCoordinator<TRawInput> {
|
|
1034
|
+
#parent: UnitOfWork<TRawInput> | null = null;
|
|
1035
|
+
#parentCoordinator: UOWChildCoordinator<TRawInput> | null = null;
|
|
1036
|
+
#children: Set<UnitOfWork<TRawInput>> = new Set();
|
|
1037
|
+
#isRestricted = false;
|
|
1038
|
+
|
|
1039
|
+
#retrievalTracker = new ReadinessTracker();
|
|
1040
|
+
#mutationTracker = new ReadinessTracker();
|
|
1041
|
+
|
|
1042
|
+
get isRestricted(): boolean {
|
|
1043
|
+
return this.#isRestricted;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
get parent(): UnitOfWork<TRawInput> | null {
|
|
1047
|
+
return this.#parent;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
get children(): ReadonlySet<UnitOfWork<TRawInput>> {
|
|
1051
|
+
return this.#children;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
get retrievalReadinessPromise(): Promise<void> {
|
|
1055
|
+
return this.#retrievalTracker.promise;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
get mutationReadinessPromise(): Promise<void> {
|
|
1059
|
+
return this.#mutationTracker.promise;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* Mark this UOW as a restricted child of the given parent
|
|
1064
|
+
*/
|
|
1065
|
+
setAsRestricted(
|
|
1066
|
+
parent: UnitOfWork<TRawInput>,
|
|
1067
|
+
parentCoordinator: UOWChildCoordinator<TRawInput>,
|
|
1068
|
+
): void {
|
|
1069
|
+
this.#parent = parent;
|
|
1070
|
+
this.#parentCoordinator = parentCoordinator;
|
|
1071
|
+
this.#isRestricted = true;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
/**
|
|
1075
|
+
* Register a child UOW
|
|
1076
|
+
*/
|
|
1077
|
+
addChild(child: UnitOfWork<TRawInput>): void {
|
|
1078
|
+
this.#children.add(child);
|
|
1079
|
+
this.#retrievalTracker.registerChild();
|
|
1080
|
+
this.#mutationTracker.registerChild();
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
/**
|
|
1084
|
+
* Signal that this child is ready for retrieval phase execution.
|
|
1085
|
+
* Only valid for restricted (child) UOWs.
|
|
1086
|
+
*/
|
|
1087
|
+
signalReadyForRetrieval(): void {
|
|
1088
|
+
if (!this.#parentCoordinator) {
|
|
1089
|
+
throw new Error("signalReadyForRetrieval() can only be called on restricted child UOWs");
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
this.#parentCoordinator.notifyChildReadyForRetrieval();
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/**
|
|
1096
|
+
* Signal that this child is ready for mutation phase execution.
|
|
1097
|
+
* Only valid for restricted (child) UOWs.
|
|
1098
|
+
*/
|
|
1099
|
+
signalReadyForMutation(): void {
|
|
1100
|
+
if (!this.#parentCoordinator) {
|
|
1101
|
+
throw new Error("signalReadyForMutation() can only be called on restricted child UOWs");
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
this.#parentCoordinator.notifyChildReadyForMutation();
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
/**
|
|
1108
|
+
* Notify this coordinator that a child is ready for retrieval (internal use).
|
|
1109
|
+
* Called by child UOWs when they signal readiness.
|
|
1110
|
+
*/
|
|
1111
|
+
notifyChildReadyForRetrieval(): void {
|
|
1112
|
+
this.#retrievalTracker.signal();
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
/**
|
|
1116
|
+
* Notify this coordinator that a child is ready for mutation (internal use).
|
|
1117
|
+
* Called by child UOWs when they signal readiness.
|
|
1118
|
+
*/
|
|
1119
|
+
notifyChildReadyForMutation(): void {
|
|
1120
|
+
this.#mutationTracker.signal();
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* Reset coordination state for retry support
|
|
1125
|
+
*/
|
|
1126
|
+
reset(): void {
|
|
1127
|
+
this.#children.clear();
|
|
1128
|
+
this.#retrievalTracker.reset();
|
|
1129
|
+
this.#mutationTracker.reset();
|
|
1130
|
+
}
|
|
908
1131
|
}
|
|
909
1132
|
|
|
910
1133
|
/**
|
|
@@ -914,19 +1137,22 @@ export interface UnitOfWorkConfig {
|
|
|
914
1137
|
* 1. Retrieval phase: Read operations to fetch entities with their versions
|
|
915
1138
|
* 2. Mutation phase: Write operations that check versions before committing
|
|
916
1139
|
*
|
|
1140
|
+
* This is the untyped base storage. Use TypedUnitOfWork for type-safe operations.
|
|
1141
|
+
*
|
|
917
1142
|
* @example
|
|
918
1143
|
* ```ts
|
|
919
1144
|
* const uow = queryEngine.createUnitOfWork("update-user-balance");
|
|
1145
|
+
* const typedUow = uow.forSchema(mySchema);
|
|
920
1146
|
*
|
|
921
1147
|
* // Retrieval phase
|
|
922
|
-
*
|
|
1148
|
+
* typedUow.find("users", (b) => b.whereIndex("primary", (eb) => eb("id", "=", userId)));
|
|
923
1149
|
*
|
|
924
1150
|
* // Execute retrieval and transition to mutation phase
|
|
925
1151
|
* const [users] = await uow.executeRetrieve();
|
|
926
1152
|
*
|
|
927
1153
|
* // Mutation phase with version check
|
|
928
1154
|
* const user = users[0];
|
|
929
|
-
*
|
|
1155
|
+
* typedUow.update("users", user.id, (b) => b.set({ balance: newBalance }).check());
|
|
930
1156
|
*
|
|
931
1157
|
* // Execute mutations
|
|
932
1158
|
* const { success } = await uow.executeMutations();
|
|
@@ -935,20 +1161,14 @@ export interface UnitOfWorkConfig {
|
|
|
935
1161
|
* }
|
|
936
1162
|
* ```
|
|
937
1163
|
*/
|
|
938
|
-
export class UnitOfWork<
|
|
939
|
-
const TSchema extends AnySchema,
|
|
940
|
-
const TRetrievalResults extends unknown[] = [],
|
|
941
|
-
const TRawInput = unknown,
|
|
942
|
-
> implements IUnitOfWorkBase
|
|
943
|
-
{
|
|
944
|
-
#schema: TSchema;
|
|
945
|
-
|
|
1164
|
+
export class UnitOfWork<const TRawInput = unknown> implements IUnitOfWork {
|
|
946
1165
|
#name?: string;
|
|
947
1166
|
#config?: UnitOfWorkConfig;
|
|
1167
|
+
#nonce: string;
|
|
948
1168
|
|
|
949
1169
|
#state: UOWState = "building-retrieval";
|
|
950
1170
|
|
|
951
|
-
// Operations can
|
|
1171
|
+
// Operations can come from any schema
|
|
952
1172
|
#retrievalOps: RetrievalOperation<AnySchema>[] = [];
|
|
953
1173
|
#mutationOps: MutationOperation<AnySchema>[] = [];
|
|
954
1174
|
|
|
@@ -957,17 +1177,21 @@ export class UnitOfWork<
|
|
|
957
1177
|
#decoder: UOWDecoder<TRawInput>;
|
|
958
1178
|
#schemaNamespaceMap?: WeakMap<AnySchema, string>;
|
|
959
1179
|
|
|
960
|
-
#retrievalResults?:
|
|
1180
|
+
#retrievalResults?: unknown[];
|
|
961
1181
|
#createdInternalIds: (bigint | null)[] = [];
|
|
962
1182
|
|
|
963
1183
|
// Phase coordination promises
|
|
964
|
-
#
|
|
965
|
-
#
|
|
966
|
-
|
|
967
|
-
|
|
1184
|
+
#retrievalPhaseDeferred = new DeferredPromise<unknown[]>();
|
|
1185
|
+
#mutationPhaseDeferred = new DeferredPromise<void>();
|
|
1186
|
+
|
|
1187
|
+
// Error tracking
|
|
1188
|
+
#retrievalError: Error | null = null;
|
|
1189
|
+
#mutationError: Error | null = null;
|
|
1190
|
+
|
|
1191
|
+
// Child coordination
|
|
1192
|
+
#coordinator: UOWChildCoordinator<TRawInput> = new UOWChildCoordinator();
|
|
968
1193
|
|
|
969
1194
|
constructor(
|
|
970
|
-
schema: TSchema,
|
|
971
1195
|
compiler: UOWCompiler<unknown>,
|
|
972
1196
|
executor: UOWExecutor<unknown, TRawInput>,
|
|
973
1197
|
decoder: UOWDecoder<TRawInput>,
|
|
@@ -975,381 +1199,196 @@ export class UnitOfWork<
|
|
|
975
1199
|
config?: UnitOfWorkConfig,
|
|
976
1200
|
schemaNamespaceMap?: WeakMap<AnySchema, string>,
|
|
977
1201
|
) {
|
|
978
|
-
this.#schema = schema;
|
|
979
1202
|
this.#compiler = compiler;
|
|
980
1203
|
this.#executor = executor;
|
|
981
1204
|
this.#decoder = decoder;
|
|
1205
|
+
this.#schemaNamespaceMap = schemaNamespaceMap;
|
|
982
1206
|
this.#name = name;
|
|
983
1207
|
this.#config = config;
|
|
984
|
-
this.#
|
|
985
|
-
|
|
986
|
-
// Initialize phase coordination promises
|
|
987
|
-
this.#retrievalPhasePromise = new Promise<TRetrievalResults>((resolve) => {
|
|
988
|
-
this.#retrievalPhaseResolve = resolve;
|
|
989
|
-
});
|
|
990
|
-
this.#mutationPhasePromise = new Promise<void>((resolve) => {
|
|
991
|
-
this.#mutationPhaseResolve = resolve;
|
|
992
|
-
});
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
get schema(): TSchema {
|
|
996
|
-
return this.#schema;
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
get $results(): Prettify<TRetrievalResults> {
|
|
1000
|
-
throw new Error("type only");
|
|
1208
|
+
this.#nonce = config?.nonce ?? crypto.randomUUID();
|
|
1001
1209
|
}
|
|
1002
1210
|
|
|
1003
1211
|
/**
|
|
1004
|
-
* Get a schema-specific view of this UOW for type-safe operations
|
|
1005
|
-
* Returns a wrapper that
|
|
1212
|
+
* Get a schema-specific typed view of this UOW for type-safe operations.
|
|
1213
|
+
* Returns a wrapper that provides typed operations for the given schema.
|
|
1006
1214
|
* The namespace is automatically resolved from the schema-namespace map.
|
|
1007
1215
|
*/
|
|
1008
|
-
forSchema<TOtherSchema extends AnySchema>(
|
|
1216
|
+
forSchema<TOtherSchema extends AnySchema, TRawInput>(
|
|
1009
1217
|
schema: TOtherSchema,
|
|
1010
|
-
):
|
|
1218
|
+
): TypedUnitOfWork<TOtherSchema, [], TRawInput> {
|
|
1011
1219
|
// Look up namespace from map
|
|
1012
1220
|
const resolvedNamespace = this.#schemaNamespaceMap?.get(schema);
|
|
1013
1221
|
|
|
1014
|
-
|
|
1015
|
-
// As operations are added, the types will accumulate correctly
|
|
1016
|
-
return new UnitOfWorkSchemaView(
|
|
1017
|
-
schema,
|
|
1018
|
-
resolvedNamespace,
|
|
1019
|
-
this as unknown as UnitOfWork<AnySchema, unknown[], TRawInput>,
|
|
1020
|
-
);
|
|
1222
|
+
return new TypedUnitOfWork(schema, resolvedNamespace, this as unknown as UnitOfWork<TRawInput>);
|
|
1021
1223
|
}
|
|
1022
1224
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1225
|
+
/**
|
|
1226
|
+
* Create a restricted child UOW that cannot execute phases.
|
|
1227
|
+
* The child shares the same operation storage but must signal readiness
|
|
1228
|
+
* before the parent can execute each phase.
|
|
1229
|
+
*/
|
|
1230
|
+
restrict(): UnitOfWork<TRawInput> {
|
|
1231
|
+
const child = new UnitOfWork(
|
|
1232
|
+
this.#compiler,
|
|
1233
|
+
this.#executor,
|
|
1234
|
+
this.#decoder,
|
|
1235
|
+
this.#name,
|
|
1236
|
+
{ ...this.#config, nonce: this.#nonce },
|
|
1237
|
+
this.#schemaNamespaceMap,
|
|
1238
|
+
);
|
|
1239
|
+
child.#coordinator.setAsRestricted(this, this.#coordinator);
|
|
1026
1240
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1241
|
+
// Share state with parent
|
|
1242
|
+
child.#state = this.#state;
|
|
1243
|
+
child.#retrievalOps = this.#retrievalOps;
|
|
1244
|
+
child.#mutationOps = this.#mutationOps;
|
|
1245
|
+
child.#retrievalResults = this.#retrievalResults;
|
|
1246
|
+
child.#createdInternalIds = this.#createdInternalIds;
|
|
1247
|
+
child.#retrievalPhaseDeferred = this.#retrievalPhaseDeferred;
|
|
1248
|
+
child.#mutationPhaseDeferred = this.#mutationPhaseDeferred;
|
|
1249
|
+
child.#retrievalError = this.#retrievalError;
|
|
1250
|
+
child.#mutationError = this.#mutationError;
|
|
1251
|
+
|
|
1252
|
+
this.#coordinator.addChild(child);
|
|
1253
|
+
|
|
1254
|
+
// For synchronous usage (the common case), immediately signal readiness
|
|
1255
|
+
// This allows services called directly from handlers to work without explicit signaling
|
|
1256
|
+
child.signalReadyForRetrieval();
|
|
1257
|
+
child.signalReadyForMutation();
|
|
1258
|
+
|
|
1259
|
+
return child;
|
|
1029
1260
|
}
|
|
1030
1261
|
|
|
1031
1262
|
/**
|
|
1032
|
-
*
|
|
1033
|
-
*
|
|
1263
|
+
* Signal that this child is ready for retrieval phase execution.
|
|
1264
|
+
* Only valid for restricted (child) UOWs.
|
|
1034
1265
|
*/
|
|
1035
|
-
|
|
1036
|
-
|
|
1266
|
+
signalReadyForRetrieval(): void {
|
|
1267
|
+
this.#coordinator.signalReadyForRetrieval();
|
|
1037
1268
|
}
|
|
1038
1269
|
|
|
1039
1270
|
/**
|
|
1040
|
-
*
|
|
1041
|
-
*
|
|
1271
|
+
* Signal that this child is ready for mutation phase execution.
|
|
1272
|
+
* Only valid for restricted (child) UOWs.
|
|
1042
1273
|
*/
|
|
1043
|
-
|
|
1044
|
-
|
|
1274
|
+
signalReadyForMutation(): void {
|
|
1275
|
+
this.#coordinator.signalReadyForMutation();
|
|
1045
1276
|
}
|
|
1046
1277
|
|
|
1047
1278
|
/**
|
|
1048
|
-
*
|
|
1049
|
-
*
|
|
1279
|
+
* Reset the UOW to initial state for retry support.
|
|
1280
|
+
* Clears operations, resets state, and resets phase promises.
|
|
1050
1281
|
*/
|
|
1051
|
-
|
|
1052
|
-
if (this.#
|
|
1053
|
-
throw new Error(
|
|
1054
|
-
`Cannot execute retrieval from state ${this.#state}. Must be in building-retrieval state.`,
|
|
1055
|
-
);
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
if (this.#retrievalOps.length === 0) {
|
|
1059
|
-
this.#state = "building-mutation";
|
|
1060
|
-
const emptyResults = [] as unknown as TRetrievalResults;
|
|
1061
|
-
this.#retrievalPhaseResolve?.(emptyResults);
|
|
1062
|
-
return emptyResults;
|
|
1282
|
+
reset(): void {
|
|
1283
|
+
if (this.#coordinator.isRestricted) {
|
|
1284
|
+
throw new Error("reset() cannot be called on restricted child UOWs");
|
|
1063
1285
|
}
|
|
1064
1286
|
|
|
1065
|
-
//
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
this.#config?.onQuery?.(compiled);
|
|
1071
|
-
retrievalBatch.push(compiled);
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1287
|
+
// Clear operations
|
|
1288
|
+
this.#retrievalOps = [];
|
|
1289
|
+
this.#mutationOps = [];
|
|
1290
|
+
this.#retrievalResults = undefined;
|
|
1291
|
+
this.#createdInternalIds = [];
|
|
1074
1292
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1293
|
+
// Reset state
|
|
1294
|
+
this.#state = "building-retrieval";
|
|
1295
|
+
this.#retrievalError = null;
|
|
1296
|
+
this.#mutationError = null;
|
|
1079
1297
|
|
|
1080
|
-
//
|
|
1081
|
-
|
|
1298
|
+
// Reset phase promises
|
|
1299
|
+
this.#retrievalPhaseDeferred.reset();
|
|
1300
|
+
this.#mutationPhaseDeferred.reset();
|
|
1082
1301
|
|
|
1083
|
-
//
|
|
1084
|
-
|
|
1302
|
+
// Reset child coordination
|
|
1303
|
+
this.#coordinator.reset();
|
|
1304
|
+
}
|
|
1085
1305
|
|
|
1086
|
-
|
|
1087
|
-
this.#
|
|
1088
|
-
|
|
1306
|
+
get state(): UOWState {
|
|
1307
|
+
return this.#state;
|
|
1308
|
+
}
|
|
1089
1309
|
|
|
1090
|
-
|
|
1091
|
-
this.#
|
|
1310
|
+
get name(): string | undefined {
|
|
1311
|
+
return this.#name;
|
|
1312
|
+
}
|
|
1092
1313
|
|
|
1093
|
-
|
|
1314
|
+
get nonce(): string {
|
|
1315
|
+
return this.#nonce;
|
|
1094
1316
|
}
|
|
1095
1317
|
|
|
1096
1318
|
/**
|
|
1097
|
-
*
|
|
1319
|
+
* Promise that resolves when the retrieval phase is executed
|
|
1320
|
+
* Service methods can await this to coordinate multi-phase logic
|
|
1098
1321
|
*/
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
builderFn: (
|
|
1102
|
-
builder: Omit<FindBuilder<TSchema["tables"][TTableName]>, "build">,
|
|
1103
|
-
) => TBuilderResult,
|
|
1104
|
-
): UnitOfWork<
|
|
1105
|
-
TSchema,
|
|
1106
|
-
[
|
|
1107
|
-
...TRetrievalResults,
|
|
1108
|
-
SelectResult<
|
|
1109
|
-
TSchema["tables"][TTableName],
|
|
1110
|
-
ExtractJoinOut<TBuilderResult>,
|
|
1111
|
-
Extract<ExtractSelect<TBuilderResult>, SelectClause<TSchema["tables"][TTableName]>>
|
|
1112
|
-
>[],
|
|
1113
|
-
],
|
|
1114
|
-
TRawInput
|
|
1115
|
-
>;
|
|
1116
|
-
find<TTableName extends keyof TSchema["tables"] & string>(
|
|
1117
|
-
tableName: TTableName,
|
|
1118
|
-
): UnitOfWork<
|
|
1119
|
-
TSchema,
|
|
1120
|
-
[...TRetrievalResults, SelectResult<TSchema["tables"][TTableName], {}, true>[]],
|
|
1121
|
-
TRawInput
|
|
1122
|
-
>;
|
|
1123
|
-
find<TTableName extends keyof TSchema["tables"] & string, const TBuilderResult>(
|
|
1124
|
-
tableName: TTableName,
|
|
1125
|
-
builderFn?: (
|
|
1126
|
-
builder: Omit<FindBuilder<TSchema["tables"][TTableName]>, "build">,
|
|
1127
|
-
) => TBuilderResult,
|
|
1128
|
-
): UnitOfWork<
|
|
1129
|
-
TSchema,
|
|
1130
|
-
[
|
|
1131
|
-
...TRetrievalResults,
|
|
1132
|
-
SelectResult<
|
|
1133
|
-
TSchema["tables"][TTableName],
|
|
1134
|
-
ExtractJoinOut<TBuilderResult>,
|
|
1135
|
-
Extract<ExtractSelect<TBuilderResult>, SelectClause<TSchema["tables"][TTableName]>>
|
|
1136
|
-
>[],
|
|
1137
|
-
],
|
|
1138
|
-
TRawInput
|
|
1139
|
-
> {
|
|
1140
|
-
if (this.#state !== "building-retrieval") {
|
|
1141
|
-
throw new Error(
|
|
1142
|
-
`find() can only be called during retrieval phase. Current state: ${this.#state}`,
|
|
1143
|
-
);
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
const table = this.#schema.tables[tableName];
|
|
1147
|
-
if (!table) {
|
|
1148
|
-
throw new Error(`Table ${tableName} not found in schema`);
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
// Create builder, pass to callback (or use default), then extract configuration
|
|
1152
|
-
const builder = new FindBuilder(tableName, table as TSchema["tables"][TTableName]);
|
|
1153
|
-
if (builderFn) {
|
|
1154
|
-
builderFn(builder);
|
|
1155
|
-
} else {
|
|
1156
|
-
// Default to primary index with no filter
|
|
1157
|
-
builder.whereIndex("primary");
|
|
1158
|
-
}
|
|
1159
|
-
const { indexName, options, type } = builder.build();
|
|
1160
|
-
|
|
1161
|
-
this.#retrievalOps.push({
|
|
1162
|
-
type,
|
|
1163
|
-
schema: this.#schema,
|
|
1164
|
-
// Safe: we know the table is part of the schema from the find() method
|
|
1165
|
-
table: table as TSchema["tables"][TTableName],
|
|
1166
|
-
indexName,
|
|
1167
|
-
// Safe: we're storing the options for later compilation
|
|
1168
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1169
|
-
options: options as any,
|
|
1170
|
-
});
|
|
1171
|
-
|
|
1172
|
-
// Safe: return type is correctly specified in the method signature
|
|
1173
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1174
|
-
return this as any;
|
|
1322
|
+
get retrievalPhase(): Promise<unknown[]> {
|
|
1323
|
+
return this.#retrievalPhaseDeferred.promise;
|
|
1175
1324
|
}
|
|
1176
1325
|
|
|
1177
1326
|
/**
|
|
1178
|
-
*
|
|
1327
|
+
* Promise that resolves when the mutation phase is executed
|
|
1328
|
+
* Service methods can await this to coordinate multi-phase logic
|
|
1179
1329
|
*/
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
builderFn: (
|
|
1183
|
-
// We omit "build" because we don't want to expose it to the user
|
|
1184
|
-
builder: Omit<FindBuilder<TSchema["tables"][TTableName]>, "build">,
|
|
1185
|
-
) => TBuilderResult,
|
|
1186
|
-
): UnitOfWork<
|
|
1187
|
-
TSchema,
|
|
1188
|
-
[
|
|
1189
|
-
...TRetrievalResults,
|
|
1190
|
-
CursorResult<
|
|
1191
|
-
SelectResult<
|
|
1192
|
-
TSchema["tables"][TTableName],
|
|
1193
|
-
ExtractJoinOut<TBuilderResult>,
|
|
1194
|
-
Extract<ExtractSelect<TBuilderResult>, SelectClause<TSchema["tables"][TTableName]>>
|
|
1195
|
-
>
|
|
1196
|
-
>,
|
|
1197
|
-
],
|
|
1198
|
-
TRawInput
|
|
1199
|
-
> {
|
|
1200
|
-
if (this.#state !== "building-retrieval") {
|
|
1201
|
-
throw new Error(
|
|
1202
|
-
`findWithCursor() can only be called during retrieval phase. Current state: ${this.#state}`,
|
|
1203
|
-
);
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
const table = this.#schema.tables[tableName];
|
|
1207
|
-
if (!table) {
|
|
1208
|
-
throw new Error(`Table ${tableName} not found in schema`);
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
// Create builder and pass to callback
|
|
1212
|
-
const builder = new FindBuilder(tableName, table as TSchema["tables"][TTableName]);
|
|
1213
|
-
builderFn(builder);
|
|
1214
|
-
const { indexName, options, type } = builder.build();
|
|
1215
|
-
|
|
1216
|
-
this.#retrievalOps.push({
|
|
1217
|
-
type,
|
|
1218
|
-
schema: this.#schema,
|
|
1219
|
-
// Safe: we know the table is part of the schema from the findWithCursor() method
|
|
1220
|
-
table: table as TSchema["tables"][TTableName],
|
|
1221
|
-
indexName,
|
|
1222
|
-
// Safe: we're storing the options for later compilation
|
|
1223
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1224
|
-
options: options as any,
|
|
1225
|
-
withCursor: true,
|
|
1226
|
-
});
|
|
1227
|
-
|
|
1228
|
-
// Safe: return type is correctly specified in the method signature
|
|
1229
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1230
|
-
return this as any;
|
|
1330
|
+
get mutationPhase(): Promise<void> {
|
|
1331
|
+
return this.#mutationPhaseDeferred.promise;
|
|
1231
1332
|
}
|
|
1232
1333
|
|
|
1233
1334
|
/**
|
|
1234
|
-
*
|
|
1235
|
-
* Returns
|
|
1335
|
+
* Execute the retrieval phase and transition to mutation phase
|
|
1336
|
+
* Returns all results from find operations
|
|
1236
1337
|
*/
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
): FragnoId {
|
|
1241
|
-
if (this.#state === "executed") {
|
|
1242
|
-
throw new Error(`create() can only be called during mutation phase.`);
|
|
1338
|
+
async executeRetrieve(): Promise<unknown[]> {
|
|
1339
|
+
if (this.#coordinator.isRestricted) {
|
|
1340
|
+
throw new Error("executeRetrieve() cannot be called on restricted child UOWs");
|
|
1243
1341
|
}
|
|
1244
1342
|
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1343
|
+
if (this.#state !== "building-retrieval") {
|
|
1344
|
+
throw new Error(
|
|
1345
|
+
`Cannot execute retrieval from state ${this.#state}. Must be in building-retrieval state.`,
|
|
1346
|
+
);
|
|
1248
1347
|
}
|
|
1249
1348
|
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
// Check if ID value is provided in values
|
|
1255
|
-
const providedIdValue = (values as Record<string, unknown>)[idColumn.ormName];
|
|
1349
|
+
try {
|
|
1350
|
+
// Wait for all children to signal readiness
|
|
1351
|
+
await this.#coordinator.retrievalReadinessPromise;
|
|
1256
1352
|
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
"externalId" in providedIdValue
|
|
1263
|
-
) {
|
|
1264
|
-
externalId = (providedIdValue as FragnoId).externalId;
|
|
1265
|
-
} else {
|
|
1266
|
-
externalId = providedIdValue as string;
|
|
1353
|
+
if (this.#retrievalOps.length === 0) {
|
|
1354
|
+
this.#state = "building-mutation";
|
|
1355
|
+
const emptyResults: unknown[] = [];
|
|
1356
|
+
this.#retrievalPhaseDeferred.resolve(emptyResults);
|
|
1357
|
+
return emptyResults;
|
|
1267
1358
|
}
|
|
1268
|
-
} else {
|
|
1269
|
-
// Generate using the column's default configuration
|
|
1270
|
-
const generated = idColumn.generateDefaultValue();
|
|
1271
|
-
if (generated === undefined) {
|
|
1272
|
-
throw new Error(
|
|
1273
|
-
`No ID value provided and ID column ${idColumn.ormName} has no default generator`,
|
|
1274
|
-
);
|
|
1275
|
-
}
|
|
1276
|
-
externalId = generated as string;
|
|
1277
1359
|
|
|
1278
|
-
//
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1360
|
+
// Compile retrieval operations using single compiler
|
|
1361
|
+
const retrievalBatch: unknown[] = [];
|
|
1362
|
+
for (const op of this.#retrievalOps) {
|
|
1363
|
+
const compiled = this.#compiler.compileRetrievalOperation(op);
|
|
1364
|
+
if (compiled !== null) {
|
|
1365
|
+
this.#config?.onQuery?.(compiled);
|
|
1366
|
+
retrievalBatch.push(compiled);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1284
1369
|
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
});
|
|
1370
|
+
if (this.#config?.dryRun) {
|
|
1371
|
+
this.#state = "executed";
|
|
1372
|
+
const emptyResults: unknown[] = [];
|
|
1373
|
+
this.#retrievalPhaseDeferred.resolve(emptyResults);
|
|
1374
|
+
return emptyResults;
|
|
1375
|
+
}
|
|
1292
1376
|
|
|
1293
|
-
|
|
1294
|
-
}
|
|
1377
|
+
const rawResults = await this.#executor.executeRetrievalPhase(retrievalBatch);
|
|
1295
1378
|
|
|
1296
|
-
|
|
1297
|
-
* Add an update operation using a builder callback (mutation phase only)
|
|
1298
|
-
*/
|
|
1299
|
-
update<TableName extends keyof TSchema["tables"] & string>(
|
|
1300
|
-
table: TableName,
|
|
1301
|
-
id: FragnoId | string,
|
|
1302
|
-
builderFn: (
|
|
1303
|
-
// We omit "build" because we don't want to expose it to the user
|
|
1304
|
-
builder: Omit<UpdateBuilder<TSchema["tables"][TableName]>, "build">,
|
|
1305
|
-
) => Omit<UpdateBuilder<TSchema["tables"][TableName]>, "build"> | void,
|
|
1306
|
-
): void {
|
|
1307
|
-
if (this.#state === "executed") {
|
|
1308
|
-
throw new Error(`update() can only be called during mutation phase.`);
|
|
1309
|
-
}
|
|
1379
|
+
const results = this.#decoder(rawResults, this.#retrievalOps);
|
|
1310
1380
|
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
const { id: opId, checkVersion, set } = builder.build();
|
|
1381
|
+
// Store results and transition to mutation phase
|
|
1382
|
+
this.#retrievalResults = results;
|
|
1383
|
+
this.#state = "building-mutation";
|
|
1315
1384
|
|
|
1316
|
-
|
|
1317
|
-
type: "update",
|
|
1318
|
-
schema: this.#schema,
|
|
1319
|
-
table,
|
|
1320
|
-
id: opId,
|
|
1321
|
-
checkVersion,
|
|
1322
|
-
set,
|
|
1323
|
-
});
|
|
1324
|
-
}
|
|
1385
|
+
this.#retrievalPhaseDeferred.resolve(this.#retrievalResults);
|
|
1325
1386
|
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
table: TableName,
|
|
1331
|
-
id: FragnoId | string,
|
|
1332
|
-
builderFn?: (
|
|
1333
|
-
// We omit "build" because we don't want to expose it to the user
|
|
1334
|
-
builder: Omit<DeleteBuilder, "build">,
|
|
1335
|
-
) => Omit<DeleteBuilder, "build"> | void,
|
|
1336
|
-
): void {
|
|
1337
|
-
if (this.#state === "executed") {
|
|
1338
|
-
throw new Error(`delete() can only be called during mutation phase.`);
|
|
1387
|
+
return this.#retrievalResults;
|
|
1388
|
+
} catch (error) {
|
|
1389
|
+
this.#retrievalError = error instanceof Error ? error : new Error(String(error));
|
|
1390
|
+
throw error;
|
|
1339
1391
|
}
|
|
1340
|
-
|
|
1341
|
-
// Create builder, optionally pass to callback, then extract configuration
|
|
1342
|
-
const builder = new DeleteBuilder(table, id);
|
|
1343
|
-
builderFn?.(builder);
|
|
1344
|
-
const { id: opId, checkVersion } = builder.build();
|
|
1345
|
-
|
|
1346
|
-
this.#mutationOps.push({
|
|
1347
|
-
type: "delete",
|
|
1348
|
-
schema: this.#schema,
|
|
1349
|
-
table,
|
|
1350
|
-
id: opId,
|
|
1351
|
-
checkVersion,
|
|
1352
|
-
});
|
|
1353
1392
|
}
|
|
1354
1393
|
|
|
1355
1394
|
/**
|
|
@@ -1357,41 +1396,54 @@ export class UnitOfWork<
|
|
|
1357
1396
|
* Returns success flag indicating if mutations completed without conflicts
|
|
1358
1397
|
*/
|
|
1359
1398
|
async executeMutations(): Promise<{ success: boolean }> {
|
|
1399
|
+
if (this.#coordinator.isRestricted) {
|
|
1400
|
+
throw new Error("executeMutations() cannot be called on restricted child UOWs");
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1360
1403
|
if (this.#state === "executed") {
|
|
1361
1404
|
throw new Error(`Cannot execute mutations from state ${this.#state}.`);
|
|
1362
1405
|
}
|
|
1363
1406
|
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1407
|
+
try {
|
|
1408
|
+
// Wait for all children to signal readiness
|
|
1409
|
+
await this.#coordinator.mutationReadinessPromise;
|
|
1410
|
+
|
|
1411
|
+
// Compile mutation operations using single compiler
|
|
1412
|
+
const mutationBatch: CompiledMutation<unknown>[] = [];
|
|
1413
|
+
for (const op of this.#mutationOps) {
|
|
1414
|
+
const compiled = this.#compiler.compileMutationOperation(op);
|
|
1415
|
+
if (compiled !== null) {
|
|
1416
|
+
this.#config?.onQuery?.(compiled);
|
|
1417
|
+
mutationBatch.push(compiled);
|
|
1418
|
+
}
|
|
1371
1419
|
}
|
|
1372
|
-
}
|
|
1373
1420
|
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1421
|
+
if (this.#config?.dryRun) {
|
|
1422
|
+
this.#state = "executed";
|
|
1423
|
+
this.#mutationPhaseDeferred.resolve();
|
|
1424
|
+
return {
|
|
1425
|
+
success: true,
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1380
1428
|
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1429
|
+
// Execute mutation phase
|
|
1430
|
+
const result = await this.#executor.executeMutationPhase(mutationBatch);
|
|
1431
|
+
this.#state = "executed";
|
|
1384
1432
|
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1433
|
+
if (result.success) {
|
|
1434
|
+
this.#createdInternalIds = result.createdInternalIds;
|
|
1435
|
+
}
|
|
1388
1436
|
|
|
1389
|
-
|
|
1390
|
-
|
|
1437
|
+
// Resolve the mutation phase promise to unblock waiting service methods
|
|
1438
|
+
this.#mutationPhaseDeferred.resolve();
|
|
1391
1439
|
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1440
|
+
return {
|
|
1441
|
+
success: result.success,
|
|
1442
|
+
};
|
|
1443
|
+
} catch (error) {
|
|
1444
|
+
this.#mutationError = error instanceof Error ? error : new Error(String(error));
|
|
1445
|
+
throw error;
|
|
1446
|
+
}
|
|
1395
1447
|
}
|
|
1396
1448
|
|
|
1397
1449
|
/**
|
|
@@ -1410,18 +1462,26 @@ export class UnitOfWork<
|
|
|
1410
1462
|
|
|
1411
1463
|
/**
|
|
1412
1464
|
* @internal
|
|
1413
|
-
* Add a retrieval operation (used by
|
|
1465
|
+
* Add a retrieval operation (used by TypedUnitOfWork)
|
|
1414
1466
|
*/
|
|
1415
1467
|
addRetrievalOperation(op: RetrievalOperation<AnySchema>): number {
|
|
1468
|
+
if (this.#state !== "building-retrieval") {
|
|
1469
|
+
throw new Error(
|
|
1470
|
+
`Cannot add retrieval operation in state ${this.#state}. Must be in building-retrieval state.`,
|
|
1471
|
+
);
|
|
1472
|
+
}
|
|
1416
1473
|
this.#retrievalOps.push(op);
|
|
1417
1474
|
return this.#retrievalOps.length - 1;
|
|
1418
1475
|
}
|
|
1419
1476
|
|
|
1420
1477
|
/**
|
|
1421
1478
|
* @internal
|
|
1422
|
-
* Add a mutation operation (used by
|
|
1479
|
+
* Add a mutation operation (used by TypedUnitOfWork)
|
|
1423
1480
|
*/
|
|
1424
1481
|
addMutationOperation(op: MutationOperation<AnySchema>): void {
|
|
1482
|
+
if (this.#state === "executed") {
|
|
1483
|
+
throw new Error(`Cannot add mutation operation in executed state.`);
|
|
1484
|
+
}
|
|
1425
1485
|
this.#mutationOps.push(op);
|
|
1426
1486
|
}
|
|
1427
1487
|
|
|
@@ -1494,28 +1554,26 @@ export class UnitOfWork<
|
|
|
1494
1554
|
}
|
|
1495
1555
|
|
|
1496
1556
|
/**
|
|
1497
|
-
* A
|
|
1498
|
-
* All operations are stored in the
|
|
1557
|
+
* A typed facade around a UnitOfWork that provides type-safe operations for a specific schema.
|
|
1558
|
+
* All operations are stored in the underlying UOW, but this facade ensures type safety and
|
|
1559
|
+
* filters retrieval results to only include operations added through this facade.
|
|
1499
1560
|
*/
|
|
1500
|
-
export class
|
|
1561
|
+
export class TypedUnitOfWork<
|
|
1501
1562
|
const TSchema extends AnySchema,
|
|
1502
1563
|
const TRetrievalResults extends unknown[] = [],
|
|
1503
1564
|
const TRawInput = unknown,
|
|
1504
|
-
> implements
|
|
1565
|
+
> implements IUnitOfWork
|
|
1505
1566
|
{
|
|
1506
1567
|
#schema: TSchema;
|
|
1507
1568
|
#namespace?: string;
|
|
1508
|
-
#
|
|
1569
|
+
#uow: UnitOfWork<TRawInput>;
|
|
1509
1570
|
#operationIndices: number[] = [];
|
|
1571
|
+
#cachedRetrievalPhase?: Promise<TRetrievalResults>;
|
|
1510
1572
|
|
|
1511
|
-
constructor(
|
|
1512
|
-
schema: TSchema,
|
|
1513
|
-
namespace: string | undefined,
|
|
1514
|
-
parent: UnitOfWork<AnySchema, unknown[], TRawInput>,
|
|
1515
|
-
) {
|
|
1573
|
+
constructor(schema: TSchema, namespace: string | undefined, uow: UnitOfWork<TRawInput>) {
|
|
1516
1574
|
this.#schema = schema;
|
|
1517
1575
|
this.#namespace = namespace;
|
|
1518
|
-
this.#
|
|
1576
|
+
this.#uow = uow;
|
|
1519
1577
|
}
|
|
1520
1578
|
|
|
1521
1579
|
get $results(): Prettify<TRetrievalResults> {
|
|
@@ -1527,43 +1585,81 @@ export class UnitOfWorkSchemaView<
|
|
|
1527
1585
|
}
|
|
1528
1586
|
|
|
1529
1587
|
get name(): string | undefined {
|
|
1530
|
-
return this.#
|
|
1588
|
+
return this.#uow.name;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
get nonce(): string {
|
|
1592
|
+
return this.#uow.nonce;
|
|
1531
1593
|
}
|
|
1532
1594
|
|
|
1533
1595
|
get state() {
|
|
1534
|
-
return this.#
|
|
1596
|
+
return this.#uow.state;
|
|
1535
1597
|
}
|
|
1536
1598
|
|
|
1537
1599
|
get retrievalPhase(): Promise<TRetrievalResults> {
|
|
1538
|
-
//
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1600
|
+
// Cache the filtered promise to avoid recreating it on every access
|
|
1601
|
+
if (!this.#cachedRetrievalPhase) {
|
|
1602
|
+
this.#cachedRetrievalPhase = this.#uow.retrievalPhase.then((allResults) => {
|
|
1603
|
+
const allOperations = this.#uow.getRetrievalOperations();
|
|
1604
|
+
const filteredResults = this.#operationIndices.map((opIndex) => {
|
|
1605
|
+
const result = allResults[opIndex];
|
|
1606
|
+
const operation = allOperations[opIndex];
|
|
1607
|
+
// Transform array to single item for findFirst operations
|
|
1608
|
+
if (operation?.type === "find" && operation.withSingleResult) {
|
|
1609
|
+
return Array.isArray(result) ? (result[0] ?? null) : result;
|
|
1610
|
+
}
|
|
1611
|
+
return result;
|
|
1612
|
+
});
|
|
1613
|
+
return filteredResults as TRetrievalResults;
|
|
1614
|
+
});
|
|
1615
|
+
}
|
|
1616
|
+
return this.#cachedRetrievalPhase;
|
|
1543
1617
|
}
|
|
1544
1618
|
|
|
1545
1619
|
get mutationPhase(): Promise<void> {
|
|
1546
|
-
return this.#
|
|
1620
|
+
return this.#uow.mutationPhase;
|
|
1547
1621
|
}
|
|
1548
1622
|
|
|
1549
1623
|
getRetrievalOperations() {
|
|
1550
|
-
return this.#
|
|
1624
|
+
return this.#uow.getRetrievalOperations();
|
|
1551
1625
|
}
|
|
1552
1626
|
|
|
1553
1627
|
getMutationOperations() {
|
|
1554
|
-
return this.#
|
|
1628
|
+
return this.#uow.getMutationOperations();
|
|
1555
1629
|
}
|
|
1556
1630
|
|
|
1557
1631
|
getCreatedIds() {
|
|
1558
|
-
return this.#
|
|
1632
|
+
return this.#uow.getCreatedIds();
|
|
1559
1633
|
}
|
|
1560
1634
|
|
|
1561
|
-
async executeRetrieve(): Promise<
|
|
1562
|
-
return this.#
|
|
1635
|
+
async executeRetrieve(): Promise<TRetrievalResults> {
|
|
1636
|
+
return this.#uow.executeRetrieve() as Promise<TRetrievalResults>;
|
|
1563
1637
|
}
|
|
1564
1638
|
|
|
1565
1639
|
async executeMutations(): Promise<{ success: boolean }> {
|
|
1566
|
-
return this.#
|
|
1640
|
+
return this.#uow.executeMutations();
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
restrict(): IUnitOfWork {
|
|
1644
|
+
return this.#uow.restrict();
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
reset(): void {
|
|
1648
|
+
return this.#uow.reset();
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
forSchema<TOtherSchema extends AnySchema>(
|
|
1652
|
+
schema: TOtherSchema,
|
|
1653
|
+
): TypedUnitOfWork<TOtherSchema, [], TRawInput> {
|
|
1654
|
+
return this.#uow.forSchema(schema);
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
compile<TOutput>(compiler: UOWCompiler<TOutput>): {
|
|
1658
|
+
name?: string;
|
|
1659
|
+
retrievalBatch: TOutput[];
|
|
1660
|
+
mutationBatch: CompiledMutation<TOutput>[];
|
|
1661
|
+
} {
|
|
1662
|
+
return this.#uow.compile(compiler);
|
|
1567
1663
|
}
|
|
1568
1664
|
|
|
1569
1665
|
find<TTableName extends keyof TSchema["tables"] & string, const TBuilderResult>(
|
|
@@ -1571,7 +1667,7 @@ export class UnitOfWorkSchemaView<
|
|
|
1571
1667
|
builderFn: (
|
|
1572
1668
|
builder: Omit<FindBuilder<TSchema["tables"][TTableName]>, "build">,
|
|
1573
1669
|
) => TBuilderResult,
|
|
1574
|
-
):
|
|
1670
|
+
): TypedUnitOfWork<
|
|
1575
1671
|
TSchema,
|
|
1576
1672
|
[
|
|
1577
1673
|
...TRetrievalResults,
|
|
@@ -1585,7 +1681,7 @@ export class UnitOfWorkSchemaView<
|
|
|
1585
1681
|
>;
|
|
1586
1682
|
find<TTableName extends keyof TSchema["tables"] & string>(
|
|
1587
1683
|
tableName: TTableName,
|
|
1588
|
-
):
|
|
1684
|
+
): TypedUnitOfWork<
|
|
1589
1685
|
TSchema,
|
|
1590
1686
|
[...TRetrievalResults, SelectResult<TSchema["tables"][TTableName], {}, true>[]],
|
|
1591
1687
|
TRawInput
|
|
@@ -1595,7 +1691,7 @@ export class UnitOfWorkSchemaView<
|
|
|
1595
1691
|
builderFn?: (
|
|
1596
1692
|
builder: Omit<FindBuilder<TSchema["tables"][TTableName]>, "build">,
|
|
1597
1693
|
) => TBuilderResult,
|
|
1598
|
-
):
|
|
1694
|
+
): TypedUnitOfWork<
|
|
1599
1695
|
TSchema,
|
|
1600
1696
|
[
|
|
1601
1697
|
...TRetrievalResults,
|
|
@@ -1620,7 +1716,81 @@ export class UnitOfWorkSchemaView<
|
|
|
1620
1716
|
}
|
|
1621
1717
|
const { indexName, options, type } = builder.build();
|
|
1622
1718
|
|
|
1623
|
-
const operationIndex = this.#
|
|
1719
|
+
const operationIndex = this.#uow.addRetrievalOperation({
|
|
1720
|
+
type,
|
|
1721
|
+
schema: this.#schema,
|
|
1722
|
+
namespace: this.#namespace,
|
|
1723
|
+
table: table as TSchema["tables"][TTableName],
|
|
1724
|
+
indexName,
|
|
1725
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1726
|
+
options: options as any,
|
|
1727
|
+
});
|
|
1728
|
+
|
|
1729
|
+
// Track which operation index belongs to this view
|
|
1730
|
+
this.#operationIndices.push(operationIndex);
|
|
1731
|
+
|
|
1732
|
+
// Safe: return type is correctly specified in the method signature
|
|
1733
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1734
|
+
return this as any;
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
findFirst<TTableName extends keyof TSchema["tables"] & string, const TBuilderResult>(
|
|
1738
|
+
tableName: TTableName,
|
|
1739
|
+
builderFn: (
|
|
1740
|
+
builder: Omit<FindBuilder<TSchema["tables"][TTableName]>, "build">,
|
|
1741
|
+
) => TBuilderResult,
|
|
1742
|
+
): TypedUnitOfWork<
|
|
1743
|
+
TSchema,
|
|
1744
|
+
[
|
|
1745
|
+
...TRetrievalResults,
|
|
1746
|
+
SelectResult<
|
|
1747
|
+
TSchema["tables"][TTableName],
|
|
1748
|
+
ExtractJoinOut<TBuilderResult>,
|
|
1749
|
+
Extract<ExtractSelect<TBuilderResult>, SelectClause<TSchema["tables"][TTableName]>>
|
|
1750
|
+
> | null,
|
|
1751
|
+
],
|
|
1752
|
+
TRawInput
|
|
1753
|
+
>;
|
|
1754
|
+
findFirst<TTableName extends keyof TSchema["tables"] & string>(
|
|
1755
|
+
tableName: TTableName,
|
|
1756
|
+
): TypedUnitOfWork<
|
|
1757
|
+
TSchema,
|
|
1758
|
+
[...TRetrievalResults, SelectResult<TSchema["tables"][TTableName], {}, true> | null],
|
|
1759
|
+
TRawInput
|
|
1760
|
+
>;
|
|
1761
|
+
findFirst<TTableName extends keyof TSchema["tables"] & string, const TBuilderResult>(
|
|
1762
|
+
tableName: TTableName,
|
|
1763
|
+
builderFn?: (
|
|
1764
|
+
builder: Omit<FindBuilder<TSchema["tables"][TTableName]>, "build">,
|
|
1765
|
+
) => TBuilderResult,
|
|
1766
|
+
): TypedUnitOfWork<
|
|
1767
|
+
TSchema,
|
|
1768
|
+
[
|
|
1769
|
+
...TRetrievalResults,
|
|
1770
|
+
SelectResult<
|
|
1771
|
+
TSchema["tables"][TTableName],
|
|
1772
|
+
ExtractJoinOut<TBuilderResult>,
|
|
1773
|
+
Extract<ExtractSelect<TBuilderResult>, SelectClause<TSchema["tables"][TTableName]>>
|
|
1774
|
+
> | null,
|
|
1775
|
+
],
|
|
1776
|
+
TRawInput
|
|
1777
|
+
> {
|
|
1778
|
+
const table = this.#schema.tables[tableName];
|
|
1779
|
+
if (!table) {
|
|
1780
|
+
throw new Error(`Table ${tableName} not found in schema`);
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
const builder = new FindBuilder(tableName, table as TSchema["tables"][TTableName]);
|
|
1784
|
+
if (builderFn) {
|
|
1785
|
+
builderFn(builder);
|
|
1786
|
+
} else {
|
|
1787
|
+
builder.whereIndex("primary");
|
|
1788
|
+
}
|
|
1789
|
+
// Automatically set pageSize to 1 for findFirst
|
|
1790
|
+
builder.pageSize(1);
|
|
1791
|
+
const { indexName, options, type } = builder.build();
|
|
1792
|
+
|
|
1793
|
+
const operationIndex = this.#uow.addRetrievalOperation({
|
|
1624
1794
|
type,
|
|
1625
1795
|
schema: this.#schema,
|
|
1626
1796
|
namespace: this.#namespace,
|
|
@@ -1628,6 +1798,7 @@ export class UnitOfWorkSchemaView<
|
|
|
1628
1798
|
indexName,
|
|
1629
1799
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1630
1800
|
options: options as any,
|
|
1801
|
+
withSingleResult: true,
|
|
1631
1802
|
});
|
|
1632
1803
|
|
|
1633
1804
|
// Track which operation index belongs to this view
|
|
@@ -1643,7 +1814,7 @@ export class UnitOfWorkSchemaView<
|
|
|
1643
1814
|
builderFn: (
|
|
1644
1815
|
builder: Omit<FindBuilder<TSchema["tables"][TTableName]>, "build">,
|
|
1645
1816
|
) => TBuilderResult,
|
|
1646
|
-
):
|
|
1817
|
+
): TypedUnitOfWork<
|
|
1647
1818
|
TSchema,
|
|
1648
1819
|
[
|
|
1649
1820
|
...TRetrievalResults,
|
|
@@ -1666,7 +1837,7 @@ export class UnitOfWorkSchemaView<
|
|
|
1666
1837
|
builderFn(builder);
|
|
1667
1838
|
const { indexName, options, type } = builder.build();
|
|
1668
1839
|
|
|
1669
|
-
const operationIndex = this.#
|
|
1840
|
+
const operationIndex = this.#uow.addRetrievalOperation({
|
|
1670
1841
|
type,
|
|
1671
1842
|
schema: this.#schema,
|
|
1672
1843
|
namespace: this.#namespace,
|
|
@@ -1729,7 +1900,7 @@ export class UnitOfWorkSchemaView<
|
|
|
1729
1900
|
} as TableToInsertValues<TSchema["tables"][TableName]>;
|
|
1730
1901
|
}
|
|
1731
1902
|
|
|
1732
|
-
this.#
|
|
1903
|
+
this.#uow.addMutationOperation({
|
|
1733
1904
|
type: "create",
|
|
1734
1905
|
schema: this.#schema,
|
|
1735
1906
|
namespace: this.#namespace,
|
|
@@ -1752,7 +1923,7 @@ export class UnitOfWorkSchemaView<
|
|
|
1752
1923
|
builderFn(builder);
|
|
1753
1924
|
const { id: opId, checkVersion, set } = builder.build();
|
|
1754
1925
|
|
|
1755
|
-
this.#
|
|
1926
|
+
this.#uow.addMutationOperation({
|
|
1756
1927
|
type: "update",
|
|
1757
1928
|
schema: this.#schema,
|
|
1758
1929
|
namespace: this.#namespace,
|
|
@@ -1772,7 +1943,7 @@ export class UnitOfWorkSchemaView<
|
|
|
1772
1943
|
builderFn?.(builder);
|
|
1773
1944
|
const { id: opId, checkVersion } = builder.build();
|
|
1774
1945
|
|
|
1775
|
-
this.#
|
|
1946
|
+
this.#uow.addMutationOperation({
|
|
1776
1947
|
type: "delete",
|
|
1777
1948
|
schema: this.#schema,
|
|
1778
1949
|
namespace: this.#namespace,
|
|
@@ -1782,10 +1953,32 @@ export class UnitOfWorkSchemaView<
|
|
|
1782
1953
|
});
|
|
1783
1954
|
}
|
|
1784
1955
|
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1956
|
+
/**
|
|
1957
|
+
* Check that a record's version hasn't changed since retrieval.
|
|
1958
|
+
* This is useful for ensuring related records remain unchanged during a transaction.
|
|
1959
|
+
*
|
|
1960
|
+
* @param tableName - The table name
|
|
1961
|
+
* @param id - The FragnoId with version information (string IDs are not allowed)
|
|
1962
|
+
* @throws Error if the ID is a string without version information
|
|
1963
|
+
*
|
|
1964
|
+
* @example
|
|
1965
|
+
* ```ts
|
|
1966
|
+
* // Ensure both accounts haven't changed before creating a transfer
|
|
1967
|
+
* uow.check("accounts", fromAccount.id);
|
|
1968
|
+
* uow.check("accounts", toAccount.id);
|
|
1969
|
+
* uow.create("transactions", { fromAccountId, toAccountId, amount });
|
|
1970
|
+
* ```
|
|
1971
|
+
*/
|
|
1972
|
+
check<TableName extends keyof TSchema["tables"] & string>(
|
|
1973
|
+
tableName: TableName,
|
|
1974
|
+
id: FragnoId,
|
|
1975
|
+
): void {
|
|
1976
|
+
this.#uow.addMutationOperation({
|
|
1977
|
+
type: "check",
|
|
1978
|
+
schema: this.#schema,
|
|
1979
|
+
namespace: this.#namespace,
|
|
1980
|
+
table: tableName,
|
|
1981
|
+
id,
|
|
1982
|
+
});
|
|
1790
1983
|
}
|
|
1791
1984
|
}
|