@syncular/server 0.15.44 → 0.15.46
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/README.md +135 -5
- package/dist/admin.d.ts +12 -6
- package/dist/admin.js +11 -1
- package/dist/authoritative-query.d.ts +20 -0
- package/dist/authoritative-query.js +184 -0
- package/dist/blob-store.d.ts +2 -2
- package/dist/context.d.ts +11 -2
- package/dist/context.js +2 -0
- package/dist/d1-storage.d.ts +10 -1
- package/dist/d1-storage.js +219 -3
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +43 -1
- package/dist/events-ring.d.ts +1 -1
- package/dist/events-ring.js +1 -1
- package/dist/events.d.ts +52 -3
- package/dist/handler.js +6 -3
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/operations-realtime.d.ts +16 -0
- package/dist/operations-realtime.js +196 -0
- package/dist/operations.d.ts +97 -0
- package/dist/operations.js +392 -0
- package/dist/pg-executor.d.ts +1 -1
- package/dist/pg-executor.js +1 -1
- package/dist/postgres-fanout.d.ts +1 -1
- package/dist/postgres-storage.d.ts +11 -2
- package/dist/postgres-storage.js +223 -3
- package/dist/pull.js +1 -1
- package/dist/push.d.ts +8 -2
- package/dist/push.js +75 -21
- package/dist/reactions.d.ts +167 -0
- package/dist/reactions.js +442 -0
- package/dist/realtime.js +4 -1
- package/dist/relational-rows.d.ts +9 -9
- package/dist/relational-rows.js +9 -9
- package/dist/s3-blob-store.js +1 -1
- package/dist/s3-segment-store.js +1 -1
- package/dist/schema.d.ts +4 -5
- package/dist/schema.js +3 -3
- package/dist/seed.js +1 -1
- package/dist/segment-store.d.ts +3 -3
- package/dist/signed-url.js +2 -2
- package/dist/sigv4.d.ts +2 -4
- package/dist/sigv4.js +3 -3
- package/dist/sqlite-blob-store.d.ts +1 -1
- package/dist/sqlite-blob-store.js +1 -1
- package/dist/sqlite-dialect.d.ts +3 -4
- package/dist/sqlite-dialect.js +21 -1
- package/dist/sqlite-image.d.ts +1 -1
- package/dist/sqlite-lease-store.d.ts +1 -1
- package/dist/sqlite-lease-store.js +1 -1
- package/dist/sqlite-segment-store.d.ts +2 -2
- package/dist/sqlite-segment-store.js +2 -2
- package/dist/sqlite-storage.d.ts +11 -2
- package/dist/sqlite-storage.js +219 -4
- package/dist/storage.d.ts +112 -3
- package/dist/validate.js +1 -0
- package/package.json +2 -2
- package/src/admin.ts +30 -6
- package/src/authoritative-query.ts +218 -0
- package/src/blob-store.ts +0 -0
- package/src/context.ts +12 -2
- package/src/d1-storage.ts +355 -3
- package/src/errors.ts +43 -1
- package/src/events-ring.ts +1 -1
- package/src/events.ts +64 -2
- package/src/handler.ts +15 -3
- package/src/index.ts +33 -1
- package/src/operations-realtime.ts +272 -0
- package/src/operations.ts +720 -0
- package/src/pg-executor.ts +1 -1
- package/src/postgres-fanout.ts +1 -1
- package/src/postgres-storage.ts +355 -4
- package/src/pull.ts +1 -1
- package/src/push.ts +97 -29
- package/src/reactions.ts +741 -0
- package/src/realtime.ts +7 -1
- package/src/relational-rows.ts +9 -9
- package/src/s3-blob-store.ts +1 -1
- package/src/s3-segment-store.ts +1 -1
- package/src/schema.ts +6 -7
- package/src/seed.ts +1 -1
- package/src/segment-store.ts +3 -3
- package/src/signed-url.ts +2 -2
- package/src/sigv4.ts +3 -5
- package/src/sqlite-blob-store.ts +1 -1
- package/src/sqlite-dialect.ts +22 -3
- package/src/sqlite-image.ts +1 -1
- package/src/sqlite-lease-store.ts +1 -1
- package/src/sqlite-segment-store.ts +2 -2
- package/src/sqlite-storage.ts +369 -4
- package/src/storage.ts +168 -3
- package/src/validate.ts +1 -0
package/src/push.ts
CHANGED
|
@@ -33,6 +33,12 @@ import type { SyncRequestContext } from './context';
|
|
|
33
33
|
import { clockOf } from './context';
|
|
34
34
|
import type { CrdtMergerRegistry } from './crdt-merger';
|
|
35
35
|
import { SyncError } from './errors';
|
|
36
|
+
import { emitEvent } from './events';
|
|
37
|
+
import {
|
|
38
|
+
prepareReactions,
|
|
39
|
+
type PreparedReaction,
|
|
40
|
+
toNewReactions,
|
|
41
|
+
} from './reactions';
|
|
36
42
|
import type { CompiledSchema, CompiledTable } from './schema';
|
|
37
43
|
import type { ResolvedScopes } from './scopes';
|
|
38
44
|
import { authorizeWrite, renderScopeValue, storedScopesForRow } from './scopes';
|
|
@@ -837,6 +843,31 @@ export async function processPushCommitWithTrace(
|
|
|
837
843
|
resolved: ResolvedScopes,
|
|
838
844
|
clientId: string,
|
|
839
845
|
frame: PushCommitFrame,
|
|
846
|
+
): Promise<ProcessedPushCommit> {
|
|
847
|
+
return processPushOperationsWithTrace(
|
|
848
|
+
ctx,
|
|
849
|
+
schema,
|
|
850
|
+
resolved,
|
|
851
|
+
clientId,
|
|
852
|
+
frame.clientCommitId,
|
|
853
|
+
async () => frame.operations,
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* Shared serialized apply path for SSP2 commits and authoritative commands.
|
|
859
|
+
* The builder runs after the partition lock and idempotency re-check, so its
|
|
860
|
+
* reads and the returned operations share the transaction that is committed.
|
|
861
|
+
*/
|
|
862
|
+
export async function processPushOperationsWithTrace(
|
|
863
|
+
ctx: SyncRequestContext,
|
|
864
|
+
schema: CompiledSchema,
|
|
865
|
+
resolved: ResolvedScopes,
|
|
866
|
+
clientId: string,
|
|
867
|
+
clientCommitId: string,
|
|
868
|
+
buildOperations: (
|
|
869
|
+
tx: StorageTransaction,
|
|
870
|
+
) => Promise<readonly PushOperation[]>,
|
|
840
871
|
): Promise<ProcessedPushCommit> {
|
|
841
872
|
const { storage, partition } = ctx;
|
|
842
873
|
let persisted: StoredPushResult | undefined;
|
|
@@ -844,7 +875,7 @@ export async function processPushCommitWithTrace(
|
|
|
844
875
|
persisted = await storage.getPushResult(
|
|
845
876
|
partition,
|
|
846
877
|
clientId,
|
|
847
|
-
|
|
878
|
+
clientCommitId,
|
|
848
879
|
);
|
|
849
880
|
} catch (error) {
|
|
850
881
|
if (
|
|
@@ -854,14 +885,14 @@ export async function processPushCommitWithTrace(
|
|
|
854
885
|
// §6.3: answer the retryable cache-miss for this commit rather than
|
|
855
886
|
// re-applying. Not persisted — a retry may find a readable record.
|
|
856
887
|
return {
|
|
857
|
-
frame: idempotencyCacheMissFrame(
|
|
888
|
+
frame: idempotencyCacheMissFrame(clientCommitId, error),
|
|
858
889
|
replayed: false,
|
|
859
890
|
};
|
|
860
891
|
}
|
|
861
892
|
throw error;
|
|
862
893
|
}
|
|
863
894
|
if (persisted !== undefined) {
|
|
864
|
-
return processedPushCommit(
|
|
895
|
+
return processedPushCommit(clientCommitId, persisted, true);
|
|
865
896
|
}
|
|
866
897
|
|
|
867
898
|
const createdAtMs = clockOf(ctx)();
|
|
@@ -869,6 +900,7 @@ export async function processPushCommitWithTrace(
|
|
|
869
900
|
const crdtMergers = ctx.crdtMergers;
|
|
870
901
|
const validators = ctx.validators;
|
|
871
902
|
const commitValidator = ctx.commitValidator;
|
|
903
|
+
const reactionPlanner = ctx.reactionPlanner;
|
|
872
904
|
const tx = await storage.begin(partition);
|
|
873
905
|
const lockPartitionForPush =
|
|
874
906
|
tx.lockPartitionForPush?.bind(tx) ??
|
|
@@ -877,10 +909,11 @@ export async function processPushCommitWithTrace(
|
|
|
877
909
|
try {
|
|
878
910
|
if (
|
|
879
911
|
lockPartitionForPush === undefined ||
|
|
880
|
-
commitRejectedPushResult === undefined
|
|
912
|
+
commitRejectedPushResult === undefined ||
|
|
913
|
+
(reactionPlanner !== undefined && tx.enqueueReactions === undefined)
|
|
881
914
|
) {
|
|
882
915
|
throw new Error(
|
|
883
|
-
'storage transaction does not support serialized push apply
|
|
916
|
+
'storage transaction does not support serialized push apply, atomic rejection finalization, and configured durable reactions',
|
|
884
917
|
);
|
|
885
918
|
}
|
|
886
919
|
await lockPartitionForPush();
|
|
@@ -895,19 +928,11 @@ export async function processPushCommitWithTrace(
|
|
|
895
928
|
try {
|
|
896
929
|
const serializedPersisted =
|
|
897
930
|
tx.getPushResult !== undefined
|
|
898
|
-
? await tx.getPushResult(clientId,
|
|
899
|
-
: await storage.getPushResult(
|
|
900
|
-
partition,
|
|
901
|
-
clientId,
|
|
902
|
-
frame.clientCommitId,
|
|
903
|
-
);
|
|
931
|
+
? await tx.getPushResult(clientId, clientCommitId)
|
|
932
|
+
: await storage.getPushResult(partition, clientId, clientCommitId);
|
|
904
933
|
if (serializedPersisted !== undefined) {
|
|
905
934
|
await tx.rollback();
|
|
906
|
-
return processedPushCommit(
|
|
907
|
-
frame.clientCommitId,
|
|
908
|
-
serializedPersisted,
|
|
909
|
-
true,
|
|
910
|
-
);
|
|
935
|
+
return processedPushCommit(clientCommitId, serializedPersisted, true);
|
|
911
936
|
}
|
|
912
937
|
} catch (error) {
|
|
913
938
|
if (
|
|
@@ -916,7 +941,7 @@ export async function processPushCommitWithTrace(
|
|
|
916
941
|
) {
|
|
917
942
|
await tx.rollback();
|
|
918
943
|
return {
|
|
919
|
-
frame: idempotencyCacheMissFrame(
|
|
944
|
+
frame: idempotencyCacheMissFrame(clientCommitId, error),
|
|
920
945
|
replayed: false,
|
|
921
946
|
};
|
|
922
947
|
}
|
|
@@ -926,8 +951,9 @@ export async function processPushCommitWithTrace(
|
|
|
926
951
|
const changes: NewChange[] = [];
|
|
927
952
|
const validatedOperations: ValidateCommitOperation[] = [];
|
|
928
953
|
let terminated: PushOperationResult | undefined;
|
|
929
|
-
|
|
930
|
-
|
|
954
|
+
const operations = await buildOperations(tx);
|
|
955
|
+
for (let opIndex = 0; opIndex < operations.length; opIndex++) {
|
|
956
|
+
const op = operations[opIndex];
|
|
931
957
|
if (op === undefined) continue;
|
|
932
958
|
const outcome = await applyOperation(
|
|
933
959
|
tx,
|
|
@@ -956,7 +982,7 @@ export async function processPushCommitWithTrace(
|
|
|
956
982
|
tx,
|
|
957
983
|
schema,
|
|
958
984
|
clientId,
|
|
959
|
-
|
|
985
|
+
clientCommitId,
|
|
960
986
|
ctx.actorId,
|
|
961
987
|
partition,
|
|
962
988
|
validatedOperations,
|
|
@@ -966,6 +992,18 @@ export async function processPushCommitWithTrace(
|
|
|
966
992
|
}
|
|
967
993
|
}
|
|
968
994
|
|
|
995
|
+
let preparedReactions: PreparedReaction[] = [];
|
|
996
|
+
if (terminated === undefined && reactionPlanner !== undefined) {
|
|
997
|
+
preparedReactions = await prepareReactions(reactionPlanner, {
|
|
998
|
+
clientId,
|
|
999
|
+
clientCommitId,
|
|
1000
|
+
actorId: ctx.actorId,
|
|
1001
|
+
partition,
|
|
1002
|
+
operations: validatedOperations,
|
|
1003
|
+
read: commitValidationReader(tx, schema),
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
|
|
969
1007
|
if (terminated !== undefined) {
|
|
970
1008
|
// §6.3 rejected: only the terminating operation's record; §6.4:
|
|
971
1009
|
// every write of the commit rolls back.
|
|
@@ -975,11 +1013,11 @@ export async function processPushCommitWithTrace(
|
|
|
975
1013
|
});
|
|
976
1014
|
// Discard candidates and persist the rejection while retaining the same
|
|
977
1015
|
// partition lock. There is no unlock gap in which a duplicate can rerun.
|
|
978
|
-
await commitRejectedPushResult(clientId,
|
|
1016
|
+
await commitRejectedPushResult(clientId, clientCommitId, stored);
|
|
979
1017
|
const canonical = await storage.getPushResult(
|
|
980
1018
|
partition,
|
|
981
1019
|
clientId,
|
|
982
|
-
|
|
1020
|
+
clientCommitId,
|
|
983
1021
|
);
|
|
984
1022
|
if (canonical === undefined) {
|
|
985
1023
|
throw new Error(
|
|
@@ -987,7 +1025,7 @@ export async function processPushCommitWithTrace(
|
|
|
987
1025
|
);
|
|
988
1026
|
}
|
|
989
1027
|
return processedPushCommit(
|
|
990
|
-
|
|
1028
|
+
clientCommitId,
|
|
991
1029
|
canonical,
|
|
992
1030
|
canonical.cacheIdentity !== stored.cacheIdentity,
|
|
993
1031
|
);
|
|
@@ -995,18 +1033,48 @@ export async function processPushCommitWithTrace(
|
|
|
995
1033
|
|
|
996
1034
|
const commitSeq = await tx.appendCommit({
|
|
997
1035
|
clientId,
|
|
998
|
-
clientCommitId
|
|
1036
|
+
clientCommitId,
|
|
999
1037
|
actorId: ctx.actorId,
|
|
1000
1038
|
createdAtMs,
|
|
1001
1039
|
changes,
|
|
1002
1040
|
});
|
|
1041
|
+
const newReactions = toNewReactions(preparedReactions, {
|
|
1042
|
+
clientId,
|
|
1043
|
+
clientCommitId,
|
|
1044
|
+
commitSeq,
|
|
1045
|
+
createdAtMs,
|
|
1046
|
+
});
|
|
1047
|
+
if (newReactions.length > 0) {
|
|
1048
|
+
const enqueueReactions = tx.enqueueReactions;
|
|
1049
|
+
if (enqueueReactions === undefined) {
|
|
1050
|
+
throw new Error('storage lost durable reaction enqueue support');
|
|
1051
|
+
}
|
|
1052
|
+
await enqueueReactions.call(tx, newReactions);
|
|
1053
|
+
}
|
|
1003
1054
|
const stored = newStoredPushResult(createdAtMs, {
|
|
1004
1055
|
status: 'applied',
|
|
1005
1056
|
commitSeq,
|
|
1006
1057
|
results,
|
|
1007
1058
|
});
|
|
1008
|
-
await tx.putPushResult(clientId,
|
|
1059
|
+
await tx.putPushResult(clientId, clientCommitId, stored);
|
|
1009
1060
|
await tx.commit();
|
|
1061
|
+
if (ctx.events !== undefined) {
|
|
1062
|
+
const atMs = clockOf(ctx)();
|
|
1063
|
+
for (const reaction of newReactions) {
|
|
1064
|
+
emitEvent(ctx.events, {
|
|
1065
|
+
type: 'reaction.queued',
|
|
1066
|
+
atMs,
|
|
1067
|
+
partition,
|
|
1068
|
+
actorId: ctx.actorId,
|
|
1069
|
+
clientId,
|
|
1070
|
+
clientCommitId,
|
|
1071
|
+
commitSeq,
|
|
1072
|
+
idempotencyKey: reaction.idempotencyKey,
|
|
1073
|
+
reactionType: reaction.type,
|
|
1074
|
+
version: reaction.version,
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1010
1078
|
if (ctx.realtime !== undefined && changes.length > 0) {
|
|
1011
1079
|
await ctx.realtime.notifyCommit(partition, {
|
|
1012
1080
|
commitSeq,
|
|
@@ -1015,7 +1083,7 @@ export async function processPushCommitWithTrace(
|
|
|
1015
1083
|
changes,
|
|
1016
1084
|
});
|
|
1017
1085
|
}
|
|
1018
|
-
return processedPushCommit(
|
|
1086
|
+
return processedPushCommit(clientCommitId, stored, false);
|
|
1019
1087
|
} catch (error) {
|
|
1020
1088
|
if (error instanceof StorageConstraintError) {
|
|
1021
1089
|
const stored = newStoredPushResult(createdAtMs, {
|
|
@@ -1037,11 +1105,11 @@ export async function processPushCommitWithTrace(
|
|
|
1037
1105
|
);
|
|
1038
1106
|
}
|
|
1039
1107
|
try {
|
|
1040
|
-
await commitRejectedPushResult(clientId,
|
|
1108
|
+
await commitRejectedPushResult(clientId, clientCommitId, stored);
|
|
1041
1109
|
const canonical = await storage.getPushResult(
|
|
1042
1110
|
partition,
|
|
1043
1111
|
clientId,
|
|
1044
|
-
|
|
1112
|
+
clientCommitId,
|
|
1045
1113
|
);
|
|
1046
1114
|
if (canonical === undefined) {
|
|
1047
1115
|
throw new Error(
|
|
@@ -1049,7 +1117,7 @@ export async function processPushCommitWithTrace(
|
|
|
1049
1117
|
);
|
|
1050
1118
|
}
|
|
1051
1119
|
return processedPushCommit(
|
|
1052
|
-
|
|
1120
|
+
clientCommitId,
|
|
1053
1121
|
canonical,
|
|
1054
1122
|
canonical.cacheIdentity !== stored.cacheIdentity,
|
|
1055
1123
|
);
|