@selvajs/server 0.2.0-beta.2 → 0.2.0-beta.3

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.
Files changed (72) hide show
  1. package/dist/compute/__tests__/client-cache.test.js +18 -0
  2. package/dist/compute/__tests__/client-cache.test.js.map +1 -1
  3. package/dist/compute/__tests__/definition-byte-cache.test.d.ts +2 -0
  4. package/dist/compute/__tests__/definition-byte-cache.test.d.ts.map +1 -0
  5. package/dist/compute/__tests__/definition-byte-cache.test.js +107 -0
  6. package/dist/compute/__tests__/definition-byte-cache.test.js.map +1 -0
  7. package/dist/compute/__tests__/solve-cache.test.d.ts +13 -0
  8. package/dist/compute/__tests__/solve-cache.test.d.ts.map +1 -0
  9. package/dist/compute/__tests__/solve-cache.test.js +234 -0
  10. package/dist/compute/__tests__/solve-cache.test.js.map +1 -0
  11. package/dist/compute/__tests__/solve-pipeline.test.js +267 -2
  12. package/dist/compute/__tests__/solve-pipeline.test.js.map +1 -1
  13. package/dist/compute/client-cache.d.ts +12 -0
  14. package/dist/compute/client-cache.d.ts.map +1 -1
  15. package/dist/compute/client-cache.js +12 -3
  16. package/dist/compute/client-cache.js.map +1 -1
  17. package/dist/compute/definition-byte-cache.d.ts +85 -0
  18. package/dist/compute/definition-byte-cache.d.ts.map +1 -0
  19. package/dist/compute/definition-byte-cache.js +110 -0
  20. package/dist/compute/definition-byte-cache.js.map +1 -0
  21. package/dist/compute/index.d.ts +7 -2
  22. package/dist/compute/index.d.ts.map +1 -1
  23. package/dist/compute/index.js +6 -1
  24. package/dist/compute/index.js.map +1 -1
  25. package/dist/compute/limits.d.ts +52 -0
  26. package/dist/compute/limits.d.ts.map +1 -1
  27. package/dist/compute/limits.js +33 -1
  28. package/dist/compute/limits.js.map +1 -1
  29. package/dist/compute/memory-solve-cache.d.ts +58 -0
  30. package/dist/compute/memory-solve-cache.d.ts.map +1 -0
  31. package/dist/compute/memory-solve-cache.js +0 -0
  32. package/dist/compute/memory-solve-cache.js.map +1 -0
  33. package/dist/compute/solve-cache-envelope.d.ts +53 -0
  34. package/dist/compute/solve-cache-envelope.d.ts.map +1 -0
  35. package/dist/compute/solve-cache-envelope.js +66 -0
  36. package/dist/compute/solve-cache-envelope.js.map +1 -0
  37. package/dist/compute/solve-cache-key.d.ts +65 -0
  38. package/dist/compute/solve-cache-key.d.ts.map +1 -0
  39. package/dist/compute/solve-cache-key.js +50 -0
  40. package/dist/compute/solve-cache-key.js.map +1 -0
  41. package/dist/compute/solve-cache-single-flight.d.ts +35 -0
  42. package/dist/compute/solve-cache-single-flight.d.ts.map +1 -0
  43. package/dist/compute/solve-cache-single-flight.js +48 -0
  44. package/dist/compute/solve-cache-single-flight.js.map +1 -0
  45. package/dist/compute/solve-pipeline.d.ts +71 -8
  46. package/dist/compute/solve-pipeline.d.ts.map +1 -1
  47. package/dist/compute/solve-pipeline.js +178 -8
  48. package/dist/compute/solve-pipeline.js.map +1 -1
  49. package/dist/definitions/__tests__/definition-service.test.d.ts +14 -0
  50. package/dist/definitions/__tests__/definition-service.test.d.ts.map +1 -0
  51. package/dist/definitions/__tests__/definition-service.test.js +95 -0
  52. package/dist/definitions/__tests__/definition-service.test.js.map +1 -0
  53. package/dist/definitions/definition-service.d.ts +1 -1
  54. package/dist/definitions/definition-service.d.ts.map +1 -1
  55. package/dist/definitions/definition-service.js +8 -4
  56. package/dist/definitions/definition-service.js.map +1 -1
  57. package/package.json +2 -2
  58. package/src/compute/__tests__/client-cache.test.ts +20 -0
  59. package/src/compute/__tests__/definition-byte-cache.test.ts +129 -0
  60. package/src/compute/__tests__/solve-cache.test.ts +268 -0
  61. package/src/compute/__tests__/solve-pipeline.test.ts +320 -1
  62. package/src/compute/client-cache.ts +24 -3
  63. package/src/compute/definition-byte-cache.ts +168 -0
  64. package/src/compute/index.ts +34 -0
  65. package/src/compute/limits.ts +79 -1
  66. package/src/compute/memory-solve-cache.ts +0 -0
  67. package/src/compute/solve-cache-envelope.ts +98 -0
  68. package/src/compute/solve-cache-key.ts +85 -0
  69. package/src/compute/solve-cache-single-flight.ts +59 -0
  70. package/src/compute/solve-pipeline.ts +279 -15
  71. package/src/definitions/__tests__/definition-service.test.ts +131 -0
  72. package/src/definitions/definition-service.ts +8 -4
@@ -0,0 +1,35 @@
1
+ /**
2
+ * In-process single-flight (R4) — dogpile protection above the L2 cache.
3
+ *
4
+ * N identical live solves arriving while the first is still running would each
5
+ * enqueue and each hit Rhino (the scheduler has no in-flight coalescing, and
6
+ * Rhino's `cachesolve` still costs a round trip per repeat). The classic cold-key
7
+ * stampede: a hot public definition + a deploy = a thundering herd onto compute.
8
+ *
9
+ * This coalesces them: the first caller for a key runs the work; concurrent
10
+ * callers for the SAME key await the same promise and share its result. Once the
11
+ * work settles (resolve OR reject), the key is released so the next request runs
12
+ * fresh — the map never retains a settled entry, so it can't serve a stale
13
+ * result or leak memory.
14
+ *
15
+ * It sits ABOVE the `ISolveResultCache` interface (in-process, per app instance).
16
+ * A cross-instance lease (Redis `SET NX`) is a later backend capability; this is
17
+ * the free first slice that covers the common same-instance burst.
18
+ *
19
+ * Correctness note: the shared promise resolves to ONE value for all waiters, so
20
+ * the wrapped work must return an immutable / independently-serializable result
21
+ * (the solve pipeline returns a fresh envelope object — safe to share by
22
+ * reference here because the app serializes it per response).
23
+ */
24
+ export interface SolveCacheSingleFlight {
25
+ /**
26
+ * Run `work` under `key`, coalescing concurrent identical calls. The first
27
+ * caller executes; overlapping callers for the same key await the same promise.
28
+ * The key is freed as soon as the promise settles (success or failure).
29
+ */
30
+ run<T>(key: string, work: () => Promise<T>): Promise<T>;
31
+ /** Number of keys currently in flight (observability / tests). */
32
+ inFlight(): number;
33
+ }
34
+ export declare function createSolveCacheSingleFlight(): SolveCacheSingleFlight;
35
+ //# sourceMappingURL=solve-cache-single-flight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"solve-cache-single-flight.d.ts","sourceRoot":"","sources":["../../src/compute/solve-cache-single-flight.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,WAAW,sBAAsB;IACtC;;;;OAIG;IACH,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACxD,kEAAkE;IAClE,QAAQ,IAAI,MAAM,CAAC;CACnB;AAED,wBAAgB,4BAA4B,IAAI,sBAAsB,CAuBrE"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * In-process single-flight (R4) — dogpile protection above the L2 cache.
3
+ *
4
+ * N identical live solves arriving while the first is still running would each
5
+ * enqueue and each hit Rhino (the scheduler has no in-flight coalescing, and
6
+ * Rhino's `cachesolve` still costs a round trip per repeat). The classic cold-key
7
+ * stampede: a hot public definition + a deploy = a thundering herd onto compute.
8
+ *
9
+ * This coalesces them: the first caller for a key runs the work; concurrent
10
+ * callers for the SAME key await the same promise and share its result. Once the
11
+ * work settles (resolve OR reject), the key is released so the next request runs
12
+ * fresh — the map never retains a settled entry, so it can't serve a stale
13
+ * result or leak memory.
14
+ *
15
+ * It sits ABOVE the `ISolveResultCache` interface (in-process, per app instance).
16
+ * A cross-instance lease (Redis `SET NX`) is a later backend capability; this is
17
+ * the free first slice that covers the common same-instance burst.
18
+ *
19
+ * Correctness note: the shared promise resolves to ONE value for all waiters, so
20
+ * the wrapped work must return an immutable / independently-serializable result
21
+ * (the solve pipeline returns a fresh envelope object — safe to share by
22
+ * reference here because the app serializes it per response).
23
+ */
24
+ export function createSolveCacheSingleFlight() {
25
+ const inflight = new Map();
26
+ return {
27
+ run(key, work) {
28
+ const existing = inflight.get(key);
29
+ if (existing)
30
+ return existing;
31
+ // `.finally` releases the key on both settle paths. Start the work inside
32
+ // the promise chain so a synchronous throw in `work` still rejects the
33
+ // shared promise (and still frees the key) rather than escaping.
34
+ const p = (async () => work())().finally(() => {
35
+ // Only delete if it's still OUR promise — a key freed and re-taken by a
36
+ // later caller must not be clobbered.
37
+ if (inflight.get(key) === p)
38
+ inflight.delete(key);
39
+ });
40
+ inflight.set(key, p);
41
+ return p;
42
+ },
43
+ inFlight() {
44
+ return inflight.size;
45
+ }
46
+ };
47
+ }
48
+ //# sourceMappingURL=solve-cache-single-flight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"solve-cache-single-flight.js","sourceRoot":"","sources":["../../src/compute/solve-cache-single-flight.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAaH,MAAM,UAAU,4BAA4B;IAC3C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA4B,CAAC;IAErD,OAAO;QACN,GAAG,CAAI,GAAW,EAAE,IAAsB;YACzC,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACnC,IAAI,QAAQ;gBAAE,OAAO,QAAsB,CAAC;YAE5C,0EAA0E;YAC1E,uEAAuE;YACvE,iEAAiE;YACjE,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;gBAC7C,wEAAwE;gBACxE,sCAAsC;gBACtC,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC;oBAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACnD,CAAC,CAAC,CAAC;YACH,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YACrB,OAAO,CAAC,CAAC;QACV,CAAC;QACD,QAAQ;YACP,OAAO,QAAQ,CAAC,IAAI,CAAC;QACtB,CAAC;KACD,CAAC;AACH,CAAC"}
@@ -19,9 +19,11 @@
19
19
  * The Server-Timing string it emits IS the versioned wire contract (audit D5):
20
20
  * see {@link COMPUTE_CONTRACT_VERSION} and {@link COMPUTE_VERSION_HEADER}.
21
21
  */
22
- import { type GrasshopperComputeResponse } from '@selvajs/compute';
22
+ import { type GrasshopperComputeResponse, type SolveDefinition } from '@selvajs/compute';
23
23
  import type { SchemaInput } from '@selvajs/schemas';
24
24
  import type { CachedClient } from './client-cache.js';
25
+ import type { ByteRefOutcome } from './definition-byte-cache.js';
26
+ import { type SolveCacheConfigSubset } from './solve-cache-key.js';
25
27
  /** Current compute-response contract version. Additive header; bump on shape change. */
26
28
  export declare const COMPUTE_CONTRACT_VERSION: 1;
27
29
  /** Response header carrying {@link COMPUTE_CONTRACT_VERSION}. */
@@ -33,8 +35,20 @@ export type PipelineInput = SchemaInput & {
33
35
  stepSize?: number;
34
36
  };
35
37
  export interface SolvePipelineArgs {
36
- /** The `.gh` bytes to solve (stored blob or remote fetch — resolved by the caller). */
37
- definitionSource: Uint8Array;
38
+ /**
39
+ * The definition to solve. Either raw `.gh` bytes (remote fetch, or a caller
40
+ * that resolved them eagerly) or a `DefinitionRef` — an identity-keyed lazy
41
+ * loader (from the definition-byte cache) whose bytes the scheduler
42
+ * materializes ONLY when an upload is unavoidable. A pointer-known solve of a
43
+ * `DefinitionRef` moves zero bytes.
44
+ */
45
+ definitionSource: SolveDefinition;
46
+ /**
47
+ * When `definitionSource` is a byte-cache `DefinitionRef`, its mutable outcome
48
+ * so the pipeline can emit the `def_bytes` Server-Timing verdict (skipped =
49
+ * `load` never ran, hit = warm cache, miss = loader). Omit for raw-bytes solves.
50
+ */
51
+ byteRefOutcome?: ByteRefOutcome;
38
52
  /** Persisted input params; only those with a `paramType` are sent to the solve. */
39
53
  inputs: PipelineInput[];
40
54
  /** User-chosen values keyed by input id; missing keys fall back to the schema default. */
@@ -66,6 +80,30 @@ export interface SolvePipelineArgs {
66
80
  * Server-Timing entries. The caller assembles these; the pipeline only echoes.
67
81
  */
68
82
  prepMarks?: [string, number][];
83
+ /**
84
+ * Optional durable L2 solve cache (H1). Present ONLY for live-channel solves
85
+ * whose definition has a caching quota > 0 — its presence IS the channel gate
86
+ * (zero policy conditionals in the pipeline). When set, the pipeline derives
87
+ * the wide input key from the transformed tree + `configSubset`, checks
88
+ * `lookup` before solving (a hit skips compute entirely, returning the stored
89
+ * gzipped envelope), and write-throughs `store` on a successful solve.
90
+ */
91
+ solveCache?: SolvePipelineCacheHook;
92
+ }
93
+ /**
94
+ * The app-supplied L2 hook. The app binds `lookup`/`store` to the org/definition/
95
+ * version scope and the definition's quota; the pipeline supplies the SHA-256
96
+ * `inputKey` (derived from the transformed tree + `configSubset`) and the opaque
97
+ * encoded entry bytes. Both are best-effort — `lookup` may miss/throw (treated as
98
+ * a miss), `store` is fire-and-forget.
99
+ */
100
+ export interface SolvePipelineCacheHook {
101
+ /** Solve-affecting config folded into the key (R8) — model units, tolerances, server id. */
102
+ configSubset: SolveCacheConfigSubset;
103
+ /** Fetch a stored entry for `inputKey`, or `null` on miss. Must not reject fatally. */
104
+ lookup(inputKey: string): Promise<Uint8Array | null>;
105
+ /** Store the encoded entry for `inputKey`. Fire-and-forget; best-effort. */
106
+ store(inputKey: string, entry: Uint8Array): void;
69
107
  }
70
108
  /** Phase timings the pipeline measured; the caller uses them for debug logging. */
71
109
  export interface SolvePhaseMetrics {
@@ -78,6 +116,12 @@ export interface SolvePhaseMetrics {
78
116
  serializedBytes: number;
79
117
  /** Gzipped length in bytes, or null when compression was skipped. */
80
118
  compressedBytes: number | null;
119
+ /**
120
+ * L2 durable-cache verdict: `true` = served from L2 (no solve ran), `false` =
121
+ * L2 consulted and missed (solved), `null` = L2 not consulted (no hook — draft
122
+ * or caching off). Drives the `l2_cache` Server-Timing entry.
123
+ */
124
+ l2CacheHit: boolean | null;
81
125
  }
82
126
  /** A ready-to-send response: body + headers + the solve result + phase metrics. */
83
127
  export interface SolveEnvelope {
@@ -87,17 +131,23 @@ export interface SolveEnvelope {
87
131
  encoding?: 'gzip';
88
132
  /** Fully-assembled response headers (Content-Type/-Length/-Encoding, Vary, Server-Timing, version). */
89
133
  headers: Record<string, string>;
90
- /** The solved definition — the caller reads `errors`/`warnings` for its metric record. */
91
- result: GrasshopperComputeResponse;
134
+ /**
135
+ * The solved definition object, present only on a fresh (non-L2) solve. Absent
136
+ * on an L2 hit — the pipeline serves the stored gzipped body without ever
137
+ * materializing the object. The caller reads `errorCount`/`warningCount` off
138
+ * the `ok` outcome for its metric record, not this field.
139
+ */
140
+ result?: GrasshopperComputeResponse;
92
141
  /** Phase timings, for the caller's optional debug breakdown. */
93
142
  metrics: SolvePhaseMetrics;
94
143
  }
95
144
  /**
96
145
  * Discriminated result. `ok` carries the envelope; every other variant names an
97
146
  * expected failure the transport maps to a status code (the app route maps
98
- * timeout→504, client_abort→499, too_large→413; `compute_error` re-surfaces the
99
- * original error for the generic 500/503 path). `durationMs` on the error
100
- * variants is the solve wall time up to the failure, for the metric record.
147
+ * timeout→504, client_abort→499, too_large→413, shed→503+Retry-After;
148
+ * `compute_error` re-surfaces the original error for the generic 500/503 path).
149
+ * `durationMs` on the error variants is the solve wall time up to the failure,
150
+ * for the metric record.
101
151
  */
102
152
  export type SolveOutcome = {
103
153
  kind: 'ok';
@@ -114,6 +164,19 @@ export type SolveOutcome = {
114
164
  durationMs: number;
115
165
  } | {
116
166
  kind: 'too_large';
167
+ }
168
+ /**
169
+ * Scheduler backpressure shed the solve before it executed — the per-server
170
+ * queue was full (`QUEUE_FULL`) or it sat queued past the wait deadline
171
+ * (`QUEUE_TIMEOUT`). Retryable: the route maps this to 503 + `Retry-After`.
172
+ * `retryAfterSeconds` is a suggested backoff hint for the client.
173
+ */
174
+ | {
175
+ kind: 'shed';
176
+ durationMs: number;
177
+ reason: 'queue_full' | 'queue_timeout';
178
+ retryAfterSeconds: number;
179
+ message: string;
117
180
  } | {
118
181
  kind: 'compute_error';
119
182
  durationMs: number;
@@ -1 +1 @@
1
- {"version":3,"file":"solve-pipeline.d.ts","sourceRoot":"","sources":["../../src/compute/solve-pipeline.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"solve-pipeline.d.ts","sourceRoot":"","sources":["../../src/compute/solve-pipeline.ts"],"names":[],"mappings":"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"}
@@ -19,9 +19,14 @@
19
19
  * The Server-Timing string it emits IS the versioned wire contract (audit D5):
20
20
  * see {@link COMPUTE_CONTRACT_VERSION} and {@link COMPUTE_VERSION_HEADER}.
21
21
  */
22
- import { TreeBuilder } from '@selvajs/compute';
23
- import { gzipSync } from 'node:zlib';
22
+ import { ErrorCodes, RhinoComputeError, TreeBuilder } from '@selvajs/compute';
23
+ import { gzip } from 'node:zlib';
24
+ import { promisify } from 'node:util';
24
25
  import { transformInputParameter } from './transform-input.js';
26
+ import { deriveSolveCacheInputKey } from './solve-cache-key.js';
27
+ import { decodeSolveCacheEntry, encodeSolveCacheEntry, gunzipEntryBody } from './solve-cache-envelope.js';
28
+ /** Async gzip (audit B8) — off the event loop, unlike the old `gzipSync`. */
29
+ const gzipAsync = promisify(gzip);
25
30
  // ============================================================================
26
31
  // Wire contract version (audit D5)
27
32
  // ============================================================================
@@ -50,6 +55,29 @@ export async function runSolvePipeline(args) {
50
55
  .filter((input) => input.paramType)
51
56
  .map((input) => transformInputParameter(input, args.values[input.id])));
52
57
  const treeBuildMs = performance.now() - treeBuildStart;
58
+ // --- L2 durable cache lookup (H1) ---------------------------------------
59
+ // Derive the wide key from the TRANSFORMED tree (R13) + config subset (R8),
60
+ // then check the injected hook. Present only for cacheable live solves — its
61
+ // presence is the channel gate. A hit skips the solve entirely and serves the
62
+ // stored gzipped envelope; the derived key is reused for the write-through on
63
+ // a miss. Lookup is best-effort: any rejection is swallowed to a miss.
64
+ let inputKey = null;
65
+ if (args.solveCache) {
66
+ inputKey = deriveSolveCacheInputKey(inputTree, args.solveCache.configSubset).hash;
67
+ let stored = null;
68
+ try {
69
+ stored = await args.solveCache.lookup(inputKey);
70
+ }
71
+ catch {
72
+ stored = null;
73
+ }
74
+ if (stored) {
75
+ const hit = buildEnvelopeFromCacheEntry(stored, inputKey, args);
76
+ if (hit)
77
+ return hit;
78
+ // Corrupt/tampered entry → fall through and solve as a miss.
79
+ }
80
+ }
53
81
  // --- solve --------------------------------------------------------------
54
82
  // Snapshot the shared telemetry sequence counters. The holders live on the
55
83
  // warm client and are written by every request on this server; with
@@ -80,6 +108,30 @@ export async function runSolvePipeline(args) {
80
108
  message: `Solve exceeded the ${Math.round(args.maxSolveDurationMs / 1000)}s deadline.`
81
109
  };
82
110
  }
111
+ // Scheduler backpressure (audit B7): a full queue or an over-deadline queue
112
+ // wait sheds the solve BEFORE compute runs. Classify these as `shed` (503 +
113
+ // Retry-After) rather than the generic 500 `compute_error` path — they're
114
+ // load signals, not failures, and the client should back off and retry.
115
+ if (err instanceof RhinoComputeError) {
116
+ if (err.code === ErrorCodes.QUEUE_FULL) {
117
+ return {
118
+ kind: 'shed',
119
+ durationMs,
120
+ reason: 'queue_full',
121
+ retryAfterSeconds: 1,
122
+ message: 'Compute server is at capacity. Retry shortly.'
123
+ };
124
+ }
125
+ if (err.code === ErrorCodes.QUEUE_TIMEOUT) {
126
+ return {
127
+ kind: 'shed',
128
+ durationMs,
129
+ reason: 'queue_timeout',
130
+ retryAfterSeconds: 1,
131
+ message: 'Compute request waited too long in the queue. Retry shortly.'
132
+ };
133
+ }
134
+ }
83
135
  return { kind: 'compute_error', durationMs, error: err };
84
136
  }
85
137
  const solveMs = performance.now() - solveStart;
@@ -104,13 +156,36 @@ export async function runSolvePipeline(args) {
104
156
  // in Server-Timing, included in `total`). Buffered (not streamed) so
105
157
  // Content-Length is known and a connection cut mid-transfer fails hard
106
158
  // instead of truncating the JSON.
159
+ // gzip is async (audit B8) — a multi-MB `gzipSync` blocked the event loop for
160
+ // every other request on this instance. Two independent reasons to compress:
161
+ // - the client advertised gzip AND the body clears the 1 KB break-even (tiny
162
+ // bodies aren't worth compressing for the wire);
163
+ // - an L2 write-through is pending — the entry stores compressed bytes so
164
+ // every future hit is near-CPU-free, so we compress even a small body (it
165
+ // will be cached), just not for the wire below the threshold.
166
+ const clientWantsGzip = /\bgzip\b/i.test(args.acceptEncoding);
167
+ const worthSendingGzip = clientWantsGzip && serialized.length > 1024;
168
+ const willCache = args.solveCache != null;
107
169
  let compressed = null;
108
170
  let gzipMs = 0;
109
- if (/\bgzip\b/i.test(args.acceptEncoding) && serialized.length > 1024) {
171
+ if (worthSendingGzip || willCache) {
110
172
  const gzipStart = performance.now();
111
- compressed = gzipSync(Buffer.from(serialized));
173
+ compressed = await gzipAsync(Buffer.from(serialized));
112
174
  gzipMs = performance.now() - gzipStart;
113
175
  }
176
+ // --- L2 write-through (H1) ----------------------------------------------
177
+ // On a cacheable solve with a compressed body, store the gzipped envelope so
178
+ // future hits are near-CPU-free. Fire-and-forget through the hook; the store
179
+ // itself is best-effort (may silently drop under quota/byte pressure).
180
+ if (args.solveCache && inputKey && compressed) {
181
+ const entry = encodeSolveCacheEntry({
182
+ errorCount: result.errors?.length ?? 0,
183
+ warningCount: result.warnings?.length ?? 0,
184
+ serializedBytes: serialized.length,
185
+ inputHash: inputKey
186
+ }, new Uint8Array(compressed));
187
+ args.solveCache.store(inputKey, entry);
188
+ }
114
189
  const serverTotalMs = performance.now() - args.loadStartMs;
115
190
  // --- per-request telemetry attribution (see snapshot above) -------------
116
191
  // settle: ours iff exactly one settle happened since the snapshot. rhino:
@@ -121,6 +196,10 @@ export async function runSolvePipeline(args) {
121
196
  const rhino = settle && !settle.fromCache && client.rhinoTiming.seq === rhinoSeqBefore + 1
122
197
  ? client.rhinoTiming.last
123
198
  : null;
199
+ // Send gzip to the client only when it's worth it for the wire — a body
200
+ // compressed solely to feed the L2 write-through (or one below the 1 KB
201
+ // break-even) goes out uncompressed.
202
+ const sendCompressed = compressed && worthSendingGzip ? compressed : null;
124
203
  // --- Server-Timing envelope (the versioned wire contract) ---------------
125
204
  const serverTiming = buildServerTiming({
126
205
  defLoadMs: args.defLoadMs,
@@ -131,6 +210,9 @@ export async function runSolvePipeline(args) {
131
210
  serverTotalMs,
132
211
  rhino,
133
212
  settle,
213
+ byteRefOutcome: args.byteRefOutcome,
214
+ // L2 was consulted (hook present) and missed — otherwise not consulted.
215
+ l2CacheHit: args.solveCache ? false : null,
134
216
  prepMarks: args.prepMarks ?? []
135
217
  });
136
218
  const headers = {
@@ -146,15 +228,22 @@ export async function runSolvePipeline(args) {
146
228
  gzipMs,
147
229
  serverTotalMs,
148
230
  serializedBytes: serialized.length,
149
- compressedBytes: compressed?.byteLength ?? null
231
+ compressedBytes: sendCompressed?.byteLength ?? null,
232
+ l2CacheHit: args.solveCache ? false : null
150
233
  };
151
234
  let envelope;
152
- if (compressed) {
235
+ if (sendCompressed) {
153
236
  // A downstream proxy's `encode gzip` skips already-encoded responses, so
154
237
  // this never double-compresses. Vary is set on both branches.
155
238
  headers['Content-Encoding'] = 'gzip';
156
- headers['Content-Length'] = String(compressed.byteLength);
157
- envelope = { body: new Uint8Array(compressed), encoding: 'gzip', headers, result, metrics };
239
+ headers['Content-Length'] = String(sendCompressed.byteLength);
240
+ envelope = {
241
+ body: new Uint8Array(sendCompressed),
242
+ encoding: 'gzip',
243
+ headers,
244
+ result,
245
+ metrics
246
+ };
158
247
  }
159
248
  else {
160
249
  headers['Content-Length'] = String(Buffer.byteLength(serialized));
@@ -168,6 +257,74 @@ export async function runSolvePipeline(args) {
168
257
  warningCount: result.warnings?.length ?? 0
169
258
  };
170
259
  }
260
+ // ============================================================================
261
+ // L2 cache-hit envelope
262
+ // ============================================================================
263
+ /**
264
+ * Build the `ok` outcome from a stored L2 entry — the near-CPU-free hit path (no
265
+ * solve, no `JSON.stringify`, no gzip). Returns `null` when the entry is corrupt
266
+ * or its stored `inputHash` doesn't match the requesting key (defense-in-depth
267
+ * against a wide-hash collision), so the caller falls through and solves.
268
+ */
269
+ function buildEnvelopeFromCacheEntry(entryBytes, inputKey, args) {
270
+ const decoded = decodeSolveCacheEntry(entryBytes);
271
+ // A collision that produced the same storage key still can't serve the wrong
272
+ // geometry: the stored hash must match the requesting key's hash.
273
+ if (!decoded || decoded.header.inputHash !== inputKey)
274
+ return null;
275
+ const { header, gzippedBody } = decoded;
276
+ const clientWantsGzip = /\bgzip\b/i.test(args.acceptEncoding);
277
+ const serverTotalMs = performance.now() - args.loadStartMs;
278
+ const serverTiming = buildServerTiming({
279
+ defLoadMs: args.defLoadMs,
280
+ treeBuildMs: 0,
281
+ solveMs: 0,
282
+ serializeMs: 0,
283
+ gzipMs: 0,
284
+ serverTotalMs,
285
+ rhino: null,
286
+ settle: null,
287
+ byteRefOutcome: args.byteRefOutcome,
288
+ l2CacheHit: true,
289
+ prepMarks: args.prepMarks ?? []
290
+ });
291
+ const headers = {
292
+ 'Content-Type': 'application/json',
293
+ 'Server-Timing': serverTiming,
294
+ [COMPUTE_VERSION_HEADER]: String(COMPUTE_CONTRACT_VERSION),
295
+ Vary: 'Accept-Encoding'
296
+ };
297
+ const metrics = {
298
+ treeBuildMs: 0,
299
+ solveMs: 0,
300
+ serializeMs: 0,
301
+ gzipMs: 0,
302
+ serverTotalMs,
303
+ serializedBytes: header.serializedBytes,
304
+ compressedBytes: clientWantsGzip ? gzippedBody.byteLength : null,
305
+ l2CacheHit: true
306
+ };
307
+ let envelope;
308
+ if (clientWantsGzip) {
309
+ // Serve the stored compressed bytes verbatim — the whole point of R6.
310
+ headers['Content-Encoding'] = 'gzip';
311
+ headers['Content-Length'] = String(gzippedBody.byteLength);
312
+ envelope = { body: new Uint8Array(gzippedBody), encoding: 'gzip', headers, metrics };
313
+ }
314
+ else {
315
+ // Rare non-gzip client: gunzip back to the JSON string for the response.
316
+ const json = gunzipEntryBody(gzippedBody);
317
+ headers['Content-Length'] = String(Buffer.byteLength(json));
318
+ envelope = { body: json, headers, metrics };
319
+ }
320
+ return {
321
+ kind: 'ok',
322
+ envelope,
323
+ solveMs: 0,
324
+ errorCount: header.errorCount,
325
+ warningCount: header.warningCount
326
+ };
327
+ }
171
328
  /**
172
329
  * Assemble the `Server-Timing` header. The browser reads these to attribute its
173
330
  * round-trip: `total` here == the server's headers-to-out wall time, so browser
@@ -203,6 +360,19 @@ function buildServerTiming(parts) {
203
360
  header += `, def_reupload;dur=${settle.definitionReuploaded ? 1 : 0}`;
204
361
  }
205
362
  }
363
+ // Byte-cache verdict for `DefinitionRef` solves: skipped = the scheduler never
364
+ // materialized bytes (pointer-known solve — the whole point of this path), hit
365
+ // = warm byte cache, miss = fell through to storage. Absent for raw-bytes solves.
366
+ const byteRef = parts.byteRefOutcome;
367
+ if (byteRef) {
368
+ const verdict = !byteRef.loaded ? 'skipped' : byteRef.fromCache ? 'hit' : 'miss';
369
+ header += `, def_bytes;desc=${verdict}`;
370
+ }
371
+ // Durable L2 verdict (H1): 1 = hit (no solve ran), 0 = miss (consulted then
372
+ // solved). Absent when the L2 wasn't consulted (draft channel / caching off).
373
+ if (parts.l2CacheHit !== null) {
374
+ header += `, l2_cache;dur=${parts.l2CacheHit ? 1 : 0}`;
375
+ }
206
376
  return header;
207
377
  }
208
378
  //# sourceMappingURL=solve-pipeline.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"solve-pipeline.js","sourceRoot":"","sources":["../../src/compute/solve-pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,WAAW,EAAoD,MAAM,kBAAkB,CAAC;AAEjG,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAG/D,+EAA+E;AAC/E,mCAAmC;AACnC,+EAA+E;AAC/E,EAAE;AACF,+EAA+E;AAC/E,4EAA4E;AAC5E,6EAA6E;AAC7E,iFAAiF;AACjF,8DAA8D;AAE9D,wFAAwF;AACxF,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAU,CAAC;AACnD,iEAAiE;AACjE,MAAM,CAAC,MAAM,sBAAsB,GAAG,yBAAyB,CAAC;AAgGhE,+EAA+E;AAC/E,WAAW;AACX,+EAA+E;AAE/E;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAuB;IAC7D,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAEhC,2EAA2E;IAC3E,MAAM,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;IACzC,MAAM,SAAS,GAAG,WAAW,CAAC,eAAe,CAC5C,IAAI,CAAC,MAAM;SACT,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC;SAClC,GAAG,CAAC,CAAC,KAAK,EAAc,EAAE,CAAC,uBAAuB,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CACnF,CAAC;IACF,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,cAAc,CAAC;IAEvD,2EAA2E;IAC3E,2EAA2E;IAC3E,oEAAoE;IACpE,2EAA2E;IAC3E,wEAAwE;IACxE,2EAA2E;IAC3E,uEAAuE;IACvE,yEAAyE;IACzE,MAAM,eAAe,GAAG,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC;IAC7C,MAAM,cAAc,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC;IAE9C,IAAI,MAAkC,CAAC;IACvC,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;IACrC,IAAI,CAAC;QACJ,MAAM,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,gBAAgB,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IACrF,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC;QAClD,wEAAwE;QACxE,yEAAyE;QACzE,qEAAqE;QACrE,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,CAAC;QAClE,IAAI,OAAO,EAAE,CAAC;YACb,IAAI,MAAM,CAAC,OAAO;gBAAE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,UAAU,EAAE,CAAC;YAChE,OAAO;gBACN,IAAI,EAAE,SAAS;gBACf,UAAU;gBACV,OAAO,EAAE,sBAAsB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,aAAa;aACtF,CAAC;QACH,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IAC1D,CAAC;IACD,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC;IAE/C,0EAA0E;IAC1E,MAAM,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;IACzC,IAAI,UAAkB,CAAC;IACvB,IAAI,CAAC;QACJ,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACrC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,yEAAyE;QACzE,6EAA6E;QAC7E,IAAI,GAAG,YAAY,UAAU;YAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;QAC5D,MAAM,GAAG,CAAC;IACX,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,GAAG,IAAI,CAAC,gBAAgB;QAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IAC5E,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,cAAc,CAAC;IAEvD,2EAA2E;IAC3E,8EAA8E;IAC9E,qEAAqE;IACrE,uEAAuE;IACvE,kCAAkC;IAClC,IAAI,UAAU,GAAkB,IAAI,CAAC;IACrC,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,UAAU,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC;QACvE,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QACpC,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAC/C,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IACxC,CAAC;IAED,MAAM,aAAa,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC;IAE3D,2EAA2E;IAC3E,0EAA0E;IAC1E,yEAAyE;IACzE,0EAA0E;IAC1E,0DAA0D;IAC1D,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,GAAG,KAAK,eAAe,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3F,MAAM,KAAK,GACV,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,WAAW,CAAC,GAAG,KAAK,cAAc,GAAG,CAAC;QAC3E,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI;QACzB,CAAC,CAAC,IAAI,CAAC;IAET,2EAA2E;IAC3E,MAAM,YAAY,GAAG,iBAAiB,CAAC;QACtC,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,WAAW;QACX,OAAO;QACP,WAAW;QACX,MAAM;QACN,aAAa;QACb,KAAK;QACL,MAAM;QACN,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,EAAE;KAC/B,CAAC,CAAC;IAEH,MAAM,OAAO,GAA2B;QACvC,cAAc,EAAE,kBAAkB;QAClC,eAAe,EAAE,YAAY;QAC7B,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC,wBAAwB,CAAC;QAC1D,IAAI,EAAE,iBAAiB;KACvB,CAAC;IAEF,MAAM,OAAO,GAAsB;QAClC,WAAW;QACX,OAAO;QACP,WAAW;QACX,MAAM;QACN,aAAa;QACb,eAAe,EAAE,UAAU,CAAC,MAAM;QAClC,eAAe,EAAE,UAAU,EAAE,UAAU,IAAI,IAAI;KAC/C,CAAC;IAEF,IAAI,QAAuB,CAAC;IAC5B,IAAI,UAAU,EAAE,CAAC;QAChB,yEAAyE;QACzE,8DAA8D;QAC9D,OAAO,CAAC,kBAAkB,CAAC,GAAG,MAAM,CAAC;QACrC,OAAO,CAAC,gBAAgB,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;QAC1D,QAAQ,GAAG,EAAE,IAAI,EAAE,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IAC7F,CAAC;SAAM,CAAC;QACP,OAAO,CAAC,gBAAgB,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC;QAClE,QAAQ,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IAC3D,CAAC;IAED,OAAO;QACN,IAAI,EAAE,IAAI;QACV,QAAQ;QACR,OAAO;QACP,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC;QACtC,YAAY,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;KAC1C,CAAC;AACH,CAAC;AAkBD;;;;;;;GAOG;AACH,SAAS,iBAAiB,CAAC,KAAwB;IAClD,IAAI,MAAM,GACT,YAAY,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI;QAC1C,YAAY,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI;QAC5C,aAAa,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI;QACzC,iBAAiB,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI;QACjD,YAAY,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI;QACvC,aAAa,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAE/C,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;IAC1B,IAAI,KAAK,EAAE,CAAC;QACX,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC;QAC5D,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;QAC7D,MAAM;YACL,sBAAsB,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;gBAC/C,qBAAqB,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;gBAC7C,sBAAsB,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;gBAC/C,sBAAsB,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IACnD,CAAC;IAED,KAAK,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QAC3C,MAAM,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/C,CAAC;IAED,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAC5B,IAAI,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,qBAAqB,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1D,IAAI,MAAM,CAAC,oBAAoB,KAAK,SAAS,EAAE,CAAC;YAC/C,MAAM,IAAI,sBAAsB,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACvE,CAAC;IACF,CAAC;IAED,OAAO,MAAM,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"solve-pipeline.js","sourceRoot":"","sources":["../../src/compute/solve-pipeline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Tests for `DefinitionService.uploadVersion` version-number allocation (caching
3
+ * Phase 1.5). The root-cause fix: the next version number comes from the store's
4
+ * monotonic `reserveNextVersionNumber` counter — NOT max(existing)+1, which
5
+ * reused a number (and its `fileKey`) after delete-latest and let a stale storage
6
+ * blob serve the old version's bytes for new content.
7
+ *
8
+ * These run against fakes that record the store/storage calls, so they pin the
9
+ * SERVICE's contract (reserve → derive fileKey from the reserved number). That a
10
+ * real provider's counter is truly monotonic across delete-then-reupload is
11
+ * covered by the definition-store conformance suite (both providers).
12
+ */
13
+ export {};
14
+ //# sourceMappingURL=definition-service.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definition-service.test.d.ts","sourceRoot":"","sources":["../../../src/definitions/__tests__/definition-service.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Tests for `DefinitionService.uploadVersion` version-number allocation (caching
3
+ * Phase 1.5). The root-cause fix: the next version number comes from the store's
4
+ * monotonic `reserveNextVersionNumber` counter — NOT max(existing)+1, which
5
+ * reused a number (and its `fileKey`) after delete-latest and let a stale storage
6
+ * blob serve the old version's bytes for new content.
7
+ *
8
+ * These run against fakes that record the store/storage calls, so they pin the
9
+ * SERVICE's contract (reserve → derive fileKey from the reserved number). That a
10
+ * real provider's counter is truly monotonic across delete-then-reupload is
11
+ * covered by the definition-store conformance suite (both providers).
12
+ */
13
+ import { describe, it, expect } from 'vitest';
14
+ import { DefinitionService } from '../definition-service.js';
15
+ const CTX = { userId: 'u-1' };
16
+ const SCHEMA = {};
17
+ function fakeRecord(guid, nextVersionNumber) {
18
+ const now = new Date().toISOString();
19
+ return {
20
+ guid,
21
+ projectId: 'p-1',
22
+ ownerId: 'u-1',
23
+ createdBy: 'u-1',
24
+ updatedBy: 'u-1',
25
+ displayName: 'Def',
26
+ status: 'draft',
27
+ solveCount: 0,
28
+ nextVersionNumber,
29
+ liveVersionId: 'v-existing',
30
+ draftVersionId: 'v-existing',
31
+ createdAt: now,
32
+ updatedAt: now,
33
+ deletedAt: null
34
+ };
35
+ }
36
+ /**
37
+ * A minimal data provider whose `definitions` store hands out the reserved
38
+ * number and records the version row it was told to create. Only the methods
39
+ * `uploadVersion` touches are implemented.
40
+ */
41
+ function fakeDeps(record) {
42
+ let counter = record.nextVersionNumber;
43
+ const created = [];
44
+ const puts = [];
45
+ const data = {
46
+ definitions: {
47
+ get: async () => record,
48
+ reserveNextVersionNumber: async () => counter++, // monotonic, never reused
49
+ createVersion: async (_ctx, v) => {
50
+ created.push(v);
51
+ },
52
+ setDraftVersion: async () => { }
53
+ }
54
+ };
55
+ const storage = {
56
+ put: async (key) => {
57
+ puts.push(key);
58
+ }
59
+ };
60
+ return { data, storage, created, puts };
61
+ }
62
+ describe('DefinitionService.uploadVersion — version-number allocation', () => {
63
+ it('reserves the next number from the record counter and derives the fileKey from it', async () => {
64
+ const record = fakeRecord('11111111-1111-1111-1111-111111111111', 3);
65
+ const { data, storage, created, puts } = fakeDeps(record);
66
+ const svc = new DefinitionService(data, storage);
67
+ const version = await svc.uploadVersion(CTX, record.guid, new Uint8Array([1, 2, 3]), 'gh', 'model.gh', SCHEMA);
68
+ expect(version.versionNumber).toBe(3);
69
+ // fileKey encodes the reserved number — so a fresh number ⇒ fresh key.
70
+ expect(version.fileKey).toContain('v3');
71
+ expect(puts).toEqual([version.fileKey]);
72
+ expect(created).toHaveLength(1);
73
+ expect(created[0].versionNumber).toBe(3);
74
+ });
75
+ it('two consecutive uploads mint distinct numbers and distinct fileKeys', async () => {
76
+ const record = fakeRecord('22222222-2222-2222-2222-222222222222', 5);
77
+ const { data, storage } = fakeDeps(record);
78
+ const svc = new DefinitionService(data, storage);
79
+ const a = await svc.uploadVersion(CTX, record.guid, new Uint8Array([1]), 'gh', 'a.gh', SCHEMA);
80
+ const b = await svc.uploadVersion(CTX, record.guid, new Uint8Array([2]), 'gh', 'b.gh', SCHEMA);
81
+ expect(a.versionNumber).toBe(5);
82
+ expect(b.versionNumber).toBe(6);
83
+ expect(a.fileKey).not.toBe(b.fileKey);
84
+ });
85
+ it('does NOT derive the number from the version list (no listVersions call)', async () => {
86
+ // The old bug read the version list; the fix must not. A deps object with no
87
+ // listVersions still works — reserveNextVersionNumber is the only source.
88
+ const record = fakeRecord('33333333-3333-3333-3333-333333333333', 9);
89
+ const { data, storage } = fakeDeps(record);
90
+ const svc = new DefinitionService(data, storage);
91
+ const version = await svc.uploadVersion(CTX, record.guid, new Uint8Array([1]), 'gh', 'a.gh', SCHEMA);
92
+ expect(version.versionNumber).toBe(9);
93
+ });
94
+ });
95
+ //# sourceMappingURL=definition-service.test.js.map