@brimble/sandbox 0.1.0 → 0.1.2

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 (61) hide show
  1. package/README.md +9 -5
  2. package/dist/package.json +10 -3
  3. package/dist/src/client.d.ts +3 -3
  4. package/dist/src/client.js +3 -3
  5. package/dist/src/index.d.ts +3 -3
  6. package/dist/src/index.js +2 -2
  7. package/dist/src/resources/files.d.ts +6 -1
  8. package/dist/src/resources/files.js +36 -0
  9. package/dist/src/resources/sandbox-handle.d.ts +6 -1
  10. package/dist/src/resources/sandbox-handle.js +8 -0
  11. package/dist/src/resources/scoped-sandbox.d.ts +3 -1
  12. package/dist/src/resources/scoped-sandbox.js +4 -0
  13. package/dist/src/types/files.d.ts +16 -0
  14. package/dist/src/types/index.d.ts +1 -1
  15. package/package.json +7 -3
  16. package/CODEX.md +0 -188
  17. package/PLAN.md +0 -364
  18. package/src/client.ts +0 -61
  19. package/src/constants.ts +0 -17
  20. package/src/enums/code-language.ts +0 -4
  21. package/src/enums/destroy-reason.ts +0 -8
  22. package/src/enums/destroy-timeout.ts +0 -8
  23. package/src/enums/index.ts +0 -7
  24. package/src/enums/sandbox-status.ts +0 -9
  25. package/src/enums/snapshot-mode.ts +0 -4
  26. package/src/enums/snapshot-status.ts +0 -5
  27. package/src/enums/volume-type.ts +0 -3
  28. package/src/errors/index.ts +0 -2
  29. package/src/errors/sandbox-api-error.ts +0 -54
  30. package/src/index.ts +0 -71
  31. package/src/resources/exec.ts +0 -56
  32. package/src/resources/files.ts +0 -46
  33. package/src/resources/index.ts +0 -8
  34. package/src/resources/path.ts +0 -16
  35. package/src/resources/sandbox-handle.ts +0 -215
  36. package/src/resources/sandboxes.ts +0 -297
  37. package/src/resources/scoped-sandbox.ts +0 -65
  38. package/src/resources/snapshots.ts +0 -104
  39. package/src/resources/stats.ts +0 -30
  40. package/src/resources/volumes.ts +0 -95
  41. package/src/transport/auth.ts +0 -4
  42. package/src/transport/http.ts +0 -501
  43. package/src/transport/pagination.ts +0 -10
  44. package/src/types/exec.ts +0 -42
  45. package/src/types/files.ts +0 -1
  46. package/src/types/index.ts +0 -23
  47. package/src/types/pagination.ts +0 -16
  48. package/src/types/region.ts +0 -19
  49. package/src/types/sandbox.ts +0 -103
  50. package/src/types/snapshot.ts +0 -17
  51. package/src/types/stats.ts +0 -35
  52. package/src/types/template.ts +0 -5
  53. package/src/types/volume.ts +0 -26
  54. package/test/integration/sandbox.integration.test.ts +0 -269
  55. package/test/unit/client.test.ts +0 -87
  56. package/test/unit/sandboxes.test.ts +0 -69
  57. package/test/unit/transport.test.ts +0 -126
  58. package/test/unit/volumes.test.ts +0 -122
  59. package/tsconfig.json +0 -16
  60. package/vitest.config.ts +0 -12
  61. package/vitest.integration.config.ts +0 -15
package/README.md CHANGED
@@ -24,9 +24,9 @@ BRIMBLE_SANDBOX_KEY=your_key_here npm run test:all
24
24
  ## Quickstart
25
25
 
26
26
  ```ts
27
- import { CodeLanguage, SandboxClient } from '@brimble/sandbox';
27
+ import { CodeLanguage, Sandbox } from '@brimble/sandbox';
28
28
 
29
- const client = new SandboxClient();
29
+ const client = new Sandbox();
30
30
 
31
31
  const sandbox = await client.sandboxes.createReady({
32
32
  template: 'node-22',
@@ -37,6 +37,10 @@ const sandbox = await client.sandboxes.createReady({
37
37
  await sandbox.exec({ cmd: 'node -v' });
38
38
 
39
39
  await sandbox.putFile('tmp/notes.txt', Buffer.from('hello sandbox'));
40
+ await sandbox.putFiles([
41
+ { path: '/tmp/hello.txt', body: 'hello from batch' },
42
+ { path: '/tmp/config.json', body: JSON.stringify({ mode: 'dev' }) },
43
+ ]);
40
44
  const stream = await sandbox.getFile('tmp/notes.txt');
41
45
 
42
46
  await sandbox.runCode({
@@ -98,7 +102,7 @@ Use `client.sandboxes.create({ ..., volumeId })` or `client.sandboxes.withVolume
98
102
  ## Retry, timeouts, and idempotency
99
103
 
100
104
  ```ts
101
- const client = new SandboxClient({
105
+ const client = new Sandbox({
102
106
  timeoutMs: 30_000,
103
107
  retry: {
104
108
  maxAttempts: 3,
@@ -120,9 +124,9 @@ If `region` is omitted, the SDK resolves the first available sandbox region auto
120
124
  - `client.sandboxes`
121
125
  - `create`, `createReady`, `withVolume`, `list`, `iterate`, `get`, `getReady`, `listRegions`, `listTemplates`, `getTemplate`, `destroy`, `pause`, `resume`, `quickstartNode`, `quickstartPython`, `use`
122
126
  - `sandbox` handle (returned from `create/get/list`)
123
- - `waitUntilReady`, `refresh`, `destroy`, `pause`, `resume`, `exec`, `runCode`, `putFile`, `getFile`, `stats`, `createSnapshot`, `listSnapshots`, `snapshots.create`, `snapshots.list`
127
+ - `waitUntilReady`, `refresh`, `destroy`, `pause`, `resume`, `exec`, `runCode`, `putFile`, `putFiles`, `getFile`, `stats`, `createSnapshot`, `listSnapshots`, `snapshots.create`, `snapshots.list`
124
128
  - `client.sandboxes.use(id)`
125
- - `exec`, `runCode`, `putFile`, `getFile`, `stats`, `createSnapshot`, `listSnapshots`
129
+ - `exec`, `runCode`, `putFile`, `putFiles`, `getFile`, `stats`, `createSnapshot`, `listSnapshots`
126
130
  - `client.snapshots`
127
131
  - `listAll`, `iterateAll`, `delete`
128
132
  - `client.volumes`
package/dist/package.json CHANGED
@@ -1,9 +1,16 @@
1
1
  {
2
2
  "name": "@brimble/sandbox",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "TypeScript SDK for the Brimble Sandbox API",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
5
+ "main": "dist/src/index.js",
6
+ "types": "dist/src/index.d.ts",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md"
13
+ ],
7
14
  "scripts": {
8
15
  "build": "rm -rf dist && tsc -p .",
9
16
  "test": "vitest run --config vitest.config.ts",
@@ -1,13 +1,13 @@
1
1
  import { SandboxesResource, SnapshotsResource, VolumesResource } from './resources';
2
2
  import type { RetryOptions } from './transport/http';
3
- export type SandboxClientOptions = {
3
+ export type SandboxOptions = {
4
4
  apiKey?: string;
5
5
  baseUrl?: string;
6
6
  timeoutMs?: number;
7
7
  retry?: RetryOptions;
8
8
  fetchImpl?: typeof fetch;
9
9
  };
10
- export declare class SandboxClient {
10
+ export declare class Sandbox {
11
11
  /** Access sandbox lifecycle and per-sandbox scoped operations. */
12
12
  readonly sandboxes: SandboxesResource;
13
13
  /** Access account-level snapshot operations. */
@@ -19,5 +19,5 @@ export declare class SandboxClient {
19
19
  * Creates a client instance for the Brimble Sandbox API.
20
20
  * Pass `apiKey` directly or set `BRIMBLE_SANDBOX_KEY` in your environment.
21
21
  */
22
- constructor(options?: SandboxClientOptions);
22
+ constructor(options?: SandboxOptions);
23
23
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SandboxClient = void 0;
3
+ exports.Sandbox = void 0;
4
4
  const constants_1 = require("./constants");
5
5
  const resources_1 = require("./resources");
6
6
  const http_1 = require("./transport/http");
@@ -18,7 +18,7 @@ function resolveApiKey(options) {
18
18
  }
19
19
  throw new Error(`Sandbox API key is required. Pass "apiKey" explicitly or set ${constants_1.SANDBOX_API_KEY_ENV_NAME} in your environment.`);
20
20
  }
21
- class SandboxClient {
21
+ class Sandbox {
22
22
  /** Access sandbox lifecycle and per-sandbox scoped operations. */
23
23
  sandboxes;
24
24
  /** Access account-level snapshot operations. */
@@ -43,4 +43,4 @@ class SandboxClient {
43
43
  this.volumes = new resources_1.VolumesResource(this.transport);
44
44
  }
45
45
  }
46
- exports.SandboxClient = SandboxClient;
46
+ exports.Sandbox = Sandbox;
@@ -1,10 +1,10 @@
1
1
  export { DEFAULT_BASE_URL, DEFAULT_PAGE, DEFAULT_PAGE_LIMIT, DEFAULT_RETRY_BASE_DELAY_MS, DEFAULT_RETRY_MAX_ATTEMPTS, DEFAULT_RETRY_MAX_DELAY_MS, DEFAULT_RETRY_STATUSES, DEFAULT_TIMEOUT_MS, MAX_PAGE_LIMIT, SANDBOX_API_KEY_ENV_NAME, } from './constants';
2
- export { SandboxClient } from './client';
3
- export type { SandboxClientOptions } from './client';
2
+ export { Sandbox } from './client';
3
+ export type { SandboxOptions } from './client';
4
4
  export { AuthError, NotFoundError, RateLimitError, SandboxApiError, ValidationError } from './errors';
5
5
  export type { SandboxApiErrorArgs } from './errors';
6
6
  export { CodeLanguage, DestroyReason, DestroyTimeout, SandboxStatus, SnapshotMode, SnapshotStatus, VolumeType, } from './enums';
7
7
  export { ExecResource, FilesResource, SandboxHandle, SandboxesResource, ScopedSandboxResource, SnapshotScopeResource, SnapshotsResource, StatsResource, VolumesResource, } from './resources';
8
- export type { AckMessage, CodeInput, CreateSandboxInput, CreateSandboxResult, CreateSnapshotInput, CreateVolumeInput, ExecInput, ExecResult, ExecStreamFrame, FileUploadBody, Paginated, Pagination, RegionSummary, SandboxRegion, SandboxRegionsResult, Sandbox, SandboxSpecs, Snapshot, Stats, StatsAverageNetwork, StatsAverageNumeric, StatsQuery, StatsTimelinePoint, TeamScopedPagination, WaitUntilReadyOptions, Volume, } from './types';
8
+ export type { AckMessage, CodeInput, CreateSandboxInput, CreateSandboxResult, CreateSnapshotInput, CreateVolumeInput, ExecInput, ExecResult, ExecStreamFrame, FileUploadBody, Paginated, Pagination, RegionSummary, SandboxRegion, SandboxRegionsResult, Sandbox as SandboxData, SandboxSpecs, Snapshot, Stats, StatsAverageNetwork, StatsAverageNumeric, StatsQuery, StatsTimelinePoint, TeamScopedPagination, WaitUntilReadyOptions, Volume, } from './types';
9
9
  export type { RequestOptions } from './transport/http';
10
10
  export type { RetryOptions } from './transport/http';
package/dist/src/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.VolumesResource = exports.StatsResource = exports.SnapshotsResource = exports.SnapshotScopeResource = exports.ScopedSandboxResource = exports.SandboxesResource = exports.SandboxHandle = exports.FilesResource = exports.ExecResource = exports.VolumeType = exports.SnapshotStatus = exports.SnapshotMode = exports.SandboxStatus = exports.DestroyTimeout = exports.DestroyReason = exports.CodeLanguage = exports.ValidationError = exports.SandboxApiError = exports.RateLimitError = exports.NotFoundError = exports.AuthError = exports.SandboxClient = exports.SANDBOX_API_KEY_ENV_NAME = exports.MAX_PAGE_LIMIT = exports.DEFAULT_TIMEOUT_MS = exports.DEFAULT_RETRY_STATUSES = exports.DEFAULT_RETRY_MAX_DELAY_MS = exports.DEFAULT_RETRY_MAX_ATTEMPTS = exports.DEFAULT_RETRY_BASE_DELAY_MS = exports.DEFAULT_PAGE_LIMIT = exports.DEFAULT_PAGE = exports.DEFAULT_BASE_URL = void 0;
3
+ exports.VolumesResource = exports.StatsResource = exports.SnapshotsResource = exports.SnapshotScopeResource = exports.ScopedSandboxResource = exports.SandboxesResource = exports.SandboxHandle = exports.FilesResource = exports.ExecResource = exports.VolumeType = exports.SnapshotStatus = exports.SnapshotMode = exports.SandboxStatus = exports.DestroyTimeout = exports.DestroyReason = exports.CodeLanguage = exports.ValidationError = exports.SandboxApiError = exports.RateLimitError = exports.NotFoundError = exports.AuthError = exports.Sandbox = exports.SANDBOX_API_KEY_ENV_NAME = exports.MAX_PAGE_LIMIT = exports.DEFAULT_TIMEOUT_MS = exports.DEFAULT_RETRY_STATUSES = exports.DEFAULT_RETRY_MAX_DELAY_MS = exports.DEFAULT_RETRY_MAX_ATTEMPTS = exports.DEFAULT_RETRY_BASE_DELAY_MS = exports.DEFAULT_PAGE_LIMIT = exports.DEFAULT_PAGE = exports.DEFAULT_BASE_URL = void 0;
4
4
  var constants_1 = require("./constants");
5
5
  Object.defineProperty(exports, "DEFAULT_BASE_URL", { enumerable: true, get: function () { return constants_1.DEFAULT_BASE_URL; } });
6
6
  Object.defineProperty(exports, "DEFAULT_PAGE", { enumerable: true, get: function () { return constants_1.DEFAULT_PAGE; } });
@@ -13,7 +13,7 @@ Object.defineProperty(exports, "DEFAULT_TIMEOUT_MS", { enumerable: true, get: fu
13
13
  Object.defineProperty(exports, "MAX_PAGE_LIMIT", { enumerable: true, get: function () { return constants_1.MAX_PAGE_LIMIT; } });
14
14
  Object.defineProperty(exports, "SANDBOX_API_KEY_ENV_NAME", { enumerable: true, get: function () { return constants_1.SANDBOX_API_KEY_ENV_NAME; } });
15
15
  var client_1 = require("./client");
16
- Object.defineProperty(exports, "SandboxClient", { enumerable: true, get: function () { return client_1.SandboxClient; } });
16
+ Object.defineProperty(exports, "Sandbox", { enumerable: true, get: function () { return client_1.Sandbox; } });
17
17
  var errors_1 = require("./errors");
18
18
  Object.defineProperty(exports, "AuthError", { enumerable: true, get: function () { return errors_1.AuthError; } });
19
19
  Object.defineProperty(exports, "NotFoundError", { enumerable: true, get: function () { return errors_1.NotFoundError; } });
@@ -1,4 +1,4 @@
1
- import type { FileUploadBody } from '../types';
1
+ import type { BatchFileUploadInput, BatchFileUploadResponse, FileUploadBody } from '../types';
2
2
  import type { RequestOptions } from '../transport/http';
3
3
  import { HttpTransport } from '../transport/http';
4
4
  export declare class FilesResource {
@@ -13,4 +13,9 @@ export declare class FilesResource {
13
13
  put(path: string, body: FileUploadBody, options?: RequestOptions): Promise<void>;
14
14
  /** Download a file from the sandbox as a stream. */
15
15
  get(path: string, options?: RequestOptions): Promise<ReadableStream<Uint8Array>>;
16
+ /**
17
+ * Upload multiple files in one request using base64 payloads.
18
+ * Best for small/medium known files (max 100 per call).
19
+ */
20
+ putFiles(files: BatchFileUploadInput[], options?: RequestOptions): Promise<BatchFileUploadResponse>;
16
21
  }
@@ -37,5 +37,41 @@ class FilesResource {
37
37
  ...options,
38
38
  });
39
39
  }
40
+ /**
41
+ * Upload multiple files in one request using base64 payloads.
42
+ * Best for small/medium known files (max 100 per call).
43
+ */
44
+ async putFiles(files, options) {
45
+ if (files.length === 0) {
46
+ throw new Error('putFiles requires at least one file.');
47
+ }
48
+ if (files.length > 100) {
49
+ throw new Error('putFiles supports at most 100 files per request.');
50
+ }
51
+ const response = await this.transport.requestJson({
52
+ endpoint: `/sandboxes/${this.sandboxId}/files/batch`,
53
+ method: 'POST',
54
+ body: {
55
+ files: files.map((file) => ({
56
+ path: normalizeBatchPath(file.path),
57
+ content_base64: encodeBatchBody(file.body),
58
+ })),
59
+ },
60
+ ...options,
61
+ });
62
+ if (!response) {
63
+ throw new Error('Batch upload returned an empty response.');
64
+ }
65
+ return response;
66
+ }
40
67
  }
41
68
  exports.FilesResource = FilesResource;
69
+ function normalizeBatchPath(path) {
70
+ return path.startsWith('/') ? path : `/${path}`;
71
+ }
72
+ function encodeBatchBody(body) {
73
+ if (typeof body === 'string') {
74
+ return Buffer.from(body, 'utf-8').toString('base64');
75
+ }
76
+ return Buffer.from(body).toString('base64');
77
+ }
@@ -1,6 +1,6 @@
1
1
  import { SandboxStatus } from '../enums';
2
2
  import type { RequestOptions } from '../transport/http';
3
- import type { AckMessage, CodeInput, CreateSandboxResult, CreateSnapshotInput, ExecInput, ExecResult, FileUploadBody, Paginated, Pagination, Sandbox, SandboxRuntimeOptions, Snapshot, Stats, StatsQuery, WaitUntilReadyOptions } from '../types';
3
+ import type { AckMessage, BatchFileUploadInput, BatchFileUploadResponse, CodeInput, CreateSandboxResult, CreateSnapshotInput, ExecInput, ExecResult, FileUploadBody, Paginated, Pagination, Sandbox, SandboxRuntimeOptions, Snapshot, Stats, StatsQuery, WaitUntilReadyOptions } from '../types';
4
4
  import type { SandboxesResource } from './sandboxes';
5
5
  export declare class SandboxHandle {
6
6
  private readonly sandboxes;
@@ -58,6 +58,11 @@ export declare class SandboxHandle {
58
58
  * By default this throws when not ready; set `waitUntilReady` to auto-wait.
59
59
  */
60
60
  getFile(path: string, options?: SandboxRuntimeOptions): Promise<ReadableStream<Uint8Array>>;
61
+ /**
62
+ * Upload multiple files in one call.
63
+ * By default this throws when not ready; set `waitUntilReady` to auto-wait.
64
+ */
65
+ putFiles(files: BatchFileUploadInput[], options?: SandboxRuntimeOptions): Promise<BatchFileUploadResponse>;
61
66
  /**
62
67
  * Fetch usage stats for this sandbox.
63
68
  * By default this throws when not ready; set `waitUntilReady` to auto-wait.
@@ -104,6 +104,14 @@ class SandboxHandle {
104
104
  await this.ensureReady(options.waitUntilReady);
105
105
  return this.scope.getFile(path, options);
106
106
  }
107
+ /**
108
+ * Upload multiple files in one call.
109
+ * By default this throws when not ready; set `waitUntilReady` to auto-wait.
110
+ */
111
+ async putFiles(files, options = {}) {
112
+ await this.ensureReady(options.waitUntilReady);
113
+ return this.scope.putFiles(files, options);
114
+ }
107
115
  /**
108
116
  * Fetch usage stats for this sandbox.
109
117
  * By default this throws when not ready; set `waitUntilReady` to auto-wait.
@@ -3,7 +3,7 @@ import { FilesResource } from './files';
3
3
  import { SnapshotScopeResource } from './snapshots';
4
4
  import { StatsResource } from './stats';
5
5
  import { HttpTransport } from '../transport/http';
6
- import type { CodeInput, CreateSnapshotInput, ExecInput, ExecResult, FileUploadBody, Paginated, Pagination, Snapshot, Stats, StatsQuery } from '../types';
6
+ import type { BatchFileUploadInput, BatchFileUploadResponse, CodeInput, CreateSnapshotInput, ExecInput, ExecResult, FileUploadBody, Paginated, Pagination, Snapshot, Stats, StatsQuery } from '../types';
7
7
  import type { RequestOptions } from '../transport/http';
8
8
  export declare class ScopedSandboxResource {
9
9
  /** Lower-level exec/code runner resource. */
@@ -30,6 +30,8 @@ export declare class ScopedSandboxResource {
30
30
  putFile(path: string, body: FileUploadBody, options?: RequestOptions): Promise<void>;
31
31
  /** Download file bytes from this sandbox as a stream. */
32
32
  getFile(path: string, options?: RequestOptions): Promise<ReadableStream<Uint8Array>>;
33
+ /** Upload multiple files to this sandbox in one request. */
34
+ putFiles(files: BatchFileUploadInput[], options?: RequestOptions): Promise<BatchFileUploadResponse>;
33
35
  /** Fetch CPU, memory, and network stats for this sandbox. */
34
36
  stats(query?: StatsQuery, options?: RequestOptions): Promise<Stats>;
35
37
  /** Create a snapshot for this sandbox. */
@@ -35,6 +35,10 @@ class ScopedSandboxResource {
35
35
  getFile(path, options) {
36
36
  return this.files.get(path, options);
37
37
  }
38
+ /** Upload multiple files to this sandbox in one request. */
39
+ putFiles(files, options) {
40
+ return this.files.putFiles(files, options);
41
+ }
38
42
  /** Fetch CPU, memory, and network stats for this sandbox. */
39
43
  stats(query = {}, options) {
40
44
  return this.statsResource.stats(query, options);
@@ -1 +1,17 @@
1
1
  export type FileUploadBody = ReadableStream<Uint8Array> | Buffer | Uint8Array;
2
+ export type BatchFileUploadBody = Buffer | Uint8Array | string;
3
+ export type BatchFileUploadInput = {
4
+ path: string;
5
+ body: BatchFileUploadBody;
6
+ };
7
+ export type BatchFileUploadResult = {
8
+ path: string;
9
+ bytes: number;
10
+ success: boolean;
11
+ error?: string;
12
+ };
13
+ export type BatchFileUploadResponse = {
14
+ uploaded: number;
15
+ failed: number;
16
+ results: BatchFileUploadResult[];
17
+ };
@@ -1,5 +1,5 @@
1
1
  export type { CodeInput, ExecInput, ExecResult, ExecStreamFrame } from './exec';
2
- export type { FileUploadBody } from './files';
2
+ export type { BatchFileUploadBody, BatchFileUploadInput, BatchFileUploadResponse, BatchFileUploadResult, FileUploadBody } from './files';
3
3
  export type { Paginated, Pagination, TeamScopedPagination } from './pagination';
4
4
  export type { RegionSummary, SandboxRegion, SandboxRegionsResult } from './region';
5
5
  export type { AckMessage, CreateSandboxInput, CreateSandboxRequest, CreateSandboxResult, CreateSandboxWithVolumeInput, CreateSandboxWithVolumeResult, Sandbox, SandboxReadyRequestOptions, SandboxRegionInput, SandboxRuntimeOptions, SandboxSpecs, WaitPreference, WaitUntilReadyOptions, } from './sandbox';
package/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "@brimble/sandbox",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "TypeScript SDK for the Brimble Sandbox API",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
5
+ "main": "dist/src/index.js",
6
+ "types": "dist/src/index.d.ts",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
10
+ "files": [
11
+ "dist",
12
+ "README.md"
13
+ ],
10
14
  "scripts": {
11
15
  "build": "rm -rf dist && tsc -p .",
12
16
  "test": "vitest run --config vitest.config.ts",
package/CODEX.md DELETED
@@ -1,188 +0,0 @@
1
- # Coding Guidelines
2
-
3
- These are the rules you follow when writing or modifying code in this codebase. Read them once, apply them always.
4
-
5
- ## 1. Separation of concerns
6
-
7
- Keep functions, types, enums, and classes in their own files (or at minimum, their own clearly-scoped sections). Do not mix concerns.
8
-
9
- - One responsibility per unit. A function does one thing; a class models one concept; a type describes one shape.
10
- - Types and enums live in dedicated files (`types.ts`, `enums.ts`, or a `types/` / `enums/` folder) unless they are trivially local to a single consumer.
11
- - Do not co-locate unrelated helpers inside a class or module just because they happen to touch the same data.
12
- - If a file starts doing two jobs, split it.
13
-
14
- ## 2. Ternaries: short and flat, or not at all
15
-
16
- Use a ternary only when it is short, single-line, and obvious at a glance.
17
-
18
- ```ts
19
- // Good
20
- const label = isActive ? 'on' : 'off';
21
-
22
- // Bad — nested
23
- const label = isActive ? (isAdmin ? 'admin-on' : 'user-on') : isAdmin ? 'admin-off' : 'user-off';
24
- ```
25
-
26
- No nested ternaries. Ever. If branching gets past one level, use `if`/`else` or extract a function. Readability wins over cleverness.
27
-
28
- ## 3. Stop using `typeof` everywhere
29
-
30
- `typeof` is a narrowing tool, not a default check. Do not reach for it when a simple truthy/falsy check is enough.
31
-
32
- ```ts
33
- // Good
34
- if (!user) return;
35
- if (value) process(value);
36
-
37
- // Bad
38
- if (typeof user !== "undefined" && user !== null) { ... }
39
- if (typeof value === "string" && value.length > 0) { ... } // when you just need truthiness
40
- ```
41
-
42
- Use `typeof` only when you genuinely need to discriminate primitive types (e.g. inside a union where `string | number` matters). Otherwise, trust truthiness, optional chaining, and nullish coalescing.
43
-
44
- ## 4. No over-engineered guard rails
45
-
46
- Write defenses for realistic failure modes, not hypothetical ones.
47
-
48
- - Validate at system boundaries (HTTP input, DB reads, external APIs). Do not re-validate the same data at every internal function call.
49
- - No `assert` chains at the top of ever4y function.
50
- - No wrapping every call in try/catch "just in case" — let errors bubble to a single handler that knows what to do with them.
51
- - If a guard clause is protecting against a case that cannot happen given the types, delete it.
52
- - Prefer types and schemas over runtime paranoia.
53
-
54
- The goal is correctness, not defensiveness theatre.
55
-
56
- ## 5. Follow popular conventions
57
-
58
- Use the community standard for the language and ecosystem. Do not invent personal styles.
59
-
60
- - **TypeScript/JavaScript**: ESLint + Prettier defaults, camelCase for variables/functions, PascalCase for types/classes, `kebab-case` or `camelCase` file names consistent with the project.
61
- - **Go**: `gofmt`, idiomatic error handling (`if err != nil`), package names short and lowercase, no stutter.
62
- - **Python**: PEP 8, `ruff`/`black` formatting, `snake_case`.
63
- - Match the project's existing style before imposing anything new. When in doubt, grep the codebase and do what it already does.
64
-
65
- ## 6. Comments: only when they add something
66
-
67
- Code should read itself. Comments are for things the code cannot say.
68
-
69
- Write a comment when:
70
-
71
- - The _why_ is non-obvious (business rule, workaround for an upstream bug, performance-sensitive choice).
72
- - A public API needs a docstring for consumers.
73
- - A `TODO` / `FIXME` with context and ownership.
74
-
75
- Do not write a comment when:
76
-
77
- - It restates the code (`// increment counter` above `counter++`).
78
- - It narrates obvious control flow (`// loop through users`).
79
- - It was auto-generated boilerplate that nobody reads.
80
-
81
- Delete stale comments on sight. A wrong comment is worse than no comment.
82
-
83
- ## 7. Don't re-invent what already exists
84
-
85
- Before writing a helper, check:
86
-
87
- 1. The language's stdlib.
88
- 2. The framework the project uses.
89
- 3. Utilities already in this codebase.
90
-
91
- Do not write a custom `debounce`, `deepClone`, `groupBy`, UUID generator, date parser, retry loop, or config loader when a well-tested one is already available. Do not introduce a second HTTP client, a second logger, or a second error class when the repo already has one — use what's there.
92
-
93
- The only reasons to roll your own: the existing option is genuinely insufficient, or pulling it in costs more than writing it.
94
-
95
- ## 8. No `any`, no `as unknown as T`
96
-
97
- When types get annoying, fix the type — don't escape it.
98
-
99
- - `any` is banned except at genuine interop edges (and even then, narrow immediately).
100
- - `as` casts are a last resort. If you're writing `as unknown as T`, the real answer is usually a type guard, a discriminated union, or a schema parse (Zod, etc.).
101
- - `// @ts-ignore` and `// @ts-expect-error` require a comment explaining why.
102
-
103
- The type system earns its keep when you respect it.
104
-
105
- ## 9. Delete dead code
106
-
107
- - No commented-out blocks "for reference." Git remembers.
108
- - No unused imports, unused variables, unused parameters (prefix with `_` if the signature forces it).
109
- - No stale feature flags still wired up months after launch.
110
- - No "v2" helper sitting next to the original with no caller.
111
-
112
- If it isn't used, it leaves.
113
-
114
- ## 10. Consistent error handling
115
-
116
- Pick one error strategy per layer and stick to it.
117
-
118
- - Don't mix `throw`, `Result<T, E>`, `null` returns, and `{ error, data }` tuples in the same module.
119
- - Don't catch errors only to re-throw them unchanged — that's noise.
120
- - Don't swallow errors with empty `catch {}`. If you genuinely want to ignore one, log it or leave a comment saying why.
121
- - Handle errors at the layer that knows what to do with them (usually the HTTP handler or job runner), not at every function on the way down.
122
-
123
- ## 11. No magic numbers or magic strings
124
-
125
- Named constants and enums beat literals scattered through the code.
126
-
127
- ```ts
128
- // Bad
129
- setTimeout(retry, 86400000);
130
- if (user.status === "active") { ... }
131
-
132
- // Good
133
- const ONE_DAY_MS = 24 * 60 * 60 * 1000;
134
- setTimeout(retry, ONE_DAY_MS);
135
- if (user.status === UserStatus.Active) { ... }
136
- ```
137
-
138
- If a value appears more than once, or its meaning isn't obvious from the number/string alone, name it.
139
-
140
- ## 12. Async correctness
141
-
142
- - Always `await` or explicitly handle the promise. No dangling promises.
143
- - Parallelize independent work with `Promise.all`; don't serialize it in a `for` loop by accident.
144
- - Don't mix `.then()` chains and `await` in the same function — pick one.
145
- - Handle rejections. An unhandled rejection in production is a bug waiting to page you.
146
-
147
- ## 13. Don't mutate function arguments
148
-
149
- Treat inputs as read-only. If you need a modified version, return a new one.
150
-
151
- ```ts
152
- // Bad
153
- function addTag(user, tag) {
154
- user.tags.push(tag);
155
- return user;
156
- }
157
-
158
- // Good
159
- function addTag(user, tag) {
160
- return { ...user, tags: [...user.tags, tag] };
161
- }
162
- ```
163
-
164
- Hidden mutation through a function call is one of the worst bugs to trace in a large system.
165
-
166
- ## 14. Respect the codebase's existing patterns
167
-
168
- Before introducing something new, look around:
169
-
170
- - If the project uses a specific HTTP client, logger, error class, config loader, or validation library — use it.
171
- - If there's an established folder structure, follow it.
172
- - If there's a naming convention in neighboring files, match it.
173
-
174
- Don't bring in a new dependency or pattern just because it's what you reached for first. Consistency across the codebase is worth more than any individual preference.
175
-
176
- ## 15. No hardcoded environment values
177
-
178
- URLs, ports, credentials, feature flags, and tunable limits belong in config or environment variables — not in source.
179
-
180
- - Secrets never land in the repo. Not even temporarily.
181
- - Defaults for local dev are fine in a `.env.example` or config file, but the real values are injected.
182
- - If a value differs between staging and prod, it's config.
183
-
184
- ---
185
-
186
- ## Summary
187
-
188
- Clean separation, flat logic, minimal runtime paranoia, community idioms, and comments only where they earn their place. Reuse what exists, respect the type system, delete what's dead, handle errors consistently, and keep configuration out of source. If a change adds complexity without earning it, reject the change.