toiljs 0.0.63 → 0.0.65

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.
@@ -26,7 +26,7 @@ import {
26
26
  } from './types.js';
27
27
 
28
28
  function validReplication(value: number): boolean {
29
- return value >= 0 && value <= 5;
29
+ return value === 0 || value === 1 || value === 2 || value === 5;
30
30
  }
31
31
 
32
32
  function validPlacement(value: number): boolean {
@@ -51,6 +51,15 @@ function dbKindForHttpMethod(method: string): DbFunctionKind {
51
51
  }
52
52
  }
53
53
 
54
+ export function dbFunctionKindForRequest(
55
+ routes: readonly RouteKindEntry[],
56
+ method: string,
57
+ path: string,
58
+ ): DbFunctionKind {
59
+ const routeKind = routeKindForRequest(routes, method, path);
60
+ return routeKind === DbFunctionKind.Query ? DbFunctionKind.Query : dbKindForHttpMethod(method);
61
+ }
62
+
54
63
  /** The shaped outcome of one guest dispatch. */
55
64
  export interface WasmDispatchResult {
56
65
  readonly status: number;
@@ -186,20 +195,9 @@ export class WasmServerModule {
186
195
  const ref: MemoryRef = { memory: null };
187
196
  const state = freshDispatchState();
188
197
  state.clientIp = req.clientIp ?? '';
189
- // Enforce per-route DB-kind gating ONLY when the guest declares its route
190
- // kinds (the `toildb.route_kinds` custom section). A guest built with a
191
- // toolchain that does not emit that section leaves `routeKinds` empty;
192
- // inferring a kind from the HTTP method and enforcing it would wrongly
193
- // reject legitimate bounded reads (e.g. a GET that reads `events.latest`,
194
- // a scan-class op denied in `Query`). With no declarations we keep the
195
- // unenforced default (`Job`, see `freshDbState`).
196
- const routeKind = routeKindForRequest(this.routeKinds, req.method, req.path);
197
- state.db.functionKind =
198
- this.routeKinds.length === 0
199
- ? DbFunctionKind.Job
200
- : routeKind === DbFunctionKind.Query
201
- ? DbFunctionKind.Query
202
- : dbKindForHttpMethod(req.method);
198
+ // Match the edge DB gate: the HTTP method is the baseline authority,
199
+ // and `toildb.route_kinds` can only tighten a mutating route to query.
200
+ state.db.functionKind = dbFunctionKindForRequest(this.routeKinds, req.method, req.path);
203
201
  const instance = new WebAssembly.Instance(this.module, buildHostImports(ref, state));
204
202
  const exports = instance.exports as unknown as HandleExports;
205
203
  ref.memory = exports.memory;
@@ -16,6 +16,7 @@ import {
16
16
  setDbCatalog,
17
17
  } from '../src/devserver/db/index.js';
18
18
  import { parseRouteKinds, routeKindForRequest } from '../src/devserver/db/routeKinds.js';
19
+ import { dbFunctionKindForRequest } from '../src/devserver/runtime/module.js';
19
20
  import type { MemoryRef } from '../src/devserver/runtime/host.js';
20
21
 
21
22
  const DEFAULT_CATALOG = {
@@ -91,7 +92,7 @@ function routeKindsSection(routes: readonly (readonly [number, number, string])[
91
92
  return Buffer.concat(chunks);
92
93
  }
93
94
 
94
- function catalogSectionV2(fillMaxWaitMs: number, fillAllowStale: number): Buffer {
95
+ function catalogSectionV2(fillMaxWaitMs: number, fillAllowStale: number, replication = 5): Buffer {
95
96
  const chunks: Buffer[] = [];
96
97
  const u8 = (v: number) => chunks.push(Buffer.from([v & 0xff]));
97
98
  const u16 = (v: number) => {
@@ -121,7 +122,7 @@ function catalogSectionV2(fillMaxWaitMs: number, fillAllowStale: number): Buffer
121
122
  u32(0xaaaa);
122
123
  u32(0x1234);
123
124
  u32(0); // generation
124
- u8(5); // replication = localOnly, accepted by the backend ABI
125
+ u8(replication); // replication
125
126
  u8(0); // placement = hashKey
126
127
  u32(fillMaxWaitMs);
127
128
  u8(fillAllowStale);
@@ -151,6 +152,31 @@ describe('toildb dev emulator (record family)', () => {
151
152
  expect(routeKindForRequest(routes, 'POST', '/api/items')).toBeNull();
152
153
  });
153
154
 
155
+ it('matches the edge method clamp when route-kind metadata is absent', () => {
156
+ expect(dbFunctionKindForRequest([], 'GET', '/api/users')).toBe(DbFunctionKind.Query);
157
+ expect(dbFunctionKindForRequest([], 'HEAD', '/api/users')).toBe(DbFunctionKind.Query);
158
+ expect(dbFunctionKindForRequest([], 'OPTIONS', '/api/users')).toBe(DbFunctionKind.Query);
159
+ expect(dbFunctionKindForRequest([], 'POST', '/api/users')).toBe(DbFunctionKind.Action);
160
+ expect(dbFunctionKindForRequest([], 'PUT', '/api/users')).toBe(DbFunctionKind.Action);
161
+ expect(dbFunctionKindForRequest([], 'PATCH', '/api/users')).toBe(DbFunctionKind.Action);
162
+ expect(dbFunctionKindForRequest([], 'DELETE', '/api/users')).toBe(DbFunctionKind.Action);
163
+ });
164
+
165
+ it('uses route-kind metadata only to tighten mutating routes to query', () => {
166
+ const wasm = wasmWithSection(
167
+ 'toildb.route_kinds',
168
+ routeKindsSection([
169
+ [1, 0, '/api/search'],
170
+ [0, 1, '/api/users'],
171
+ ]),
172
+ );
173
+ const routes = parseRouteKinds(wasm);
174
+
175
+ expect(dbFunctionKindForRequest(routes, 'POST', '/api/search')).toBe(DbFunctionKind.Query);
176
+ expect(dbFunctionKindForRequest(routes, 'POST', '/api/write')).toBe(DbFunctionKind.Action);
177
+ expect(dbFunctionKindForRequest(routes, 'GET', '/api/users')).toBe(DbFunctionKind.Query);
178
+ });
179
+
154
180
  it('ignores malformed route-kind metadata like the edge method clamp fallback', () => {
155
181
  expect(
156
182
  parseRouteKinds(wasmWithSection('toildb.route_kinds', Buffer.from([1, 0, 1, 0, 1]))),
@@ -205,6 +231,16 @@ describe('toildb dev emulator (record family)', () => {
205
231
  });
206
232
  });
207
233
 
234
+ it('rejects replication policies that require catalog v3 metadata', () => {
235
+ for (const replication of [3, 4]) {
236
+ setDbCatalog(wasmWithSection('toildb.catalog', catalogSectionV2(7, 0, replication)));
237
+ const { imports, buf } = setupRaw();
238
+ const [p, l] = put(buf, 0, 'App/users');
239
+
240
+ expect(imports['data.resolve_collection'](p, l, 16)).toBe(-1070);
241
+ }
242
+ });
243
+
208
244
  it('rejects malformed catalog v2 fill policy', () => {
209
245
  setDbCatalog(wasmWithSection('toildb.catalog', catalogSectionV2(7, 2)));
210
246
  const { imports, buf } = setupRaw();