Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 41 additions & 26 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,21 @@ and structured `details`.

```tree
micro509/
├── src/ # domain barrels + public modules
├── src/ # public entrypoints (src/*.ts) + domain implementation
│ ├── index.ts # micro509
│ ├── der.ts # micro509/der
│ ├── keys.ts # micro509/keys
│ ├── pem.ts # micro509/pem
│ ├── pkcs.ts # micro509/pkcs
│ ├── result.ts # micro509/result
│ ├── revocation.ts # micro509/revocation
│ ├── verify.ts # micro509/verify
│ ├── x509.ts # micro509/x509
│ ├── x509/ # cert + CSR + extension + parse APIs
│ ├── verify/ # chain validation + policy + identity checks
│ ├── revocation/ # CRL/OCSP lifecycles
│ ├── keys/ # key import/export and generation
│ ├── der/ # public DER encode/decode surface (micro509/der)
│ ├── der/ # public DER encode/decode surface
│ ├── pem/ # PEM encode/decode boundary
│ ├── pkcs/ # PKCS-7 and PKCS#12 workflows
│ ├── result/ # shared result/error algebra
Expand All @@ -68,26 +77,28 @@ micro509/

## WHERE TO LOOK

| Task | Location | Notes |
| --------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------- |
| Public API surface | `src/index.ts` | root barrel for `micro509` |
| Domain entrypoints | `src/x509/`, `src/verify/`, `src/revocation/`, `src/keys/` | domain-specific high-level entry surfaces |
| Package entry routing | `package.json` | `exports`, `imports`, scripts |
| Chain validation | `src/verify/verify.ts` | candidate path building, policy composition |
| Certificate/CSR parsing | `src/x509/parse.ts` | DER/PEM parse boundary + extension decoding |
| Revocation | `src/revocation/crl.ts`, `src/revocation/ocsp.ts` | CRL + OCSP creation, parse, validate, verify |
| Key import/export | `src/keys/keys.ts` | PKCS#1/8, SEC1, SPKI, JWK flows |
| DER codec surface | `src/der/der.ts` | public DER encode/decode (`micro509/der`) |
| Extension model/builders | `src/x509/extensions.ts` | typed extension schema and encoder helpers |
| Test helpers and internals probes | `test/helpers.ts`, `test/internals.test.ts` | shared DER helpers, internal probing through imports |
| Standards scope | `docs/PKIX-SCOPE.md` | claim boundaries |
| Docs site | `site/.vitepress/config.ts`, `site/guide/`, `site/api/` | VitePress config, authored guides, generated API |
| Reusable CI actions | `.github/actions/` | shared setup + release version validation |
| Task | Location | Notes |
| --------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------- |
| Public API surface | `src/index.ts` | root barrel for `micro509` |
| Domain entrypoints | `src/x509.ts`, `src/verify.ts`, `src/revocation.ts`, `src/keys.ts` | domain-specific high-level entry surfaces |
| Package entry routing | `package.json`, `tsdown.config.ts` | `exports` and `jsr.json` are derived from `src/*.ts` |
| Chain validation | `src/verify/verify.ts` | candidate path building, policy composition |
| Certificate/CSR parsing | `src/x509/parse.ts` | DER/PEM parse boundary + extension decoding |
| Revocation | `src/revocation/crl.ts`, `src/revocation/ocsp.ts` | CRL + OCSP creation, parse, validate, verify |
| Key import/export | `src/keys/keys.ts` | PKCS#1/8, SEC1, SPKI, JWK flows |
| DER codec surface | `src/der/der.ts` | public DER encode/decode (`micro509/der`) |
| Extension model/builders | `src/x509/extensions.ts` | typed extension schema and encoder helpers |
| Test helpers and internals probes | `test/helpers.ts`, `test/internals.test.ts` | shared DER helpers, internal probing through imports |
| Standards scope | `docs/PKIX-SCOPE.md` | claim boundaries |
| Docs site | `site/.vitepress/config.ts`, `site/guide/`, `site/api/` | VitePress config, authored guides, generated API |
| Reusable CI actions | `.github/actions/` | shared setup + release version validation |

## CONVENTIONS

- Domain entrypoints own feature ownership; concrete lifecycle modules do implementation.
- `src/*/` barrels are re-export-only unless local file owners expand naturally.
- Root `src/*.ts` files are the package entrypoints and are re-export-only; tsdown
globs them, so a new root file publishes a new `micro509/<name>` subpath. Domain
implementation goes in `src/<domain>/`, internals in `src/internal/`.
- Relative imports use `.ts` extensions; `#micro509/*` subpath imports are extensionless (the `imports` map supplies `.ts`).
- Import boundaries: public leaf modules may use `#micro509/internal/*`, not sibling barrels.
- Return typed result unions for expected failures; throw only for invariants.
Expand All @@ -112,14 +123,18 @@ Avoid the following in this project:

## CODE MAP

| Symbol | Type | Location | Refs |
| ------------------------- | ------------- | ------------------- | ----------------------------------------- |
| `src/index.ts` | barrel export | root surface | Re-exports all stable API slices |
| `src/x509/index.ts` | domain barrel | X.509 feature slice | Certificate, CSR, parse, extension APIs |
| `src/verify/index.ts` | domain barrel | verification slice | Path, policy, identity, name constraints |
| `src/revocation/index.ts` | domain barrel | revocation slice | CRL/OCSP orchestration |
| `src/der/index.ts` | domain barrel | DER slice | public DER encode/decode surface |
| `src/result/result.ts` | result ADT | shared model | central `Result`/`Micro509Error` contract |
| Symbol | Type | Location | Refs |
| ---------------------- | ------------- | ------------------- | ----------------------------------------- |
| `src/index.ts` | barrel export | root surface | Re-exports all stable API slices |
| `src/der.ts` | domain barrel | DER slice | public DER encode/decode surface |
| `src/keys.ts` | domain barrel | key slice | import/export, generation, encryption |
| `src/pem.ts` | domain barrel | PEM slice | encode/decode and block classification |
| `src/pkcs.ts` | domain barrel | PKCS slice | PKCS-7 and PKCS#12 workflows |
| `src/result.ts` | domain barrel | result slice | `Result` and error constructors |
| `src/revocation.ts` | domain barrel | revocation slice | CRL/OCSP orchestration |
| `src/verify.ts` | domain barrel | verification slice | Path, policy, identity, name constraints |
| `src/x509.ts` | domain barrel | X.509 feature slice | Certificate, CSR, parse, extension APIs |
| `src/result/result.ts` | result ADT | shared model | central `Result`/`Micro509Error` contract |

## COMMANDS

Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Public source entrypoints are flat `src/*.ts` files: `src/x509/index.ts`
becomes `src/x509.ts`, and the eight nested barrels are gone. `tsdown.config.ts`
globs `entry: ['src/*.ts']`, so `package.json#exports` and `jsr.json#exports`
derive from the filesystem rather than a hand-maintained entry map, and
`package.json#imports` collapses onto the `#micro509/*` wildcard. Package
specifiers (`micro509`, `micro509/x509`, …) and every exported symbol are
unchanged; JSR consumers reading source paths see the new flat layout.
- Builder input-validation now throws a `ResultError` carrying a stable
machine-readable `code` rather than a bare `Error`. `createCertificate`, the
`encode*` extension helpers, distinguished-name encoding, and CRL/IDP encoding
Expand Down
59 changes: 49 additions & 10 deletions biome.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,50 @@
"enabled": true,
"domains": {
"project": "recommended",
// "types": "recommended", await [biomejs/biome#10885](https://github.com/biomejs/biome/issues/10885) resolution
"types": "recommended",
"test": "recommended"
},
"rules": {
"a11y": { "recommended": true },
"preset": "recommended",
"a11y": {},
"complexity": {
"recommended": true,
"useLiteralKeys": "off",
"noExcessiveCognitiveComplexity": {
"options": { "maxAllowedComplexity": 20 },
"level": "info"
}
},
"correctness": { "recommended": true },
"performance": { "recommended": true },
"nursery": { "recommended": true },
"security": { "recommended": true },
"style": { "recommended": true },
"suspicious": { "recommended": true }
"correctness": {
"useQwikClasslist": "off",
"noQwikUseVisibleTask": "off",
"useQwikMethodUsage": "off",
"useQwikValidLexicalScope": "off",
"noUndeclaredDependencies": "off",
"noUnresolvedImports": "off"
},
"performance": {},
"nursery": {},
"security": {},
"style": {
"noProcessEnv": "off",
"noNestedTernary": "off",
"noTernary": "off",
"useBlockStatements": "off"
},
"suspicious": {
"noConsole": "off"
}
}
},
"plugins": [
{
"path": "./plugins/grit/no-classes.grit",
"includes": ["**/src/**/*.ts", "!**/packages/**", "!**/examples/**", "!**/site/**"]
}
],
"javascript": {
"formatter": { "quoteStyle": "single" }
"formatter": { "quoteStyle": "single" },
"globals": ["Bun", "Deno"]
},
"json": { "formatter": { "enabled": false } },
"assist": {
Expand All @@ -50,6 +71,24 @@
}
},
"overrides": [
{
"includes": ["**", "**/src/**"],
"linter": {
"rules": {
"correctness": {
"noNodejsModules": "off",
"noUnusedImports": "off"
},
"performance": {
"useTopLevelRegex": "off"
}
}
}
},
{
"includes": ["src/**/*.ts"],
"linter": { "rules": { "style": { "noDefaultExport": "error" } } }
},
{
"includes": ["**/*.jsonc", "!biome.jsonc"],
"json": { "formatter": { "trailingCommas": "all" } }
Expand Down
16 changes: 8 additions & 8 deletions jsr.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
"license": "MIT",
"exports": {
".": "./src/index.ts",
"./der": "./src/der/index.ts",
"./keys": "./src/keys/index.ts",
"./pem": "./src/pem/index.ts",
"./pkcs": "./src/pkcs/index.ts",
"./result": "./src/result/index.ts",
"./revocation": "./src/revocation/index.ts",
"./verify": "./src/verify/index.ts",
"./x509": "./src/x509/index.ts"
"./der": "./src/der.ts",
"./keys": "./src/keys.ts",
"./pem": "./src/pem.ts",
"./pkcs": "./src/pkcs.ts",
"./result": "./src/result.ts",
"./revocation": "./src/revocation.ts",
"./verify": "./src/verify.ts",
"./x509": "./src/x509.ts"
},
"publish": {
"include": ["src/**/*.ts", "src/**/*.json", "package.json", "LICENSE", "README.md"],
Expand Down
8 changes: 0 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,6 @@
"imports": {
"#micro509": "./src/index.ts",
"#micro509/*": "./src/*.ts",
"#micro509/der": "./src/der/index.ts",
"#micro509/keys": "./src/keys/index.ts",
"#micro509/pem": "./src/pem/index.ts",
"#micro509/pkcs": "./src/pkcs/index.ts",
"#micro509/result": "./src/result/index.ts",
"#micro509/revocation": "./src/revocation/index.ts",
"#micro509/verify": "./src/verify/index.ts",
"#micro509/x509": "./src/x509/index.ts",
"#micro509/internal/*": "./src/internal/*.ts",
"#pkg": "./package.json",
"#jsr": "./jsr.json",
Expand Down
6 changes: 6 additions & 0 deletions plugins/grit/no-classes.grit
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
or { JsClassDeclaration(), JsClassExpression() } as $class where {
register_diagnostic(
span = $class,
message = "src/ declares no classes (AGENTS.md ANTI-PATTERNS)"
)
}
7 changes: 5 additions & 2 deletions site/.vitepress/config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/** biome-ignore-all lint/style/noExcessiveLinesPerFile: explanation */
/** biome-ignore-all lint/correctness/noNodejsModules: explanation */
import { writeImportMap } from 'importmapify';
import { entrypointsOf } from '@micro509/doc-render';
import { apiDocsPlugin, generateApiDocs } from '@micro509/vitepress-api-docs';
Expand All @@ -15,6 +17,7 @@ import robotsTxt from 'vite-robots-txt';
import svgToIco from 'vite-svg-to-ico';
import type { DefaultTheme } from 'vitepress';
import { defineConfig } from 'vitepress';
import process from 'node:process';

interface DocsThemeConfig extends DefaultTheme.Config {
readonly versions: readonly DocsVersion[];
Expand Down Expand Up @@ -253,10 +256,10 @@ const versionPrefixes = docs.versions
.sort((left, right) => right.length - left.length);

function versionPrefixOfPath(pathname: string): string {
const path = pathname.replace(/^\//, '');
const p = pathname.replace(/^\//, '');
return (
versionPrefixes.find(
(prefix) => prefix === '' || path === prefix.slice(0, -1) || path.startsWith(prefix),
(prefix) => prefix === '' || p === prefix.slice(0, -1) || p.startsWith(prefix),
) ?? ''
);
}
Expand Down
1 change: 1 addition & 0 deletions site/.vitepress/shims.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/** biome-ignore-all lint/style/noDefaultExport: explanation */
declare module '*.css' {
const stylesheet: CSSStyleSheet;
export default stylesheet;
Expand Down
1 change: 1 addition & 0 deletions site/.vitepress/theme/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/** biome-ignore-all lint/style/noDefaultExport: explanation */
import LiveCode from '#components/LiveCode.vue' with { type: 'vue' };
import VersionedNav from '#components/VersionedNav.vue' with { type: 'vue' };
import VersionSwitcher from '#components/VersionSwitcher.vue' with { type: 'vue' };
Expand Down
30 changes: 16 additions & 14 deletions src/AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
# `src/` - Module Map

Public domain entrypoints are one-level buckets, implementation details stay in
`src/internal/`.
Public entrypoints are the root `src/*.ts` files; domain implementation stays in
`src/<domain>/` and internals in `src/internal/`.

## OVERVIEW

`src/` is the library boundary. High-level workflow ownership is in domain
barrels under `x509`, `verify`, `revocation`, `keys`, `pem`, `pkcs`, and
`result`.
`src/` is the library boundary. Each root `src/<domain>.ts` file is a package
entrypoint that re-exports its domain; implementation lives in the matching
`src/<domain>/` directory.

## WHERE TO LOOK

| Area | File/dir | Notes |
| ------------------ | --------------------------------- | --------------------------------------------------- |
| Package root | `index.ts` | all stable package exports |
| Domain entrypoints | `x509/`, `verify/`, `revocation/` | re-export-only in most cases |
| Key APIs | `keys/` | import/export, generation, encryption options |
| PEM boundary | `pem/` | encode/decode and block classification |
| PKCS workflows | `pkcs/` | PFX and PKCS#7 data lifecycles |
| Result model | `result/` | shared typed `Result` and error constructors |
| Internal spine | `internal/` | ASN.1, crypto, shared helpers, verification engines |
| Area | File/dir | Notes |
| ------------------ | ---------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| Package root | `index.ts` | all stable package exports |
| Domain entrypoints | `der.ts`, `keys.ts`, `pem.ts`, `pkcs.ts`, `result.ts`, `revocation.ts`, `verify.ts`, `x509.ts` | re-export-only |

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to enumerate all in a fucking table cell

| Key APIs | `keys/` | import/export, generation, encryption options |
| PEM boundary | `pem/` | encode/decode and block classification |
| PKCS workflows | `pkcs/` | PFX and PKCS#7 data lifecycles |
| Result model | `result/` | shared typed `Result` and error constructors |
| Internal spine | `internal/` | ASN.1, crypto, shared helpers, verification engines |

## LOCAL CONVENTIONS

- Keep APIs by domain barrel; add files inside existing domain unless the domain
model clearly needs a new public ownership file.
- Root `src/*.ts` is reserved for package entrypoints. tsdown globs it, so a new
root file publishes `micro509/<name>`; `test/conventions.test.ts` pins the set.
- New OIDs go in `src/internal/asn1/oids.json` under their registration arc and
are consumed as `OIDS.<name>`; never inline a dotted-decimal literal in
source.
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion src/internal/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Non-public internals for codecs, cryptography, and verification engines.
## OVERVIEW

`src/internal/` hosts reusable low-level spines consumed by all domains.
Public barrels should stay in domain folders.
Public barrels are the root `src/*.ts` files and never live here.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NO SHIT, THE DIRECTORY IS CALLED INTERNAL.


## STRUCTURE

Expand Down
5 changes: 2 additions & 3 deletions src/internal/crypto/pbes2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,7 @@ function parsePbes2KdfFields(
} {
const kdfDer = paramsDer.slice(kdf.start - kdf.headerLength, kdf.end);
const kdfChildren = readSequenceChildren(kdfDer);
const kdfOid = kdfChildren[0];
const kdfParams = kdfChildren[1];
const [kdfOid, kdfParams] = kdfChildren;
if (kdfOid === undefined || kdfParams === undefined) {
throw new Error('Malformed KDF params');
}
Expand Down Expand Up @@ -335,7 +334,7 @@ function parsePbkdf2Prf(
const prfDer = readSequenceChildren(
pbkdf2Der.slice(element.start - element.headerLength, element.end),
);
const oid = prfDer[0];
const [oid] = prfDer;
if (oid === undefined) {
throw new Error('Malformed PBKDF2 PRF');
}
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading
Loading