@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.
Files changed (89) hide show
  1. package/CHANGELOG.md +148 -0
  2. package/LICENSE +166 -0
  3. package/README.md +140 -0
  4. package/api/public-api.md +1070 -0
  5. package/dist/adapter-C_0mhENy.d.ts +320 -0
  6. package/dist/admin/admin.css +5 -0
  7. package/dist/admin/index.d.ts +265 -0
  8. package/dist/admin/index.js +393 -0
  9. package/dist/admin/index.js.map +1 -0
  10. package/dist/admin/tokens.css +597 -0
  11. package/dist/admin/ui.d.ts +3013 -0
  12. package/dist/admin/ui.js +9783 -0
  13. package/dist/admin/ui.js.map +1 -0
  14. package/dist/auth/index.d.ts +4 -0
  15. package/dist/auth/index.js +9 -0
  16. package/dist/auth/index.js.map +1 -0
  17. package/dist/auth-B2gBtHl5.d.ts +28 -0
  18. package/dist/can-Chy7zmx7.d.ts +69 -0
  19. package/dist/chunk-3I6337WJ.js +83 -0
  20. package/dist/chunk-3I6337WJ.js.map +1 -0
  21. package/dist/chunk-4Z6P3XCE.js +62 -0
  22. package/dist/chunk-4Z6P3XCE.js.map +1 -0
  23. package/dist/chunk-6E3ZS5Z5.js +1007 -0
  24. package/dist/chunk-6E3ZS5Z5.js.map +1 -0
  25. package/dist/chunk-77JDRXXU.js +3 -0
  26. package/dist/chunk-77JDRXXU.js.map +1 -0
  27. package/dist/chunk-FHYWJCG6.js +171 -0
  28. package/dist/chunk-FHYWJCG6.js.map +1 -0
  29. package/dist/chunk-JAE7HHMM.js +506 -0
  30. package/dist/chunk-JAE7HHMM.js.map +1 -0
  31. package/dist/chunk-NPDBQBLW.js +68 -0
  32. package/dist/chunk-NPDBQBLW.js.map +1 -0
  33. package/dist/chunk-ORMYARXD.js +6 -0
  34. package/dist/chunk-ORMYARXD.js.map +1 -0
  35. package/dist/chunk-R4O7APIX.js +51 -0
  36. package/dist/chunk-R4O7APIX.js.map +1 -0
  37. package/dist/chunk-ROLKRJWI.js +187 -0
  38. package/dist/chunk-ROLKRJWI.js.map +1 -0
  39. package/dist/chunk-SOTQ3WP5.js +31 -0
  40. package/dist/chunk-SOTQ3WP5.js.map +1 -0
  41. package/dist/chunk-TCFFHTY3.js +97 -0
  42. package/dist/chunk-TCFFHTY3.js.map +1 -0
  43. package/dist/chunk-TS7Y7GEZ.js +7 -0
  44. package/dist/chunk-TS7Y7GEZ.js.map +1 -0
  45. package/dist/chunk-VLGR5PZX.js +3 -0
  46. package/dist/chunk-VLGR5PZX.js.map +1 -0
  47. package/dist/chunk-ZW2PUSVS.js +40 -0
  48. package/dist/chunk-ZW2PUSVS.js.map +1 -0
  49. package/dist/chunk-ZYNBMYJ6.js +392 -0
  50. package/dist/chunk-ZYNBMYJ6.js.map +1 -0
  51. package/dist/cli/nonext-husk.js +3360 -0
  52. package/dist/cli/nonext-husk.js.map +1 -0
  53. package/dist/cli/templates/firestore.rules +393 -0
  54. package/dist/cli/templates/storage.rules +165 -0
  55. package/dist/client/index.d.ts +27 -0
  56. package/dist/client/index.js +15 -0
  57. package/dist/client/index.js.map +1 -0
  58. package/dist/cms-9fSA3uQV.d.ts +158 -0
  59. package/dist/core/index.d.ts +1267 -0
  60. package/dist/core/index.js +12 -0
  61. package/dist/core/index.js.map +1 -0
  62. package/dist/entry-D3kiE8T1.d.ts +163 -0
  63. package/dist/field-Diyf5siF.d.ts +158 -0
  64. package/dist/field-type-D25Vojb0.d.ts +157 -0
  65. package/dist/firebase/index.d.ts +251 -0
  66. package/dist/firebase/index.js +991 -0
  67. package/dist/firebase/index.js.map +1 -0
  68. package/dist/index-5ylaMBUH.d.ts +155 -0
  69. package/dist/index.d.ts +14 -0
  70. package/dist/index.js +17 -0
  71. package/dist/index.js.map +1 -0
  72. package/dist/permissions/index.d.ts +101 -0
  73. package/dist/permissions/index.js +7 -0
  74. package/dist/permissions/index.js.map +1 -0
  75. package/dist/registry-CUC25qay.d.ts +59 -0
  76. package/dist/richtext/index.d.ts +85 -0
  77. package/dist/richtext/index.js +127 -0
  78. package/dist/richtext/index.js.map +1 -0
  79. package/dist/schema/index.d.ts +50 -0
  80. package/dist/schema/index.js +5 -0
  81. package/dist/schema/index.js.map +1 -0
  82. package/dist/server/index.d.ts +39 -0
  83. package/dist/server/index.js +25 -0
  84. package/dist/server/index.js.map +1 -0
  85. package/dist/types/index.d.ts +36 -0
  86. package/dist/types/index.js +6 -0
  87. package/dist/types/index.js.map +1 -0
  88. package/dist/write-limits-Djmm6L3R.d.ts +215 -0
  89. 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 };