@reventlessdev/reventless-spec 3.0.0-alpha.96 → 3.0.0-alpha.97

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/CHANGELOG.md CHANGED
@@ -3,6 +3,13 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 3.0.0-alpha.97 (2026-08-02)
7
+
8
+ ### Features
9
+
10
+ * **ppx:** add [@offload](https://github.com/offload) field shorthand with per-field threshold ([3d5e3b5](https://github.com/ReventlessDev/reventless-core/commit/3d5e3b5da5010547ce0eaf7d94d660daec67feed))
11
+
12
+
6
13
  # 3.0.0-alpha.96 (2026-08-02)
7
14
 
8
15
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-spec",
3
- "version": "3.0.0-alpha.96",
3
+ "version": "3.0.0-alpha.97",
4
4
  "description": "Specifications for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {
@@ -44,7 +44,7 @@ let apiTargetSchema = S.union([
44
44
  S.literal("Platform")
45
45
  ]);
46
46
 
47
- let apiSchemaFragmentOffloadSchema = Offload$Reventless.optionSchema(undefined, "pluginApiFragments", apiSchemaFragmentSchema);
47
+ let apiSchemaFragmentOffloadSchema = Offload$Reventless.optionSchema(undefined, "pluginApiFragments", undefined, apiSchemaFragmentSchema);
48
48
 
49
49
  let dcbEventLogOptionSchema = SuryResMjs.js_nullable(dcbEventLogDefinitionSchema);
50
50
 
@@ -202,7 +202,7 @@ let pluginStructureSchema = S.schema(s => ({
202
202
  requiredStoreDeclarations: s.m(requiredStoreDeclarationArrayOptionSchema)
203
203
  }));
204
204
 
205
- let pluginStructureOffloadSchema = Offload$Reventless.optionSchema(undefined, "pluginStructures", pluginStructureSchema);
205
+ let pluginStructureOffloadSchema = Offload$Reventless.optionSchema(undefined, "pluginStructures", undefined, pluginStructureSchema);
206
206
 
207
207
  let pluginDefinitionSchema = S.schema(s => ({
208
208
  id: s.m(S.string),
@@ -127,8 +127,13 @@ plugin's own store; qualify as `"<plugin>.<store>"` to point at another's.
127
127
 
128
128
  Prefer the `@offload("<store>")` ppx shorthand over calling this by hand.
129
129
  */
130
- let forStore = (~plugin: option<string>=?, ~store: string, inner: S.t<'a>): S.t<payload<'a>> =>
131
- schema(inner)->Semantic.mark(~id=Semantic.Id.offload, ~payload=StoredIn({plugin, store}))
130
+ let forStore = (
131
+ ~plugin: option<string>=?,
132
+ ~store: string,
133
+ ~threshold: option<int>=?,
134
+ inner: S.t<'a>,
135
+ ): S.t<payload<'a>> =>
136
+ schema(inner)->Semantic.mark(~id=Semantic.Id.offload, ~payload=StoredIn({plugin, store, threshold}))
132
137
 
133
138
  /**
134
139
  The codec wrapped for an **optional** field (`js_nullable`), plus the `StoredIn`
@@ -139,11 +144,12 @@ for older protocol versions, say). The marker sits on the outer schema, where
139
144
  let optionSchema = (
140
145
  ~plugin: option<string>=?,
141
146
  ~store: string,
147
+ ~threshold: option<int>=?,
142
148
  inner: S.t<'a>,
143
149
  ): S.t<option<payload<'a>>> =>
144
150
  _jsNullable(schema(inner), ())->Semantic.mark(
145
151
  ~id=Semantic.Id.offload,
146
- ~payload=StoredIn({plugin, store}),
152
+ ~payload=StoredIn({plugin, store, threshold}),
147
153
  )
148
154
 
149
155
  /** Serialize a payload to its **untagged** wire JSON: `Inline` becomes the bare
@@ -253,3 +259,36 @@ let getStore = (schema: S.t<'a>): option<Semantic.storeTarget> =>
253
259
  | Some({id, payload: StoredIn(target)}) if id == Semantic.Id.offload => Some(target)
254
260
  | _ => None
255
261
  }
262
+
263
+ /** The framework-default inline-vs-offloaded byte cut, used when neither the field
264
+ marker nor the platform declares one. Retuning any level is safe: both arms of
265
+ the codec read back to identical bytes, so the threshold only decides how
266
+ *future* values are split — no wire change, no re-encoding, existing events
267
+ stay valid. */
268
+ let defaultThreshold = 8192
269
+
270
+ /** The per-field threshold an `@offload` field declares, if any — the top of the
271
+ precedence chain (`@offload({..., threshold})`). `None` when the field left it
272
+ unset, which defers to the platform default and then {!defaultThreshold}. */
273
+ let getThreshold = (schema: S.t<'a>): option<int> =>
274
+ switch Semantic.get(schema) {
275
+ | Some({id, payload: StoredIn({threshold})}) if id == Semantic.Id.offload => threshold
276
+ | _ => None
277
+ }
278
+
279
+ /**
280
+ The effective threshold for a field, resolving the precedence chain most-specific
281
+ first: the per-field `@offload({threshold})` (read from the field's schema), then
282
+ the platform config default (`~platformDefault`, e.g. `MakeWithConfig`'s
283
+ `offloadThreshold`), then {!defaultThreshold}.
284
+
285
+ A client drives an offloadable field by reading its field schema, calling this to
286
+ get the cut, and passing the result as {!prepare}'s `~threshold`. `prepare` stays
287
+ threshold-explicit (it holds the *value* schema, not the field schema that carries
288
+ the marker), so this is the seam that turns the declaration into a number.
289
+ */
290
+ let effectiveThreshold = (schema: S.t<'a>, ~platformDefault: option<int>=?, ()): int =>
291
+ switch getThreshold(schema) {
292
+ | Some(t) => t
293
+ | None => platformDefault->Option.getOr(defaultThreshold)
294
+ }
@@ -61,22 +61,24 @@ function schema(inner) {
61
61
  ]);
62
62
  }
63
63
 
64
- function forStore(plugin, store, inner) {
64
+ function forStore(plugin, store, threshold, inner) {
65
65
  return Semantic$Reventless.mark(schema(inner), Semantic$Reventless.Id.offload, {
66
66
  TAG: "StoredIn",
67
67
  _0: {
68
68
  plugin: plugin,
69
- store: store
69
+ store: store,
70
+ threshold: threshold
70
71
  }
71
72
  });
72
73
  }
73
74
 
74
- function optionSchema(plugin, store, inner) {
75
+ function optionSchema(plugin, store, threshold, inner) {
75
76
  return Semantic$Reventless.mark(SuryResMjs.js_nullable(schema(inner)), Semantic$Reventless.Id.offload, {
76
77
  TAG: "StoredIn",
77
78
  _0: {
78
79
  plugin: plugin,
79
- store: store
80
+ store: store,
81
+ threshold: threshold
80
82
  }
81
83
  });
82
84
  }
@@ -146,6 +148,30 @@ function getStore(schema) {
146
148
  }
147
149
  }
148
150
 
151
+ function getThreshold(schema) {
152
+ let match = Semantic$Reventless.get(schema);
153
+ if (match === undefined) {
154
+ return;
155
+ }
156
+ let match$1 = match.payload;
157
+ if (typeof match$1 !== "object" || match$1.TAG === "ReferenceTo" || match.id !== Semantic$Reventless.Id.offload) {
158
+ return;
159
+ } else {
160
+ return match$1._0.threshold;
161
+ }
162
+ }
163
+
164
+ function effectiveThreshold(schema, platformDefault, param) {
165
+ let t = getThreshold(schema);
166
+ if (t !== undefined) {
167
+ return t;
168
+ } else {
169
+ return Stdlib_Option.getOr(platformDefault, 8192);
170
+ }
171
+ }
172
+
173
+ let defaultThreshold = 8192;
174
+
149
175
  export {
150
176
  offloadedRefSchema,
151
177
  sentinelKey,
@@ -158,5 +184,8 @@ export {
158
184
  resolve,
159
185
  cachedFetch,
160
186
  getStore,
187
+ defaultThreshold,
188
+ getThreshold,
189
+ effectiveThreshold,
161
190
  }
162
191
  /* offloadedRefSchema Not a pure module */
@@ -24,9 +24,12 @@ that can fail at runtime.
24
24
  /** Which entity a reference field points to. */
25
25
  type referenceTarget = {entity: string, plugin: option<string>}
26
26
 
27
- /** Which object store a storage-ref field's value lives in. `plugin` is absent
28
- when the store belongs to the declaring plugin, which is the common case. */
29
- type storeTarget = {plugin: option<string>, store: string}
27
+ /** Which object store a storage-ref / offload field's value lives in. `plugin` is
28
+ absent when the store belongs to the declaring plugin, which is the common
29
+ case. `threshold` is the per-field inline-vs-offloaded byte cut an `@offload`
30
+ field may declare (`None` for `@storageRef`, which is always a ref, and for
31
+ `@offload` fields that leave it to the platform default). */
32
+ type storeTarget = {plugin: option<string>, store: string, threshold: option<int>}
30
33
 
31
34
  /** Per-semantic detail, for the semantics that carry any. */
32
35
  type payload =
@@ -114,7 +114,7 @@ let forStore = (~plugin: option<string>=?, ~store: string): S.t<t> =>
114
114
  }
115
115
  }
116
116
  )
117
- ->Semantic.mark(~id=Semantic.Id.storageRef, ~payload=StoredIn({plugin, store}))
117
+ ->Semantic.mark(~id=Semantic.Id.storageRef, ~payload=StoredIn({plugin, store, threshold: None}))
118
118
 
119
119
  /** The store a field's schema declares its refs live in, if any. */
120
120
  let getStore = (schema: S.t<'a>): option<Semantic.storeTarget> =>
@@ -59,7 +59,8 @@ function forStore(plugin, store) {
59
59
  TAG: "StoredIn",
60
60
  _0: {
61
61
  plugin: plugin,
62
- store: store
62
+ store: store,
63
+ threshold: undefined
63
64
  }
64
65
  });
65
66
  }