@nonext/husk 0.2.3
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/CHANGELOG.md +148 -0
- package/LICENSE +166 -0
- package/README.md +140 -0
- package/api/public-api.md +1070 -0
- package/dist/adapter-C_0mhENy.d.ts +320 -0
- package/dist/admin/admin.css +5 -0
- package/dist/admin/index.d.ts +265 -0
- package/dist/admin/index.js +393 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/tokens.css +597 -0
- package/dist/admin/ui.d.ts +3013 -0
- package/dist/admin/ui.js +9783 -0
- package/dist/admin/ui.js.map +1 -0
- package/dist/auth/index.d.ts +4 -0
- package/dist/auth/index.js +9 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth-B2gBtHl5.d.ts +28 -0
- package/dist/can-Chy7zmx7.d.ts +69 -0
- package/dist/chunk-3I6337WJ.js +83 -0
- package/dist/chunk-3I6337WJ.js.map +1 -0
- package/dist/chunk-4Z6P3XCE.js +62 -0
- package/dist/chunk-4Z6P3XCE.js.map +1 -0
- package/dist/chunk-6E3ZS5Z5.js +1007 -0
- package/dist/chunk-6E3ZS5Z5.js.map +1 -0
- package/dist/chunk-77JDRXXU.js +3 -0
- package/dist/chunk-77JDRXXU.js.map +1 -0
- package/dist/chunk-FHYWJCG6.js +171 -0
- package/dist/chunk-FHYWJCG6.js.map +1 -0
- package/dist/chunk-JAE7HHMM.js +506 -0
- package/dist/chunk-JAE7HHMM.js.map +1 -0
- package/dist/chunk-NPDBQBLW.js +68 -0
- package/dist/chunk-NPDBQBLW.js.map +1 -0
- package/dist/chunk-ORMYARXD.js +6 -0
- package/dist/chunk-ORMYARXD.js.map +1 -0
- package/dist/chunk-R4O7APIX.js +51 -0
- package/dist/chunk-R4O7APIX.js.map +1 -0
- package/dist/chunk-ROLKRJWI.js +187 -0
- package/dist/chunk-ROLKRJWI.js.map +1 -0
- package/dist/chunk-SOTQ3WP5.js +31 -0
- package/dist/chunk-SOTQ3WP5.js.map +1 -0
- package/dist/chunk-TCFFHTY3.js +97 -0
- package/dist/chunk-TCFFHTY3.js.map +1 -0
- package/dist/chunk-TS7Y7GEZ.js +7 -0
- package/dist/chunk-TS7Y7GEZ.js.map +1 -0
- package/dist/chunk-VLGR5PZX.js +3 -0
- package/dist/chunk-VLGR5PZX.js.map +1 -0
- package/dist/chunk-ZW2PUSVS.js +40 -0
- package/dist/chunk-ZW2PUSVS.js.map +1 -0
- package/dist/chunk-ZYNBMYJ6.js +392 -0
- package/dist/chunk-ZYNBMYJ6.js.map +1 -0
- package/dist/cli/nonext-husk.js +3360 -0
- package/dist/cli/nonext-husk.js.map +1 -0
- package/dist/cli/templates/firestore.rules +393 -0
- package/dist/cli/templates/storage.rules +165 -0
- package/dist/client/index.d.ts +27 -0
- package/dist/client/index.js +15 -0
- package/dist/client/index.js.map +1 -0
- package/dist/cms-9fSA3uQV.d.ts +158 -0
- package/dist/core/index.d.ts +1267 -0
- package/dist/core/index.js +12 -0
- package/dist/core/index.js.map +1 -0
- package/dist/entry-D3kiE8T1.d.ts +163 -0
- package/dist/field-Diyf5siF.d.ts +158 -0
- package/dist/field-type-D25Vojb0.d.ts +157 -0
- package/dist/firebase/index.d.ts +251 -0
- package/dist/firebase/index.js +991 -0
- package/dist/firebase/index.js.map +1 -0
- package/dist/index-5ylaMBUH.d.ts +155 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/permissions/index.d.ts +101 -0
- package/dist/permissions/index.js +7 -0
- package/dist/permissions/index.js.map +1 -0
- package/dist/registry-CUC25qay.d.ts +59 -0
- package/dist/richtext/index.d.ts +85 -0
- package/dist/richtext/index.js +127 -0
- package/dist/richtext/index.js.map +1 -0
- package/dist/schema/index.d.ts +50 -0
- package/dist/schema/index.js +5 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/server/index.d.ts +39 -0
- package/dist/server/index.js +25 -0
- package/dist/server/index.js.map +1 -0
- package/dist/types/index.d.ts +36 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/dist/write-limits-Djmm6L3R.d.ts +215 -0
- package/package.json +136 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { b as ContentTypeKind, A as AnyContentTypeShape, f as Entry } from './entry-D3kiE8T1.js';
|
|
2
|
+
import { M as MediaItem } from './field-Diyf5siF.js';
|
|
3
|
+
import { C as CmsAdapter, L as ListQuery, P as Page } from './adapter-C_0mhENy.js';
|
|
4
|
+
|
|
5
|
+
/** The minimum a kind guard needs to know about a content type. */
|
|
6
|
+
interface KindShape {
|
|
7
|
+
slug: string;
|
|
8
|
+
kind: ContentTypeKind;
|
|
9
|
+
}
|
|
10
|
+
declare function isCollection(type: KindShape): boolean;
|
|
11
|
+
declare function isSingleton(type: KindShape): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* The one document id a singleton is allowed to use.
|
|
14
|
+
*
|
|
15
|
+
* Derived from the slug rather than generated, so "exactly one entry" is a property of the
|
|
16
|
+
* path itself: a second create targets the same document and is a write conflict rather
|
|
17
|
+
* than a second entry. See .docs/decisions/2026-09-18-firestore-layout.md.
|
|
18
|
+
*/
|
|
19
|
+
declare function singletonEntryId(slug: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Guards a create against a singleton.
|
|
22
|
+
*
|
|
23
|
+
* `exists` is what the adapter read a moment ago. The path-derived id makes the race
|
|
24
|
+
* harmless: two concurrent creates address the same document, so the second is an
|
|
25
|
+
* overwrite of the first rather than a duplicate entry.
|
|
26
|
+
*/
|
|
27
|
+
declare function assertSingletonCreatable(type: KindShape, exists: boolean): void;
|
|
28
|
+
/** Guards an addressed write or read against a singleton. */
|
|
29
|
+
declare function assertSingletonEntryId(type: KindShape, entryId: string): void;
|
|
30
|
+
/** Guards list-shaped operations, which a singleton does not have. */
|
|
31
|
+
declare function assertListable(type: KindShape): void;
|
|
32
|
+
/** Guards deletion, which would leave a singleton in a state the schema does not describe. */
|
|
33
|
+
declare function assertDeletable(type: KindShape): void;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The public read SDK. The surface a customer project programs against.
|
|
37
|
+
*
|
|
38
|
+
* Everything here goes through `CmsAdapter`, which is the whole point: the concept document
|
|
39
|
+
* says the SDK will probably outlive the admin, and that a change to the internal Firestore
|
|
40
|
+
* layout should cost a rewrite of the adapter rather than a rewrite of every customer
|
|
41
|
+
* project. Nothing in this file names a collection, a document id, or a field path in
|
|
42
|
+
* storage, so there is no layout knowledge here to invalidate.
|
|
43
|
+
*
|
|
44
|
+
* It is read-only on purpose. Writing is the admin's job, and it goes through the adapter
|
|
45
|
+
* with a signed-in user, permission checks, and validation. Adding a `create` here would
|
|
46
|
+
* mean a second write path with none of that.
|
|
47
|
+
*/
|
|
48
|
+
/**
|
|
49
|
+
* The code-defined content types an instance knows at compile time, keyed by slug.
|
|
50
|
+
*
|
|
51
|
+
* Keyed rather than a list, because that is what makes `cms.collection("projects")` return
|
|
52
|
+
* a typed entry. `ContentType<TFields>` declares `slug: string`, not the literal, so a list
|
|
53
|
+
* of content types offers the type system nothing to index by. The key is the index.
|
|
54
|
+
*
|
|
55
|
+
* The key must equal the type's own `slug`; `createCMS` rejects a map where it does not,
|
|
56
|
+
* because a mismatch would type one content type and read another.
|
|
57
|
+
*/
|
|
58
|
+
type ContentTypeMap = {
|
|
59
|
+
readonly [slug: string]: KindShape & AnyContentTypeShape;
|
|
60
|
+
};
|
|
61
|
+
/** The slugs a map declares. */
|
|
62
|
+
type KnownSlug<TTypes extends ContentTypeMap> = keyof TTypes & string;
|
|
63
|
+
/**
|
|
64
|
+
* A known slug, or any other string.
|
|
65
|
+
*
|
|
66
|
+
* Both halves are required. The known slugs are what an editor offers and what carries the
|
|
67
|
+
* inference; the open half is what keeps a database-defined content type readable, since
|
|
68
|
+
* one created in the Schema Builder has no compile-time existence at all.
|
|
69
|
+
*/
|
|
70
|
+
type SlugOf<TTypes extends ContentTypeMap> = KnownSlug<TTypes> | (string & Record<never, never>);
|
|
71
|
+
/**
|
|
72
|
+
* What one slug reads as.
|
|
73
|
+
*
|
|
74
|
+
* A declared slug resolves to its inferred entry, so `project.title` is `string`. Anything
|
|
75
|
+
* else resolves to the erased `Entry`, whose field values are the union of every field
|
|
76
|
+
* type's value, nullable. That is deliberately not a lie: a database-defined type has no
|
|
77
|
+
* shape at compile time, so the honest type is "some field value or null", and a consumer
|
|
78
|
+
* has to narrow before using it. Typing it as `any`, or pretending it has the fields of
|
|
79
|
+
* some other type, would make the compiler agree to code that cannot work.
|
|
80
|
+
*/
|
|
81
|
+
type EntryOf<TTypes extends ContentTypeMap, TSlug extends string> = TSlug extends keyof TTypes ? Entry<TTypes[TSlug]> : Entry;
|
|
82
|
+
/**
|
|
83
|
+
* What a public read may narrow by.
|
|
84
|
+
*
|
|
85
|
+
* `ListQuery.status` is removed rather than defaulted. A caller on a customer's website
|
|
86
|
+
* cannot ask for drafts at all, which means a leak cannot come from a forwarded query
|
|
87
|
+
* object, a URL parameter that ends up in a filter, or a copied snippet. Status is decided
|
|
88
|
+
* once, by whether the instance is a preview instance.
|
|
89
|
+
*/
|
|
90
|
+
type CmsQuery = Omit<ListQuery, "status">;
|
|
91
|
+
/** The narrowing a count understands. The rest of a query does not change a total. */
|
|
92
|
+
type CmsCountQuery = Pick<CmsQuery, "filters" | "search">;
|
|
93
|
+
interface CollectionReader<TEntry = Entry> {
|
|
94
|
+
/** One page of entries. Pass `page.cursor` back as `query.cursor` for the next one. */
|
|
95
|
+
findMany(query?: CmsQuery): Promise<Page<TEntry>>;
|
|
96
|
+
/** By the value of the content type's slug field, which is what a URL segment carries. */
|
|
97
|
+
findBySlug(value: string): Promise<TEntry | null>;
|
|
98
|
+
/** By entry id. */
|
|
99
|
+
findById(entryId: string): Promise<TEntry | null>;
|
|
100
|
+
/** How many entries the same narrowing would return. */
|
|
101
|
+
count(query?: CmsCountQuery): Promise<number>;
|
|
102
|
+
}
|
|
103
|
+
interface SingleReader<TEntry = Entry> {
|
|
104
|
+
/** The one entry of a singleton. Null when it has never been saved. */
|
|
105
|
+
get(): Promise<TEntry | null>;
|
|
106
|
+
}
|
|
107
|
+
interface MediaReader {
|
|
108
|
+
getById(mediaId: string): Promise<MediaItem | null>;
|
|
109
|
+
/** Missing ids are omitted rather than returned as holes, so the result may be shorter. */
|
|
110
|
+
getMany(mediaIds: readonly string[]): Promise<readonly MediaItem[]>;
|
|
111
|
+
}
|
|
112
|
+
interface SettingsReader {
|
|
113
|
+
/** A settings document by key, or null when it has never been written. */
|
|
114
|
+
get(key: string): Promise<Record<string, unknown> | null>;
|
|
115
|
+
}
|
|
116
|
+
interface Cms<TTypes extends ContentTypeMap = ContentTypeMap> {
|
|
117
|
+
collection<TSlug extends SlugOf<TTypes>>(slug: TSlug): CollectionReader<EntryOf<TTypes, TSlug>>;
|
|
118
|
+
single<TSlug extends SlugOf<TTypes>>(slug: TSlug): SingleReader<EntryOf<TTypes, TSlug>>;
|
|
119
|
+
media(): MediaReader;
|
|
120
|
+
settings(): SettingsReader;
|
|
121
|
+
/** True when this instance reads unpublished entries. False on every public instance. */
|
|
122
|
+
readonly isPreview: boolean;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Everything an instance needs except the decision about unpublished content.
|
|
126
|
+
*
|
|
127
|
+
* Split out so the client entry can accept exactly this and nothing more: a config type
|
|
128
|
+
* with no `preview` key makes passing one a compile error rather than a review comment.
|
|
129
|
+
*/
|
|
130
|
+
interface CmsReadConfig<TTypes extends ContentTypeMap = ContentTypeMap> {
|
|
131
|
+
/** The storage seam. The SDK holds no other handle on data. */
|
|
132
|
+
adapter: CmsAdapter;
|
|
133
|
+
/**
|
|
134
|
+
* The code-defined content types, keyed by slug. Omitting them costs the inference and
|
|
135
|
+
* nothing else: every read still works and every entry is typed as the erased `Entry`.
|
|
136
|
+
*/
|
|
137
|
+
contentTypes?: TTypes;
|
|
138
|
+
}
|
|
139
|
+
interface CmsConfig<TTypes extends ContentTypeMap = ContentTypeMap> extends CmsReadConfig<TTypes> {
|
|
140
|
+
/**
|
|
141
|
+
* Reads every status instead of published only.
|
|
142
|
+
*
|
|
143
|
+
* Off unless it is passed, and there is no way to turn it on after construction: a
|
|
144
|
+
* preview instance is a different object built by a different call. See
|
|
145
|
+
* `.docs/decisions/2026-09-19-cms-sdk-surface.md` for why it is not a per-call argument.
|
|
146
|
+
*/
|
|
147
|
+
preview?: boolean;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Builds a CMS instance over an adapter.
|
|
151
|
+
*
|
|
152
|
+
* Server and client variants live in `@nonext/husk/server` and `@nonext/husk/client`; this is
|
|
153
|
+
* the shared implementation both are built from, and it is exported so a test or a custom
|
|
154
|
+
* runtime can construct one directly over a test double.
|
|
155
|
+
*/
|
|
156
|
+
declare function createCMS<TTypes extends ContentTypeMap = ContentTypeMap>(config: CmsConfig<TTypes>): Cms<TTypes>;
|
|
157
|
+
|
|
158
|
+
export { type Cms as C, type EntryOf as E, type KnownSlug as K, type MediaReader as M, type SettingsReader as S, type CmsConfig as a, type CmsCountQuery as b, type CmsQuery as c, type CmsReadConfig as d, type CollectionReader as e, type ContentTypeMap as f, type SingleReader as g, type SlugOf as h, assertDeletable as i, assertListable as j, assertSingletonCreatable as k, assertSingletonEntryId as l, createCMS as m, isCollection as n, isSingleton as o, singletonEntryId as s };
|