Skip to content

Commit b9d0613

Browse files
authored
Merge branch 'main' into tsgo
2 parents 478a8d7 + a3a6ef6 commit b9d0613

File tree

105 files changed

+10085
-1836
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

105 files changed

+10085
-1836
lines changed

.github/CODEOWNERS

Lines changed: 0 additions & 3 deletions
This file was deleted.

.github/ISSUE_TEMPLATE/bug_report.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ Example
2222

2323
<!-- A clear and concise description of what you expected to happen. -->
2424

25+
**Actual behavior**
26+
27+
<!-- Actual output of the steps, in your environment. -->
28+
2529
**Environment**
2630

2731
<!-- please complete the following information -->

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: ci
22

33
permissions:
4-
contents: write
4+
contents: read
55

66
on:
77
push:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ _tmp/
2222

2323
# tsconfig for bun
2424
/tsconfig.json
25+
.claude/

AGENTS.md

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# Deno Standard Library
2+
3+
The Deno Standard Library is a collection of high-quality packages for Deno and
4+
the web, distributed via JSR as `@std/*`. It supports multiple runtimes: Deno,
5+
Node.js, Bun, browsers, and Cloudflare Workers.
6+
7+
## Repository Structure
8+
9+
- Each top-level directory is a package (e.g., `async/`, `http/`,
10+
`collections/`)
11+
- `_tools/` contains internal CI/dev tooling (lint plugins, doc checkers, etc.)
12+
- Root `deno.json` defines the workspace, tasks, and compiler options
13+
- `import_map.json` maps all `@std/*` packages and dev dependencies
14+
15+
### Package Layout
16+
17+
Every package follows this structure:
18+
19+
```
20+
<package>/
21+
├── deno.json # Name (@std/<package>), version, exports
22+
├── mod.ts # Public entry point
23+
├── <api>.ts # Implementation (one function/class per file)
24+
├── <api>_test.ts # Tests for the corresponding source file
25+
└── unstable_<api>.ts # Experimental APIs (stable packages only)
26+
```
27+
28+
- **Minimal exports**: each file exports a single function/class and related
29+
types
30+
- **Unstable APIs** (`unstable_*.ts`): used in packages with version >= 1.0.0;
31+
must have `@experimental` TSDoc tag; must NOT be exported from `mod.ts`
32+
- Packages with version < 1.0.0 have no unstable file restrictions
33+
34+
## Quality Gate
35+
36+
Run `deno task ok` before submitting. This runs:
37+
38+
- `deno lint` (includes custom style-guide plugin at `_tools/lint_plugin.ts`)
39+
- `deno fmt --check`
40+
- `deno task test:browser` (browser compatibility check)
41+
- `deno task test` (full test suite with coverage and doc tests)
42+
43+
Additional lint tasks available:
44+
45+
- `deno task lint:circular` — circular dependency detection
46+
- `deno task lint:mod-exports` — validates `mod.ts` exports
47+
- `deno task lint:docs` — validates JSDoc completeness
48+
- `deno task lint:import-map` — validates import map
49+
- `deno task lint:export-names` — verifies export naming
50+
- `deno task lint:unstable-deps` — tracks unstable feature usage
51+
- `deno task typos` — spell checking
52+
53+
## PR Conventions
54+
55+
### Title Format
56+
57+
PR titles must follow semantic format with a package scope:
58+
59+
```
60+
feat(http): add streaming response support
61+
fix(csv): handle escaped quotes correctly
62+
docs(fmt): update docstrings
63+
deprecation(encoding): base32hex
64+
```
65+
66+
The scope must match an existing package name. New packages must first add their
67+
name to the `scopes` list in `.github/workflows/title.yml`.
68+
69+
### Commit Messages
70+
71+
Follow the same semantic format as PR titles.
72+
73+
## Testing
74+
75+
- Framework: `Deno.test()` with `@std/assert` for assertions
76+
- Test names: descriptive, include the symbol and criteria
77+
- `delay() resolves immediately with ms = 0`
78+
- `ensureDirSync() creates dir if it does not exist`
79+
- Each source file gets a corresponding `_test.ts` file
80+
- Do NOT use `@std/assert` in implementation code — only in tests
81+
82+
## Documentation Requirements
83+
84+
All public symbols must have JSDoc with:
85+
86+
1. Short description
87+
2. `@typeParam` for each type parameter
88+
3. `@param` for each parameter
89+
4. `@returns` for return value
90+
5. At least one `@example` with a title and runnable code snippet
91+
92+
Example snippets must be reproducible and use `@std/assert` assertions. Use
93+
`ignore` directive to skip running a snippet, `expect-error` for expected
94+
failures.
95+
96+
Module files (`mod.ts`) need a `@module` tag.
97+
98+
## Error Message Style
99+
100+
- Sentence case, no trailing period
101+
- Active voice: "Cannot parse input x" not "Input x cannot be parsed"
102+
- No contractions: "Cannot" not "Can't"
103+
- Quote string values: `Cannot parse input "hello, world"`
104+
- Use colons for context: `Cannot parse input x: value is empty`
105+
- State current and desired state when possible
106+
107+
Exception: `@std/assert` uses periods in error messages (downstream compat).
108+
109+
## CI Pipeline
110+
111+
Tests run on Ubuntu, Windows, and macOS against Deno v1.x, v2.x, and canary.
112+
Cross-runtime testing includes Node.js and Bun. Timezone-sensitive tests run
113+
across Sydney, London, and Toronto.
114+
115+
## Versioning and Releases
116+
117+
- Packages >= 1.0.0: follow semver
118+
- Packages < 1.0.0: follow semver proposal
119+
(https://github.com/semver/semver/pull/923)
120+
- Release tags: `release-YYYY.MM.DD`
121+
- Publishing: `workspace_publish` workflow pushes to JSR
122+
123+
## Deprecation Policy
124+
125+
Deprecated APIs use the format:
126+
127+
```ts
128+
/**
129+
* @deprecated This will be removed in X.Y.Z. Use {@linkcode bar} instead.
130+
*/
131+
```
132+
133+
Deprecated symbols are removed in the next major version. PRs use the title
134+
format `deprecation(<package>): <symbol>`.

_tools/check_docs.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ function assertHasExampleTag(
177177
"@example tag must have a title and TypeScript code snippet",
178178
document,
179179
);
180+
if (tag.doc === undefined) continue;
180181
/**
181182
* Otherwise, if the example title is undefined, it is given the title
182183
* "Example #" by default.
@@ -202,6 +203,7 @@ function assertHasTypeParamTags(
202203
`Symbol must have a @typeParam tag for ${typeParamName}`,
203204
document,
204205
);
206+
if (tag === undefined) return;
205207
assert(
206208
// @ts-ignore doc is defined
207209
tag.doc !== undefined,
Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ export type SettledRecord<T extends Record<PropertyKey, unknown>> = {
4545
* {@link https://github.com/tc39/proposal-await-dictionary | Await Dictionary}
4646
* proposal (`Promise.allKeyed`).
4747
*
48-
* @experimental **UNSTABLE**: New API, yet to be vetted.
49-
*
5048
* @typeParam T The record shape with resolved (unwrapped) value types. For
5149
* example, if passing `{ foo: Promise<number> }`, `T` would be `{ foo: number }`.
5250
* @param record A record where values are promise-like (thenables) or plain values.
@@ -57,7 +55,7 @@ export type SettledRecord<T extends Record<PropertyKey, unknown>> = {
5755
*
5856
* @example Basic usage
5957
* ```ts
60-
* import { allKeyed } from "@std/async/unstable-all-keyed";
58+
* import { allKeyed } from "@std/async/all-keyed";
6159
* import { assertEquals } from "@std/assert";
6260
*
6361
* const result = await allKeyed({
@@ -70,7 +68,7 @@ export type SettledRecord<T extends Record<PropertyKey, unknown>> = {
7068
*
7169
* @example Parallel HTTP requests
7270
* ```ts no-assert ignore
73-
* import { allKeyed } from "@std/async/unstable-all-keyed";
71+
* import { allKeyed } from "@std/async/all-keyed";
7472
*
7573
* const { user, posts } = await allKeyed({
7674
* user: fetch("/api/user").then((r) => r.json()),
@@ -80,7 +78,7 @@ export type SettledRecord<T extends Record<PropertyKey, unknown>> = {
8078
*
8179
* @example Mixed promises and plain values
8280
* ```ts
83-
* import { allKeyed } from "@std/async/unstable-all-keyed";
81+
* import { allKeyed } from "@std/async/all-keyed";
8482
* import { assertEquals } from "@std/assert";
8583
*
8684
* const result = await allKeyed({
@@ -124,8 +122,6 @@ export function allKeyed<T extends Record<PropertyKey, unknown>>(
124122
* {@link https://github.com/tc39/proposal-await-dictionary | Await Dictionary}
125123
* proposal (`Promise.allSettledKeyed`).
126124
*
127-
* @experimental **UNSTABLE**: New API, yet to be vetted.
128-
*
129125
* @typeParam T The record shape with resolved (unwrapped) value types. For
130126
* example, if passing `{ foo: Promise<number> }`, `T` would be `{ foo: number }`.
131127
* @param record A record where values are promise-like (thenables) or plain values.
@@ -134,7 +130,7 @@ export function allKeyed<T extends Record<PropertyKey, unknown>>(
134130
*
135131
* @example Basic usage
136132
* ```ts
137-
* import { allSettledKeyed } from "@std/async/unstable-all-keyed";
133+
* import { allSettledKeyed } from "@std/async/all-keyed";
138134
* import { assertEquals } from "@std/assert";
139135
*
140136
* const settled = await allSettledKeyed({
@@ -148,7 +144,7 @@ export function allKeyed<T extends Record<PropertyKey, unknown>>(
148144
*
149145
* @example Error handling
150146
* ```ts
151-
* import { allSettledKeyed } from "@std/async/unstable-all-keyed";
147+
* import { allSettledKeyed } from "@std/async/all-keyed";
152148
* import { assertEquals, assertExists } from "@std/assert";
153149
*
154150
* const settled = await allSettledKeyed({
Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Copyright 2018-2026 the Deno authors. MIT license.
2-
import { allKeyed, allSettledKeyed } from "./unstable_all_keyed.ts";
2+
import { allKeyed, allSettledKeyed } from "./all_keyed.ts";
33
import {
44
assertEquals,
55
assertFalse,
@@ -195,3 +195,25 @@ Deno.test("allKeyed() ignores non-enumerable symbol keys", async () => {
195195
assertEquals(Object.keys(result), ["visible"]);
196196
assertFalse(sym in result);
197197
});
198+
199+
Deno.test("allKeyed() resolves custom thenables", async () => {
200+
const thenable = {
201+
then(resolve: (value: number) => void) {
202+
resolve(99);
203+
},
204+
} as PromiseLike<number>;
205+
const result = await allKeyed({ val: thenable });
206+
207+
assertEquals(result.val, 99);
208+
});
209+
210+
Deno.test("allSettledKeyed() resolves custom thenables", async () => {
211+
const thenable = {
212+
then(resolve: (value: string) => void) {
213+
resolve("from thenable");
214+
},
215+
} as PromiseLike<string>;
216+
const result = await allSettledKeyed({ val: thenable });
217+
218+
assertEquals(result.val, { status: "fulfilled", value: "from thenable" });
219+
});

async/debounce.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
// This module is browser compatible.
33

44
/**
5-
* A debounced function that will be delayed by a given `wait`
6-
* time in milliseconds. If the method is called again before
5+
* A debounced function whose execution is delayed by a given `wait`
6+
* time in milliseconds. If the function is called again before
77
* the timeout expires, the previous call will be aborted.
88
*/
99
export interface DebouncedFunction<T extends Array<unknown>> {
@@ -36,45 +36,50 @@ export interface DebouncedFunction<T extends Array<unknown>> {
3636
* log(event);
3737
* }
3838
* // wait 200ms ...
39-
* // output: Function debounced after 200ms with baz
39+
* // output: [modify] /path/to/file
4040
* ```
4141
*
4242
* @typeParam T The arguments of the provided function.
4343
* @param fn The function to debounce.
4444
* @param wait The time in milliseconds to delay the function.
45+
* Must be a positive integer.
46+
* @throws {RangeError} If `wait` is not a non-negative integer.
4547
* @returns The debounced function.
4648
*/
4749
// deno-lint-ignore no-explicit-any
4850
export function debounce<T extends Array<any>>(
4951
fn: (this: DebouncedFunction<T>, ...args: T) => void,
5052
wait: number,
5153
): DebouncedFunction<T> {
54+
if (!Number.isInteger(wait) || wait < 0) {
55+
throw new RangeError("'wait' must be a positive integer");
56+
}
5257
let timeout: number | null = null;
53-
let flush: (() => void) | null = null;
58+
let pendingFlush: (() => void) | null = null;
5459

5560
const debounced: DebouncedFunction<T> = ((...args: T) => {
5661
debounced.clear();
57-
flush = () => {
62+
pendingFlush = () => {
5863
debounced.clear();
5964
fn.call(debounced, ...args);
6065
};
61-
timeout = Number(setTimeout(flush, wait));
66+
timeout = Number(setTimeout(pendingFlush, wait));
6267
}) as DebouncedFunction<T>;
6368

6469
debounced.clear = () => {
65-
if (typeof timeout === "number") {
70+
if (timeout !== null) {
6671
clearTimeout(timeout);
6772
timeout = null;
68-
flush = null;
73+
pendingFlush = null;
6974
}
7075
};
7176

7277
debounced.flush = () => {
73-
flush?.();
78+
pendingFlush?.();
7479
};
7580

7681
Object.defineProperty(debounced, "pending", {
77-
get: () => typeof timeout === "number",
82+
get: () => timeout !== null,
7883
});
7984

8085
return debounced;

0 commit comments

Comments
 (0)