@cancia/astro 0.0.1
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/LICENSE +21 -0
- package/dist/chunk-22DJVJBR.js +169 -0
- package/dist/chunk-5IPHDIC6.js +19 -0
- package/dist/chunk-7IA5B5CF.js +11 -0
- package/dist/chunk-DGCGIEFD.js +14 -0
- package/dist/chunk-NG5GJME5.js +39 -0
- package/dist/chunk-QAM5VKAF.js +73 -0
- package/dist/chunk-YPVZDWTW.js +34 -0
- package/dist/chunk-YQDZQSES.js +393 -0
- package/dist/endpoints/auth.d.ts +5 -0
- package/dist/endpoints/auth.js +56 -0
- package/dist/endpoints/content.d.ts +11 -0
- package/dist/endpoints/content.js +56 -0
- package/dist/endpoints/health.d.ts +3 -0
- package/dist/endpoints/health.js +9 -0
- package/dist/endpoints/lists.d.ts +14 -0
- package/dist/endpoints/lists.js +36 -0
- package/dist/endpoints/publish.d.ts +5 -0
- package/dist/endpoints/publish.js +34 -0
- package/dist/endpoints/schemas.d.ts +5 -0
- package/dist/endpoints/schemas.js +20 -0
- package/dist/endpoints/upload.d.ts +5 -0
- package/dist/endpoints/upload.js +41 -0
- package/dist/index.d.ts +145 -0
- package/dist/index.js +712 -0
- package/dist/loader/index.d.ts +26 -0
- package/dist/loader/index.js +7 -0
- package/dist/runtime.d.ts +34 -0
- package/dist/runtime.js +8 -0
- package/dist/schema/index.d.ts +96 -0
- package/dist/schema/index.js +10 -0
- package/dist/types-BMlLS-OS.d.ts +161 -0
- package/dist/upload-DwCGjXbz.d.ts +13 -0
- package/package.json +58 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Loader } from 'astro/loaders';
|
|
2
|
+
import { a as CanciaStorageV2 } from '../types-BMlLS-OS.js';
|
|
3
|
+
|
|
4
|
+
interface CanciaLoaderOptions {
|
|
5
|
+
/** The list name as declared in src/cms/schemas.ts. */
|
|
6
|
+
list: string;
|
|
7
|
+
/**
|
|
8
|
+
* Site identifier. Must match the integration's `site` option so the
|
|
9
|
+
* loader reads from the same directory the routes write to.
|
|
10
|
+
*/
|
|
11
|
+
site: string;
|
|
12
|
+
/**
|
|
13
|
+
* Custom storage adapter. Defaults to the JSON-file v2 adapter rooted at
|
|
14
|
+
* `process.cwd()` — which is the project root during Astro's content sync.
|
|
15
|
+
* Override to point at a different location (testing, monorepos, etc.).
|
|
16
|
+
*/
|
|
17
|
+
storage?: CanciaStorageV2;
|
|
18
|
+
/**
|
|
19
|
+
* Override the project root used when constructing the default JSON-file
|
|
20
|
+
* adapter. Ignored when `storage` is supplied.
|
|
21
|
+
*/
|
|
22
|
+
projectRoot?: string;
|
|
23
|
+
}
|
|
24
|
+
declare function canciaLoader(opts: CanciaLoaderOptions): Loader;
|
|
25
|
+
|
|
26
|
+
export { type CanciaLoaderOptions, canciaLoader };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { C as CanciaStorage, a as CanciaStorageV2 } from './types-BMlLS-OS.js';
|
|
2
|
+
import { U as UploadHandler } from './upload-DwCGjXbz.js';
|
|
3
|
+
|
|
4
|
+
interface CanciaRuntime {
|
|
5
|
+
/** v1 KV-only storage. Kept for the existing /content endpoint. */
|
|
6
|
+
storage: CanciaStorage;
|
|
7
|
+
/**
|
|
8
|
+
* v2 aggregate storage (KV + pages + lists). Optional for the transition
|
|
9
|
+
* period: integrations that haven't opted in to v2 leave this undefined,
|
|
10
|
+
* and list/page routes return 503 in that case.
|
|
11
|
+
*/
|
|
12
|
+
storageV2: CanciaStorageV2 | undefined;
|
|
13
|
+
/** Absolute path to the user's project root, for dynamic schema imports. */
|
|
14
|
+
projectRoot: string;
|
|
15
|
+
/** Optional override for the schemas.ts path. */
|
|
16
|
+
schemasPath: string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Default locale to use when a write request omits `?locale=`. Sourced
|
|
19
|
+
* from Astro's i18n.defaultLocale, falling back to "en" when Astro i18n
|
|
20
|
+
* isn't configured (single-language sites still go through the locale
|
|
21
|
+
* pipeline, they just always write to "en/").
|
|
22
|
+
*/
|
|
23
|
+
defaultLocale: string;
|
|
24
|
+
/** All configured locales, in order with defaultLocale first. */
|
|
25
|
+
locales: string[];
|
|
26
|
+
secret: string | undefined;
|
|
27
|
+
uploadHandler: UploadHandler;
|
|
28
|
+
deployHook: string | undefined;
|
|
29
|
+
maxUploadMB: number;
|
|
30
|
+
}
|
|
31
|
+
declare function setCanciaRuntime(runtime: CanciaRuntime): void;
|
|
32
|
+
declare function getCanciaRuntime(): CanciaRuntime;
|
|
33
|
+
|
|
34
|
+
export { type CanciaRuntime, getCanciaRuntime, setCanciaRuntime };
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Widget hint the modal form uses to pick an input element. The Zod type
|
|
6
|
+
* is authoritative for validation; this only changes the UI affordance.
|
|
7
|
+
*
|
|
8
|
+
* Inferred widgets (when .meta({ widget }) is omitted):
|
|
9
|
+
* z.string() → "text"
|
|
10
|
+
* z.string() (long) → "textarea" (heuristic by maxLength or label "body")
|
|
11
|
+
* z.string().url() → "url"
|
|
12
|
+
* z.string().email() → "email"
|
|
13
|
+
* z.string().datetime()→ "datetime"
|
|
14
|
+
* z.number() → "number"
|
|
15
|
+
* z.boolean() → "checkbox"
|
|
16
|
+
* z.enum([...]) → "select"
|
|
17
|
+
*/
|
|
18
|
+
type FieldWidget = "text" | "textarea" | "url" | "email" | "datetime" | "number" | "checkbox" | "select" | "image";
|
|
19
|
+
/**
|
|
20
|
+
* Metadata attached to a Zod field via `.meta({...})`. All optional.
|
|
21
|
+
*/
|
|
22
|
+
interface FieldMeta {
|
|
23
|
+
/** Display label for the field. Defaults to humanised field name. */
|
|
24
|
+
label?: string;
|
|
25
|
+
/** Help text shown under the input. */
|
|
26
|
+
description?: string;
|
|
27
|
+
/** Override widget choice. */
|
|
28
|
+
widget?: FieldWidget;
|
|
29
|
+
/** Placeholder text in the input. */
|
|
30
|
+
placeholder?: string;
|
|
31
|
+
}
|
|
32
|
+
interface ListSchemaOptions<TFields extends Record<string, z.ZodTypeAny>> {
|
|
33
|
+
/** Display label for the list (e.g. "Blog Posts"). */
|
|
34
|
+
label: string;
|
|
35
|
+
/** Singular form for "Add new" buttons. Defaults to label minus trailing "s". */
|
|
36
|
+
labelSingular?: string;
|
|
37
|
+
/** Field name to use as the entry title in list views. */
|
|
38
|
+
titleField: keyof TFields & string;
|
|
39
|
+
/**
|
|
40
|
+
* Field name treated as the long-form body. The list view shows it as a
|
|
41
|
+
* preview snippet; the modal renders it last so it sits below all metadata.
|
|
42
|
+
* Optional — set only when the list has long-form prose.
|
|
43
|
+
*/
|
|
44
|
+
bodyField?: keyof TFields & string;
|
|
45
|
+
/**
|
|
46
|
+
* Field name to use as the URL slug for entries (e.g. /blog/[slug]).
|
|
47
|
+
* Defaults to the entry ID. The server validates slug uniqueness on save.
|
|
48
|
+
*/
|
|
49
|
+
slugField?: keyof TFields & string;
|
|
50
|
+
/** Field definitions. */
|
|
51
|
+
fields: TFields;
|
|
52
|
+
}
|
|
53
|
+
interface ListSchema<TFields extends Record<string, z.ZodTypeAny> = Record<string, z.ZodTypeAny>> {
|
|
54
|
+
label: string;
|
|
55
|
+
labelSingular: string;
|
|
56
|
+
titleField: string;
|
|
57
|
+
bodyField: string | undefined;
|
|
58
|
+
slugField: string | undefined;
|
|
59
|
+
fields: TFields;
|
|
60
|
+
/** Zod object schema combining all fields, for one-shot validation. */
|
|
61
|
+
validator: z.ZodObject<TFields>;
|
|
62
|
+
}
|
|
63
|
+
declare function defineList<TFields extends Record<string, z.ZodTypeAny>>(opts: ListSchemaOptions<TFields>): ListSchema<TFields>;
|
|
64
|
+
/**
|
|
65
|
+
* One field as seen by the modal form. Plain JSON, no Zod refs.
|
|
66
|
+
*/
|
|
67
|
+
interface FieldDescription {
|
|
68
|
+
name: string;
|
|
69
|
+
label: string;
|
|
70
|
+
description?: string;
|
|
71
|
+
widget: FieldWidget;
|
|
72
|
+
required: boolean;
|
|
73
|
+
placeholder?: string;
|
|
74
|
+
/** For select widgets: the enum values. */
|
|
75
|
+
options?: string[];
|
|
76
|
+
/** For string widgets: min/max length. */
|
|
77
|
+
minLength?: number;
|
|
78
|
+
maxLength?: number;
|
|
79
|
+
/** For number widgets: min/max. */
|
|
80
|
+
min?: number;
|
|
81
|
+
max?: number;
|
|
82
|
+
}
|
|
83
|
+
interface ListDescription {
|
|
84
|
+
name: string;
|
|
85
|
+
label: string;
|
|
86
|
+
labelSingular: string;
|
|
87
|
+
titleField: string;
|
|
88
|
+
bodyField?: string;
|
|
89
|
+
slugField?: string;
|
|
90
|
+
fields: FieldDescription[];
|
|
91
|
+
}
|
|
92
|
+
declare function describeList(name: string, schema: ListSchema): ListDescription;
|
|
93
|
+
/** Map of list name → schema. What users export from src/cms/schemas.ts. */
|
|
94
|
+
type SchemasModule = Record<string, ListSchema>;
|
|
95
|
+
|
|
96
|
+
export { type FieldDescription, type FieldMeta, type FieldWidget, type ListDescription, type ListSchema, type ListSchemaOptions, type SchemasModule, defineList, describeList };
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Revision token for optimistic concurrency. Opaque to callers — only equality
|
|
3
|
+
* matters. JSON-file adapter uses a SHA-256 hash of canonical JSON; a SQL
|
|
4
|
+
* adapter could use a row version column. Mismatches on write must throw
|
|
5
|
+
* a RevConflictError.
|
|
6
|
+
*/
|
|
7
|
+
type Rev = string;
|
|
8
|
+
declare class RevConflictError extends Error {
|
|
9
|
+
readonly name = "RevConflictError";
|
|
10
|
+
constructor(message?: string);
|
|
11
|
+
}
|
|
12
|
+
type CMSData = Record<string, string>;
|
|
13
|
+
interface CanciaKVStore {
|
|
14
|
+
/** Get a single value. Returns null if not found. */
|
|
15
|
+
get(site: string, key: string, lang: string): Promise<string | null>;
|
|
16
|
+
/** Upsert a value. */
|
|
17
|
+
set(site: string, key: string, lang: string, value: string): Promise<void>;
|
|
18
|
+
/** Get all overrides for a site as a flat map: "key.lang" → value. */
|
|
19
|
+
getAll(site: string): Promise<CMSData>;
|
|
20
|
+
/** Delete a single override. */
|
|
21
|
+
delete(site: string, key: string, lang: string): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* @deprecated Use CanciaKVStore. Kept as an alias so existing adapter code
|
|
25
|
+
* (createJsonFileAdapter, createSQLiteAdapter) compiles without changes.
|
|
26
|
+
*/
|
|
27
|
+
type CanciaStorage = CanciaKVStore;
|
|
28
|
+
/**
|
|
29
|
+
* SEO fields the agent and visual editor can write. Subset of what a typical
|
|
30
|
+
* SEO integration consumes (e.g. astro-seo-graph). Fields are optional so
|
|
31
|
+
* pages can override only what they need; the site default fills the rest.
|
|
32
|
+
*/
|
|
33
|
+
interface PageSEO {
|
|
34
|
+
title?: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
/** Override the canonical URL. Rarely needed — Astro emits one by default. */
|
|
37
|
+
canonical?: string;
|
|
38
|
+
/** Open Graph image URL (already uploaded; not a file). */
|
|
39
|
+
ogImage?: string;
|
|
40
|
+
/** Twitter card image URL — defaults to ogImage if omitted. */
|
|
41
|
+
twitterImage?: string;
|
|
42
|
+
/** robots meta: e.g. "noindex, nofollow". Omit for default indexing. */
|
|
43
|
+
robots?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* One record per route. `metadata` is open-ended for future fields the agent
|
|
47
|
+
* might want to attach (publish date overrides, A/B variant flags, etc.) so
|
|
48
|
+
* the schema doesn't need to grow every time.
|
|
49
|
+
*/
|
|
50
|
+
interface PageMeta {
|
|
51
|
+
seo?: PageSEO;
|
|
52
|
+
metadata?: Record<string, unknown>;
|
|
53
|
+
}
|
|
54
|
+
interface PageRecord {
|
|
55
|
+
/** Route path, e.g. "/", "/blog", "/blog/[slug]". Includes the leading slash. */
|
|
56
|
+
route: string;
|
|
57
|
+
meta: PageMeta;
|
|
58
|
+
_rev: Rev;
|
|
59
|
+
}
|
|
60
|
+
interface CanciaPageStore {
|
|
61
|
+
/** Get the meta for a route. Returns null if no overrides exist. */
|
|
62
|
+
get(site: string, route: string): Promise<PageRecord | null>;
|
|
63
|
+
/** List all routes that have meta. */
|
|
64
|
+
list(site: string): Promise<PageRecord[]>;
|
|
65
|
+
/**
|
|
66
|
+
* Upsert page meta. Pass the `_rev` from the last read to enforce
|
|
67
|
+
* optimistic concurrency. Omit `_rev` on first write (route had no prior
|
|
68
|
+
* record). Throws RevConflictError on mismatch.
|
|
69
|
+
*/
|
|
70
|
+
set(site: string, route: string, meta: PageMeta, rev?: Rev): Promise<PageRecord>;
|
|
71
|
+
/** Remove all meta for a route. */
|
|
72
|
+
delete(site: string, route: string): Promise<void>;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* One entry in a list. Shape is validated at the route layer against the
|
|
76
|
+
* schema declared in `src/cms/schemas.ts`; storage is shape-agnostic.
|
|
77
|
+
*
|
|
78
|
+
* `id` is stable across edits and is the URL slug component for entries
|
|
79
|
+
* exposed on the public site (e.g. `/blog/[id].astro`). Callers pass an
|
|
80
|
+
* explicit ID on create (typically the user's slug); if omitted, the
|
|
81
|
+
* adapter generates a random one — but the recommended pattern is to
|
|
82
|
+
* have the schema declare a slugField so creates are always deterministic.
|
|
83
|
+
*
|
|
84
|
+
* `locale` identifies which language this entry was authored in. Translation
|
|
85
|
+
* pairs share the same `id` across locales — that's how the loader and the
|
|
86
|
+
* toolbar know two entries are translations of each other.
|
|
87
|
+
*/
|
|
88
|
+
interface ListEntry {
|
|
89
|
+
id: string;
|
|
90
|
+
/** Locale this entry was authored in (e.g. "en", "es"). */
|
|
91
|
+
locale: string;
|
|
92
|
+
/** Arbitrary fields — validated against the list's schema upstream. */
|
|
93
|
+
data: Record<string, unknown>;
|
|
94
|
+
/** When this entry was first created. */
|
|
95
|
+
createdAt: string;
|
|
96
|
+
/** When this entry was last updated. */
|
|
97
|
+
updatedAt: string;
|
|
98
|
+
/** Per-entry revision token for optimistic concurrency. */
|
|
99
|
+
_rev: Rev;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Translation status for a single list entry — which locales have a
|
|
103
|
+
* file for this ID. The toolbar uses this to render "not translated"
|
|
104
|
+
* badges and to power the "translate this entry" affordance.
|
|
105
|
+
*/
|
|
106
|
+
interface TranslationStatus {
|
|
107
|
+
id: string;
|
|
108
|
+
/** Locales where an entry with this ID exists. */
|
|
109
|
+
locales: string[];
|
|
110
|
+
}
|
|
111
|
+
interface CanciaListStore {
|
|
112
|
+
/**
|
|
113
|
+
* List entries in a list. When `locale` is provided, returns only entries
|
|
114
|
+
* authored in that locale. When omitted, returns entries from all locales
|
|
115
|
+
* tagged with their `locale` field — used by the Content Layer loader to
|
|
116
|
+
* emit every translation in one pass.
|
|
117
|
+
* Stored order is shared across locales (driven by a single _order index),
|
|
118
|
+
* so calling list(...,"en") and list(...,"es") returns the same IDs in the
|
|
119
|
+
* same positions when both locales have the entry.
|
|
120
|
+
*/
|
|
121
|
+
list(site: string, listName: string, locale?: string): Promise<ListEntry[]>;
|
|
122
|
+
/** Get one entry by ID + locale. Returns null if not found. */
|
|
123
|
+
get(site: string, listName: string, id: string, locale: string): Promise<ListEntry | null>;
|
|
124
|
+
/**
|
|
125
|
+
* Create a new entry in `locale`. If `id` is omitted, the adapter
|
|
126
|
+
* generates a random one. If an entry with this id+locale already exists,
|
|
127
|
+
* throws. New IDs are appended to the shared order index; if the ID
|
|
128
|
+
* already exists in another locale (i.e. this create is a translation),
|
|
129
|
+
* the existing position is reused.
|
|
130
|
+
*/
|
|
131
|
+
create(site: string, listName: string, data: Record<string, unknown>, locale: string, id?: string): Promise<ListEntry>;
|
|
132
|
+
/**
|
|
133
|
+
* Update an entry's data. Pass the `_rev` from the last read; mismatch
|
|
134
|
+
* throws RevConflictError. Position is unchanged.
|
|
135
|
+
*/
|
|
136
|
+
update(site: string, listName: string, id: string, locale: string, data: Record<string, unknown>, rev: Rev): Promise<ListEntry>;
|
|
137
|
+
/**
|
|
138
|
+
* Delete an entry for a specific locale. If this was the last locale
|
|
139
|
+
* with the entry, the ID is also removed from the order index.
|
|
140
|
+
*/
|
|
141
|
+
delete(site: string, listName: string, id: string, locale: string): Promise<void>;
|
|
142
|
+
/**
|
|
143
|
+
* Replace the canonical order of entries by ID. Order is shared across
|
|
144
|
+
* locales — an ID at position 0 sits there in every locale. IDs not in
|
|
145
|
+
* `ids` are dropped from the order (and from storage in every locale).
|
|
146
|
+
* Atomic.
|
|
147
|
+
*/
|
|
148
|
+
reorder(site: string, listName: string, ids: string[]): Promise<void>;
|
|
149
|
+
/**
|
|
150
|
+
* For each entry ID in the list, report which locales have it.
|
|
151
|
+
* Used by the toolbar to flag untranslated entries.
|
|
152
|
+
*/
|
|
153
|
+
translations(site: string, listName: string): Promise<TranslationStatus[]>;
|
|
154
|
+
}
|
|
155
|
+
interface CanciaStorageV2 {
|
|
156
|
+
kv: CanciaKVStore;
|
|
157
|
+
pages: CanciaPageStore;
|
|
158
|
+
lists: CanciaListStore;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export { type CanciaStorage as C, type ListEntry as L, type PageMeta as P, type Rev as R, type CanciaStorageV2 as a, type CanciaKVStore as b, type CanciaListStore as c, type CanciaPageStore as d, type PageRecord as e, type PageSEO as f, RevConflictError as g };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
interface DetectedImage {
|
|
2
|
+
mime: string;
|
|
3
|
+
ext: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
type UploadHandler = (file: File, site: string, detected: DetectedImage) => Promise<string>;
|
|
7
|
+
declare function makeLocalUploadHandler(opts: {
|
|
8
|
+
uploadDir?: string;
|
|
9
|
+
publicUrlBase?: string;
|
|
10
|
+
maxMB?: number;
|
|
11
|
+
}): UploadHandler;
|
|
12
|
+
|
|
13
|
+
export { type UploadHandler as U, makeLocalUploadHandler as m };
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cancia/astro",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Astro integration for Cancia CMS — inline editing with zero separate server",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/ninuzdellalb/cancia",
|
|
9
|
+
"directory": "packages/astro"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./schema": {
|
|
20
|
+
"types": "./dist/schema/index.d.ts",
|
|
21
|
+
"import": "./dist/schema/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./loader": {
|
|
24
|
+
"types": "./dist/loader/index.d.ts",
|
|
25
|
+
"import": "./dist/loader/index.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist"
|
|
30
|
+
],
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"astro": ">=5.0.0 <8.0.0",
|
|
33
|
+
"better-sqlite3": ">=9.0.0"
|
|
34
|
+
},
|
|
35
|
+
"peerDependenciesMeta": {
|
|
36
|
+
"astro": {
|
|
37
|
+
"optional": false
|
|
38
|
+
},
|
|
39
|
+
"better-sqlite3": {
|
|
40
|
+
"optional": true
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/better-sqlite3": "^7.6.0",
|
|
45
|
+
"astro": "^7.0.0",
|
|
46
|
+
"better-sqlite3": "^11.0.0",
|
|
47
|
+
"tsup": "^8.0.0",
|
|
48
|
+
"typescript": "^5.4.0",
|
|
49
|
+
"vite": "^8.0.0"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"zod": "^4.4.3"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsup",
|
|
56
|
+
"typecheck": "tsc --noEmit"
|
|
57
|
+
}
|
|
58
|
+
}
|