{"_id":"@aletheia-ios/sdk","_rev":"5-e9a19ab5556dc09ed353c7e53a61b207","name":"@aletheia-ios/sdk","dist-tags":{"latest":"0.5.0"},"versions":{"0.1.0":{"name":"@aletheia-ios/sdk","version":"0.1.0","license":"MIT","_id":"@aletheia-ios/sdk@0.1.0","maintainers":[{"name":"shiroies","email":"angelocarasig15@gmail.com"}],"homepage":"https://github.com/aletheia-ios/sdk#readme","bugs":{"url":"https://github.com/aletheia-ios/sdk/issues"},"dist":{"shasum":"8eba5ad3e1cef78f99208e9b6c22a96589c879af","tarball":"https://registry.npmjs.org/@aletheia-ios/sdk/-/sdk-0.1.0.tgz","fileCount":18,"integrity":"sha512-NUQCCoaO3dDnpqIdi1gTIjMpFr2jAgF0zGYnz8nHpONObrxUfVtjln3Lh+cBd0ZTfGE85pl3eH8F1y6V96WPqA==","signatures":[{"sig":"MEUCIQCND179KBjT6pyIZ9YMiCuvIPTzH+cLTThYzehO/Rt9dwIgcELMEtWJdO6KcMa//OQ2SB2OBTLhGSqtrdy5FbjfF3M=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":130242},"type":"module","engines":{"node":">=22"},"exports":{"./host":{"types":"./dist/host.d.ts","import":"./dist/host.js"},"./types":{"types":"./dist/types.d.ts","import":"./dist/types.js"},"./utils":{"types":"./dist/utils.d.ts","import":"./dist/utils.js"},"./schemas":{"types":"./dist/schemas.d.ts","import":"./dist/schemas.js"},"./json-schema/*":"./dist/json-schema/*"},"gitHead":"ea1e71f00d98d583f41c4dd685c8c93ff5a44ca4","scripts":{"lint":"biome check .","test":"vitest run --coverage","build":"tsdown && node scripts/json-schema.js","check":"pnpm typecheck && pnpm lint && pnpm build && pnpm test && publint && attw --pack . --profile esm-only","format":"biome format --write .","prepare":"lefthook install","typecheck":"tsc"},"_npmUser":{"name":"shiroies","email":"angelocarasig15@gmail.com"},"repository":{"url":"git+https://github.com/aletheia-ios/sdk.git","type":"git"},"_npmVersion":"11.5.1","description":"Contract, host bridge and helpers for writing Aletheia source packages","directories":{},"sideEffects":false,"_nodeVersion":"24.7.0","dependencies":{"zod":"^4.5.4"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.16.1","devDependencies":{"tsdown":"^0.23.0","vitest":"^5.0.0","publint":"^0.3.24","lefthook":"^2.1.12","typescript":"^7.0.2","@types/node":"^24.13.3","@biomejs/biome":"^2.5.12","@vitest/coverage-v8":"5.0.0","@arethetypeswrong/cli":"^0.18.5"},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.1.0_1788592040106_0.943963113177533","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@aletheia-ios/sdk","version":"0.2.0","license":"MIT","_id":"@aletheia-ios/sdk@0.2.0","maintainers":[{"name":"shiroies","email":"angelocarasig15@gmail.com"}],"homepage":"https://github.com/aletheia-ios/sdk#readme","bugs":{"url":"https://github.com/aletheia-ios/sdk/issues"},"dist":{"shasum":"d5e938c4ac779c04bcb213e2020c8dff293dc40c","tarball":"https://registry.npmjs.org/@aletheia-ios/sdk/-/sdk-0.2.0.tgz","fileCount":18,"integrity":"sha512-2Q4vPqwgFNZItXxFQUroRWwfq5xpp1uMYT47ZaUhxypKLDyGFVKYlVCNOvIUiqibo97BANW+X5bQo2FpHzce0A==","signatures":[{"sig":"MEQCIE4zSRUmZhiY681AGhDFOIiKZ/6cWCvCFBicHmN+U73VAiBzGNumJOLEVSGeEbuQrIfC4zVntFs89r28VnWHkgiilQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":132274},"type":"module","engines":{"node":">=22"},"exports":{"./host":{"types":"./dist/host.d.ts","import":"./dist/host.js"},"./types":{"types":"./dist/types.d.ts","import":"./dist/types.js"},"./utils":{"types":"./dist/utils.d.ts","import":"./dist/utils.js"},"./schemas":{"types":"./dist/schemas.d.ts","import":"./dist/schemas.js"},"./json-schema/*":"./dist/json-schema/*"},"gitHead":"b044cde4ab39a31b03f9ffdd5d41723bc6836309","scripts":{"lint":"biome check .","test":"vitest run --coverage","build":"tsdown && node scripts/json-schema.js","check":"pnpm typecheck && pnpm lint && pnpm build && pnpm test && publint && attw --pack . --profile esm-only","format":"biome format --write .","prepare":"lefthook install","typecheck":"tsc"},"_npmUser":{"name":"shiroies","email":"angelocarasig15@gmail.com"},"repository":{"url":"git+https://github.com/aletheia-ios/sdk.git","type":"git"},"_npmVersion":"11.5.1","description":"Contract, host bridge and helpers for writing Aletheia source packages","directories":{},"sideEffects":false,"_nodeVersion":"24.7.0","dependencies":{"zod":"^4.5.4"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.16.1","devDependencies":{"tsdown":"^0.23.0","vitest":"^5.0.0","publint":"^0.3.24","lefthook":"^2.1.12","typescript":"^7.0.2","@types/node":"^24.13.3","@biomejs/biome":"^2.5.12","@vitest/coverage-v8":"5.0.0","@arethetypeswrong/cli":"^0.18.5"},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.2.0_1788610541679_0.3944078610110422","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@aletheia-ios/sdk","version":"0.3.0","license":"MIT","_id":"@aletheia-ios/sdk@0.3.0","maintainers":[{"name":"shiroies","email":"angelocarasig15@gmail.com"}],"homepage":"https://github.com/aletheia-ios/sdk#readme","bugs":{"url":"https://github.com/aletheia-ios/sdk/issues"},"dist":{"shasum":"e5c0cadc1476c7422fd539a811246b37f15d6aa0","tarball":"https://registry.npmjs.org/@aletheia-ios/sdk/-/sdk-0.3.0.tgz","fileCount":18,"integrity":"sha512-1y7CBPoY1SU8743LVgwiyCWvjCOsMWfVuZ8hHYIy2ERDQdP+lmmNWyAv7o7WEVgJ+ae11ZbbsdZWub/+kaRc/Q==","signatures":[{"sig":"MEQCIFVKPUk0PM9e34S71eXEgNsqtKNiziDv5Vl/r0QlHo0dAiBbIciwOJuBaKRRa/odFifLbt/mklnF5+P+MZTMgW0+sA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":133375},"type":"module","engines":{"node":">=22"},"exports":{"./host":{"types":"./dist/host.d.ts","import":"./dist/host.js"},"./types":{"types":"./dist/types.d.ts","import":"./dist/types.js"},"./utils":{"types":"./dist/utils.d.ts","import":"./dist/utils.js"},"./schemas":{"types":"./dist/schemas.d.ts","import":"./dist/schemas.js"},"./json-schema/*":"./dist/json-schema/*"},"gitHead":"b044cde4ab39a31b03f9ffdd5d41723bc6836309","scripts":{"lint":"biome check .","test":"vitest run --coverage","build":"tsdown && node scripts/json-schema.js","check":"pnpm typecheck && pnpm lint && pnpm build && pnpm test && publint && attw --pack . --profile esm-only","format":"biome format --write .","prepare":"lefthook install","typecheck":"tsc"},"_npmUser":{"name":"shiroies","email":"angelocarasig15@gmail.com"},"repository":{"url":"git+https://github.com/aletheia-ios/sdk.git","type":"git"},"_npmVersion":"11.5.1","description":"Contract, host bridge and helpers for writing Aletheia source packages","directories":{},"sideEffects":false,"_nodeVersion":"24.7.0","dependencies":{"zod":"^4.5.4"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.16.1","devDependencies":{"tsdown":"^0.23.0","vitest":"^5.0.0","publint":"^0.3.24","lefthook":"^2.1.12","typescript":"^7.0.2","@types/node":"^24.13.3","@biomejs/biome":"^2.5.12","@vitest/coverage-v8":"5.0.0","@arethetypeswrong/cli":"^0.18.5"},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.3.0_1788610922309_0.2998120760835785","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@aletheia-ios/sdk","version":"0.4.0","license":"MIT","_id":"@aletheia-ios/sdk@0.4.0","maintainers":[{"name":"shiroies","email":"angelocarasig15@gmail.com"}],"homepage":"https://github.com/aletheia-ios/sdk#readme","bugs":{"url":"https://github.com/aletheia-ios/sdk/issues"},"dist":{"shasum":"cd09ddab01ac841aa2df9a3a81521e2d3732c6ce","tarball":"https://registry.npmjs.org/@aletheia-ios/sdk/-/sdk-0.4.0.tgz","fileCount":18,"integrity":"sha512-tvPtg1IaflgrsfDi8yb+QyL+O6C3uEzDi7pA9qrpeLYKzEW0xPzBX+geUg2gRxQfitgKU130MUayFoST4sH79A==","signatures":[{"sig":"MEUCIAy/gjYlvXz5SCvkdOeHa1ITPJf9pJMvjFoV6CtzPFKEAiEAuvCQxvAcifvATYBy60n2TTJmOH+QXXiOYJgW3nF8eH4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":133923},"type":"module","engines":{"node":">=22"},"exports":{"./host":{"types":"./dist/host.d.ts","import":"./dist/host.js"},"./types":{"types":"./dist/types.d.ts","import":"./dist/types.js"},"./utils":{"types":"./dist/utils.d.ts","import":"./dist/utils.js"},"./schemas":{"types":"./dist/schemas.d.ts","import":"./dist/schemas.js"},"./json-schema/*":"./dist/json-schema/*"},"gitHead":"f76656c867157b61e5b9de7d6709d8acb35777e7","scripts":{"lint":"biome check .","test":"vitest run --coverage","build":"tsdown && node scripts/json-schema.js","check":"pnpm typecheck && pnpm lint && pnpm build && pnpm test && publint && attw --pack . --profile esm-only","format":"biome format --write .","prepare":"lefthook install","typecheck":"tsc"},"_npmUser":{"name":"shiroies","email":"angelocarasig15@gmail.com"},"repository":{"url":"git+https://github.com/aletheia-ios/sdk.git","type":"git"},"_npmVersion":"11.5.1","description":"Contract, host bridge and helpers for writing Aletheia source packages","directories":{},"sideEffects":false,"_nodeVersion":"24.7.0","dependencies":{"zod":"^4.5.4"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.16.1","devDependencies":{"tsdown":"^0.23.0","vitest":"^5.0.0","publint":"^0.3.24","lefthook":"^2.1.12","typescript":"^7.0.2","@types/node":"^24.13.3","@biomejs/biome":"^2.5.12","@vitest/coverage-v8":"5.0.0","@arethetypeswrong/cli":"^0.18.5"},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.4.0_1788611583433_0.8538049531016716","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@aletheia-ios/sdk","version":"0.5.0","description":"Contract, host bridge and helpers for writing Aletheia source packages","license":"MIT","repository":{"type":"git","url":"git+https://github.com/aletheia-ios/sdk.git"},"publishConfig":{"access":"public"},"type":"module","packageManager":"pnpm@10.16.1","engines":{"node":">=22"},"sideEffects":false,"exports":{"./types":{"types":"./dist/types.d.ts","import":"./dist/types.js"},"./schemas":{"types":"./dist/schemas.d.ts","import":"./dist/schemas.js"},"./host":{"types":"./dist/host.d.ts","import":"./dist/host.js"},"./utils":{"types":"./dist/utils.d.ts","import":"./dist/utils.js"},"./json-schema/*":"./dist/json-schema/*"},"scripts":{"build":"tsdown && node scripts/json-schema.js","typecheck":"tsc","lint":"biome check .","format":"biome format --write .","test":"vitest run --coverage","check":"pnpm typecheck && pnpm lint && pnpm build && pnpm test && publint && attw --pack . --profile esm-only","prepare":"lefthook install"},"dependencies":{"zod":"^4.5.4"},"devDependencies":{"@arethetypeswrong/cli":"^0.18.5","@biomejs/biome":"^2.5.12","@types/node":"^24.13.3","@vitest/coverage-v8":"5.0.0","lefthook":"^2.1.12","publint":"^0.3.24","tsdown":"^0.23.0","typescript":"^7.0.2","vitest":"^5.0.0"},"gitHead":"edc64dead5340c5a5661a112cbc14c46d1fde9d5","_id":"@aletheia-ios/sdk@0.5.0","bugs":{"url":"https://github.com/aletheia-ios/sdk/issues"},"homepage":"https://github.com/aletheia-ios/sdk#readme","_nodeVersion":"24.20.0","_npmVersion":"12.0.2","dist":{"integrity":"sha512-YyUV1z9kxA/0qaI/tHM0sP+3TPA8VZtXtGbsL2e/1xt5TqMwi10qBZg7NMu5UQUSF679yO4BfGyNztqeHyrn9g==","shasum":"c5a8af4e581ef4e0dc908ca081d8265818260ed8","tarball":"https://registry.npmjs.org/@aletheia-ios/sdk/-/sdk-0.5.0.tgz","fileCount":18,"unpackedSize":134276,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aletheia-ios%2fsdk@0.5.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIGe2wNpPWm7O4BGfD+Rt8umztFSvyEE0UC0EK+FfB5qNAiAvpi06A02M7xigKJ4aXvyij0gcIhfjyUrtIHxTj3bo1A=="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:048d9d00-123e-48af-a711-454dc92bab68"}},"directories":{},"maintainers":[{"name":"shiroies","email":"angelocarasig15@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/sdk_0.5.0_1788626832536_0.205047147172152"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-05T07:07:19.920Z","modified":"2026-09-05T16:47:13.021Z","0.1.0":"2026-09-05T07:07:20.250Z","0.2.0":"2026-09-05T12:15:41.806Z","0.3.0":"2026-09-05T12:22:02.457Z","0.4.0":"2026-09-05T12:33:03.606Z","0.5.0":"2026-09-05T16:47:12.682Z"},"bugs":{"url":"https://github.com/aletheia-ios/sdk/issues"},"license":"MIT","homepage":"https://github.com/aletheia-ios/sdk#readme","repository":{"type":"git","url":"git+https://github.com/aletheia-ios/sdk.git"},"description":"Contract, host bridge and helpers for writing Aletheia source packages","maintainers":[{"name":"shiroies","email":"angelocarasig15@gmail.com"}],"readme":"# @aletheia-ios/sdk\n\n[![npm](https://img.shields.io/npm/v/@aletheia-ios/sdk?logo=npm)](https://www.npmjs.com/package/@aletheia-ios/sdk)\n[![ci](https://github.com/aletheia-ios/sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/aletheia-ios/sdk/actions/workflows/ci.yml)\n[![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n[![TypeScript](https://img.shields.io/badge/TypeScript-7-3178c6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)\n\nThe contract for writing a source package for [Aletheia](https://aletheia.moe), an offline-first\nreader for series, manhwa and manhua on iOS.\n\nA source is a small TypeScript program that runs inside the app in JavaScriptCore. It tells the\napp how to search a site, read a series, list its chapters and find its pages. The app does the\nnetworking, the credentials and the HTML parsing; the source describes requests and maps\nresponses.\n\n## Contents\n\n- [Install](#install)\n- [Quick start](#quick-start)\n- [Usage](#usage)\n  - [`types`](#aletheia-iossdktypes)\n  - [`host`](#aletheia-iossdkhost)\n  - [`utils`](#aletheia-iossdkutils)\n  - [`schemas` and `json-schema`](#aletheia-iossdkschemas-and-json-schema)\n- [A package](#a-package)\n- [The runtime](#the-runtime)\n- [Development](#development)\n- [Related](#related)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Install\n\n```sh\npnpm add -D @aletheia-ios/sdk\n```\n\nRequires Node 22 or later to build with. The published output is ES2022 and runs in\nJavaScriptCore on iOS 26.\n\nThere is no root export. Import the subpath you need:\n\n| Subpath | Holds | Reaches the device? |\n|---|---|---|\n| `@aletheia-ios/sdk/types` | the contract as TypeScript types: `Source`, the opt-in interfaces, every DTO | no, types only |\n| `@aletheia-ios/sdk/host` | the bridge to the app: `fetchJSON`, `fetchText`, `request`, `parseHTML`, `markdown` | yes, about 0.7 kB |\n| `@aletheia-ios/sdk/utils` | date parsers, byte codecs, query encoding, `allowsAdult`, `SourceError` | yes, only what you use |\n| `@aletheia-ios/sdk/schemas` | the same contract as Zod schemas, for tooling that validates package files | no, never import this from a source |\n| `@aletheia-ios/sdk/json-schema/*` | JSON Schema for `source.json`, `filters.json`, `auth.json`, `index.json` | no, for your editor |\n\n## Quick start\n\nThe fastest route is [`@aletheia-ios/tools`](https://github.com/aletheia-ios/tools), which\nscaffolds a working package and builds, checks and packs it:\n\n```sh\npnpm add -D @aletheia-ios/sdk @aletheia-ios/tools\npnpm exec aletheia new mysite --name \"My Site\"\npnpm exec aletheia check\n```\n\n`new` gives you an offline source that already passes `check`. Replace `src/index.ts` with a\nreal one:\n\n```ts\nimport { fetchJSON } from \"@aletheia-ios/sdk/host\";\nimport type { Source } from \"@aletheia-ios/sdk/types\";\nimport { allowsAdult, parseISO, withQuery } from \"@aletheia-ios/sdk/utils\";\nimport filters from \"../filters.json\";\nimport manifest from \"../source.json\";\n\nconst source: Source = {\n  async search(query) {\n    const gateOpen = allowsAdult(query, manifest.contentRating, filters);\n    const page = Number(query.cursor ?? \"1\");\n    const listing = await fetchJSON<Listing>({\n      url: withQuery(\"https://api.example.com/series\", { q: query.text ?? \"\", page: String(page), adult: String(gateOpen) }),\n    });\n    return {\n      items: listing.results.map((row) => ({ slug: row.id, title: row.title, cover: row.cover, adult: row.nsfw })),\n      next: listing.hasMore ? String(page + 1) : null,\n    };\n  },\n\n  async details(seriesSlug) {\n    const row = await fetchJSON<Series>({ url: `https://api.example.com/series/${seriesSlug}` });\n    return {\n      slug: row.id,\n      title: row.title,\n      altTitles: row.aliases,\n      synopsis: row.summary,\n      url: `https://example.com/series/${row.id}`,\n      classification: row.nsfw ? \"Explicit\" : \"Safe\",\n      publication: row.ongoing ? \"Ongoing\" : \"Completed\",\n      covers: [row.cover],\n      tags: row.tags,\n      authors: row.authors,\n    };\n  },\n\n  async chapters(seriesSlug) {\n    const rows = await fetchJSON<Chapter[]>({ url: `https://api.example.com/series/${seriesSlug}/chapters` });\n    return rows.map((row) => ({\n      slug: row.id,\n      title: row.title,\n      number: row.number,\n      language: \"en\",\n      scanlator: row.group ?? \"My Site\",\n      url: `https://example.com/read/${row.id}`,\n      publishedDate: parseISO(row.publishedAt),\n    }));\n  },\n\n  async content(_seriesSlug, chapterSlug) {\n    const pages = await fetchJSON<string[]>({ url: `https://api.example.com/chapters/${chapterSlug}/pages` });\n    return pages.map((url, index) => ({ index, url }));\n  },\n};\n\nexport default source;\n```\n\n## Usage\n\n### `@aletheia-ios/sdk/types`\n\nEverything a source implements or returns. Four required calls on `Source`; opt into more by\nintersection, and the app derives what the source can do from which methods the exported\nobject has:\n\n```ts\nimport type { Commenting, Revalidating, Source } from \"@aletheia-ios/sdk/types\";\n\nconst source: Source & Revalidating & Commenting = {\n  // search, details, chapters, content\n  async chaptersChanged(seriesSlug, stored) { /* \"unchanged\" or the full list */ },\n  async comments(seriesSlug, chapterSlug, next) { /* one page of a thread */ },\n};\n```\n\n| Interface | Adds | For |\n|---|---|---|\n| `Commenting` | `comments`, optional `replies` | a site with per-chapter discussion |\n| `Revalidating` | `chaptersChanged` | a site whose feed states a trustworthy total |\n| `ChallengeAware` | `isChallenge` | a site whose 403 is not always a bot wall |\n| `SignedPing` | `pingURL` | a site whose health endpoint needs a signature |\n\nAuthentication is not an interface. A package that needs a credential ships an `auth.json`;\nthe app captures and applies it, and the source never sees it.\n\n### `@aletheia-ios/sdk/host`\n\nThe only way out of the sandbox.\n\n```ts\nimport { fetchJSON, fetchText, markdown, parseHTML, request } from \"@aletheia-ios/sdk/host\";\n\nconst doc = parseHTML(await fetchText({ url }));\nconst rows = doc.select(\"li.chapter\");                  // jsoup dialect: :contains, :has, :containsData\nconst title = doc.first(\"h1\")?.text() ?? \"\";\nconst synopsis = markdown(doc.first(\".summary\")?.data() ?? \"\");\n\nconst raw = await request({ url, credentials: \"omit\" }); // status never throws here\n```\n\n`request` returns the response as-is; `fetchText` and `fetchJSON` throw `SourceError` outside\n2xx. The host applies the credential, enforces the manifest's `hosts` allowlist and rate limit,\nand handles any challenge before the promise resolves. A challenge sheet the reader taps\nthrough happens inside that one `await`.\n\n### `@aletheia-ios/sdk/utils`\n\n| Export | Does |\n|---|---|\n| `parseISO`, `parseDateTime`, `parseMonthName`, `parseRelative`, `parseDate`, `fromEpochSeconds` | site date text to epoch milliseconds, or `null` |\n| `utf8Encode`, `utf8Decode`, `base64Encode`, `base64Decode`, `hexEncode`, `hexDecode` | byte codecs, for signers and decoders |\n| `encodeComponent`, `queryString`, `withQuery`, `formBody`, `FORM_CONTENT_TYPE` | RFC 3986 strict query and form encoding, order preserved |\n| `allowsAdult` | whether the reader opened the adult gate |\n| `SourceError` | the failures the app understands: `noPages`, `notFound`, `badResponse`, `parse` |\n\nEvery export has JSDoc with the reasoning and, where it applies, a link to the spec it\nimplements.\n\n### `@aletheia-ios/sdk/schemas` and `json-schema`\n\nThe contract as Zod, for tooling. `@aletheia-ios/tools` uses it to validate package files; you\ndo not import it from a source. The same schemas are emitted as JSON Schema, so an editor\nvalidates the files as you type:\n\n```json\n{\n  \"$schema\": \"./node_modules/@aletheia-ios/sdk/dist/json-schema/source.json\",\n  \"slug\": \"com.example.mysite\"\n}\n```\n\nA slug is reverse-DNS and namespaced under a domain you control, because it is a global key:\na reader's library rows reference it, so two publishers both shipping `mysite` would collide.\nIf you rename a package, list what it used to be called in `replaces` so existing rows follow\nit rather than being stranded.\n\n## A package\n\nFive files, zipped as `<slug>-v<version>.althsource` by `@aletheia-ios/tools`:\n\n| File | Read by | Holds |\n|---|---|---|\n| `source.json` | the app, without running anything | identity, versions, URLs, `hosts` allowlist, sort, presets, settings |\n| `filters.json` | the app | the Refine sheet |\n| `auth.json` | the app | how to obtain a credential; present only when the site needs one |\n| `icon.png` | the app | 512x512, rasterised from your `icon.svg` |\n| `main.js` | JavaScriptCore | your source, bundled to one file |\n\nOnly `main.js` runs. The Sources screen, filter sheets and preset grids render from the JSON\nalone.\n\n## The runtime\n\nBare JavaScriptCore. No `fetch`, no DOM, no `TextEncoder`, no `atob`, no `URL`, no\n`setTimeout`. This package compiles against `lib: [\"es2022\"]` alone, so reaching for one of\nthose is a type error rather than a crash on a phone. The host installs `console` and one\nobject, `__host`, which `host` wraps. Everything else you need is in `utils`.\n\nRules a source lives by:\n\n- Never call `__host` directly. Use the wrappers so a bridge change never reaches your code.\n- Never catch a rejection from a fetch without rethrowing. A cancelled request must stay cancelled.\n- Return the contract's shapes exactly. The app validates every result; an extra field is\n  ignored, a missing one fails the call.\n- Dates are epoch milliseconds or `null`. Cursors and `PageURL.meta` are opaque strings the app\n  hands back untouched.\n- When `allowsAdult` says the gate is shut, the request must actively exclude adult content.\n  Omitting the parameter is not the same: most sites default an unasked question the other way.\n\n## Development\n\n```sh\npnpm install     # also installs the git hooks\npnpm check       # typecheck, lint, build, test with coverage, publint, attw\npnpm test        # vitest with coverage thresholds\npnpm build       # dist/ plus dist/json-schema/\n```\n\n| Tool | Role |\n|---|---|\n| TypeScript 7 | strict, ES2022, no DOM lib |\n| tsdown | one bundle per subpath, `.d.ts` included, zod left external |\n| Biome | formatting and linting at the `all` preset |\n| vitest | tests beside their modules, v8 coverage with thresholds |\n| lefthook | pre-commit formats staged files and runs typecheck and tests; pre-push runs `pnpm check` |\n\nPublishing is by tag through GitHub's OIDC trusted publishing; no npm token exists.\n\n## Related\n\n| Repository | What |\n|---|---|\n| [aletheia-ios/tools](https://github.com/aletheia-ios/tools) | the `aletheia` CLI: build, check, pack, index, serve, new, live |\n| [aletheia-ios/sample](https://github.com/aletheia-ios/sample) | a real source (MangaDex) and the sample source list |\n\n## Contributing\n\nIssues and pull requests are welcome. Run `pnpm check` before opening one; the hooks run the\nsame checks on commit and push. Commit messages follow Conventional Commits, subject only:\n`feat: add a date format`.\n\n## License\n\n[MIT](LICENSE)\n","readmeFilename":"README.md"}