@we8/client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # @we8/client
2
+
3
+ A typed client for the we8 public API. It is a thin convenience layer over the
4
+ OpenAPI-documented HTTP contract: every method maps 1:1 to a `/v1` route, so you
5
+ never lose anything by dropping down to `fetch`. The API is the product; this
6
+ package is the ergonomic way to call it.
7
+
8
+ - Zero runtime dependencies. The wire contract lives in this package as plain
9
+ TypeScript (`src/contract.ts`), so nothing is pulled in at build or run time.
10
+ - Runs in browsers, Node, and edge runtimes (anywhere `fetch` exists).
11
+ - Speaks the same `/v1` dialect as a managed we8 tenant, so the same code works
12
+ against a self-hosted CMS and a managed one.
13
+
14
+ ## Requirements
15
+
16
+ - Node 20 or newer, any modern browser, or an edge runtime. Zero runtime
17
+ dependencies.
18
+ - A we8 CMS to talk to: the self-hosted `@we8/cms` or a managed we8.io tenant.
19
+ Both speak the same `/v1` dialect. For a self-hosted CMS, pass its origin as
20
+ `baseUrl` (the default `/api` is the managed platform's same-origin shape).
21
+ - A publishable (`pk_`) key for reads and beacons; a secret (`sk_`) key only
22
+ where a route demands one.
23
+ ## Install
24
+
25
+ ```sh
26
+ npm install @we8/client
27
+ ```
28
+
29
+ ## Quick start
30
+
31
+ ```ts
32
+ import { createClient } from '@we8/client';
33
+
34
+ const we8 = createClient({
35
+ key: process.env.WE8_PUBLISHABLE_KEY!, // pk_... (public) or sk_... (server only)
36
+ baseUrl: 'https://cms.example.com', // your CMS origin, no path on the end
37
+ });
38
+
39
+ // Posts
40
+ const page = await we8.posts.list({ page: 1, pageSize: 10, tag: 'launch' });
41
+ const { post, jsonld } = await we8.posts.get('hello-world');
42
+
43
+ // Site identity (name, canonical URL, SEO fallbacks, publisher)
44
+ const site = await we8.site.config();
45
+
46
+ // Forms (formKey plus arbitrary fields; the API validates per form)
47
+ const { id } = await we8.forms.submit('contact', { email: 'a@b.com', body: 'hi' });
48
+
49
+ // Events (analytics; safe to fire from the browser with a pk_ key)
50
+ await we8.events.visit({ pathname: '/', sessionId });
51
+ ```
52
+
53
+ ## Keys and the base URL
54
+
55
+ - A publishable key (`pk_...`) is sent as `X-We8-Key` and is safe to embed in a
56
+ shipped frontend. A secret key (`sk_...`) is sent as `Authorization: Bearer`
57
+ and is for server or build-time use only; never ship it to a browser.
58
+ - `baseUrl` is the API base every route hangs off: the client requests
59
+ `${baseUrl}/v1/...`. **Pass it explicitly for a self-hosted we8 CMS**, as the
60
+ CMS origin with no path on the end (`https://cms.example.com`, or
61
+ `http://localhost:8787` in development). The default is the same-origin
62
+ `/api`, which is the managed platform's shape, and it 404s against a
63
+ standalone CMS.
64
+ - Pass a `fetch` implementation via `options.fetch` in tests or runtimes without
65
+ a global `fetch`.
66
+
67
+ ## Surface
68
+
69
+ | Group | Methods |
70
+ | ----------- | ----------------------------------------------------------------------------- |
71
+ | `posts` | `list(params?)`, `get(slug)`, `view(slug, sessionId)`, `like(slug, sessionId)` |
72
+ | `authors` | `get(slug)` |
73
+ | `site` | `config()` |
74
+ | `documents` | `get(kind)` where `kind` is `'design-md'` or `'llms-txt'` |
75
+ | `forms` | `submit(formKey, data, { turnstileToken? })` |
76
+ | `events` | `visit(input)`, `consent(input)` |
77
+ | top level | `sitemapUrl()` |
78
+
79
+ Every call throws a `We8ApiError` (carrying the API's stable `code` and the HTTP
80
+ `status`) on failure; a transport failure surfaces as `code: 'internal'`,
81
+ `status: 0`. Use `isWe8ApiError(err)` to narrow without importing the class.
82
+
83
+ ## The contract
84
+
85
+ `src/contract.ts` is the typed statement of the `/v1` dialect: the response
86
+ envelope, the frozen vocabularies (`POST_TYPES`, `AUTHOR_TYPES`,
87
+ `CONSENT_DECISIONS`, `SITE_DOCUMENT_KINDS`, `CTA_STYLES`, `ERROR_CODES`), and
88
+ the record shapes. The vocabularies are exported as runtime constants as well
89
+ as types, so a frontend can build a filter or a select box from them without
90
+ restating the list. `test/contract.test.ts` pins every member, so a change to
91
+ the dialect cannot land quietly.
92
+
93
+ The full, authoritative interface is the OpenAPI document your CMS serves:
94
+
95
+ ```
96
+ https://cms.example.com/v1/openapi.json
97
+ ```
98
+
99
+ It is keyless, so you can read the contract before anyone has issued you a key.
100
+
101
+ ## Environment contract
102
+
103
+ Headless and build-time callers typically read these from the environment and
104
+ pass them in:
105
+
106
+ - `WE8_API_URL`: the absolute API base given to `createClient({ baseUrl })`,
107
+ which for a self-hosted CMS is its origin.
108
+ - `WE8_PUBLISHABLE_KEY`: the publishable (`pk_`) key given to
109
+ `createClient({ key })`.
110
+
111
+ See [the API doc](https://github.com/reveriext/we8-package/blob/main/docs/api.md) for the dialect in prose, and [the
112
+ frontend doc](https://github.com/reveriext/we8-package/blob/main/docs/frontend.md) for how `@we8/astro` and the starter
113
+ template build on this.
@@ -0,0 +1,74 @@
1
+ import type { SiteDocumentKind } from './contract.js';
2
+ import type { We8Author, We8ConsentInput, We8Document, We8FormData, We8FormSubmitOptions, We8JsonLd, We8LikeResult, We8PostDetail, We8PostList, We8PostListParams, We8SiteConfig, We8VisitInput } from './types.js';
3
+ export interface We8ClientOptions {
4
+ /**
5
+ * The site API key. A publishable key (`pk_...`) is sent as `X-We8-Key`; a
6
+ * secret key (`sk_...`) is sent as `Authorization: Bearer`. Publishable keys
7
+ * are safe to embed in a shipped frontend (that is their purpose); secret
8
+ * keys are for server and build-time use only and must never reach a browser.
9
+ */
10
+ key: string;
11
+ /**
12
+ * API base URL: every route is requested as `${baseUrl}/v1/...`. It
13
+ * defaults to the same-origin `/api`, which is the shape managed we8 serves
14
+ * behind its router. A SELF-HOSTED @we8/cms worker is the API origin itself
15
+ * and serves `/v1` at its root, so pass its origin with no path on the end:
16
+ * `https://cms.example.com`, or `http://localhost:8787` in development.
17
+ */
18
+ baseUrl?: string;
19
+ /**
20
+ * A `fetch` implementation to use instead of the global. Useful in tests and
21
+ * in runtimes without a global `fetch`.
22
+ */
23
+ fetch?: typeof fetch;
24
+ }
25
+ /** Result of `forms.submit`. `id` is null when the submission was accepted but silently dropped (honeypot or dedupe). */
26
+ export interface We8FormSubmitResult {
27
+ id: string | null;
28
+ }
29
+ export interface We8Client {
30
+ posts: {
31
+ /** List published posts (paged, optionally filtered by tag, category, or type). */
32
+ list(params?: We8PostListParams): Promise<We8PostList>;
33
+ /** Fetch a single published post by slug, with its JSON-LD. */
34
+ get(slug: string): Promise<We8PostDetail>;
35
+ /** Record a view for a post (deduped per session by the API). */
36
+ view(slug: string, sessionId: string): Promise<void>;
37
+ /** Toggle a like for a post; returns the new liked state and count. */
38
+ like(slug: string, sessionId: string): Promise<We8LikeResult>;
39
+ };
40
+ authors: {
41
+ /** Fetch a single author profile by slug. */
42
+ get(slug: string): Promise<We8Author>;
43
+ };
44
+ site: {
45
+ /** Fetch the site identity card: name, canonical URL, SEO fallbacks, publisher, content paths. */
46
+ config(): Promise<We8SiteConfig>;
47
+ };
48
+ documents: {
49
+ /** Fetch a site document (design.md or llms.txt); empty content when never written. */
50
+ get(kind: SiteDocumentKind): Promise<We8Document>;
51
+ };
52
+ forms: {
53
+ /** Submit a form by its key. Extra fields pass through; the API validates per form. */
54
+ submit(formKey: string, data: We8FormData, options?: We8FormSubmitOptions): Promise<We8FormSubmitResult>;
55
+ };
56
+ events: {
57
+ /** Record a page visit. */
58
+ visit(input: We8VisitInput): Promise<void>;
59
+ /** Record a cookie-consent decision. */
60
+ consent(input: We8ConsentInput): Promise<void>;
61
+ };
62
+ /**
63
+ * The absolute-or-relative URL of this site's sitemap, fetchable AS-IS: the
64
+ * public router requires a key on every request, so for a publishable
65
+ * (`pk_`) key the key is embedded as `?key=` (pk keys are public by design;
66
+ * this is what makes the URL usable in a robots.txt or by a search engine).
67
+ * A secret (`sk_`) key must never appear in a URL, so an sk-keyed client
68
+ * gets the bare URL and must fetch it with auth headers itself.
69
+ */
70
+ sitemapUrl(): string;
71
+ }
72
+ export declare function createClient(options: We8ClientOptions): We8Client;
73
+ export type { We8JsonLd };
74
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAuB,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAE3E,OAAO,KAAK,EACV,SAAS,EACT,eAAe,EACf,WAAW,EACX,WAAW,EACX,oBAAoB,EACpB,SAAS,EACT,aAAa,EACb,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,aAAa,EACb,aAAa,EACd,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,gBAAgB;IAC/B;;;;;OAKG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAED,yHAAyH;AACzH,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE;QACL,mFAAmF;QACnF,IAAI,CAAC,MAAM,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;QACvD,+DAA+D;QAC/D,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;QAC1C,iEAAiE;QACjE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QACrD,uEAAuE;QACvE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;KAC/D,CAAC;IACF,OAAO,EAAE;QACP,6CAA6C;QAC7C,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;KACvC,CAAC;IACF,IAAI,EAAE;QACJ,kGAAkG;QAClG,MAAM,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC;KAClC,CAAC;IACF,SAAS,EAAE;QACT,uFAAuF;QACvF,GAAG,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;KACnD,CAAC;IACF,KAAK,EAAE;QACL,uFAAuF;QACvF,MAAM,CACJ,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,mBAAmB,CAAC,CAAC;KACjC,CAAC;IACF,MAAM,EAAE;QACN,2BAA2B;QAC3B,KAAK,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAC3C,wCAAwC;QACxC,OAAO,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KAChD,CAAC;IACF;;;;;;;OAOG;IACH,UAAU,IAAI,MAAM,CAAC;CACtB;AAiCD,wBAAgB,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,CA+HjE;AAED,YAAY,EAAE,SAAS,EAAE,CAAC"}
package/dist/client.js ADDED
@@ -0,0 +1,143 @@
1
+ import { We8ApiError } from './errors.js';
2
+ function authHeader(key) {
3
+ // The public router accepts a secret key via `Authorization: Bearer` and a
4
+ // publishable key via `X-We8-Key`. Anything that is not explicitly `sk_` is
5
+ // treated as publishable, so an unknown or future key prefix still reaches
6
+ // the router on the header that does not read as a bearer credential.
7
+ return key.startsWith('sk_') ? { Authorization: `Bearer ${key}` } : { 'X-We8-Key': key };
8
+ }
9
+ function statusToCode(status) {
10
+ switch (status) {
11
+ case 400:
12
+ return 'validation_error';
13
+ case 401:
14
+ return 'unauthorized';
15
+ case 403:
16
+ return 'forbidden';
17
+ case 404:
18
+ return 'not_found';
19
+ case 413:
20
+ return 'payload_too_large';
21
+ case 429:
22
+ return 'rate_limited';
23
+ default:
24
+ return 'internal';
25
+ }
26
+ }
27
+ function messageOf(cause) {
28
+ return cause instanceof Error ? cause.message : String(cause);
29
+ }
30
+ export function createClient(options) {
31
+ if (!options.key)
32
+ throw new Error('createClient: a `key` is required');
33
+ const { key } = options;
34
+ const baseUrl = (options.baseUrl ?? '/api').replace(/\/+$/, '');
35
+ const fetchImpl = options.fetch ?? globalThis.fetch;
36
+ if (typeof fetchImpl !== 'function') {
37
+ throw new Error('createClient: no global `fetch` found; pass one via `options.fetch`');
38
+ }
39
+ function buildUrl(path, query) {
40
+ let url = `${baseUrl}/v1/${path}`;
41
+ if (query) {
42
+ const qs = new URLSearchParams(query).toString();
43
+ if (qs)
44
+ url += `?${qs}`;
45
+ }
46
+ return url;
47
+ }
48
+ async function request(method, path, opts = {}) {
49
+ const url = buildUrl(path, opts.query);
50
+ const headers = { Accept: 'application/json', ...authHeader(key) };
51
+ let body;
52
+ if (opts.body !== undefined) {
53
+ headers['Content-Type'] = 'application/json';
54
+ body = JSON.stringify(opts.body);
55
+ }
56
+ let res;
57
+ try {
58
+ res = await fetchImpl(url, { method, headers, ...(body === undefined ? {} : { body }) });
59
+ }
60
+ catch (cause) {
61
+ // Transport-level failure (offline, DNS, CORS block): no HTTP status.
62
+ throw new We8ApiError('internal', `request to ${url} failed: ${messageOf(cause)}`, 0);
63
+ }
64
+ const text = await res.text();
65
+ let envelope;
66
+ if (text) {
67
+ try {
68
+ envelope = JSON.parse(text);
69
+ }
70
+ catch {
71
+ // Non-JSON body; handled below by the status checks.
72
+ }
73
+ }
74
+ if (envelope && envelope.ok === false) {
75
+ throw new We8ApiError(envelope.code, envelope.error, res.status);
76
+ }
77
+ if (!res.ok) {
78
+ throw new We8ApiError(statusToCode(res.status), `HTTP ${res.status} from ${url}`, res.status);
79
+ }
80
+ if (!envelope) {
81
+ throw new We8ApiError('internal', `unexpected non-JSON response from ${url}`, res.status);
82
+ }
83
+ // Strip the `ok: true` discriminant, leaving just the typed data payload.
84
+ const { ok: _ok, ...data } = envelope;
85
+ return data;
86
+ }
87
+ const enc = encodeURIComponent;
88
+ // Normalize the typed list params into a string query record, dropping
89
+ // absent or empty values. Explicit (rather than a generic index signature)
90
+ // so it stays clean under exactOptionalPropertyTypes and the client owns
91
+ // exactly which query keys it forwards.
92
+ function postListQuery(params) {
93
+ const q = {};
94
+ if (params?.page !== undefined)
95
+ q['page'] = String(params.page);
96
+ if (params?.pageSize !== undefined)
97
+ q['pageSize'] = String(params.pageSize);
98
+ if (params?.tag)
99
+ q['tag'] = params.tag;
100
+ if (params?.category)
101
+ q['category'] = params.category;
102
+ if (params?.type)
103
+ q['type'] = params.type;
104
+ return q;
105
+ }
106
+ return {
107
+ posts: {
108
+ list: (params) => request('GET', 'posts', { query: postListQuery(params) }),
109
+ get: (slug) => request('GET', `posts/${enc(slug)}`),
110
+ view: (slug, sessionId) => request('POST', `posts/${enc(slug)}/view`, {
111
+ body: { sessionId },
112
+ }).then(() => undefined),
113
+ like: (slug, sessionId) => request('POST', `posts/${enc(slug)}/like`, { body: { sessionId } }),
114
+ },
115
+ authors: {
116
+ get: (slug) => request('GET', `authors/${enc(slug)}`).then((d) => d.author),
117
+ },
118
+ site: {
119
+ config: () => request('GET', 'site').then((d) => d.site),
120
+ },
121
+ documents: {
122
+ get: (kind) => request('GET', `documents/${enc(kind)}`).then((d) => d.document),
123
+ },
124
+ forms: {
125
+ submit: (formKey, data, opts) => request('POST', `forms/${enc(formKey)}/submissions`, {
126
+ body: opts?.turnstileToken ? { ...data, turnstileToken: opts.turnstileToken } : data,
127
+ }),
128
+ },
129
+ events: {
130
+ visit: (input) => request('POST', 'events/visit', { body: input }).then(() => undefined),
131
+ consent: (input) => request('POST', 'events/consent', { body: input }).then(() => undefined),
132
+ },
133
+ sitemapUrl: () => {
134
+ const url = `${baseUrl}/v1/sitemap.xml`;
135
+ // Without a key the public router 401s (it requires a key on EVERY
136
+ // path), so a bare URL would be dead on arrival for any consumer that
137
+ // cannot send headers (robots.txt, a search engine). Embed a
138
+ // publishable key via the router's ?key= channel; never embed a secret.
139
+ return key.startsWith('sk_') ? url : `${url}?key=${encodeURIComponent(key)}`;
140
+ },
141
+ };
142
+ }
143
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,179 @@
1
+ /**
2
+ * The wire contract of the we8 public API, expressed as plain TypeScript.
3
+ *
4
+ * In the managed platform these shapes are derived from Zod schemas in a
5
+ * shared package. This package has zero runtime dependencies by design, so the
6
+ * contract is restated here as types plus a handful of frozen vocabularies.
7
+ * The `/v1` dialect is the same one a managed we8 tenant speaks, so a site
8
+ * built on this client works against either backend unchanged.
9
+ *
10
+ * Everything here is the PUBLIC half of the contract: what `/v1/*` serves and
11
+ * accepts. Admin-only shapes live in the CMS package, not here.
12
+ */
13
+ /** Every stable machine-readable error code the API can return. */
14
+ export declare const ERROR_CODES: readonly ["validation_error", "unauthorized", "forbidden", "not_found", "conflict", "rate_limited", "payload_too_large", "internal"];
15
+ export type ErrorCode = (typeof ERROR_CODES)[number];
16
+ /** A successful envelope: `ok: true` plus the payload fields, flattened. */
17
+ export type Ok<T extends object = object> = {
18
+ ok: true;
19
+ } & T;
20
+ /** A failed envelope. `code` is stable; `error` is a human-readable message. */
21
+ export interface Err {
22
+ ok: false;
23
+ error: string;
24
+ code: ErrorCode;
25
+ }
26
+ export type Envelope<T extends object = object> = Ok<T> | Err;
27
+ /**
28
+ * Content types: a fixed vocabulary, deliberately not extensible per site,
29
+ * because type drives routing (contentPaths), the sitemap, the JSON-LD
30
+ * `@type`, template switching, and filters. Adding a member later is an
31
+ * additive change; open values would break all five consumers.
32
+ */
33
+ export declare const POST_TYPES: readonly ["blog", "article", "news", "research", "whitepaper", "case-study"];
34
+ export type PostType = (typeof POST_TYPES)[number];
35
+ export declare const AUTHOR_TYPES: readonly ["team", "guest", "advisor", "partner"];
36
+ export type AuthorType = (typeof AUTHOR_TYPES)[number];
37
+ export declare const CONSENT_DECISIONS: readonly ["accepted", "partial", "declined"];
38
+ export type ConsentDecision = (typeof CONSENT_DECISIONS)[number];
39
+ /**
40
+ * Site documents: small site-authored texts served at the frontend's root.
41
+ * A fixed two-member vocabulary, one entry per document the platform knows how
42
+ * to serve.
43
+ */
44
+ export declare const SITE_DOCUMENT_KINDS: readonly ["design-md", "llms-txt"];
45
+ export type SiteDocumentKind = (typeof SITE_DOCUMENT_KINDS)[number];
46
+ export declare const CTA_STYLES: readonly ["link", "gated-download"];
47
+ export type CtaStyle = (typeof CTA_STYLES)[number];
48
+ /**
49
+ * An inline call to action, versioned with the post's content. `link` renders
50
+ * a plain button to `url`. `gated-download` embeds the form named by `formKey`
51
+ * and reveals `assetUrl` once the form submits successfully: thank-you-surface
52
+ * delivery, not DRM.
53
+ */
54
+ export interface Cta {
55
+ style: CtaStyle;
56
+ heading?: string;
57
+ buttonLabel: string;
58
+ /** Required when `style` is `link`. */
59
+ url?: string;
60
+ /** The form's stable key (for example `contact`). Required when `style` is `gated-download`. */
61
+ formKey?: string;
62
+ /** The asset revealed after a successful gated submission. */
63
+ assetUrl?: string;
64
+ }
65
+ /**
66
+ * The content fields of a published version. `We8Post` composes this with the
67
+ * post-level identity and counters to form what `/v1/posts` serves.
68
+ */
69
+ export interface PostVersionRecord {
70
+ id: string;
71
+ postId: string;
72
+ version: number;
73
+ title: string;
74
+ excerpt: string | null;
75
+ content: string;
76
+ coverImageUrl: string | null;
77
+ category: string | null;
78
+ tags: string[];
79
+ ogTitle: string | null;
80
+ ogDescription: string | null;
81
+ ogImageUrl: string | null;
82
+ /** AEO (0.1): editor-authored answer summary. Absent on older backends. */
83
+ answerSummary?: string | null;
84
+ /** AEO (0.1): the question this post answers. Absent on older backends. */
85
+ questionHeading?: string | null;
86
+ cta: Cta | null;
87
+ authorIds: string[];
88
+ wordCount: number | null;
89
+ note: string | null;
90
+ publishedAt: string;
91
+ publishedBy: string | null;
92
+ }
93
+ export interface AuthorLink {
94
+ label: string;
95
+ url: string;
96
+ }
97
+ export interface AuthorRecord {
98
+ id: string;
99
+ slug: string;
100
+ name: string;
101
+ type: AuthorType;
102
+ roleTitle?: string;
103
+ avatarUrl?: string;
104
+ bio?: string;
105
+ links: AuthorLink[];
106
+ createdAt: string;
107
+ updatedAt: string;
108
+ }
109
+ export interface SiteDocumentRecord {
110
+ kind: SiteDocumentKind;
111
+ content: string;
112
+ /** AEO (0.1): editor-authored answer summary. Absent on older backends. */
113
+ answerSummary?: string | null;
114
+ /** `null` when the document was never written. */
115
+ updatedAt: string | null;
116
+ }
117
+ export interface VisitEventInput {
118
+ pathname: string;
119
+ referrer?: string;
120
+ sessionId: string;
121
+ }
122
+ export interface ConsentEventInput {
123
+ sessionId: string;
124
+ decision: ConsentDecision;
125
+ analytics: boolean;
126
+ performance: boolean;
127
+ }
128
+ /**
129
+ * Site-level SEO fallbacks. A template's own values always win; these fill the
130
+ * gaps. `titleTemplate` uses `%s` as the page-title placeholder, for example
131
+ * `%s | Northgate Tools`.
132
+ */
133
+ export interface SeoSettings {
134
+ defaultTitle?: string;
135
+ titleTemplate?: string;
136
+ metaDescription?: string;
137
+ ogTitle?: string;
138
+ ogDescription?: string;
139
+ ogImageUrl?: string;
140
+ twitterHandle?: string;
141
+ discourageSearchEngines: boolean;
142
+ }
143
+ /** schema.org Organization, minimal shape. Used as the JSON-LD `publisher`. */
144
+ export interface PublisherSettings {
145
+ name: string;
146
+ logoUrl?: string;
147
+ }
148
+ /** Brand asset slots: the minimal icon set plus display logos. */
149
+ export interface BrandSettings {
150
+ logoUrl?: string;
151
+ logoDarkUrl?: string;
152
+ iconSvgUrl?: string;
153
+ faviconIcoUrl?: string;
154
+ appleTouchIconUrl?: string;
155
+ icon192Url?: string;
156
+ icon512Url?: string;
157
+ icon512MaskUrl?: string;
158
+ }
159
+ /** A partial map from post type to the path prefix that section is served at. */
160
+ export type ContentPaths = Partial<Record<PostType, string>>;
161
+ /**
162
+ * The public site-config payload (`GET /v1/site`): the site's identity card
163
+ * for templates and SDKs. The code-first merge over `seo` lives in
164
+ * `@we8/astro` `mergeSeo`, not in the backend.
165
+ */
166
+ export interface SiteConfig {
167
+ name: string | null;
168
+ siteUrl: string;
169
+ seo: SeoSettings;
170
+ publisher: PublisherSettings | null;
171
+ /** The universal prefix used for any type without its own path. */
172
+ postsPathPrefix: string;
173
+ /** Fully resolved per-type paths: every post type is present. */
174
+ contentPaths: Record<PostType, string>;
175
+ /** `null` when unconfigured; `''` is never served. */
176
+ gaMeasurementId: string | null;
177
+ brand: BrandSettings;
178
+ }
179
+ //# sourceMappingURL=contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The wire contract of the we8 public API, expressed as plain TypeScript.
3
+ *
4
+ * In the managed platform these shapes are derived from Zod schemas in a
5
+ * shared package. This package has zero runtime dependencies by design, so the
6
+ * contract is restated here as types plus a handful of frozen vocabularies.
7
+ * The `/v1` dialect is the same one a managed we8 tenant speaks, so a site
8
+ * built on this client works against either backend unchanged.
9
+ *
10
+ * Everything here is the PUBLIC half of the contract: what `/v1/*` serves and
11
+ * accepts. Admin-only shapes live in the CMS package, not here.
12
+ */
13
+ /* -------------------------------------------------------------------------- */
14
+ /* Response envelope */
15
+ /* -------------------------------------------------------------------------- */
16
+ /** Every stable machine-readable error code the API can return. */
17
+ export const ERROR_CODES = [
18
+ 'validation_error',
19
+ 'unauthorized',
20
+ 'forbidden',
21
+ 'not_found',
22
+ 'conflict',
23
+ 'rate_limited',
24
+ 'payload_too_large',
25
+ 'internal',
26
+ ];
27
+ /* -------------------------------------------------------------------------- */
28
+ /* Frozen vocabularies */
29
+ /* -------------------------------------------------------------------------- */
30
+ /**
31
+ * Content types: a fixed vocabulary, deliberately not extensible per site,
32
+ * because type drives routing (contentPaths), the sitemap, the JSON-LD
33
+ * `@type`, template switching, and filters. Adding a member later is an
34
+ * additive change; open values would break all five consumers.
35
+ */
36
+ export const POST_TYPES = [
37
+ 'blog',
38
+ 'article',
39
+ 'news',
40
+ 'research',
41
+ 'whitepaper',
42
+ 'case-study',
43
+ ];
44
+ export const AUTHOR_TYPES = ['team', 'guest', 'advisor', 'partner'];
45
+ export const CONSENT_DECISIONS = ['accepted', 'partial', 'declined'];
46
+ /**
47
+ * Site documents: small site-authored texts served at the frontend's root.
48
+ * A fixed two-member vocabulary, one entry per document the platform knows how
49
+ * to serve.
50
+ */
51
+ export const SITE_DOCUMENT_KINDS = ['design-md', 'llms-txt'];
52
+ export const CTA_STYLES = ['link', 'gated-download'];
53
+ //# sourceMappingURL=contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAEhF,mEAAmE;AACnE,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,kBAAkB;IAClB,cAAc;IACd,WAAW;IACX,WAAW;IACX,UAAU;IACV,cAAc;IACd,mBAAmB;IACnB,UAAU;CACF,CAAC;AAgBX,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAEhF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,MAAM;IACN,SAAS;IACT,MAAM;IACN,UAAU;IACV,YAAY;IACZ,YAAY;CACJ,CAAC;AAIX,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,CAAU,CAAC;AAG7E,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,UAAU,EAAE,SAAS,EAAE,UAAU,CAAU,CAAC;AAG9E;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,WAAW,EAAE,UAAU,CAAU,CAAC;AAGtE,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAU,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { ErrorCode } from './contract.js';
2
+ /**
3
+ * The single error type every we8 client call can throw. It carries the API's
4
+ * own error `code` (the stable machine-readable enum from the response
5
+ * envelope) and the HTTP `status`, so callers can branch on either.
6
+ *
7
+ * - A non-2xx response with a decodable error envelope surfaces that envelope's
8
+ * `code`/`error` verbatim.
9
+ * - A non-2xx response WITHOUT a usable envelope synthesizes a code from the
10
+ * status (for example 404 becomes `not_found`, else `internal`).
11
+ * - A transport failure (fetch rejected, DNS, offline) surfaces as
12
+ * `code: 'internal'`, `status: 0`.
13
+ */
14
+ export declare class We8ApiError extends Error {
15
+ readonly code: ErrorCode;
16
+ readonly status: number;
17
+ constructor(code: ErrorCode, message: string, status: number);
18
+ }
19
+ /** Narrowing helper so consumers do not need to import the class to `instanceof`. */
20
+ export declare function isWe8ApiError(value: unknown): value is We8ApiError;
21
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE/C;;;;;;;;;;;GAWG;AACH,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAS7D;AAED,qFAAqF;AACrF,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAElE"}
package/dist/errors.js ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The single error type every we8 client call can throw. It carries the API's
3
+ * own error `code` (the stable machine-readable enum from the response
4
+ * envelope) and the HTTP `status`, so callers can branch on either.
5
+ *
6
+ * - A non-2xx response with a decodable error envelope surfaces that envelope's
7
+ * `code`/`error` verbatim.
8
+ * - A non-2xx response WITHOUT a usable envelope synthesizes a code from the
9
+ * status (for example 404 becomes `not_found`, else `internal`).
10
+ * - A transport failure (fetch rejected, DNS, offline) surfaces as
11
+ * `code: 'internal'`, `status: 0`.
12
+ */
13
+ export class We8ApiError extends Error {
14
+ code;
15
+ status;
16
+ constructor(code, message, status) {
17
+ super(message);
18
+ this.name = 'We8ApiError';
19
+ this.code = code;
20
+ this.status = status;
21
+ // Restore the prototype chain when the output is transpiled down to ES5-ish
22
+ // targets (a bundler may do this for older browser targets).
23
+ Object.setPrototypeOf(this, We8ApiError.prototype);
24
+ }
25
+ }
26
+ /** Narrowing helper so consumers do not need to import the class to `instanceof`. */
27
+ export function isWe8ApiError(value) {
28
+ return value instanceof We8ApiError;
29
+ }
30
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,WAAY,SAAQ,KAAK;IAC3B,IAAI,CAAY;IAChB,MAAM,CAAS;IAExB,YAAY,IAAe,EAAE,OAAe,EAAE,MAAc;QAC1D,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,4EAA4E;QAC5E,6DAA6D;QAC7D,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;IACrD,CAAC;CACF;AAED,qFAAqF;AACrF,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,OAAO,KAAK,YAAY,WAAW,CAAC;AACtC,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { createClient } from './client.js';
2
+ export type { We8Client, We8ClientOptions, We8FormSubmitResult } from './client.js';
3
+ export { We8ApiError, isWe8ApiError } from './errors.js';
4
+ export type { SiteDocumentKind, We8Author, We8ConsentInput, We8Document, We8FormData, We8FormSubmitOptions, We8JsonLd, We8LikeResult, We8Post, We8PostDetail, We8PostList, We8PostListParams, We8SiteConfig, We8VisitInput, } from './types.js';
5
+ export { AUTHOR_TYPES, CONSENT_DECISIONS, CTA_STYLES, ERROR_CODES, POST_TYPES, SITE_DOCUMENT_KINDS, } from './contract.js';
6
+ export type { AuthorLink, AuthorRecord, AuthorType, BrandSettings, ConsentDecision, ConsentEventInput, ContentPaths, Cta, CtaStyle, Envelope, Err, ErrorCode, Ok, PostType, PostVersionRecord, PublisherSettings, SeoSettings, SiteConfig, SiteDocumentRecord, VisitEventInput, } from './contract.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACpF,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACzD,YAAY,EACV,gBAAgB,EAChB,SAAS,EACT,eAAe,EACf,WAAW,EACX,WAAW,EACX,oBAAoB,EACpB,SAAS,EACT,aAAa,EACb,OAAO,EACP,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,aAAa,EACb,aAAa,GACd,MAAM,YAAY,CAAC;AAKpB,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,UAAU,EACV,mBAAmB,GACpB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,UAAU,EACV,YAAY,EACZ,UAAU,EACV,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,YAAY,EACZ,GAAG,EACH,QAAQ,EACR,QAAQ,EACR,GAAG,EACH,SAAS,EACT,EAAE,EACF,QAAQ,EACR,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,kBAAkB,EAClB,eAAe,GAChB,MAAM,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { createClient } from './client.js';
2
+ export { We8ApiError, isWe8ApiError } from './errors.js';
3
+ // The wire contract itself. Types are what most callers need; the frozen
4
+ // vocabularies are exported as runtime constants too, so a template can build
5
+ // a type filter or a select box from them without restating the list.
6
+ export { AUTHOR_TYPES, CONSENT_DECISIONS, CTA_STYLES, ERROR_CODES, POST_TYPES, SITE_DOCUMENT_KINDS, } from './contract.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAkBzD,yEAAyE;AACzE,8EAA8E;AAC9E,sEAAsE;AACtE,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,UAAU,EACV,mBAAmB,GACpB,MAAM,eAAe,CAAC"}
@@ -0,0 +1,80 @@
1
+ import type { AuthorRecord, ConsentEventInput, PostType, PostVersionRecord, SiteConfig, SiteDocumentKind, SiteDocumentRecord, VisitEventInput } from './contract.js';
2
+ /**
3
+ * A published post as returned by the public API: the current published
4
+ * version's content fields, plus the post-level `slug`, `type`, `viewCount`,
5
+ * and `likeCount`.
6
+ */
7
+ export type We8Post = PostVersionRecord & {
8
+ slug: string;
9
+ /** The structural content type: picks the site section via contentPaths. */
10
+ type: PostType;
11
+ viewCount: number;
12
+ likeCount: number;
13
+ };
14
+ /** A public author profile. */
15
+ export type We8Author = AuthorRecord;
16
+ /**
17
+ * JSON-LD blob the API builds for a post's detail page (schema.org
18
+ * `BlogPosting`/`Article`). Opaque to the client: pass it straight into a
19
+ * `<script type="application/ld+json">` tag.
20
+ */
21
+ export type We8JsonLd = Record<string, unknown>;
22
+ /** `GET /v1/posts`: a page of published posts. */
23
+ export interface We8PostList {
24
+ items: We8Post[];
25
+ page: number;
26
+ pageSize: number;
27
+ total: number;
28
+ }
29
+ /**
30
+ * `GET /v1/posts/:slug`: a single post, its bylined authors, and its JSON-LD.
31
+ *
32
+ * `authors` ships resolved rather than as the post's `authorIds`, because a
33
+ * public consumer has no way to turn an id into a name or an avatar: the
34
+ * public authors route is by-slug only. Any frontend rendering a byline needs
35
+ * them, and the route already loads them to build the JSON-LD.
36
+ */
37
+ export interface We8PostDetail {
38
+ post: We8Post;
39
+ authors: We8Author[];
40
+ jsonld: We8JsonLd;
41
+ }
42
+ /** Filters and paging for `posts.list()`. All optional; the API applies defaults. */
43
+ export interface We8PostListParams {
44
+ page?: number;
45
+ pageSize?: number;
46
+ tag?: string;
47
+ category?: string;
48
+ type?: PostType;
49
+ }
50
+ /** Result of toggling a like on a post. */
51
+ export interface We8LikeResult {
52
+ liked: boolean;
53
+ likeCount: number;
54
+ }
55
+ /** Fields for a form submission. Arbitrary keys; the API validates per form. */
56
+ export type We8FormData = Record<string, unknown>;
57
+ /** Options for `forms.submit()`. */
58
+ export interface We8FormSubmitOptions {
59
+ /** A Cloudflare Turnstile token, when the target form requires one. */
60
+ turnstileToken?: string;
61
+ }
62
+ /** A visit beacon payload. */
63
+ export type We8VisitInput = VisitEventInput;
64
+ /** A cookie-consent decision payload. */
65
+ export type We8ConsentInput = ConsentEventInput;
66
+ /**
67
+ * The site identity card (`GET /v1/site`): name, canonical URL, SEO
68
+ * fallbacks, publisher, brand assets, and the content paths. The code-first
69
+ * merge over `seo` lives in `@we8/astro` `mergeSeo`.
70
+ */
71
+ export type We8SiteConfig = SiteConfig;
72
+ /**
73
+ * A site document (`GET /v1/documents/:kind`): design.md or llms.txt.
74
+ * `content` is `''` and `updatedAt` is `null` when the site never wrote one,
75
+ * the same meaning a 404 would carry elsewhere, not an error.
76
+ */
77
+ export type We8Document = SiteDocumentRecord;
78
+ /** The fixed document-kind vocabulary, re-exported so callers can type a `kind` parameter. */
79
+ export type { SiteDocumentKind };
80
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,iBAAiB,EACjB,QAAQ,EACR,iBAAiB,EACjB,UAAU,EACV,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EAChB,MAAM,eAAe,CAAC;AAEvB;;;;GAIG;AACH,MAAM,MAAM,OAAO,GAAG,iBAAiB,GAAG;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,IAAI,EAAE,QAAQ,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,+BAA+B;AAC/B,MAAM,MAAM,SAAS,GAAG,YAAY,CAAC;AAErC;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEhD,kDAAkD;AAClD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,MAAM,EAAE,SAAS,CAAC;CACnB;AAED,qFAAqF;AACrF,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,QAAQ,CAAC;CACjB;AAED,2CAA2C;AAC3C,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,gFAAgF;AAChF,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAElD,oCAAoC;AACpC,MAAM,WAAW,oBAAoB;IACnC,uEAAuE;IACvE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,8BAA8B;AAC9B,MAAM,MAAM,aAAa,GAAG,eAAe,CAAC;AAE5C,yCAAyC;AACzC,MAAM,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAEhD;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC;AAEvC;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,kBAAkB,CAAC;AAE7C,8FAA8F;AAC9F,YAAY,EAAE,gBAAgB,EAAE,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@we8/client",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/reveriext/we8-package.git",
8
+ "directory": "packages/client"
9
+ },
10
+ "homepage": "https://github.com/reveriext/we8-package/tree/main/packages/client#readme",
11
+ "bugs": "https://github.com/reveriext/we8-package/issues",
12
+ "type": "module",
13
+ "description": "Typed client for the we8 public API. No runtime dependencies; works in browsers, Node, and edge runtimes.",
14
+ "keywords": [
15
+ "we8",
16
+ "cms",
17
+ "headless-cms",
18
+ "api-client",
19
+ "typescript"
20
+ ],
21
+ "main": "./dist/index.js",
22
+ "types": "./dist/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "@we8/source": "./src/index.ts",
26
+ "types": "./dist/index.d.ts",
27
+ "default": "./dist/index.js"
28
+ }
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "README.md"
33
+ ],
34
+ "sideEffects": false,
35
+ "scripts": {
36
+ "build": "tsc -p tsconfig.build.json",
37
+ "prepare": "npm run build",
38
+ "typecheck": "tsc -p tsconfig.json",
39
+ "test": "vitest run",
40
+ "prepack": "npm run build"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "devDependencies": {
46
+ "vitest": "^4.1.9"
47
+ },
48
+ "engines": {
49
+ "node": ">=20"
50
+ }
51
+ }