From 46298a816a68f3ca9108ca29a13c0f966fe1f25e Mon Sep 17 00:00:00 2001 From: Steve Purves Date: Mon, 14 Sep 2026 13:00:47 +0100 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=93=9D=20Document=20headless=20Crossr?= =?UTF-8?q?ef=20SDK=20API=20and=20implementation=20plan?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SDK.md | 394 ++++++++++++++++++++++++++++++++++++++++++++++++++++ plan-sdk.md | 135 ++++++++++++++++++ 2 files changed, 529 insertions(+) create mode 100644 SDK.md create mode 100644 plan-sdk.md diff --git a/SDK.md b/SDK.md new file mode 100644 index 0000000..fe0b1c6 --- /dev/null +++ b/SDK.md @@ -0,0 +1,394 @@ +# crossref-utils SDK + +Developer-facing API for building and validating Crossref deposit XML **in memory**. Intended for serverless and other headless callers. + +Import from the supported entrypoint: + +```ts +import { + buildDeposit, + validateDeposit, + abstractFromMdast, + mystToDepositItem, + generateDoi, + suggestDois, + DOI_PREFIXES, +} from 'crossref-utils/sdk'; +``` + +The package root (`crossref-utils`) continues to export legacy builders used by the CLI. Prefer `crossref-utils/sdk` for new integrations. + +## Design principles + +- **Crossref-oriented DTO** is the primary input — not MyST project paths or filesystem state. +- **No filesystem, no prompts** — callers supply metadata; DOI choice and abstract production happen outside or via helpers. +- **Thin layer over existing XML builders** — `buildDeposit` maps DTOs onto the current `*Xml` / `DoiBatch` implementations. +- **Validation is in-process** — pure JS against Crossref schema 5.3.1; no `xmllint`. + +## Quick start + +```ts +import { + buildDeposit, + validateDeposit, + abstractFromMdast, + generateDoi, +} from 'crossref-utils/sdk'; + +const doi = generateDoi('10.62329'); +const abstractJats = abstractFromMdast(abstractMdast); // processed MyST mdast + +const { xml, batchId } = buildDeposit({ + type: 'preprint', + batch: { + depositor: { name: 'Example Org', email: 'deposits@example.org' }, + }, + items: [ + { + title: 'Example preprint', + date: '2026-01-15', + abstractJats, + doi_data: { + doi, + resource: `https://example.org/articles/${doi}`, + }, + contributors: [ + { + nameParsed: { given: 'Ada', family: 'Lovelace', literal: 'Ada Lovelace' }, + sequence: 'first', + contributor_role: 'author', + }, + ], + }, + ], +}); + +const result = await validateDeposit(xml); +if (!result.ok) { + throw new Error(result.errors.map((e) => e.message).join('\n')); +} +``` + +--- + +## `buildDeposit` + +```ts +function buildDeposit(input: DepositInput): BuildDepositResult; +``` + +```ts +type BuildDepositResult = { + xml: string; + batchId: string; +}; +``` + +Single entrypoint for all deposit kinds. Builds a Crossref `doi_batch` (schema **5.3.1**) and returns serialized XML. + +### `DepositInput` + +```ts +type DepositType = 'journal' | 'conference' | 'preprint' | 'dataset'; + +type DepositInput = { + type: DepositType; + batch: BatchOptions; + /** Used when an item (or venue) omits `doi_data.resource`. */ + resourceResolver?: ResourceResolver; + /** Type-specific container metadata (journal / conference / database). */ + venue?: VenueOptions; + items: DepositItem[]; +}; +``` + +| `type` | `items.length` | `venue` | +|--------|----------------|---------| +| `preprint` | exactly `1` | omitted | +| `journal` | ≥ 1 | journal metadata required | +| `conference` | ≥ 1 | conference + proceedings required | +| `dataset` | ≥ 1 | database title required | + +Missing required fields throw a structured `DepositError` (see [Errors](#errors)). + +### Batch options + +```ts +type BatchOptions = { + depositor: { name: string; email: string }; + /** Defaults to a generated UUID. */ + id?: string; + /** Defaults to `"Crossref"`. */ + registrant?: string; + /** Defaults to `Date.now()`. */ + timestamp?: number; +}; +``` + +### Resource resolution + +Every deposited DOI needs a resolving `resource` URL. The SDK does **not** default to Curvenote URLs. + +Resolution order for each `doi_data`: + +1. Explicit `doi_data.resource` on the item/venue object, or +2. `resourceResolver(context)` if provided, or +3. Error — resource required. + +```ts +type ResourceResolver = (ctx: { + doi: string; + kind: 'item' | 'journal' | 'issue' | 'proceedings' | 'series' | 'database'; + item?: DepositItem; +}) => { resource: string; pdf?: string; xml?: string; zip?: string }; +``` + +### `DepositItem` + +Shared article / paper / dataset record fields: + +```ts +type DepositItem = { + title: string; + subtitle?: string; + /** Crossref subtype where applicable (see below). */ + subtype?: string; + contributors?: ContributorInput[]; + /** Publication / posted date — ISO string or structured date. */ + date?: PublicationDateInput; + license?: string; // URL + funding?: FundrefInput[]; + /** Citations keyed by citation key → DOI only. */ + citations?: Record; + pages?: { first_page: string; last_page?: string; other_pages?: string }; + /** + * Pre-built JATS abstract inner XML or a full `...` + * fragment. Typically produced by `abstractFromMdast`. + */ + abstractJats?: string; + doi_data: DoiDataInput; + /** Dataset only — defaults to `"other"` if omitted. */ + dataset_type?: 'record' | 'collection' | 'crossmark_policy' | 'other'; + /** Dataset relations, etc. — extended in implementation as needed. */ + relations?: RelationInput[]; +}; + +type DoiDataInput = { + doi: string; + resource?: string; + pdf?: string; + xml?: string; + zip?: string; +}; +``` + +### Subtypes + +v1 exposes schema subtypes where Crossref supports them: + +| Deposit type | Field | Examples | +|--------------|--------|----------| +| `preprint` (posted content) | `items[0].subtype` | `preprint`, `working_paper`, `report`, `dissertation`, `other`, … | +| `dataset` | `items[].dataset_type` or `subtype` | `record`, `collection`, `crossmark_policy`, `other` | +| `journal` / `conference` | `items[].subtype` where mapped in schema | documented per builder as implemented | + +Exact allowed enums are typed in the SDK and validated before XML emit. + +### Venue options (sketch) + +```ts +type VenueOptions = + | { + kind: 'journal'; + title: string; + abbrevTitle?: string; + doi_data: DoiDataInput; + issue?: { + volume?: string; + issue?: string; + doi_data?: DoiDataInput; + publication_dates?: PublicationDateInput[]; + }; + } + | { + kind: 'conference'; + event: { + name: string; + acronym?: string; + number?: string | number; + date?: string; + location?: string; + }; + proceedings: { + title: string; + publisher: { name: string }; + publication_date: PublicationDateInput; + subject?: string; + doi_data?: DoiDataInput; + }; + series?: { + title: string; + issn: string; + doi_data?: DoiDataInput; + }; + /** Proceedings editors / chairs. */ + contributors?: ContributorInput[]; + contributor_role?: 'editor' | 'chair'; + } + | { + kind: 'database'; + title: string; + contributors?: ContributorInput[]; + doi_data?: DoiDataInput; + description?: string; + }; +``` + +`venue.kind` should align with `DepositInput.type` (`journal` → `journal`, `conference` → `conference`, `dataset` → `database`). + +--- + +## `validateDeposit` + +```ts +function validateDeposit(xml: string): Promise; +// sync variant may also be exported if the chosen library allows: +// function validateDepositSync(xml: string): ValidationResult; +``` + +```ts +type ValidationResult = { + ok: boolean; + errors: ValidationIssue[]; +}; + +type ValidationIssue = { + message: string; + path?: string; + line?: number; + column?: number; +}; +``` + +- Validates deposit XML **in memory** against Crossref XSD **5.3.1** (or an equivalent JS schema binding). +- No temp files, no `xmllint`, no network required at call time (schema is bundled or loaded from package assets). +- Independent of `buildDeposit` — you may validate XML from any source. + +The legacy CLI `crossref validate` path (`xmllint`) remains for local use; it is not part of `crossref-utils/sdk`. + +--- + +## `abstractFromMdast` + +```ts +function abstractFromMdast(mdast: GenericParent): string; +``` + +Wraps the same pipeline the CLI uses today after mdast is available: + +1. Lightweight transforms (xrefs → links, cites → text, newlines → spaces) +2. `myst-to-jats` serialization +3. Wrap as `jats:abstract`, unwrap `jats:xref` + +**Input:** processed MyST mdast for the abstract part (caller already has this from myst-cli or an equivalent pipeline). +**Output:** JATS XML string suitable for `DepositItem.abstractJats`. + +This helper does **not** load files, run myst-cli, or extract parts from a full document — pass the abstract mdast (or part) you already have. + +```ts +const abstractJats = abstractFromMdast(abstractPart); +items[0].abstractJats = abstractJats; +``` + +--- + +## DOI helpers + +### `generateDoi` + +```ts +function generateDoi(prefix: string): string; +``` + +Generates one DOI: `{prefix}/{4 letters}{4 digits}` using an unambiguous alphabet (same logic as today). + +`prefix` may be a numeric prefix (`10.62329`) or a known alias from `DOI_PREFIXES`. + +### `suggestDois` + +```ts +function suggestDois(count: number, prefix: string): string[]; +``` + +Non-interactive helper: returns `count` candidate DOIs via repeated `generateDoi`. Use this when a UI or upstream service wants choices without inquirer. + +```ts +const candidates = suggestDois(6, 'curvenote'); +// pick one, then set item.doi_data.doi +``` + +Interactive checkbox selection stays CLI-only (`selectNewDois`). + +### `DOI_PREFIXES` + +```ts +const DOI_PREFIXES: Readonly>; +// e.g. { curvenote: '10.62329', msa: '10.69761', scipy: '10.25080', physiome: '10.36903' } +``` + +Aliases accepted by `generateDoi` / `suggestDois`. Unknown strings are treated as literal prefixes. + +--- + +## `mystToDepositItem` (optional adapter) + +```ts +function mystToDepositItem( + myst: MystFrontmatterLike, + opts?: MystAdapterOptions, +): DepositItemPartial; +``` + +Maps MyST frontmatter-shaped JSON (in memory) into Crossref DTO fields: title, subtitle, contributors, dates, license URL, funding, pages, DOI when present. + +- Does **not** touch the filesystem. +- Does **not** build abstracts — set `abstractJats` yourself (e.g. via `abstractFromMdast`). +- Does **not** invent `resource` URLs — combine with `resourceResolver` or set `doi_data` explicitly. +- Venue/journal/conference container fields may be exposed as a sibling helper (`mystToVenue`) if mapping is non-trivial; v1 documents the item mapper first. + +This adapter is convenience only. The canonical SDK input remains the Crossref DTO. + +--- + +## Errors + +```ts +class DepositError extends Error { + issues: { code: string; message: string; path?: string }[]; +} +``` + +Thrown by `buildDeposit` for missing required fields, invalid item counts, unresolved resources, or unknown subtypes. `validateDeposit` does not throw for schema failures — it returns `{ ok: false, errors }`. + +--- + +## What the SDK does not do + +- Read `myst.yml` / project paths from disk +- Run myst-cli project loading or part extraction +- Prompt for depositor info or DOI selection +- Write DOIs back into config files +- Shell out to `xmllint` + +Those remain CLI concerns. A later iteration may rewire the CLI to build DTOs and call this SDK. + +--- + +## Relationship to legacy exports + +| Surface | Role | +|---------|------| +| `crossref-utils/sdk` | Supported headless API | +| `crossref-utils` (root) | Legacy `*Xml` builders, `DoiBatch`, `generateDoi`, reader, CLI-oriented validate | + +New applications should depend only on `crossref-utils/sdk`. diff --git a/plan-sdk.md b/plan-sdk.md new file mode 100644 index 0000000..3a4e166 --- /dev/null +++ b/plan-sdk.md @@ -0,0 +1,135 @@ +# Plan: Headless Crossref SDK + +Implementation plan for the developer API documented in [`SDK.md`](./SDK.md). + +## Goals + +- Expose an in-memory SDK at `crossref-utils/sdk` for serverless / headless callers. +- Primary input: Crossref-oriented DTO → deposit XML via a thin facade over existing builders. +- Separately validate XML in-process (no `xmllint`, no filesystem). +- Helpers: `abstractFromMdast`, `generateDoi`, `suggestDois`, optional `mystToDepositItem`. +- Leave CLI behavior working; do not rewrite CLI onto the SDK in this cut (optional follow-up). + +## Non-goals (v1) + +- Interactive DOI selection / inquirer +- Path discovery, writing DOIs into `myst.yml` +- Full myst-cli loading inside the SDK +- Changing Crossref schema version (stay on 5.3.1) + +## Approach + +**Thin facade (recommended):** new `src/sdk/` maps DTOs → existing `journalXml` / `conferenceXml` / `preprintXml` / `databaseXml` + `DoiBatch`. Extract shared helpers (`abstractFromMdast`, DOI prefix map) from CLI into library modules the SDK re-exports. Avoid duplicating XML construction. + +--- + +## Workstreams + +### 1. Package surface + +- Add `src/sdk/index.ts` as the SDK entry. +- Update `package.json` `exports`: + - `"."` — existing root (legacy) + - `"./sdk"` — SDK entry + types +- Ensure ESM build emits `dist/sdk/index.js` and `.d.ts`. +- Do not bundle Node-only CLI deps into the SDK entry if avoidable (keep `inquirer` / path discovery out of `src/sdk`). + +### 2. DTO types + +- Add `src/sdk/types.ts` (or `src/sdk/dto.ts`) matching `SDK.md`: + - `DepositInput`, `DepositItem`, `BatchOptions`, `VenueOptions`, `DoiDataInput`, etc. +- Type subtype enums for posted content and `dataset_type`. +- Define `DepositError` + issue shape. + +### 3. Resource resolution + +- Implement `resolveDoiData(doi_data, resourceResolver, ctx)` used by `buildDeposit`. +- Remove Curvenote URL defaults from the **SDK path** (legacy `*FromMyst` / CLI may keep current behavior until a later migration). +- Prefer passing explicit `doi_data` into existing `*Xml` builders rather than going through `*FromMyst` where those hardcode Curvenote resources. + +### 4. `buildDeposit` + +- Implement `src/sdk/buildDeposit.ts`: + - Validate item counts and required venue fields per `type`. + - Map contributors / funding / dates / citations / `abstractJats` → structures expected by existing builders. + - Parse `abstractJats` string into XAST (`jats:abstract`) for builders that expect an `Element`. + - Set posted-content / dataset subtype attributes on emit. + - Wrap body in `DoiBatch`, return `{ xml, batchId }`. +- Unit tests per deposit type (fixtures of DTO → XML snapshots or selective XPath/string asserts). + +### 5. `abstractFromMdast` + +- Move CLI abstract pipeline pieces used after mdast exists into a shared module (e.g. `src/abstract.ts`): + - transforms from `src/cli/utils.ts` that are FS-free + - `JatsSerializer` + wrap / unwrap xref +- Export `abstractFromMdast` from SDK. +- Keep CLI `depositArticleFromSource` calling the shared helper (small refactor, behavior unchanged). +- Tests with a minimal mdast fixture. + +### 6. DOI helpers + +- Keep `generateDoi` in `src/utils.ts`. +- Move `DOI_PREFIXES` (today’s CLI `PREFIX` map) to a shared module (e.g. `src/doi.ts`). +- Add `suggestDois(count, prefix)` (non-interactive array of `generateDoi`). +- Resolve aliases inside `generateDoi` or a thin `resolvePrefix` used by both helpers. +- Re-export from `crossref-utils/sdk`; root may continue exporting `generateDoi`. +- CLI `generate` / `selectNewDois` import shared prefix map + `generateDoi` (selection stays in CLI). + +### 7. `validateDeposit` + +- Add in-memory validation module under `src/sdk/validate.ts` (or `src/validateMemory.ts`). +- Bundle or ship Crossref 5.3.1 XSD (or a maintained JS binding) as package assets. +- Choose a serverless-friendly approach (evaluate in implementation spike): + - Prefer a pure-JS XML + XSD validator that works without native bindings, **or** + - Structural validation + well-formedness if full XSD proves impractical in v1 — document any gap vs `xmllint` in `SDK.md`. +- API: `validateDeposit(xml) → Promise` with `{ ok, errors[] }`. +- Leave existing `xmllint` helpers for CLI; do not export them from `/sdk`. +- Tests: known-good deposit XML passes; deliberately broken XML fails with messages. + +### 8. `mystToDepositItem` + +- Adapter in `src/sdk/myst.ts`: map frontmatter-like JSON → `DepositItem` fields (title, authors→contributors, license URL, funding, pages, doi). +- No abstract, no resource URL invention, no FS. +- Optionally stub/document `mystToVenue` if journal/conference venue mapping is needed immediately; otherwise follow-up. +- Tests with sample frontmatter JSON. + +### 9. Docs & changelog + +- Keep [`SDK.md`](./SDK.md) as the developer contract; update enums/field names if implementation discovers schema mismatches. +- Add a short pointer in `README.md` to `SDK.md` / `crossref-utils/sdk`. +- Changeset noting new SDK export and helpers. + +--- + +## Suggested implementation order + +1. Package `exports` + empty SDK barrel +2. DOI helpers (`DOI_PREFIXES`, `suggestDois`) + tests +3. Extract `abstractFromMdast` + wire CLI to shared helper +4. DTO types + `buildDeposit` for `preprint`, then `journal`, `conference`, `dataset` +5. `validateDeposit` spike → implement +6. `mystToDepositItem` +7. README + changeset + +## Test plan + +- [ ] `generateDoi` / `suggestDois` / prefix aliases +- [ ] `abstractFromMdast` golden mdast → JATS string +- [ ] `buildDeposit` for each of the four types (required fields, multi-item journal/conference) +- [ ] Preprint rejects `items.length !== 1` +- [ ] Missing `resource` without resolver throws `DepositError` +- [ ] Subtypes appear on emitted XML +- [ ] `validateDeposit` accept/reject fixtures +- [ ] `mystToDepositItem` maps authors, doi, license; leaves abstract unset +- [ ] Existing CLI deposit / unit tests still pass + +## Follow-ups (out of this plan) + +- Rewire CLI `deposit` to build DTOs and call `buildDeposit` +- Remove Curvenote defaults from legacy `*FromMyst` once CLI uses `resourceResolver` +- Richer venue mapper from MyST project JSON +- Sync `validateDeposit` if the chosen library allows + +## Open implementation detail + +**XSD-in-JS library choice** — confirm during workstream 7 which dependency satisfies: Crossref 5.3.1, no native addon, works in typical serverless Node runtimes. If full XSD is blocked, ship well-formedness + required-element checks in v1 and note the limitation in `SDK.md`. From 06fd88bd2fa804e57a9a6406b6330d1972f642a4 Mon Sep 17 00:00:00 2001 From: Steve Purves Date: Tue, 15 Sep 2026 17:15:03 +0100 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=93=9D=20Revise=20SDK=20plan=20for=20?= =?UTF-8?q?monorepo=20split=20per=20PR=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SDK.md | 417 +++++++++++++--------------------------------------- plan-sdk.md | 193 ++++++++++++------------ 2 files changed, 200 insertions(+), 410 deletions(-) diff --git a/SDK.md b/SDK.md index fe0b1c6..408c9a6 100644 --- a/SDK.md +++ b/SDK.md @@ -1,394 +1,179 @@ -# crossref-utils SDK +# crossref-utils (library API) -Developer-facing API for building and validating Crossref deposit XML **in memory**. Intended for serverless and other headless callers. - -Import from the supported entrypoint: +Developer-facing docs for the **in-memory** Crossref library after the planned monorepo split. See [`plan-sdk.md`](./plan-sdk.md) for the implementation plan. ```ts import { - buildDeposit, - validateDeposit, + DoiBatch, + journalXml, + journalArticleFromMyst, + preprintXml, + preprintFromMyst, + conferenceXml, + conferencePaperFromMyst, + databaseXml, + datasetFromMyst, abstractFromMdast, - mystToDepositItem, generateDoi, suggestDois, - DOI_PREFIXES, -} from 'crossref-utils/sdk'; -``` - -The package root (`crossref-utils`) continues to export legacy builders used by the CLI. Prefer `crossref-utils/sdk` for new integrations. - -## Design principles - -- **Crossref-oriented DTO** is the primary input — not MyST project paths or filesystem state. -- **No filesystem, no prompts** — callers supply metadata; DOI choice and abstract production happen outside or via helpers. -- **Thin layer over existing XML builders** — `buildDeposit` maps DTOs onto the current `*Xml` / `DoiBatch` implementations. -- **Validation is in-process** — pure JS against Crossref schema 5.3.1; no `xmllint`. - -## Quick start - -```ts -import { - buildDeposit, validateDeposit, - abstractFromMdast, - generateDoi, -} from 'crossref-utils/sdk'; - -const doi = generateDoi('10.62329'); -const abstractJats = abstractFromMdast(abstractMdast); // processed MyST mdast - -const { xml, batchId } = buildDeposit({ - type: 'preprint', - batch: { - depositor: { name: 'Example Org', email: 'deposits@example.org' }, - }, - items: [ - { - title: 'Example preprint', - date: '2026-01-15', - abstractJats, - doi_data: { - doi, - resource: `https://example.org/articles/${doi}`, - }, - contributors: [ - { - nameParsed: { given: 'Ada', family: 'Lovelace', literal: 'Ada Lovelace' }, - sequence: 'first', - contributor_role: 'author', - }, - ], - }, - ], -}); - -const result = await validateDeposit(xml); -if (!result.ok) { - throw new Error(result.errors.map((e) => e.message).join('\n')); -} -``` - ---- - -## `buildDeposit` - -```ts -function buildDeposit(input: DepositInput): BuildDepositResult; -``` - -```ts -type BuildDepositResult = { - xml: string; - batchId: string; -}; -``` - -Single entrypoint for all deposit kinds. Builds a Crossref `doi_batch` (schema **5.3.1**) and returns serialized XML. - -### `DepositInput` - -```ts -type DepositType = 'journal' | 'conference' | 'preprint' | 'dataset'; - -type DepositInput = { - type: DepositType; - batch: BatchOptions; - /** Used when an item (or venue) omits `doi_data.resource`. */ - resourceResolver?: ResourceResolver; - /** Type-specific container metadata (journal / conference / database). */ - venue?: VenueOptions; - items: DepositItem[]; -}; +} from 'crossref-utils'; ``` -| `type` | `items.length` | `venue` | -|--------|----------------|---------| -| `preprint` | exactly `1` | omitted | -| `journal` | ≥ 1 | journal metadata required | -| `conference` | ≥ 1 | conference + proceedings required | -| `dataset` | ≥ 1 | database title required | - -Missing required fields throw a structured `DepositError` (see [Errors](#errors)). +CLI (filesystem, myst-cli, prompts) lives in a separate package (`crossref-cli`) and depends on this library. Prefer importing **`crossref-utils` only** from serverless / headless code. -### Batch options +## Architecture -```ts -type BatchOptions = { - depositor: { name: string; email: string }; - /** Defaults to a generated UUID. */ - id?: string; - /** Defaults to `"Crossref"`. */ - registrant?: string; - /** Defaults to `Date.now()`. */ - timestamp?: number; -}; ``` - -### Resource resolution - -Every deposited DOI needs a resolving `resource` URL. The SDK does **not** default to Curvenote URLs. - -Resolution order for each `doi_data`: - -1. Explicit `doi_data.resource` on the item/venue object, or -2. `resourceResolver(context)` if provided, or -3. Error — resource required. - -```ts -type ResourceResolver = (ctx: { - doi: string; - kind: 'item' | 'journal' | 'issue' | 'proceedings' | 'series' | 'database'; - item?: DepositItem; -}) => { resource: string; pdf?: string; xml?: string; zip?: string }; -``` - -### `DepositItem` - -Shared article / paper / dataset record fields: - -```ts -type DepositItem = { - title: string; - subtitle?: string; - /** Crossref subtype where applicable (see below). */ - subtype?: string; - contributors?: ContributorInput[]; - /** Publication / posted date — ISO string or structured date. */ - date?: PublicationDateInput; - license?: string; // URL - funding?: FundrefInput[]; - /** Citations keyed by citation key → DOI only. */ - citations?: Record; - pages?: { first_page: string; last_page?: string; other_pages?: string }; - /** - * Pre-built JATS abstract inner XML or a full `...` - * fragment. Typically produced by `abstractFromMdast`. - */ - abstractJats?: string; - doi_data: DoiDataInput; - /** Dataset only — defaults to `"other"` if omitted. */ - dataset_type?: 'record' | 'collection' | 'crossmark_policy' | 'other'; - /** Dataset relations, etc. — extended in implementation as needed. */ - relations?: RelationInput[]; -}; - -type DoiDataInput = { - doi: string; - resource?: string; - pdf?: string; - xml?: string; - zip?: string; -}; +┌─────────────────────────────────────────────────────────┐ +│ Callers (serverless, CLI, future tools) │ +└───────────────┬─────────────────────────┬───────────────┘ + │ │ + ▼ ▼ +┌───────────────────────────┐ ┌─────────────────────────┐ +│ Adapters (format → DTO) │ │ Crossref core │ +│ *FromMyst today │ │ types + *Xml │ +│ *FromOther later │──▶│ DoiBatch, dates, … │ +└───────────────────────────┘ │ abstractFromMdast │ + │ generateDoi / suggest │ + │ validateDeposit │ + └─────────────────────────┘ ``` -### Subtypes +**Important separation:** MyST (or any other) *content processing* stays outside the Crossref core. Adapters only map already-structured frontmatter (and helpers like `abstractFromMdast` for processed mdast) into Crossref shapes. That leaves room for other X→Crossref parsers without bloating the core. -v1 exposes schema subtypes where Crossref supports them: +There is **no** separate `buildDeposit` facade or new deposit DTO layer — use the existing Crossref types + `*Xml`, and/or `*FromMyst`. -| Deposit type | Field | Examples | -|--------------|--------|----------| -| `preprint` (posted content) | `items[0].subtype` | `preprint`, `working_paper`, `report`, `dissertation`, `other`, … | -| `dataset` | `items[].dataset_type` or `subtype` | `record`, `collection`, `crossmark_policy`, `other` | -| `journal` / `conference` | `items[].subtype` where mapped in schema | documented per builder as implemented | +--- -Exact allowed enums are typed in the SDK and validated before XML emit. +## Inputs (two existing paths) -### Venue options (sketch) +### 1. Crossref DTOs → `*Xml` -```ts -type VenueOptions = - | { - kind: 'journal'; - title: string; - abbrevTitle?: string; - doi_data: DoiDataInput; - issue?: { - volume?: string; - issue?: string; - doi_data?: DoiDataInput; - publication_dates?: PublicationDateInput[]; - }; - } - | { - kind: 'conference'; - event: { - name: string; - acronym?: string; - number?: string | number; - date?: string; - location?: string; - }; - proceedings: { - title: string; - publisher: { name: string }; - publication_date: PublicationDateInput; - subject?: string; - doi_data?: DoiDataInput; - }; - series?: { - title: string; - issn: string; - doi_data?: DoiDataInput; - }; - /** Proceedings editors / chairs. */ - contributors?: ContributorInput[]; - contributor_role?: 'editor' | 'chair'; - } - | { - kind: 'database'; - title: string; - contributors?: ContributorInput[]; - doi_data?: DoiDataInput; - description?: string; - }; -``` +Use types from this package (`JournalArticle`, `Preprint`, `ConferenceOptions`, `DatasetMetadata`, `DoiBatchOptions`, …) and builders: -`venue.kind` should align with `DepositInput.type` (`journal` → `journal`, `conference` → `conference`, `dataset` → `database`). +- `journalXml` / `journalArticleXml` / … +- `conferenceXml` / `conferencePaperXml` +- `preprintXml` +- `databaseXml` / `datasetXml` +- `DoiBatch` → `.toXml()` ---- +Suitable when the caller already thinks in Crossref terms, or after any future non-MyST adapter. -## `validateDeposit` +### 2. MyST frontmatter → `*FromMyst` (adapter) -```ts -function validateDeposit(xml: string): Promise; -// sync variant may also be exported if the chosen library allows: -// function validateDepositSync(xml: string): ValidationResult; -``` +Pass `myst-frontmatter` objects (in memory) into `journalArticleFromMyst`, `preprintFromMyst`, `conferencePaperFromMyst`, `datasetFromMyst`, contributor helpers, etc. -```ts -type ValidationResult = { - ok: boolean; - errors: ValidationIssue[]; -}; - -type ValidationIssue = { - message: string; - path?: string; - line?: number; - column?: number; -}; -``` - -- Validates deposit XML **in memory** against Crossref XSD **5.3.1** (or an equivalent JS schema binding). -- No temp files, no `xmllint`, no network required at call time (schema is bundled or loaded from package assets). -- Independent of `buildDeposit` — you may validate XML from any source. +- No filesystem; no myst-cli `Session` required for project loading. +- Logging via a small **logger** interface (not a full MyST session). +- **DOI `resource` URLs are not defaulted to Curvenote** — supply `doi_data` explicitly or use an injectable resolver where provided (e.g. dataset path today). -The legacy CLI `crossref validate` path (`xmllint`) remains for local use; it is not part of `crossref-utils/sdk`. +Abstracts are **not** produced from markdown here. Build abstract JATS via `abstractFromMdast` (below), then pass the resulting element/string into the paper options / wire through the adapter as implemented. --- ## `abstractFromMdast` ```ts -function abstractFromMdast(mdast: GenericParent): string; +function abstractFromMdast(mdast: GenericParent): Element; // or string — finalize in implementation ``` -Wraps the same pipeline the CLI uses today after mdast is available: +Caller supplies **processed MyST mdast** for the abstract part (from myst-cli or an equivalent pipeline). The library applies the same light transforms as today’s CLI (xrefs→links, cites→text, newlines→spaces), serializes with `myst-to-jats`, and wraps as `jats:abstract`. -1. Lightweight transforms (xrefs → links, cites → text, newlines → spaces) -2. `myst-to-jats` serialization -3. Wrap as `jats:abstract`, unwrap `jats:xref` - -**Input:** processed MyST mdast for the abstract part (caller already has this from myst-cli or an equivalent pipeline). -**Output:** JATS XML string suitable for `DepositItem.abstractJats`. - -This helper does **not** load files, run myst-cli, or extract parts from a full document — pass the abstract mdast (or part) you already have. +Out of scope for this helper: loading projects, `parseMyst`, extracting parts from a full document. ```ts -const abstractJats = abstractFromMdast(abstractPart); -items[0].abstractJats = abstractJats; +const abstract = abstractFromMdast(abstractMdast); +const article = journalArticleFromMyst(logger, frontmatter, citations, abstract); ``` --- ## DOI helpers -### `generateDoi` - ```ts function generateDoi(prefix: string): string; +function suggestDois(count: number, prefix: string): string[]; ``` -Generates one DOI: `{prefix}/{4 letters}{4 digits}` using an unambiguous alphabet (same logic as today). - -`prefix` may be a numeric prefix (`10.62329`) or a known alias from `DOI_PREFIXES`. +- `prefix` is a **numeric DOI prefix** (e.g. `10.62329`). No built-in Curvenote/org alias map in this library — pass prefixes from your config. +- `generateDoi` → `{prefix}/{4 letters}{4 digits}` (unambiguous alphabet). +- `suggestDois` → `count` candidates for a UI or review step. -### `suggestDois` +**Always human-review** generated DOIs before submitting to Crossref (avoid accidental slur-like or awkward strings). Interactive checkbox selection remains a CLI/caller concern, not part of this package. -```ts -function suggestDois(count: number, prefix: string): string[]; -``` +--- -Non-interactive helper: returns `count` candidate DOIs via repeated `generateDoi`. Use this when a UI or upstream service wants choices without inquirer. +## `validateDeposit` ```ts -const candidates = suggestDois(6, 'curvenote'); -// pick one, then set item.doi_data.doi +function validateDeposit(xml: string): Promise; + +type ValidationResult = { + ok: boolean; + errors: { message: string; path?: string; line?: number; column?: number }[]; +}; ``` -Interactive checkbox selection stays CLI-only (`selectNewDois`). +Validates deposit XML **in process** against Crossref schema **5.3.1** (bundled assets; no `xmllint`, no temp-file requirement for the happy path). Usable from serverless. -### `DOI_PREFIXES` +Independent of builders — validate XML from `DoiBatch.toXml()` or any other source. -```ts -const DOI_PREFIXES: Readonly>; -// e.g. { curvenote: '10.62329', msa: '10.69761', scipy: '10.25080', physiome: '10.36903' } -``` - -Aliases accepted by `generateDoi` / `suggestDois`. Unknown strings are treated as literal prefixes. +If a pure-JS XSD engine cannot be maintained, the API still exists with a documented backend (e.g. external service); library callers should not depend on shelling to `xmllint`. --- -## `mystToDepositItem` (optional adapter) +## Typical headless flow ```ts -function mystToDepositItem( - myst: MystFrontmatterLike, - opts?: MystAdapterOptions, -): DepositItemPartial; -``` - -Maps MyST frontmatter-shaped JSON (in memory) into Crossref DTO fields: title, subtitle, contributors, dates, license URL, funding, pages, DOI when present. - -- Does **not** touch the filesystem. -- Does **not** build abstracts — set `abstractJats` yourself (e.g. via `abstractFromMdast`). -- Does **not** invent `resource` URLs — combine with `resourceResolver` or set `doi_data` explicitly. -- Venue/journal/conference container fields may be exposed as a sibling helper (`mystToVenue`) if mapping is non-trivial; v1 documents the item mapper first. +import { + DoiBatch, + preprintFromMyst, + abstractFromMdast, + generateDoi, + validateDeposit, +} from 'crossref-utils'; -This adapter is convenience only. The canonical SDK input remains the Crossref DTO. +const doi = generateDoi(process.env.DOI_PREFIX!); // review before use +const abstract = abstractFromMdast(abstractMdast); ---- +const body = preprintFromMyst(logger, mystFrontmatter, citations, abstract); +// ensure doi_data.resource is set for your host — no Curvenote default -## Errors +const batch = new DoiBatch( + { + id: crypto.randomUUID(), + depositor: { name: 'Example', email: 'deposits@example.org' }, + }, + body, +); -```ts -class DepositError extends Error { - issues: { code: string; message: string; path?: string }[]; -} +const xml = batch.toXml(); +const { ok, errors } = await validateDeposit(xml); +if (!ok) throw new Error(errors.map((e) => e.message).join('\n')); ``` -Thrown by `buildDeposit` for missing required fields, invalid item counts, unresolved resources, or unknown subtypes. `validateDeposit` does not throw for schema failures — it returns `{ ok: false, errors }`. +Multi-article journal/conference deposits: build venue/issue XML with existing helpers, map each item with `*FromMyst` or `*Xml`, assemble body, wrap in `DoiBatch`. --- -## What the SDK does not do +## What belongs in `crossref-cli` (not this library) -- Read `myst.yml` / project paths from disk -- Run myst-cli project loading or part extraction -- Prompt for depositor info or DOI selection -- Write DOIs back into config files -- Shell out to `xmllint` +- Path discovery, reading `myst.yml` / pages from disk +- myst-cli `Session`, `getFileContent`, part extraction from projects +- `parseMyst` for frontmatter abstract strings +- inquirer prompts (deposit type, depositor, DOI checkbox selection) +- Writing DOIs back into config files -Those remain CLI concerns. A later iteration may rewire the CLI to build DTOs and call this SDK. +The CLI should call into `crossref-utils` for XML build, abstract mdast→JATS, DOI string generation, and validation. --- -## Relationship to legacy exports +## Package relationship -| Surface | Role | +| Package | Role | |---------|------| -| `crossref-utils/sdk` | Supported headless API | -| `crossref-utils` (root) | Legacy `*Xml` builders, `DoiBatch`, `generateDoi`, reader, CLI-oriented validate | +| `crossref-utils` | In-memory Crossref core + MyST adapter + validate | +| `crossref-cli` | `crossref` binary; FS + interactive workflows | -New applications should depend only on `crossref-utils/sdk`. +Root import of `crossref-utils` is the supported library surface (no `/sdk` subpath required). diff --git a/plan-sdk.md b/plan-sdk.md index 3a4e166..0fcecd9 100644 --- a/plan-sdk.md +++ b/plan-sdk.md @@ -1,135 +1,140 @@ -# Plan: Headless Crossref SDK +# Plan: Package split for headless Crossref utils -Implementation plan for the developer API documented in [`SDK.md`](./SDK.md). +Revised implementation plan after [PR #27 review](https://github.com/continuous-foundation/crossref-utils/pull/27) (Franklin Koch). Aligns with [`SDK.md`](./SDK.md). ## Goals -- Expose an in-memory SDK at `crossref-utils/sdk` for serverless / headless callers. -- Primary input: Crossref-oriented DTO → deposit XML via a thin facade over existing builders. -- Separately validate XML in-process (no `xmllint`, no filesystem). -- Helpers: `abstractFromMdast`, `generateDoi`, `suggestDois`, optional `mystToDepositItem`. -- Leave CLI behavior working; do not rewrite CLI onto the SDK in this cut (optional follow-up). +1. **Split the repo into two packages** (JATS-style monorepo): + - `packages/crossref-utils` — lightweight, in-memory library (the “SDK”) + - `packages/crossref-cli` — filesystem, myst-cli session, inquirer; depends on `-utils` +2. **Keep existing DTOs and APIs** — Crossref types + `*Xml` builders, and MyST frontmatter + `*FromMyst` adapters. No new `DepositInput` / `buildDeposit` facade. +3. **Maintain Crossref vs content-format separation** — core is format-agnostic; MyST is one adapter; future `*FromOther` helpers can map other frontmatter/layouts into Crossref DTOs without growing the core. +4. **Ship in-process XML validation** on `crossref-utils` (replace or supersede CLI-only `xmllint` for library callers). +5. **Remove Curvenote-specific hardcoding** from the library (resource URL defaults, DOI prefix alias map in code). -## Non-goals (v1) +## Non-goals -- Interactive DOI selection / inquirer -- Path discovery, writing DOIs into `myst.yml` -- Full myst-cli loading inside the SDK +- New Crossref-oriented deposit DTO layer / single `buildDeposit` entrypoint +- Shipping myst-cli / `parseMyst` / markdown pipelines inside `crossref-utils` +- Interactive DOI checkbox UI inside `-utils` (stays CLI or caller-owned) - Changing Crossref schema version (stay on 5.3.1) -## Approach +## Approach (Franklin’s three steps) -**Thin facade (recommended):** new `src/sdk/` maps DTOs → existing `journalXml` / `conferenceXml` / `preprintXml` / `databaseXml` + `DoiBatch`. Extract shared helpers (`abstractFromMdast`, DOI prefix map) from CLI into library modules the SDK re-exports. Avoid duplicating XML construction. +> Gut check: easier than a long facade plan — an initial package split gets ~90% of the way there. + +1. Tear the single package into `-cli` (FS + interactivity) and `-utils` (everything else). Keep interfaces mostly unchanged. +2. Massage details: decouple abstract *extraction* (CLI / upstream MyST) from light mdast→JATS transforms (utils); swap MyST `Session` for a logger interface on adapters; injectable DOI resource resolution. +3. Land TS-native (or otherwise in-process) XSD validation on `-utils`; retire reliance on `xmllint` for the library API. --- -## Workstreams +## Target layout + +``` +packages/ + crossref-utils/ # published as crossref-utils + src/ + # Crossref core + batch.ts, types.ts, dates.ts, contributors.ts, funding.ts, … + journal.ts, conference.ts, preprint.ts, dataset.ts + abstract.ts # mdast → jats:abstract (transforms + myst-to-jats) + doi.ts # generateDoi, suggestDois (prefix always passed in) + validate.ts # in-process XSD validate + fromMyst/ # adapter layer (not “core”) + … *FromMyst helpers, Session→logger refactors + crossref-cli/ # published as crossref-cli (bin: crossref) + src/ + deposit.ts, generate.ts, parse.ts, validate.ts (CLI wrappers) + # FS discovery, inquirer, myst-cli load/extract, write-back to myst.yml +``` + +Published names can stay `crossref-utils` for the library; CLI package name TBD (`crossref-cli` vs keeping a single npm name that re-exports — decide at implement time to minimize breakages). -### 1. Package surface +--- -- Add `src/sdk/index.ts` as the SDK entry. -- Update `package.json` `exports`: - - `"."` — existing root (legacy) - - `"./sdk"` — SDK entry + types -- Ensure ESM build emits `dist/sdk/index.js` and `.d.ts`. -- Do not bundle Node-only CLI deps into the SDK entry if avoidable (keep `inquirer` / path discovery out of `src/sdk`). +## Workstreams -### 2. DTO types +### 1. Monorepo split (first, high leverage) -- Add `src/sdk/types.ts` (or `src/sdk/dto.ts`) matching `SDK.md`: - - `DepositInput`, `DepositItem`, `BatchOptions`, `VenueOptions`, `DoiDataInput`, etc. -- Type subtype enums for posted content and `dataset_type`. -- Define `DepositError` + issue shape. +- Introduce workspace (`packages/*`) following continuous-foundation JATS / similar monorepos. +- Move existing library modules → `crossref-utils`; CLI (`src/cli/**`, bin) → `crossref-cli`. +- `-cli` depends on `-utils`; `-utils` must not depend on commander/inquirer/myst-cli FS workflows. +- Keep `myst-frontmatter` / `myst-to-jats` / light myst types only where needed (adapters + abstract helper). +- Green existing tests; CLI smoke still works via workspace link. -### 3. Resource resolution +### 2. Core vs MyST adapter boundary -- Implement `resolveDoiData(doi_data, resourceResolver, ctx)` used by `buildDeposit`. -- Remove Curvenote URL defaults from the **SDK path** (legacy `*FromMyst` / CLI may keep current behavior until a later migration). -- Prefer passing explicit `doi_data` into existing `*Xml` builders rather than going through `*FromMyst` where those hardcode Curvenote resources. +**Core (`crossref-utils`):** Crossref DTOs (`types.ts`), `*Xml`, `DoiBatch`, contributors/dates/funding XML, DOI helpers, validate, `abstractFromMdast`. -### 4. `buildDeposit` +**Adapter (`crossref-utils/fromMyst` or equivalent exports):** `*FromMyst` — maps `myst-frontmatter` → Crossref DTOs / elements. Document as the MyST adapter, not the only input path. -- Implement `src/sdk/buildDeposit.ts`: - - Validate item counts and required venue fields per `type`. - - Map contributors / funding / dates / citations / `abstractJats` → structures expected by existing builders. - - Parse `abstractJats` string into XAST (`jats:abstract`) for builders that expect an `Element`. - - Set posted-content / dataset subtype attributes on emit. - - Wrap body in `DoiBatch`, return `{ xml, batchId }`. -- Unit tests per deposit type (fixtures of DTO → XML snapshots or selective XPath/string asserts). +**Future:** other X→Crossref adapters (same package or later packages) map into core DTOs + `*Xml`. Do not fold foreign pipelines into core. -### 5. `abstractFromMdast` +Refactor `*FromMyst` / `fundrefFromMyst` to take a **logger** (or `Pick`) instead of a full myst `Session`. -- Move CLI abstract pipeline pieces used after mdast exists into a shared module (e.g. `src/abstract.ts`): - - transforms from `src/cli/utils.ts` that are FS-free - - `JatsSerializer` + wrap / unwrap xref -- Export `abstractFromMdast` from SDK. -- Keep CLI `depositArticleFromSource` calling the shared helper (small refactor, behavior unchanged). -- Tests with a minimal mdast fixture. +### 3. Abstracts: mdast in, JATS out -### 6. DOI helpers +Agreed direction (Franklin option 2 + prior spike): -- Keep `generateDoi` in `src/utils.ts`. -- Move `DOI_PREFIXES` (today’s CLI `PREFIX` map) to a shared module (e.g. `src/doi.ts`). -- Add `suggestDois(count, prefix)` (non-interactive array of `generateDoi`). -- Resolve aliases inside `generateDoi` or a thin `resolvePrefix` used by both helpers. -- Re-export from `crossref-utils/sdk`; root may continue exporting `generateDoi`. -- CLI `generate` / `selectNewDois` import shared prefix map + `generateDoi` (selection stays in CLI). +- Upstream (CLI or serverless caller) owns myst processing and supplies **processed abstract mdast**. +- `-utils` exposes **`abstractFromMdast(mdast)`** — FS-free transforms + `myst-to-jats` + `jats:abstract` wrap (logic lifted from today’s CLI). +- CLI: split `depositArticleFromSource` — extract/load with myst-cli in CLI; call `abstractFromMdast` from utils. +- Do **not** ship `parseMyst` in `-utils` (avoids pulling full MyST into the library). +- Plain-text-only abstract (lossy option 3) is out of scope as the primary path. -### 7. `validateDeposit` +### 4. DOI helpers & Curvenote cleanup -- Add in-memory validation module under `src/sdk/validate.ts` (or `src/validateMemory.ts`). -- Bundle or ship Crossref 5.3.1 XSD (or a maintained JS binding) as package assets. -- Choose a serverless-friendly approach (evaluate in implementation spike): - - Prefer a pure-JS XML + XSD validator that works without native bindings, **or** - - Structural validation + well-formedness if full XSD proves impractical in v1 — document any gap vs `xmllint` in `SDK.md`. -- API: `validateDeposit(xml) → Promise` with `{ ok, errors[] }`. -- Leave existing `xmllint` helpers for CLI; do not export them from `/sdk`. -- Tests: known-good deposit XML passes; deliberately broken XML fails with messages. +- **`generateDoi(prefix: string)`** — keep; numeric prefix **always passed in** (no `curvenote` → `10.62329` map in library code). +- **`suggestDois(count, prefix)`** — non-interactive candidates for UIs; callers must still human-review before Crossref submit (slur / pronounceability concern). +- Prefix aliases (if needed) live in **caller config** or CLI only — not hardcoded in `-utils`. +- Remove hardcoded `https://doi.curvenote.com/...` from `*FromMyst` / helpers; require explicit `doi_data.resource` or an injectable resolver callback on adapters. +- Bonus (not SDK-specific): strip other Curvenote-only defaults as found. -### 8. `mystToDepositItem` +### 5. Validation on `crossref-utils` (must land) -- Adapter in `src/sdk/myst.ts`: map frontmatter-like JSON → `DepositItem` fields (title, authors→contributors, license URL, funding, pages, doi). -- No abstract, no resource URL invention, no FS. -- Optionally stub/document `mystToVenue` if journal/conference venue mapping is needed immediately; otherwise follow-up. -- Tests with sample frontmatter JSON. +- Expose e.g. `validateDeposit(xml: string): Promise` from `-utils`. +- Spike for a **maintained pure JS/Node** XSD validator usable in serverless (no native addon if possible). +- Bundle Crossref 5.3.1 schema assets with the package (or load from package files without network). +- If in-process XSD proves blocked: document fallback (external validation service) but still ship a library `validate*` API contract — do not leave validation CLI/`xmllint`-only. +- CLI can call the utils validator; `xmllint` path may remain as optional local fallback during transition. -### 9. Docs & changelog +### 6. Docs & release -- Keep [`SDK.md`](./SDK.md) as the developer contract; update enums/field names if implementation discovers schema mismatches. -- Add a short pointer in `README.md` to `SDK.md` / `crossref-utils/sdk`. -- Changeset noting new SDK export and helpers. +- Rewrite [`SDK.md`](./SDK.md) to describe `crossref-utils` as the public library (core + MyST adapter), not a `/sdk` subpath facade. +- Update root README for monorepo / package install (`crossref-utils` vs CLI). +- Changesets per package; note breaking changes (Curvenote defaults removed, Session→logger, package layout). --- -## Suggested implementation order +## Suggested order -1. Package `exports` + empty SDK barrel -2. DOI helpers (`DOI_PREFIXES`, `suggestDois`) + tests -3. Extract `abstractFromMdast` + wire CLI to shared helper -4. DTO types + `buildDeposit` for `preprint`, then `journal`, `conference`, `dataset` -5. `validateDeposit` spike → implement -6. `mystToDepositItem` -7. README + changeset +1. Monorepo scaffolding + move files (utils vs cli) +2. Logger refactor + Curvenote resource/prefix cleanup +3. Extract `abstractFromMdast`; slim CLI deposit path +4. `generateDoi` / `suggestDois` (prefix required); CLI uses them +5. Validation spike → implement `validateDeposit` on utils +6. Docs (`SDK.md`, README) + changesets ## Test plan -- [ ] `generateDoi` / `suggestDois` / prefix aliases -- [ ] `abstractFromMdast` golden mdast → JATS string -- [ ] `buildDeposit` for each of the four types (required fields, multi-item journal/conference) -- [ ] Preprint rejects `items.length !== 1` -- [ ] Missing `resource` without resolver throws `DepositError` -- [ ] Subtypes appear on emitted XML -- [ ] `validateDeposit` accept/reject fixtures -- [ ] `mystToDepositItem` maps authors, doi, license; leaves abstract unset -- [ ] Existing CLI deposit / unit tests still pass +- [ ] Workspace build/publish layout for both packages +- [ ] Existing unit tests pass under `crossref-utils` +- [ ] CLI deposit/generate/validate still work against workspace utils +- [ ] `abstractFromMdast` fixture; CLI no longer duplicates transform logic +- [ ] `*FromMyst` works with logger only (no Session) +- [ ] No Curvenote URL/prefix defaults in utils +- [ ] `validateDeposit` catches known-bad deposit XML; known-good passes +- [ ] Serverless-shaped usage: import utils only, no FS -## Follow-ups (out of this plan) +## Open decisions (small) -- Rewire CLI `deposit` to build DTOs and call `buildDeposit` -- Remove Curvenote defaults from legacy `*FromMyst` once CLI uses `resourceResolver` -- Richer venue mapper from MyST project JSON -- Sync `validateDeposit` if the chosen library allows +- Exact npm name for the CLI package and whether the current `crossref` bin moves with a major bump. +- Which XSD-in-JS library survives the spike (or external service shape if none does). -## Open implementation detail +## Explicitly dropped from prior spike plan -**XSD-in-JS library choice** — confirm during workstream 7 which dependency satisfies: Crossref 5.3.1, no native addon, works in typical serverless Node runtimes. If full XSD is blocked, ship well-formedness + required-element checks in v1 and note the limitation in `SDK.md`. +- `crossref-utils/sdk` subpath entry +- New `DepositInput` / `buildDeposit` / `mystToDepositItem` facade types +- Bundling DOI prefix alias constants into the library +- Treating MyST frontmatter JSON as the *only* primary SDK input (both Crossref DTOs and MyST adapters remain)