@@ -44,6 +44,7 @@ import type { EffectPipeline } from '../services/EffectPipeline.ts';
4444import type { ExternalizationPolicy } from '../types/ExternalizationPolicy.ts' ;
4545import GCPolicy , { type GCPolicyConfig } from '../services/GCPolicy.ts' ;
4646import type { MaterializeSessionOpener } from '../services/controllers/MaterializeSessionBridge.ts' ;
47+ import CheckpointPolicy , { type CheckpointPolicyConfig } from './CheckpointPolicy.ts' ;
4748
4849type DeletePolicy = 'reject' | 'cascade' | 'warn' ;
4950const VALID_DELETE_POLICIES : ReadonlyArray < DeletePolicy > = [ 'reject' , 'cascade' , 'warn' ] ;
@@ -54,7 +55,7 @@ export type RuntimeHostConstructionOptions = {
5455 graphName : string ;
5556 writerId : string ;
5657 gcPolicy ?: GCPolicyConfig | GCPolicy ;
57- checkpointPolicy ?: { every : number } ;
58+ checkpointPolicy : CheckpointPolicy | null ;
5859 autoMaterialize ?: boolean ;
5960 onDeleteWithData ?: DeletePolicy ;
6061 logger ?: LoggerPort ;
@@ -89,7 +90,7 @@ export type RuntimeHostOpenOptions = {
8990 graphName : string ;
9091 writerId : string ;
9192 gcPolicy ?: GCPolicyConfig | GCPolicy ;
92- checkpointPolicy ?: { every : number } | null ;
93+ checkpointPolicy ?: CheckpointPolicyConfig | CheckpointPolicy | null ;
9394 autoMaterialize ?: boolean ;
9495 onDeleteWithData ?: DeletePolicy ;
9596 logger ?: LoggerPort ;
@@ -113,7 +114,7 @@ export class WarpOpenOptions {
113114 readonly graphName : string ;
114115 readonly writerId : string ;
115116 readonly gcPolicy : GCPolicyConfig | GCPolicy ;
116- readonly checkpointPolicy ?: { every : number } ;
117+ readonly checkpointPolicy : CheckpointPolicy | null ;
117118 readonly autoMaterialize ?: boolean ;
118119 readonly onDeleteWithData ?: DeletePolicy ;
119120 readonly logger ?: LoggerPort ;
@@ -148,10 +149,7 @@ export class WarpOpenOptions {
148149 if ( options . codec !== undefined ) { this . codec = options . codec ; }
149150 if ( options . trustCrypto !== undefined ) { this . trustCrypto = options . trustCrypto ; }
150151
151- const checkpointPolicy = normalizeCheckpointPolicy ( options . checkpointPolicy ) ;
152- if ( checkpointPolicy !== undefined ) {
153- this . checkpointPolicy = checkpointPolicy ;
154- }
152+ this . checkpointPolicy = normalizeCheckpointPolicy ( options . checkpointPolicy ) ;
155153 if ( options . autoMaterialize !== undefined ) {
156154 this . autoMaterialize = normalizeBooleanOption (
157155 options . autoMaterialize ,
@@ -202,6 +200,18 @@ export class WarpOpenOptions {
202200
203201export type RuntimeHostOpenInput = RuntimeHostOpenOptions | WarpOpenOptions ;
204202
203+ /**
204+ * Auto-checkpoint cadence applied when a caller supplies no `checkpointPolicy`.
205+ *
206+ * `every` is compared against the replay depth reported by a materialize (the
207+ * patch count since the last checkpoint), not against writes performed by the
208+ * current process, so short-lived callers still compact once the backlog
209+ * crosses the threshold.
210+ *
211+ * Pass `checkpointPolicy: null` to disable auto-checkpointing entirely.
212+ */
213+ export const DEFAULT_CHECKPOINT_POLICY : CheckpointPolicy = CheckpointPolicy . DEFAULT ;
214+
205215function normalizeBooleanOption ( value : boolean , label : string , code : string ) : boolean {
206216 if ( typeof value !== 'boolean' ) {
207217 throw new WarpError ( `${ label } must be a boolean` , code ) ;
@@ -210,18 +220,20 @@ function normalizeBooleanOption(value: boolean, label: string, code: string): bo
210220}
211221
212222function normalizeCheckpointPolicy (
213- checkpointPolicy : { every : number } | null | undefined ,
214- ) : { every : number } | undefined {
215- if ( checkpointPolicy === null || checkpointPolicy === undefined ) {
216- return undefined ;
217- }
218- if ( typeof checkpointPolicy !== 'object' ) {
219- throw new WarpError ( 'checkpointPolicy must be an object with { every: number }' , 'E_CHECKPOINT_POLICY_TYPE' ) ;
223+ checkpointPolicy : CheckpointPolicyConfig | CheckpointPolicy | null | undefined ,
224+ ) : CheckpointPolicy | null {
225+ // An omitted policy takes the default. Auto-checkpointing is what bounds
226+ // replay depth, so leaving it off by default made every caller that never
227+ // supplied a policy replay its entire patch history on each materialize,
228+ // growing without limit. `null` remains the explicit opt-out for callers
229+ // that genuinely want no compaction.
230+ if ( checkpointPolicy === undefined ) {
231+ return DEFAULT_CHECKPOINT_POLICY ;
220232 }
221- if ( ! Number . isInteger ( checkpointPolicy . every ) || checkpointPolicy . every <= 0 ) {
222- throw new WarpError ( 'checkpointPolicy.every must be a positive integer' , 'E_CHECKPOINT_POLICY_EVERY' ) ;
233+ if ( checkpointPolicy === null ) {
234+ return null ;
223235 }
224- return Object . freeze ( { every : checkpointPolicy . every } ) ;
236+ return CheckpointPolicy . from ( checkpointPolicy ) ;
225237}
226238
227239function snapshotGCPolicy ( value : GCPolicyConfig | GCPolicy | undefined ) : GCPolicyConfig | GCPolicy {
@@ -371,7 +383,7 @@ export async function resolveRuntimeHostConstructionOptions(
371383 graphName,
372384 writerId,
373385 gcPolicy,
374- ... ( checkpointPolicy !== undefined ? { checkpointPolicy } : { } ) ,
386+ checkpointPolicy,
375387 ...( autoMaterialize !== undefined ? { autoMaterialize } : { } ) ,
376388 ...( onDeleteWithData !== undefined ? { onDeleteWithData } : { } ) ,
377389 ...( logger !== undefined ? { logger } : { } ) ,
0 commit comments