cursedbelt-core 2.13.1 → 2.13.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 (76) hide show
  1. package/dist/core/analytics/range.d.ts +2 -2
  2. package/dist/core/analytics/range.js +2 -2
  3. package/dist/core/analytics/schedule.d.ts +1 -1
  4. package/dist/core/analytics/schedule.js +3 -3
  5. package/dist/core/duplicates/index.d.ts +2 -2
  6. package/dist/core/duplicates/index.js +1 -1
  7. package/dist/core/file-tree/fileTreeModel.d.ts +3 -3
  8. package/dist/core/file-tree/fileTreeModel.js +2 -2
  9. package/dist/core/folder-tree/actions.d.ts +1 -1
  10. package/dist/core/folder-tree/actions.js +1 -1
  11. package/dist/core/master-lock/kdf.js +1 -1
  12. package/dist/core/master-lock/presence.d.ts +1 -1
  13. package/dist/core/master-lock/presence.js +1 -1
  14. package/dist/core/master-lock/wire.d.ts +1 -1
  15. package/dist/core/media/mediaItem.js +1 -1
  16. package/dist/core/media/types.d.ts +4 -4
  17. package/dist/core/media/videoCuts.d.ts +1 -1
  18. package/dist/core/media/videoCuts.js +2 -2
  19. package/dist/core/navigation/index.js +1 -1
  20. package/dist/core/navigation/navigationModel.d.ts +1 -1
  21. package/dist/core/navigation/navigationModel.js +1 -1
  22. package/dist/core/navigation/navigationSelectors.d.ts +2 -2
  23. package/dist/core/navigation/navigationSelectors.js +2 -2
  24. package/dist/core/navigation/searchCatalogue.d.ts +2 -2
  25. package/dist/core/navigation/searchCatalogue.js +4 -4
  26. package/dist/core/palette/fuzzyRank.d.ts +1 -1
  27. package/dist/core/palette/fuzzyRank.js +3 -3
  28. package/dist/core/tokens/registry.d.ts +1 -1
  29. package/dist/core/tokens/registry.js +1 -1
  30. package/dist/core/wire/__fixtures__/rowsMarshalWorker.js +2 -2
  31. package/dist/core/wire/index.d.ts +1 -1
  32. package/dist/core/wire/index.js +1 -1
  33. package/dist/core/wire/ingress.d.ts +4 -4
  34. package/dist/core/wire/ingress.js +12 -12
  35. package/dist/core/wire/marshalWorker.d.ts +1 -1
  36. package/dist/core/wire/marshalWorker.js +15 -15
  37. package/dist/core/wire/marshalling.d.ts +16 -16
  38. package/dist/core/wire/marshalling.js +13 -13
  39. package/dist/ctgrClient/convertRequest.js +21 -5
  40. package/dist/testing/bytes.d.ts +1 -1
  41. package/dist/testing/bytes.js +1 -1
  42. package/package.json +1 -1
  43. package/src/core/analytics/range.spec.ts +1 -1
  44. package/src/core/analytics/range.ts +2 -2
  45. package/src/core/analytics/schedule.spec.ts +3 -3
  46. package/src/core/analytics/schedule.ts +3 -3
  47. package/src/core/duplicates/index.ts +2 -2
  48. package/src/core/file-tree/fileTreeModel.ts +4 -4
  49. package/src/core/folder-tree/actions.ts +1 -1
  50. package/src/core/master-lock/kdf.ts +1 -1
  51. package/src/core/master-lock/presence.ts +1 -1
  52. package/src/core/master-lock/wire.ts +1 -1
  53. package/src/core/media/mediaItem.ts +1 -1
  54. package/src/core/media/types.ts +4 -4
  55. package/src/core/media/videoCuts.ts +2 -2
  56. package/src/core/navigation/index.ts +1 -1
  57. package/src/core/navigation/navigation.spec.ts +3 -3
  58. package/src/core/navigation/navigationModel.ts +2 -2
  59. package/src/core/navigation/navigationSelectors.ts +3 -3
  60. package/src/core/navigation/searchCatalogue.ts +4 -4
  61. package/src/core/palette/fuzzyRank.ts +3 -3
  62. package/src/core/tokens/registry.ts +2 -2
  63. package/src/core/wire/__fixtures__/rowsMarshalWorker.ts +2 -2
  64. package/src/core/wire/index.ts +1 -1
  65. package/src/core/wire/ingress.spec.ts +12 -12
  66. package/src/core/wire/ingress.ts +15 -15
  67. package/src/core/wire/marshalWorker.spec.ts +7 -7
  68. package/src/core/wire/marshalWorker.ts +15 -15
  69. package/src/core/wire/marshalling.spec.ts +3 -3
  70. package/src/core/wire/marshalling.ts +25 -25
  71. package/src/ctgrClient/convertRequest.ts +21 -4
  72. package/src/ctgrClient/ctgrClient.spec.ts +32 -0
  73. package/src/namedSubpathsResolve.spec.ts +1 -1
  74. package/src/publishShape.spec.ts +1 -1
  75. package/src/shippedFilesAreTracked.spec.ts +2 -2
  76. package/src/testing/bytes.ts +1 -1
@@ -57,7 +57,7 @@ export async function convertIfOnlyGETRequests(
57
57
  throw new Error(`ctgr: "${method}" is not a tunnelable method`);
58
58
  }
59
59
 
60
- const body: unknown = form ? await formDataToBody(form) : bodyFromInit(init.body);
60
+ const body: unknown = form ? await formDataToBody(form) : bodyFromInit(init.body, upper, url);
61
61
  const getInit = toGetInit(init);
62
62
  const { chunks } = await encodeRequest(upper, url, body);
63
63
  return { requests: chunks.map((chunk) => ({ url: chunk.url, init: getInit })) };
@@ -89,9 +89,26 @@ async function formDataToBody(formData: FormData): Promise<Record<string, unknow
89
89
  return { __files: files, ...fields };
90
90
  }
91
91
 
92
- /** The request body as a JS value: a JSON string is parsed; anything else passes through. */
93
- function bodyFromInit(body: BodyInit | null | undefined): unknown {
94
- if (typeof body !== 'string') return body ?? undefined;
92
+ /**
93
+ * The request body as a JS value: a JSON string is parsed, a plain string rides as itself.
94
+ *
95
+ * 🔴 Anything else is REFUSED. The tunnel carries a JSON value or a form's files; a `File`, a
96
+ * `Blob`, bytes, a stream or `URLSearchParams` handed through here was JSON-serialized as `{}` —
97
+ * desk's import preview posted a 3.4 MB workbook and the server described "a JSON file" with `{}`
98
+ * as its sample (forge task 3443). A loud error at the call is the bug; a silent `{}` is data loss.
99
+ * Send an upload as `FormData`, which is carried whole.
100
+ */
101
+ function bodyFromInit(body: BodyInit | null | undefined, method: string, url: string): unknown {
102
+ if (body === null || body === undefined) return undefined;
103
+ if (typeof body !== 'string') {
104
+ // A plain object or array a caller cast to `BodyInit` serializes as itself — carried as before.
105
+ const proto = Object.getPrototypeOf(body);
106
+ if (Array.isArray(body) || proto === Object.prototype || proto === null) return body;
107
+ const shape = Object.prototype.toString.call(body).slice(8, -1);
108
+ throw new Error(
109
+ `ctgr: a ${shape} body cannot ride the GET-only tunnel (${method} ${url}) — send it as FormData or a JSON string`,
110
+ );
111
+ }
95
112
  try {
96
113
  return JSON.parse(body);
97
114
  } catch {
@@ -132,4 +132,36 @@ describe('convertIfOnlyGETRequests', () => {
132
132
  expect(decoded.files?.[0]).toMatchObject({ originalname: 'p.bin', mimetype: 'image/png' });
133
133
  expect(decoded.body).toEqual({ name: 'pic' });
134
134
  });
135
+
136
+ // forge task 3443: desk posted a workbook as a raw `File`, and it arrived as `{}`.
137
+ test('mode ON: a body the tunnel cannot carry is refused, never sent as {}', async () => {
138
+ setCtgrMode(true);
139
+ const bodies: BodyInit[] = [
140
+ new File([new Uint8Array([0x50, 0x4b, 3, 4])], 'budget.xlsx'),
141
+ new Blob(['x']),
142
+ new Uint8Array([1, 2, 3]),
143
+ new Uint8Array([1, 2, 3]).buffer,
144
+ new URLSearchParams({ a: '1' }),
145
+ ];
146
+ for (const body of bodies) {
147
+ await expect(convertIfOnlyGETRequests('/api/v1/files/preview', { method: 'POST', body })).rejects.toThrow(
148
+ /cannot ride the GET-only tunnel \(POST \/api\/v1\/files\/preview\) — send it as FormData/,
149
+ );
150
+ }
151
+ });
152
+
153
+ test('mode ON: a plain object a caller cast to BodyInit still rides as JSON', async () => {
154
+ setCtgrMode(true);
155
+ const body = { name: 'Budget' } as unknown as BodyInit;
156
+ const { requests } = await convertIfOnlyGETRequests('/api/v1/files', { method: 'POST', body });
157
+ const decoded = await decodeRequest(new URL(requests[0].url, 'http://x').searchParams);
158
+ if (decoded === 'partial') throw new Error('unreachable');
159
+ expect(decoded.body).toEqual({ name: 'Budget' });
160
+ });
161
+
162
+ test('mode OFF: any body passes through untouched — only the tunnel is particular', async () => {
163
+ const body = new Blob(['x']);
164
+ const { requests } = await convertIfOnlyGETRequests('/api/v1/files/preview', { method: 'POST', body });
165
+ expect(requests[0].init.body).toBe(body);
166
+ });
135
167
  });
@@ -103,7 +103,7 @@ describe('subpaths named in this repo resolve', () => {
103
103
  });
104
104
 
105
105
  it('knows what the manifest exports', () => {
106
- // If `exports` is ever read wrong, every offence below silently becomes one.
106
+ // If `exports` is ever read wrong, every offense below silently becomes one.
107
107
  expect(exported.has('')).toBe(true); // the "." root entry
108
108
  expect(exported.has('/master-lock')).toBe(true);
109
109
  expect(exported.has('/react')).toBe(false); // the react tier stayed in `cursedbelt` at the 2026-09-15 split
@@ -65,7 +65,7 @@ describe('publish shape', () => {
65
65
  // npm rejects a "./"-prefixed bin value, calls the NAME invalid, and drops
66
66
  // the entry — with a warning, not an error. The package publishes fine and
67
67
  // installs with no CLI at all. Invisible under a file: link, where the bin
68
- // is linked from the source manifest before npm ever normalises it.
68
+ // is linked from the source manifest before npm ever normalizes it.
69
69
  const bin = (pkg as { bin?: Record<string, string> }).bin ?? {};
70
70
  const prefixed = Object.entries(bin).filter(([, v]) => v.startsWith('./'));
71
71
  expect(
@@ -53,8 +53,8 @@ describe('every file inside the published surface is tracked', () => {
53
53
  });
54
54
 
55
55
  test('no untracked file sits inside a directory `files` ships', async () => {
56
- // --others = untracked; --exclude-standard honours .gitignore, so deliberately
57
- // ignored paths (.agent.noindex, node_modules) are not offences.
56
+ // --others = untracked; --exclude-standard honors .gitignore, so deliberately
57
+ // ignored paths (.agent.noindex, node_modules) are not offenses.
58
58
  const { ok, out } = await git('ls-files', '--others', '--exclude-standard', '--', ...shippedDirs());
59
59
  expect(ok, 'git ls-files failed').toBe(true);
60
60
  const untracked = out === '' ? [] : out.split('\n');
@@ -32,7 +32,7 @@ export const asBody = (bytes: Uint8Array): Uint8Array<ArrayBuffer> => bytes as U
32
32
  export const bytesResponse = (bytes: Uint8Array, init?: ResponseInit): Response =>
33
33
  new Response(asBody(bytes), init);
34
34
 
35
- /** A readable stream of `bytes` — what the media catalogue's ingest paths take. */
35
+ /** A readable stream of `bytes` — what the media catalog's ingest paths take. */
36
36
  export const bytesStream = (bytes: Uint8Array | string): ReadableStream<Uint8Array> =>
37
37
  new Response(typeof bytes === 'string' ? asBody(new TextEncoder().encode(bytes)) : asBody(bytes))
38
38
  .body as ReadableStream<Uint8Array>;