@nexpress/core 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/LICENSE +21 -0
- package/README.md +69 -0
- package/dist/audit-54XLVCWD.js +14 -0
- package/dist/audit-54XLVCWD.js.map +1 -0
- package/dist/auth.d.ts +640 -0
- package/dist/auth.js +94 -0
- package/dist/auth.js.map +1 -0
- package/dist/can-YLUHRJAB.js +19 -0
- package/dist/can-YLUHRJAB.js.map +1 -0
- package/dist/chunk-2G264RCD.js +68 -0
- package/dist/chunk-2G264RCD.js.map +1 -0
- package/dist/chunk-2YDGE7YX.js +92 -0
- package/dist/chunk-2YDGE7YX.js.map +1 -0
- package/dist/chunk-473S4TER.js +538 -0
- package/dist/chunk-473S4TER.js.map +1 -0
- package/dist/chunk-4ZLMEKFX.js +18 -0
- package/dist/chunk-4ZLMEKFX.js.map +1 -0
- package/dist/chunk-55FU6WED.js +179 -0
- package/dist/chunk-55FU6WED.js.map +1 -0
- package/dist/chunk-6YI5K2TI.js +1959 -0
- package/dist/chunk-6YI5K2TI.js.map +1 -0
- package/dist/chunk-BHK3AD3Q.js +41 -0
- package/dist/chunk-BHK3AD3Q.js.map +1 -0
- package/dist/chunk-CRUQBZUF.js +39 -0
- package/dist/chunk-CRUQBZUF.js.map +1 -0
- package/dist/chunk-CTSQ7BRI.js +175 -0
- package/dist/chunk-CTSQ7BRI.js.map +1 -0
- package/dist/chunk-DK2JBJH7.js +81 -0
- package/dist/chunk-DK2JBJH7.js.map +1 -0
- package/dist/chunk-DP2PREDU.js +597 -0
- package/dist/chunk-DP2PREDU.js.map +1 -0
- package/dist/chunk-EQ2Z3KMD.js +24 -0
- package/dist/chunk-EQ2Z3KMD.js.map +1 -0
- package/dist/chunk-FZ7O6DWI.js +305 -0
- package/dist/chunk-FZ7O6DWI.js.map +1 -0
- package/dist/chunk-ISLYFQWL.js +1270 -0
- package/dist/chunk-ISLYFQWL.js.map +1 -0
- package/dist/chunk-JJL74ZPK.js +68 -0
- package/dist/chunk-JJL74ZPK.js.map +1 -0
- package/dist/chunk-JKXAPSU4.js +24 -0
- package/dist/chunk-JKXAPSU4.js.map +1 -0
- package/dist/chunk-KU5M27ZC.js +24 -0
- package/dist/chunk-KU5M27ZC.js.map +1 -0
- package/dist/chunk-LSHHRDVR.js +34 -0
- package/dist/chunk-LSHHRDVR.js.map +1 -0
- package/dist/chunk-M43PGOQY.js +715 -0
- package/dist/chunk-M43PGOQY.js.map +1 -0
- package/dist/chunk-MEJAHXIO.js +150 -0
- package/dist/chunk-MEJAHXIO.js.map +1 -0
- package/dist/chunk-NUCGHWCF.js +101 -0
- package/dist/chunk-NUCGHWCF.js.map +1 -0
- package/dist/chunk-OK5HOCQI.js +845 -0
- package/dist/chunk-OK5HOCQI.js.map +1 -0
- package/dist/chunk-OROPGO65.js +13 -0
- package/dist/chunk-OROPGO65.js.map +1 -0
- package/dist/chunk-PPAS4SZR.js +176 -0
- package/dist/chunk-PPAS4SZR.js.map +1 -0
- package/dist/chunk-PPBWRKO2.js +171 -0
- package/dist/chunk-PPBWRKO2.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/chunk-QO7LAQZH.js +321 -0
- package/dist/chunk-QO7LAQZH.js.map +1 -0
- package/dist/chunk-QVJ2HCAX.js +225 -0
- package/dist/chunk-QVJ2HCAX.js.map +1 -0
- package/dist/chunk-RIPHIRPP.js +68 -0
- package/dist/chunk-RIPHIRPP.js.map +1 -0
- package/dist/chunk-S27S42QY.js +134 -0
- package/dist/chunk-S27S42QY.js.map +1 -0
- package/dist/chunk-SBCVAC2Z.js +40 -0
- package/dist/chunk-SBCVAC2Z.js.map +1 -0
- package/dist/chunk-TFJ4MKPH.js +694 -0
- package/dist/chunk-TFJ4MKPH.js.map +1 -0
- package/dist/chunk-THX3SHYA.js +75 -0
- package/dist/chunk-THX3SHYA.js.map +1 -0
- package/dist/chunk-UGQSQO5B.js +222 -0
- package/dist/chunk-UGQSQO5B.js.map +1 -0
- package/dist/chunk-V2UNHGAP.js +26 -0
- package/dist/chunk-V2UNHGAP.js.map +1 -0
- package/dist/chunk-VGTPQXNQ.js +2790 -0
- package/dist/chunk-VGTPQXNQ.js.map +1 -0
- package/dist/chunk-VNIHXQ7W.js +194 -0
- package/dist/chunk-VNIHXQ7W.js.map +1 -0
- package/dist/chunk-WV272MPW.js +31 -0
- package/dist/chunk-WV272MPW.js.map +1 -0
- package/dist/chunk-X5KKBOUS.js +26 -0
- package/dist/chunk-X5KKBOUS.js.map +1 -0
- package/dist/chunk-XANPEOJC.js +17 -0
- package/dist/chunk-XANPEOJC.js.map +1 -0
- package/dist/chunk-XPVQIHAQ.js +83 -0
- package/dist/chunk-XPVQIHAQ.js.map +1 -0
- package/dist/chunk-ZCINJSS4.js +75 -0
- package/dist/chunk-ZCINJSS4.js.map +1 -0
- package/dist/community.d.ts +1425 -0
- package/dist/community.js +206 -0
- package/dist/community.js.map +1 -0
- package/dist/config-2GDU7PCK.js +32 -0
- package/dist/config-2GDU7PCK.js.map +1 -0
- package/dist/context-MNZ4QXPC.js +16 -0
- package/dist/context-MNZ4QXPC.js.map +1 -0
- package/dist/db-schema.d.ts +4 -0
- package/dist/db-schema.js +102 -0
- package/dist/db-schema.js.map +1 -0
- package/dist/db.d.ts +7 -0
- package/dist/db.js +117 -0
- package/dist/db.js.map +1 -0
- package/dist/digest-SY42GQSU.js +17 -0
- package/dist/digest-SY42GQSU.js.map +1 -0
- package/dist/errors-5OS3S2J3.js +22 -0
- package/dist/errors-5OS3S2J3.js.map +1 -0
- package/dist/host-OBOI4MJK.js +51 -0
- package/dist/host-OBOI4MJK.js.map +1 -0
- package/dist/i18n.d.ts +301 -0
- package/dist/i18n.js +68 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index-B6-_vr_m.d.ts +590 -0
- package/dist/index-CY55LC0u.d.ts +4722 -0
- package/dist/index-CeiTvwbp.d.ts +168 -0
- package/dist/index-XwP1ET8b.d.ts +61 -0
- package/dist/index.d.ts +2037 -0
- package/dist/index.js +2205 -0
- package/dist/index.js.map +1 -0
- package/dist/job-log-VZXWQUDK.js +24 -0
- package/dist/job-log-VZXWQUDK.js.map +1 -0
- package/dist/jobs.d.ts +4 -0
- package/dist/jobs.js +76 -0
- package/dist/jobs.js.map +1 -0
- package/dist/logger-DqGaOU_j.d.ts +29 -0
- package/dist/logger-S7REWDNE.js +16 -0
- package/dist/logger-S7REWDNE.js.map +1 -0
- package/dist/media.d.ts +5 -0
- package/dist/media.js +41 -0
- package/dist/media.js.map +1 -0
- package/dist/mentions-2IHFVSHW.js +23 -0
- package/dist/mentions-2IHFVSHW.js.map +1 -0
- package/dist/mutes-EWAE5FZR.js +21 -0
- package/dist/mutes-EWAE5FZR.js.map +1 -0
- package/dist/notification-prefs-VPJDU7I6.js +21 -0
- package/dist/notification-prefs-VPJDU7I6.js.map +1 -0
- package/dist/observability.d.ts +156 -0
- package/dist/observability.js +32 -0
- package/dist/observability.js.map +1 -0
- package/dist/profanity-adapter-NU2JQSLX.js +12 -0
- package/dist/profanity-adapter-NU2JQSLX.js.map +1 -0
- package/dist/queue-XE5BC75T.js +14 -0
- package/dist/queue-XE5BC75T.js.map +1 -0
- package/dist/rate-limit.d.ts +99 -0
- package/dist/rate-limit.js +14 -0
- package/dist/rate-limit.js.map +1 -0
- package/dist/registry-XIXDEPVI.js +31 -0
- package/dist/registry-XIXDEPVI.js.map +1 -0
- package/dist/reputation-JRL2YQHM.js +11 -0
- package/dist/reputation-JRL2YQHM.js.map +1 -0
- package/dist/routes.d.ts +43 -0
- package/dist/routes.js +12 -0
- package/dist/routes.js.map +1 -0
- package/dist/scheduled-CIQM57HT.js +20 -0
- package/dist/scheduled-CIQM57HT.js.map +1 -0
- package/dist/seo.d.ts +410 -0
- package/dist/seo.js +44 -0
- package/dist/seo.js.map +1 -0
- package/dist/settings-FOBIESPB.js +17 -0
- package/dist/settings-FOBIESPB.js.map +1 -0
- package/dist/spam-adapter-XX3G737Z.js +12 -0
- package/dist/spam-adapter-XX3G737Z.js.map +1 -0
- package/dist/strings-VAE47B2C.js +29 -0
- package/dist/strings-VAE47B2C.js.map +1 -0
- package/dist/templates-IFVJMCJ6.js +12 -0
- package/dist/templates-IFVJMCJ6.js.map +1 -0
- package/dist/types-TlsbXS0T.d.ts +871 -0
- package/package.json +129 -0
|
@@ -0,0 +1,871 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical principal type — "who is the actor on this operation".
|
|
3
|
+
*
|
|
4
|
+
* Both staff routes (which carry an authenticated `NpAuthUser`) and
|
|
5
|
+
* member routes (which carry only a `memberId`) share this single
|
|
6
|
+
* union. Used by the collection pipeline, plugin hooks (surfaced as
|
|
7
|
+
* `NpHookPrincipal` for historical reasons), and the community
|
|
8
|
+
* `principalCan()` resolver.
|
|
9
|
+
*
|
|
10
|
+
* Add a new variant only after auditing every `switch (principal.kind)`
|
|
11
|
+
* site — the exhaustive switches deliberately fail to compile when the
|
|
12
|
+
* union grows.
|
|
13
|
+
*/
|
|
14
|
+
type NpPrincipal = {
|
|
15
|
+
kind: "staff";
|
|
16
|
+
user: NpAuthUser;
|
|
17
|
+
} | {
|
|
18
|
+
kind: "member";
|
|
19
|
+
memberId: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type NpUserRole = "admin" | "editor" | "moderator" | "author" | "viewer";
|
|
23
|
+
interface NpAuthUser {
|
|
24
|
+
id: string;
|
|
25
|
+
email: string;
|
|
26
|
+
name: string;
|
|
27
|
+
role: NpUserRole;
|
|
28
|
+
tokenVersion: number;
|
|
29
|
+
}
|
|
30
|
+
type NpAccessFunction = (args: {
|
|
31
|
+
user: NpAuthUser | null;
|
|
32
|
+
doc?: Record<string, unknown>;
|
|
33
|
+
data?: Record<string, unknown>;
|
|
34
|
+
}) => boolean | Promise<boolean>;
|
|
35
|
+
type NpFieldCondition = (data: Record<string, unknown>, siblingData: Record<string, unknown>) => boolean;
|
|
36
|
+
type NpFieldValidator = (value: unknown, args: {
|
|
37
|
+
data: Record<string, unknown>;
|
|
38
|
+
siblingData: Record<string, unknown>;
|
|
39
|
+
}) => string | true | Promise<string | true>;
|
|
40
|
+
type NpRichTextContent = Record<string, unknown>;
|
|
41
|
+
interface NpEditorConfig {
|
|
42
|
+
features?: string[];
|
|
43
|
+
}
|
|
44
|
+
interface NpFieldBase {
|
|
45
|
+
name: string;
|
|
46
|
+
label?: string;
|
|
47
|
+
required?: boolean;
|
|
48
|
+
defaultValue?: unknown;
|
|
49
|
+
hidden?: boolean;
|
|
50
|
+
admin?: {
|
|
51
|
+
description?: string;
|
|
52
|
+
placeholder?: string;
|
|
53
|
+
readOnly?: boolean;
|
|
54
|
+
condition?: NpFieldCondition;
|
|
55
|
+
width?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Optional override for the admin field renderer. The default
|
|
58
|
+
* renderer dispatches on `type` (text → input, textarea →
|
|
59
|
+
* textarea, etc.); `kind` overrides that with a specialized
|
|
60
|
+
* widget.
|
|
61
|
+
* - `templatePicker` (Phase 11.3) replaces the input with a
|
|
62
|
+
* dropdown sourced from the active theme's
|
|
63
|
+
* `templates.{collection}` registry.
|
|
64
|
+
* - `title` renders a large borderless headline input that
|
|
65
|
+
* sits above the rest of the form (intended for the
|
|
66
|
+
* primary title of a document). The edit view skips the
|
|
67
|
+
* Card wrapper around it so the title flows naturally
|
|
68
|
+
* into the editor canvas underneath.
|
|
69
|
+
*/
|
|
70
|
+
kind?: "templatePicker" | "title";
|
|
71
|
+
/**
|
|
72
|
+
* Where the field should land in the edit view's two-column
|
|
73
|
+
* layout. Mark publishing-related metadata (SEO, template
|
|
74
|
+
* choice, scheduling inputs) as `"sidebar"` so they group
|
|
75
|
+
* with Status / Slug in the sticky right column rather than
|
|
76
|
+
* competing with the primary editing surface.
|
|
77
|
+
*
|
|
78
|
+
* When unset, the legacy heuristic decides: `type: "date"`
|
|
79
|
+
* fields, fields with an explicit `admin.width`, and the
|
|
80
|
+
* well-known names `status` / `publishedAt` / `slug` all
|
|
81
|
+
* land in the sidebar; everything else goes to main. An
|
|
82
|
+
* explicit `"main"` overrides that heuristic — useful for
|
|
83
|
+
* surfacing a date input in the primary column.
|
|
84
|
+
*/
|
|
85
|
+
position?: "main" | "sidebar";
|
|
86
|
+
};
|
|
87
|
+
validate?: NpFieldValidator;
|
|
88
|
+
}
|
|
89
|
+
interface NpTextField extends NpFieldBase {
|
|
90
|
+
type: "text";
|
|
91
|
+
minLength?: number;
|
|
92
|
+
maxLength?: number;
|
|
93
|
+
unique?: boolean;
|
|
94
|
+
}
|
|
95
|
+
interface NpTextareaField extends NpFieldBase {
|
|
96
|
+
type: "textarea";
|
|
97
|
+
minLength?: number;
|
|
98
|
+
maxLength?: number;
|
|
99
|
+
rows?: number;
|
|
100
|
+
}
|
|
101
|
+
interface NpNumberField extends NpFieldBase {
|
|
102
|
+
type: "number";
|
|
103
|
+
min?: number;
|
|
104
|
+
max?: number;
|
|
105
|
+
step?: number;
|
|
106
|
+
integerOnly?: boolean;
|
|
107
|
+
}
|
|
108
|
+
interface NpRichTextField extends NpFieldBase {
|
|
109
|
+
type: "richText";
|
|
110
|
+
editor?: NpEditorConfig;
|
|
111
|
+
}
|
|
112
|
+
interface NpBlocksField extends NpFieldBase {
|
|
113
|
+
type: "blocks";
|
|
114
|
+
allowedBlocks?: string[];
|
|
115
|
+
minRows?: number;
|
|
116
|
+
maxRows?: number;
|
|
117
|
+
}
|
|
118
|
+
interface NpCheckboxField extends NpFieldBase {
|
|
119
|
+
type: "checkbox";
|
|
120
|
+
defaultValue?: boolean;
|
|
121
|
+
}
|
|
122
|
+
interface NpDateField extends NpFieldBase {
|
|
123
|
+
type: "date";
|
|
124
|
+
pickerOptions?: {
|
|
125
|
+
format?: string;
|
|
126
|
+
includeTime?: boolean;
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
interface NpUploadField extends NpFieldBase {
|
|
130
|
+
type: "upload";
|
|
131
|
+
relationTo: string;
|
|
132
|
+
}
|
|
133
|
+
interface NpRelationshipField extends NpFieldBase {
|
|
134
|
+
type: "relationship";
|
|
135
|
+
relationTo: string | string[];
|
|
136
|
+
hasMany?: boolean;
|
|
137
|
+
filterOptions?: Record<string, unknown>;
|
|
138
|
+
}
|
|
139
|
+
interface NpSelectField extends NpFieldBase {
|
|
140
|
+
type: "select";
|
|
141
|
+
options: Array<{
|
|
142
|
+
label: string;
|
|
143
|
+
value: string;
|
|
144
|
+
}>;
|
|
145
|
+
hasMany?: boolean;
|
|
146
|
+
}
|
|
147
|
+
interface NpRadioField extends NpFieldBase {
|
|
148
|
+
type: "radio";
|
|
149
|
+
options: Array<{
|
|
150
|
+
label: string;
|
|
151
|
+
value: string;
|
|
152
|
+
}>;
|
|
153
|
+
}
|
|
154
|
+
interface NpEmailField extends NpFieldBase {
|
|
155
|
+
type: "email";
|
|
156
|
+
}
|
|
157
|
+
interface NpJsonField extends NpFieldBase {
|
|
158
|
+
type: "json";
|
|
159
|
+
}
|
|
160
|
+
interface NpArrayField extends NpFieldBase {
|
|
161
|
+
type: "array";
|
|
162
|
+
fields: NpFieldConfig[];
|
|
163
|
+
minRows?: number;
|
|
164
|
+
maxRows?: number;
|
|
165
|
+
}
|
|
166
|
+
interface NpGroupField extends NpFieldBase {
|
|
167
|
+
type: "group";
|
|
168
|
+
fields: NpFieldConfig[];
|
|
169
|
+
}
|
|
170
|
+
interface NpRowField {
|
|
171
|
+
type: "row";
|
|
172
|
+
fields: NpFieldConfig[];
|
|
173
|
+
}
|
|
174
|
+
interface NpCollapsibleField {
|
|
175
|
+
type: "collapsible";
|
|
176
|
+
label: string;
|
|
177
|
+
fields: NpFieldConfig[];
|
|
178
|
+
}
|
|
179
|
+
type NpFieldConfig = NpTextField | NpTextareaField | NpNumberField | NpRichTextField | NpBlocksField | NpCheckboxField | NpDateField | NpUploadField | NpRelationshipField | NpSelectField | NpRadioField | NpEmailField | NpJsonField | NpArrayField | NpGroupField | NpRowField | NpCollapsibleField;
|
|
180
|
+
|
|
181
|
+
type NpHookPrincipal = NpPrincipal;
|
|
182
|
+
type NpCollectionHook = (args: {
|
|
183
|
+
data: Record<string, unknown>;
|
|
184
|
+
/**
|
|
185
|
+
* Resolved staff session, or `null` when the actor is a member.
|
|
186
|
+
* Pre-9.7o this was always non-null because member writes
|
|
187
|
+
* skipped collection hooks entirely. Hooks that key off staff
|
|
188
|
+
* identity should now switch on `principal.kind` instead.
|
|
189
|
+
*/
|
|
190
|
+
user: NpAuthUser | null;
|
|
191
|
+
/** Polymorphic actor — see `NpHookPrincipal`. */
|
|
192
|
+
principal: NpHookPrincipal;
|
|
193
|
+
collection: string;
|
|
194
|
+
originalDoc?: Record<string, unknown> | null;
|
|
195
|
+
}) => Record<string, unknown> | Promise<Record<string, unknown>>;
|
|
196
|
+
interface NpUploadConfig {
|
|
197
|
+
maxFileSize?: number;
|
|
198
|
+
allowedMimeTypes?: string[];
|
|
199
|
+
imageSizes?: NpImageSize[];
|
|
200
|
+
}
|
|
201
|
+
interface NpImageSize {
|
|
202
|
+
name: string;
|
|
203
|
+
width: number;
|
|
204
|
+
height?: number;
|
|
205
|
+
crop?: "center" | "top" | "bottom" | "left" | "right";
|
|
206
|
+
}
|
|
207
|
+
interface NpCollectionConfig {
|
|
208
|
+
slug: string;
|
|
209
|
+
labels: {
|
|
210
|
+
singular: string;
|
|
211
|
+
plural: string;
|
|
212
|
+
};
|
|
213
|
+
slugField?: boolean | {
|
|
214
|
+
useField?: string;
|
|
215
|
+
unique?: boolean;
|
|
216
|
+
};
|
|
217
|
+
/**
|
|
218
|
+
* Phase 12.1 — opt this collection into i18n. When set, the
|
|
219
|
+
* codegen adds a `locale` text column and a
|
|
220
|
+
* `translation_group_id` uuid column to the generated table.
|
|
221
|
+
* The slug uniqueness index becomes `(locale, slug)` so the
|
|
222
|
+
* same slug can appear in two locales. Fetching helpers
|
|
223
|
+
* (`findDocuments`, `getDoc`) accept a `locale` option;
|
|
224
|
+
* writes require a `locale` field (the pipeline rejects
|
|
225
|
+
* missing-locale writes with NpValidationError).
|
|
226
|
+
*
|
|
227
|
+
* Requires the top-level `i18n` config to also be set.
|
|
228
|
+
* Without it, `i18n: true` here errors at config validation
|
|
229
|
+
* time — the framework needs to know the locale enum to
|
|
230
|
+
* validate writes.
|
|
231
|
+
*/
|
|
232
|
+
i18n?: boolean;
|
|
233
|
+
fields: NpFieldConfig[];
|
|
234
|
+
access?: {
|
|
235
|
+
create?: NpAccessFunction;
|
|
236
|
+
read?: NpAccessFunction;
|
|
237
|
+
update?: NpAccessFunction;
|
|
238
|
+
delete?: NpAccessFunction;
|
|
239
|
+
};
|
|
240
|
+
hooks?: {
|
|
241
|
+
beforeCreate?: NpCollectionHook[];
|
|
242
|
+
afterCreate?: NpCollectionHook[];
|
|
243
|
+
beforeUpdate?: NpCollectionHook[];
|
|
244
|
+
afterUpdate?: NpCollectionHook[];
|
|
245
|
+
beforeDelete?: NpCollectionHook[];
|
|
246
|
+
afterDelete?: NpCollectionHook[];
|
|
247
|
+
beforeRead?: NpCollectionHook[];
|
|
248
|
+
afterRead?: NpCollectionHook[];
|
|
249
|
+
};
|
|
250
|
+
versions?: {
|
|
251
|
+
drafts?: boolean | {
|
|
252
|
+
autosave?: boolean;
|
|
253
|
+
autosaveInterval?: number;
|
|
254
|
+
};
|
|
255
|
+
max?: number;
|
|
256
|
+
};
|
|
257
|
+
/**
|
|
258
|
+
* Community features opt-in per collection. Comments are off by
|
|
259
|
+
* default; flip `comments: true` to let members post comments
|
|
260
|
+
* underneath this collection's documents. Reactions ride on the
|
|
261
|
+
* comment surface — sites enable reactions by enabling comments;
|
|
262
|
+
* a per-collection reactions toggle isn't needed today.
|
|
263
|
+
*
|
|
264
|
+
* `memberWrite.create` (9.7a) lets logged-in members create
|
|
265
|
+
* documents in this collection without needing a staff role.
|
|
266
|
+
* `memberWrite.update` / `memberWrite.delete` (9.7b) extend the
|
|
267
|
+
* member-write surface with owner-only edit / delete (the row's
|
|
268
|
+
* `member_author_id` must match the caller). The staff
|
|
269
|
+
* `access.create` / `access.delete` functions are bypassed on
|
|
270
|
+
* the member path — gating is `assertNotBanned(memberId)` plus
|
|
271
|
+
* the opt-in flag plus the ownership check, not the staff
|
|
272
|
+
* access tree. Member-authored docs default to
|
|
273
|
+
* `_status = "published"` and members CANNOT change status via
|
|
274
|
+
* update; those transitions remain admin-side affordances
|
|
275
|
+
* (a configurable default-status / moderation gate lands in a
|
|
276
|
+
* follow-up).
|
|
277
|
+
*/
|
|
278
|
+
community?: {
|
|
279
|
+
comments?: boolean;
|
|
280
|
+
memberWrite?: {
|
|
281
|
+
create?: boolean;
|
|
282
|
+
update?: boolean;
|
|
283
|
+
delete?: boolean;
|
|
284
|
+
/**
|
|
285
|
+
* Status that member-authored creates land in by default.
|
|
286
|
+
* Defaults to `"published"` (a member's thread is live as
|
|
287
|
+
* soon as it's submitted). Set to `"pending"` to require a
|
|
288
|
+
* mod to promote the row before it shows up on the public
|
|
289
|
+
* site — a flag-on-write moderation gate without writing a
|
|
290
|
+
* spam adapter. The spam adapter, if installed, can also
|
|
291
|
+
* downgrade an individual row to `pending` regardless of
|
|
292
|
+
* this default (`flag` verdict).
|
|
293
|
+
*/
|
|
294
|
+
defaultStatus?: "published" | "pending";
|
|
295
|
+
};
|
|
296
|
+
};
|
|
297
|
+
/**
|
|
298
|
+
* SEO configuration. Phase 10 introduced this surface for the
|
|
299
|
+
* sitemap / RSS / OG metadata pipeline. The contract is
|
|
300
|
+
* opt-in: a collection appears in `/sitemap.xml` iff it
|
|
301
|
+
* declares `seo.urlPath`, which maps a document row to its
|
|
302
|
+
* public URL path (e.g. `(doc) => "/blog/" + doc.slug`).
|
|
303
|
+
* Collections without `seo.urlPath` are assumed to be admin-
|
|
304
|
+
* internal or rendered through a custom route the framework
|
|
305
|
+
* can't introspect.
|
|
306
|
+
*/
|
|
307
|
+
seo?: {
|
|
308
|
+
/**
|
|
309
|
+
* Maps a document row to the public URL path the row is
|
|
310
|
+
* served at, or `null` to skip the row (e.g. a draft / a
|
|
311
|
+
* row whose URL is computed dynamically and shouldn't be
|
|
312
|
+
* indexed). Returned paths must start with `/`. The host
|
|
313
|
+
* comes from `SITE_URL` at sitemap-build time.
|
|
314
|
+
*/
|
|
315
|
+
urlPath?: (doc: Record<string, unknown>) => string | null;
|
|
316
|
+
/**
|
|
317
|
+
* Hint for sitemap consumers about how often this
|
|
318
|
+
* collection's content changes. Optional — Google now
|
|
319
|
+
* largely ignores it but other crawlers still honor it.
|
|
320
|
+
*/
|
|
321
|
+
changefreq?: "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never";
|
|
322
|
+
/**
|
|
323
|
+
* Sitemap priority hint, 0.0–1.0. Optional, same caveat as
|
|
324
|
+
* changefreq.
|
|
325
|
+
*/
|
|
326
|
+
priority?: number;
|
|
327
|
+
};
|
|
328
|
+
timestamps?: boolean;
|
|
329
|
+
admin?: {
|
|
330
|
+
listColumns?: string[];
|
|
331
|
+
defaultSort?: string;
|
|
332
|
+
group?: string;
|
|
333
|
+
hidden?: boolean;
|
|
334
|
+
description?: string;
|
|
335
|
+
components?: {
|
|
336
|
+
listView?: string;
|
|
337
|
+
editView?: string;
|
|
338
|
+
createView?: string;
|
|
339
|
+
};
|
|
340
|
+
/**
|
|
341
|
+
* Opts the collection's edit view into the "In navigation"
|
|
342
|
+
* side panel. Documents in this collection are addressable
|
|
343
|
+
* from the nav editor's `type: "page"` picker via the
|
|
344
|
+
* membership endpoint, so the operator can add/remove the
|
|
345
|
+
* doc from any nav location without leaving the page.
|
|
346
|
+
*
|
|
347
|
+
* Defaults to `false`. The reference `pages` collection in
|
|
348
|
+
* `apps/web` flips it on; sites with a `static-pages` or
|
|
349
|
+
* `landing-pages` collection that should also surface in nav
|
|
350
|
+
* can opt in here too.
|
|
351
|
+
*/
|
|
352
|
+
navMembership?: boolean;
|
|
353
|
+
/**
|
|
354
|
+
* Lucide icon name for the admin sidebar entry. Defaults to
|
|
355
|
+
* `FileText` when unset or unrecognized. Examples:
|
|
356
|
+
* `"Newspaper"` for posts, `"FileStack"` for pages,
|
|
357
|
+
* `"FolderTree"` for categories, `"Tag"` for tags.
|
|
358
|
+
*
|
|
359
|
+
* Resolved client-side by `admin-shell.tsx` against a small
|
|
360
|
+
* lucide-react registry; unknown names fall back to the
|
|
361
|
+
* default so a typo can't break the sidebar render.
|
|
362
|
+
*/
|
|
363
|
+
icon?: string;
|
|
364
|
+
};
|
|
365
|
+
upload?: NpUploadConfig;
|
|
366
|
+
}
|
|
367
|
+
interface NpBlockConfig {
|
|
368
|
+
slug: string;
|
|
369
|
+
labels: {
|
|
370
|
+
singular: string;
|
|
371
|
+
plural: string;
|
|
372
|
+
};
|
|
373
|
+
fields: NpFieldConfig[];
|
|
374
|
+
imageUrl?: string;
|
|
375
|
+
}
|
|
376
|
+
type NpBlockInstance = {
|
|
377
|
+
blockType: string;
|
|
378
|
+
[key: string]: unknown;
|
|
379
|
+
};
|
|
380
|
+
interface NpPluginConfig {
|
|
381
|
+
id: string;
|
|
382
|
+
name: string;
|
|
383
|
+
init?: (ctx: NpPluginContext) => void | Promise<void>;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Structural shape accepted by `loadPlugins()` for SDK-built plugins.
|
|
387
|
+
* Declared here rather than imported from `@nexpress/plugin-sdk` to avoid a
|
|
388
|
+
* dependency cycle (plugin-sdk already depends on core).
|
|
389
|
+
*/
|
|
390
|
+
interface NpResolvedPluginLike {
|
|
391
|
+
manifest: {
|
|
392
|
+
id: string;
|
|
393
|
+
name: string;
|
|
394
|
+
version?: string;
|
|
395
|
+
description?: string;
|
|
396
|
+
capabilities: readonly string[];
|
|
397
|
+
};
|
|
398
|
+
hooks?: Record<string, unknown>;
|
|
399
|
+
routes?: ReadonlyArray<{
|
|
400
|
+
path: string;
|
|
401
|
+
method: string;
|
|
402
|
+
handler: unknown;
|
|
403
|
+
description?: string;
|
|
404
|
+
auth?: boolean;
|
|
405
|
+
}>;
|
|
406
|
+
/**
|
|
407
|
+
* Phase 12.5 — optional UI string bundles per locale. Keys
|
|
408
|
+
* are plugin-namespaced strings the plugin's own templates /
|
|
409
|
+
* routes / admin pages call `t()` against. The host merges
|
|
410
|
+
* every plugin's bundle into the global registry at boot;
|
|
411
|
+
* later plugins overwrite earlier ones on key collision so
|
|
412
|
+
* sites can layer overrides via plugin order.
|
|
413
|
+
*/
|
|
414
|
+
i18n?: Record<string, Record<string, string>>;
|
|
415
|
+
/**
|
|
416
|
+
* Phase 14.5 — page templates the plugin contributes to the
|
|
417
|
+
* shared template registry. Same shape as a theme's
|
|
418
|
+
* `impl.templates`: keyed by collection slug, then by
|
|
419
|
+
* template id, with `{ label, description?, component }`
|
|
420
|
+
* values. The plugin host merges these at boot;
|
|
421
|
+
* `getThemeTemplateSummaries` returns plugin templates +
|
|
422
|
+
* theme templates as a union, with theme entries winning
|
|
423
|
+
* id collisions (the active theme is the site's design
|
|
424
|
+
* authority).
|
|
425
|
+
*
|
|
426
|
+
* templates: {
|
|
427
|
+
* pages: {
|
|
428
|
+
* docs: { label: "Documentation", component: DocsTemplate },
|
|
429
|
+
* },
|
|
430
|
+
* }
|
|
431
|
+
*/
|
|
432
|
+
templates?: Record<string, Record<string, unknown>>;
|
|
433
|
+
/**
|
|
434
|
+
* Plugin page routes (#623). React-free shape — the framework
|
|
435
|
+
* narrows `component` to `ComponentType<NpRouteRenderProps>`
|
|
436
|
+
* at the dispatcher site. See
|
|
437
|
+
* `docs/design/plugin-routes.md` for the contract +
|
|
438
|
+
* precedence rules.
|
|
439
|
+
*/
|
|
440
|
+
pageRoutes?: ReadonlyArray<{
|
|
441
|
+
pattern: string;
|
|
442
|
+
component: unknown;
|
|
443
|
+
metadata?: unknown;
|
|
444
|
+
surface?: "site" | "member";
|
|
445
|
+
locale?: "auto" | "none";
|
|
446
|
+
}>;
|
|
447
|
+
}
|
|
448
|
+
interface NpPluginContext {
|
|
449
|
+
addCollection: (config: NpCollectionConfig) => void;
|
|
450
|
+
addBlock: (config: NpBlockConfig) => void;
|
|
451
|
+
addHook: (collection: string, event: string, hook: NpCollectionHook) => void;
|
|
452
|
+
}
|
|
453
|
+
interface NpNavItem {
|
|
454
|
+
id: string;
|
|
455
|
+
label: string;
|
|
456
|
+
type: "link" | "collection" | "page";
|
|
457
|
+
url?: string;
|
|
458
|
+
collection?: string;
|
|
459
|
+
/**
|
|
460
|
+
* Set when `type === "page"` to record which collection the
|
|
461
|
+
* referenced doc lives in. Defaults to `"pages"` when absent so
|
|
462
|
+
* existing nav rows keep resolving against the reference page
|
|
463
|
+
* collection unchanged. The URL resolver walks the doc through
|
|
464
|
+
* the collection's `seo.urlPath` to produce the public path.
|
|
465
|
+
*
|
|
466
|
+
* The editor doesn't expose this as an editable field — the
|
|
467
|
+
* panel that adds the item knows its source collection and
|
|
468
|
+
* stamps it at write time.
|
|
469
|
+
*/
|
|
470
|
+
collectionSlug?: string;
|
|
471
|
+
pageId?: string;
|
|
472
|
+
children?: NpNavItem[];
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Phase 11.1 — theme manifest. Pure metadata, kept React-free
|
|
476
|
+
* so it can live in `@nexpress/core` (which is server-only and
|
|
477
|
+
* intentionally has no React peer). The full theme — shell,
|
|
478
|
+
* slots, templates with React component types — lives in
|
|
479
|
+
* `@nexpress/theme` via `defineTheme()`. The registry stores
|
|
480
|
+
* `NpRegisteredTheme` instances; `impl` is opaque to core but
|
|
481
|
+
* typed for consumers downstream.
|
|
482
|
+
*/
|
|
483
|
+
interface NpThemeManifest {
|
|
484
|
+
id: string;
|
|
485
|
+
name: string;
|
|
486
|
+
version: string;
|
|
487
|
+
description?: string;
|
|
488
|
+
author?: {
|
|
489
|
+
name: string;
|
|
490
|
+
url?: string;
|
|
491
|
+
};
|
|
492
|
+
/** Optional minimum NexPress version this theme requires. */
|
|
493
|
+
nexpress?: {
|
|
494
|
+
minVersion?: string;
|
|
495
|
+
};
|
|
496
|
+
/**
|
|
497
|
+
* Phase F.1 (theme v0.2) — declared data-shape requirements.
|
|
498
|
+
*
|
|
499
|
+
* Themes whose components assume specific collection fields
|
|
500
|
+
* (e.g. magazine theme reads `posts.featured`) declare them
|
|
501
|
+
* here. Two consumers read this:
|
|
502
|
+
*
|
|
503
|
+
* 1. Admin theme switcher (this phase): compares against the
|
|
504
|
+
* site's registered collections at activation time and
|
|
505
|
+
* surfaces mismatches to the operator BEFORE they click
|
|
506
|
+
* "activate" — so they don't end up with a theme that
|
|
507
|
+
* silently renders fallbacks for missing fields.
|
|
508
|
+
* 2. `pnpm nexpress theme:install` (Phase F.8, deferred):
|
|
509
|
+
* reads this to AST-patch the operator's
|
|
510
|
+
* `src/collections/*.ts` files and run codegen + migrate.
|
|
511
|
+
*
|
|
512
|
+
* F.1 ships only the type + admin warning surface. The CLI
|
|
513
|
+
* patcher is its own phase.
|
|
514
|
+
*/
|
|
515
|
+
requires?: {
|
|
516
|
+
collections?: Record<string, NpThemeCollectionRequirement>;
|
|
517
|
+
};
|
|
518
|
+
/**
|
|
519
|
+
* Phase F.3 (theme v0.2) — operator-tunable theme options.
|
|
520
|
+
*
|
|
521
|
+
* A Zod schema describing settings the admin should expose as
|
|
522
|
+
* a form. The framework generates the form fields from the
|
|
523
|
+
* schema (no per-theme admin UI code), persists submissions in
|
|
524
|
+
* `np_settings` keyed by `theme.settings:<themeId>`, and
|
|
525
|
+
* exposes the parsed value to theme components via
|
|
526
|
+
* `getThemeSettings()`.
|
|
527
|
+
*
|
|
528
|
+
* Supported field types in v0.2:
|
|
529
|
+
* - z.string() / z.string().url() / z.string().regex(...)
|
|
530
|
+
* - z.number().int().min().max()
|
|
531
|
+
* - z.boolean()
|
|
532
|
+
* - z.enum([...])
|
|
533
|
+
* - z.array(z.object({...}))
|
|
534
|
+
* - z.object({...})
|
|
535
|
+
*
|
|
536
|
+
* Use `.default(value)` for initial form values and
|
|
537
|
+
* `.describe("Help text")` for the field label/description
|
|
538
|
+
* the admin auto-form picks up.
|
|
539
|
+
*
|
|
540
|
+
* Typed as `unknown` here so `@nexpress/core` doesn't have to
|
|
541
|
+
* re-export Zod type unions through every public surface;
|
|
542
|
+
* theme authors writing `defineTheme({ manifest: { ... } })`
|
|
543
|
+
* still get the proper Zod typing because they construct the
|
|
544
|
+
* schema with `z.object(...)` themselves. The framework
|
|
545
|
+
* narrows back to `ZodTypeAny` at the call site that runs
|
|
546
|
+
* introspection / validation.
|
|
547
|
+
*/
|
|
548
|
+
settingsSchema?: unknown;
|
|
549
|
+
/**
|
|
550
|
+
* v0.3 (D) — settings schema version, used by the migration
|
|
551
|
+
* pipeline to detect when stored settings need upgrading.
|
|
552
|
+
*
|
|
553
|
+
* Theme authors bump this whenever `settingsSchema` changes
|
|
554
|
+
* shape in a non-additive way (renaming a field, removing one,
|
|
555
|
+
* tightening a default). Adding a NEW optional field is
|
|
556
|
+
* compatible without bumping — Zod fills the missing key with
|
|
557
|
+
* the field's default on parse.
|
|
558
|
+
*
|
|
559
|
+
* The framework treats absent / undefined as `1` (the v0.2
|
|
560
|
+
* baseline). Themes that never bump stay forever at v1, no
|
|
561
|
+
* migration ever runs.
|
|
562
|
+
*/
|
|
563
|
+
settingsVersion?: number;
|
|
564
|
+
/**
|
|
565
|
+
* v0.3 (D) — migration function that brings a value persisted
|
|
566
|
+
* under an older `settingsVersion` up to the current shape.
|
|
567
|
+
*
|
|
568
|
+
* Called on read when stored version < `settingsVersion`. The
|
|
569
|
+
* function receives the OLD value (whatever shape v(N-1) had)
|
|
570
|
+
* and the version it came from (so multi-step migrations can
|
|
571
|
+
* branch). Returns a value that matches the CURRENT
|
|
572
|
+
* `settingsSchema`. The framework re-parses the result and
|
|
573
|
+
* falls back to schema defaults if the migration's output
|
|
574
|
+
* still doesn't validate (defensive — a buggy migrate fn
|
|
575
|
+
* shouldn't blow up the public site).
|
|
576
|
+
*
|
|
577
|
+
* The framework persists the migrated value back on the
|
|
578
|
+
* operator's NEXT save through the admin form. Read paths
|
|
579
|
+
* don't auto-write; the migration is recomputed on each read
|
|
580
|
+
* until the operator triggers a save. That keeps read paths
|
|
581
|
+
* pure (matches every other cached read in the framework).
|
|
582
|
+
*
|
|
583
|
+
* Example for a `accent` → `accentColor` rename at v2:
|
|
584
|
+
*
|
|
585
|
+
* ```ts
|
|
586
|
+
* defineTheme({
|
|
587
|
+
* manifest: {
|
|
588
|
+
* settingsSchema: z.object({
|
|
589
|
+
* accentColor: z.string().regex(...).optional(),
|
|
590
|
+
* ...
|
|
591
|
+
* }),
|
|
592
|
+
* settingsVersion: 2,
|
|
593
|
+
* settingsMigrate: (old, from) => {
|
|
594
|
+
* if (from === 1) {
|
|
595
|
+
* const o = old as { accent?: string };
|
|
596
|
+
* return { ...o, accentColor: o.accent };
|
|
597
|
+
* }
|
|
598
|
+
* return old;
|
|
599
|
+
* },
|
|
600
|
+
* }
|
|
601
|
+
* })
|
|
602
|
+
* ```
|
|
603
|
+
*/
|
|
604
|
+
settingsMigrate?: (old: unknown, fromVersion: number) => unknown;
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* One collection's worth of theme requirements. The collection
|
|
608
|
+
* may exist (admin checks fields) or not (admin flags as missing
|
|
609
|
+
* — the CLI in F.8 will create it if `createIfAbsent` is set).
|
|
610
|
+
*/
|
|
611
|
+
interface NpThemeCollectionRequirement {
|
|
612
|
+
fields?: Record<string, NpThemeFieldRequirement>;
|
|
613
|
+
/** True → CLI in F.8 creates this collection if absent.
|
|
614
|
+
* Admin still warns at activation; the operator must run the
|
|
615
|
+
* CLI to actually create it. */
|
|
616
|
+
createIfAbsent?: boolean;
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* One field's requirement. The `type` matches an `NpFieldConfig`
|
|
620
|
+
* variant's `type` string exactly so the activation check can
|
|
621
|
+
* compare without translation.
|
|
622
|
+
*/
|
|
623
|
+
interface NpThemeFieldRequirement {
|
|
624
|
+
type: "text" | "textarea" | "richText" | "number" | "checkbox" | "date" | "select" | "upload" | "relationship" | "blocks";
|
|
625
|
+
/** For `relationship` — the collection slug it points to. */
|
|
626
|
+
relationTo?: string | string[];
|
|
627
|
+
/** For `relationship` / `select` — accepts list values. */
|
|
628
|
+
hasMany?: boolean;
|
|
629
|
+
required?: boolean;
|
|
630
|
+
/**
|
|
631
|
+
* Default `true`. Set `false` for "nice to have, theme degrades
|
|
632
|
+
* gracefully without it" — admin warning shows but at lower
|
|
633
|
+
* severity, and a future F.8 may treat it as opt-in patch.
|
|
634
|
+
*/
|
|
635
|
+
hard?: boolean;
|
|
636
|
+
}
|
|
637
|
+
interface NpRegisteredTheme {
|
|
638
|
+
manifest: NpThemeManifest;
|
|
639
|
+
/**
|
|
640
|
+
* The theme's runtime implementation — shell component,
|
|
641
|
+
* slot components, page templates, default tokens.
|
|
642
|
+
* `@nexpress/theme` types this; core treats it as opaque so
|
|
643
|
+
* the React peer dependency stays out of this package.
|
|
644
|
+
*/
|
|
645
|
+
impl: unknown;
|
|
646
|
+
}
|
|
647
|
+
interface NpI18nConfig {
|
|
648
|
+
/**
|
|
649
|
+
* Locales this site supports. Order matters only insofar as
|
|
650
|
+
* the first locale becomes the default when `defaultLocale`
|
|
651
|
+
* isn't explicitly set. Locale strings are passed through to
|
|
652
|
+
* BCP-47 consumers (HTML `lang` attribute, hreflang) so
|
|
653
|
+
* conventional codes are recommended (`en`, `en-US`, `ko`,
|
|
654
|
+
* `pt-BR`).
|
|
655
|
+
*/
|
|
656
|
+
locales: string[];
|
|
657
|
+
/**
|
|
658
|
+
* Locale used when the caller doesn't specify one — drives
|
|
659
|
+
* default writes and fallback reads. Must appear in `locales`.
|
|
660
|
+
*/
|
|
661
|
+
defaultLocale: string;
|
|
662
|
+
}
|
|
663
|
+
interface NpConfig {
|
|
664
|
+
site: {
|
|
665
|
+
name: string;
|
|
666
|
+
url: string;
|
|
667
|
+
};
|
|
668
|
+
db: {
|
|
669
|
+
connectionString: string;
|
|
670
|
+
pool?: {
|
|
671
|
+
max?: number;
|
|
672
|
+
};
|
|
673
|
+
};
|
|
674
|
+
storage?: {
|
|
675
|
+
adapter: "local" | "s3";
|
|
676
|
+
local?: {
|
|
677
|
+
directory: string;
|
|
678
|
+
baseUrl: string;
|
|
679
|
+
};
|
|
680
|
+
s3?: {
|
|
681
|
+
bucket: string;
|
|
682
|
+
region: string;
|
|
683
|
+
endpoint?: string;
|
|
684
|
+
};
|
|
685
|
+
};
|
|
686
|
+
collections: NpCollectionConfig[];
|
|
687
|
+
blocks?: NpBlockConfig[];
|
|
688
|
+
editor?: NpEditorConfig;
|
|
689
|
+
/**
|
|
690
|
+
* Phase 11.1 — multi-theme registry. Sites declare every
|
|
691
|
+
* theme they want available; admins switch between them
|
|
692
|
+
* via the settings UI without rebuilding. The first theme
|
|
693
|
+
* in the array is the default-active until an admin sets
|
|
694
|
+
* a different one (`np_settings.activeTheme`).
|
|
695
|
+
*/
|
|
696
|
+
themes?: NpRegisteredTheme[];
|
|
697
|
+
/**
|
|
698
|
+
* Phase 12.1 — i18n config. Sites that want multi-language
|
|
699
|
+
* content declare every locale they intend to support here.
|
|
700
|
+
* Per-collection opt-in via `defineCollection({ i18n: true })`
|
|
701
|
+
* is required: only collections that declare `i18n` get the
|
|
702
|
+
* `locale` / `translation_group_id` columns codegen'd onto
|
|
703
|
+
* their generated table. Sites with no i18n config (or that
|
|
704
|
+
* opt no collections in) keep the existing single-locale
|
|
705
|
+
* shape — i18n is purely additive.
|
|
706
|
+
*
|
|
707
|
+
* i18n: { locales: ["en", "ko", "ja"], defaultLocale: "en" }
|
|
708
|
+
*
|
|
709
|
+
* `defaultLocale` is what new docs land in when the caller
|
|
710
|
+
* doesn't pass an explicit locale, and what the framework
|
|
711
|
+
* falls back to when a translation is missing for a requested
|
|
712
|
+
* locale (the public site renders a 404 only when the doc
|
|
713
|
+
* doesn't exist in any locale).
|
|
714
|
+
*/
|
|
715
|
+
i18n?: NpI18nConfig;
|
|
716
|
+
images?: {
|
|
717
|
+
sizes?: NpImageSize[];
|
|
718
|
+
format?: "webp" | "avif" | "jpeg" | "png";
|
|
719
|
+
quality?: number;
|
|
720
|
+
};
|
|
721
|
+
auth?: {
|
|
722
|
+
secret: string;
|
|
723
|
+
tokenExpiration?: number;
|
|
724
|
+
refreshTokenExpiration?: number;
|
|
725
|
+
maxLoginAttempts?: number;
|
|
726
|
+
lockoutDuration?: number;
|
|
727
|
+
};
|
|
728
|
+
plugins?: Array<NpPluginConfig | NpResolvedPluginLike>;
|
|
729
|
+
typescript?: {
|
|
730
|
+
outputFile?: string;
|
|
731
|
+
};
|
|
732
|
+
/**
|
|
733
|
+
* Phase 23.5 — operational thresholds and policies for the job
|
|
734
|
+
* queue. Currently only carries the stuck-job thresholds the
|
|
735
|
+
* admin Jobs widget compares against; future entries land
|
|
736
|
+
* additively.
|
|
737
|
+
*/
|
|
738
|
+
jobs?: {
|
|
739
|
+
/**
|
|
740
|
+
* Per-state count thresholds for the admin stuck-job widget.
|
|
741
|
+
* When the live + archive UNION count for a state exceeds the
|
|
742
|
+
* configured value the widget shows a warning indicator. Unset
|
|
743
|
+
* values fall back to sensible defaults applied by the widget
|
|
744
|
+
* itself (currently `failed: 10`, `expired: 50`).
|
|
745
|
+
*/
|
|
746
|
+
stuckThreshold?: {
|
|
747
|
+
failed?: number;
|
|
748
|
+
expired?: number;
|
|
749
|
+
};
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
type NpJobType = "content:afterSave" | "content:afterDelete" | "content:publishScheduled" | "media:processImage" | "media:cleanup" | "plugin:scheduledTask" | "system:revisionPrune" | "system:sessionCleanup" | "system:jobLogPrune" | "auth:sendPasswordReset" | "members:sendVerifyEmail" | "members:sendPasswordReset" | "notifications:sendDigest";
|
|
753
|
+
/**
|
|
754
|
+
* System-level filters that aren't part of any collection's
|
|
755
|
+
* document shape but still belong on the `where` clause: tenant
|
|
756
|
+
* scoping, visibility gating, locale narrowing. Kept separate
|
|
757
|
+
* from the document type so `Partial<T>` can stay tight while
|
|
758
|
+
* advanced callers (admin queries, bulk exports) can pass these
|
|
759
|
+
* escape-hatch tokens.
|
|
760
|
+
*/
|
|
761
|
+
interface NpFindWhereSystemTokens {
|
|
762
|
+
/**
|
|
763
|
+
* Multi-site scoping. Defaults to the resolved current site.
|
|
764
|
+
* Pass `"*"` to query across every site (admin / migration
|
|
765
|
+
* use only — leaks cross-site rows).
|
|
766
|
+
*/
|
|
767
|
+
siteId?: string;
|
|
768
|
+
/**
|
|
769
|
+
* Visibility gate. Anonymous traffic is auto-restricted to
|
|
770
|
+
* `"public"`. Pass `"*"` to bypass (the pipeline drops the
|
|
771
|
+
* filter when a user is also passed).
|
|
772
|
+
*/
|
|
773
|
+
visibility?: "public" | "private" | "*";
|
|
774
|
+
/**
|
|
775
|
+
* `where: { locale: "ko" }` is equivalent to the top-level
|
|
776
|
+
* `locale` option. Listed here so a typed where clause can
|
|
777
|
+
* still pass it without the document type having to declare
|
|
778
|
+
* a `locale` field (only i18n-enabled collections do).
|
|
779
|
+
*/
|
|
780
|
+
locale?: string;
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Strip `null` and unwrap arrays so a hasMany field like
|
|
784
|
+
* `categories: string[] | null` reads as a `string` for the
|
|
785
|
+
* single-target filter case.
|
|
786
|
+
*/
|
|
787
|
+
type NpFindWhereUnwrap<V> = V extends (infer U)[] | null ? U : V extends (infer U)[] ? U : V extends infer U | null ? U : V;
|
|
788
|
+
/**
|
|
789
|
+
* The accepted value shape for a single where field. Either the
|
|
790
|
+
* unwrapped scalar (single match) or an array (IN match). Array
|
|
791
|
+
* with zero elements short-circuits the query to no rows; the
|
|
792
|
+
* pipeline guards against the SQL syntax error this would
|
|
793
|
+
* otherwise produce.
|
|
794
|
+
*/
|
|
795
|
+
type NpFindWhereValue<V> = NpFindWhereUnwrap<V> | NpFindWhereUnwrap<V>[];
|
|
796
|
+
/**
|
|
797
|
+
* Per-row filter. With the default `T = Record<string, unknown>`,
|
|
798
|
+
* any keys are allowed (back-compat). With a typed `T` (the
|
|
799
|
+
* generated wrapper functions pass their `${Pascal}Document`
|
|
800
|
+
* here), only document fields plus the system tokens above are
|
|
801
|
+
* accepted — typos against field names become compile errors.
|
|
802
|
+
*
|
|
803
|
+
* Each field accepts a single value (matched with `=`) or an
|
|
804
|
+
* array (matched with `IN (...)`). For hasMany relationships
|
|
805
|
+
* (where the document's field type is `string[] | null`), the
|
|
806
|
+
* single-value form is the common case — "posts in this one
|
|
807
|
+
* category" — and the array form picks up the `OR` semantics
|
|
808
|
+
* across multiple targets — "posts in any of these categories".
|
|
809
|
+
*/
|
|
810
|
+
type NpFindWhere<T extends object = Record<string, unknown>> = {
|
|
811
|
+
[K in keyof T]?: NpFindWhereValue<T[K]>;
|
|
812
|
+
} & {
|
|
813
|
+
[K in keyof NpFindWhereSystemTokens]?: NpFindWhereSystemTokens[K];
|
|
814
|
+
};
|
|
815
|
+
interface NpFindOptions<T extends object = Record<string, unknown>> {
|
|
816
|
+
page?: number;
|
|
817
|
+
limit?: number;
|
|
818
|
+
sort?: string;
|
|
819
|
+
search?: string;
|
|
820
|
+
where?: NpFindWhere<T>;
|
|
821
|
+
/**
|
|
822
|
+
* Phase 12.1 — restrict the result set to one locale on
|
|
823
|
+
* i18n-enabled collections. Equivalent to passing
|
|
824
|
+
* `where: { locale }`, but kept top-level for ergonomics
|
|
825
|
+
* (callers don't have to know it's a column). Ignored on
|
|
826
|
+
* non-i18n collections (no `locale` column to match).
|
|
827
|
+
*/
|
|
828
|
+
locale?: string;
|
|
829
|
+
}
|
|
830
|
+
interface NpFindResult<T = Record<string, unknown>> {
|
|
831
|
+
docs: T[];
|
|
832
|
+
totalDocs: number;
|
|
833
|
+
totalPages: number;
|
|
834
|
+
page: number;
|
|
835
|
+
limit: number;
|
|
836
|
+
hasNextPage: boolean;
|
|
837
|
+
hasPrevPage: boolean;
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Document lifecycle status. `pending` (Phase 9.7c) is a moderation
|
|
841
|
+
* holding pen for member-authored docs that haven't cleared review
|
|
842
|
+
* — flagged by the spam adapter or sent there because the
|
|
843
|
+
* collection set `community.memberWrite.defaultStatus = "pending"`.
|
|
844
|
+
* Public listings filter to `published`, so pending rows are
|
|
845
|
+
* invisible to anonymous and non-staff members until a mod
|
|
846
|
+
* promotes them.
|
|
847
|
+
*/
|
|
848
|
+
type NpDocumentStatus = "draft" | "scheduled" | "published" | "archived" | "pending";
|
|
849
|
+
interface NpSaveOptions {
|
|
850
|
+
status?: NpDocumentStatus;
|
|
851
|
+
}
|
|
852
|
+
interface NpSaveResult {
|
|
853
|
+
doc: Record<string, unknown>;
|
|
854
|
+
operation: "create" | "update";
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Numeric ranking of staff roles, retained for the few non-capability
|
|
858
|
+
* call sites that still need to compare role rank — chiefly
|
|
859
|
+
* `hasRoleOnSite()` in `sites/memberships.ts`, which evaluates a
|
|
860
|
+
* per-site membership row's role against the user's. `moderator`
|
|
861
|
+
* shares author-rank because the two are parallel tracks
|
|
862
|
+
* (community-mod vs. content-author authority); the rank is meaningful
|
|
863
|
+
* only on the content-authoring axis.
|
|
864
|
+
*
|
|
865
|
+
* For staff-user authorization, use `can(user, capability)` from
|
|
866
|
+
* `auth/capabilities.ts` (#273) — this hierarchy is no longer the
|
|
867
|
+
* primary check.
|
|
868
|
+
*/
|
|
869
|
+
declare const ROLE_HIERARCHY: Record<NpUserRole, number>;
|
|
870
|
+
|
|
871
|
+
export { type NpFieldCondition as A, type NpFieldValidator as B, type NpFindWhere as C, type NpFindWhereSystemTokens as D, type NpGroupField as E, type NpPrincipal as F, type NpI18nConfig as G, type NpImageSize as H, type NpJobType as I, type NpJsonField as J, type NpNumberField as K, type NpPluginContext as L, type NpRadioField as M, type NpRichTextContent as N, type NpRelationshipField as O, type NpResolvedPluginLike as P, type NpRichTextField as Q, type NpRowField as R, type NpSelectField as S, type NpTextField as T, type NpTextareaField as U, type NpThemeCollectionRequirement as V, type NpUploadConfig as W, type NpUploadField as X, ROLE_HIERARCHY as Y, type NpNavItem as a, type NpBlockInstance as b, type NpConfig as c, type NpCollectionConfig as d, type NpAuthUser as e, type NpSaveOptions as f, type NpSaveResult as g, type NpFindOptions as h, type NpFindResult as i, type NpFieldConfig as j, type NpRegisteredTheme as k, type NpThemeFieldRequirement as l, type NpThemeManifest as m, type NpUserRole as n, type NpPluginConfig as o, type NpAccessFunction as p, type NpArrayField as q, type NpBlockConfig as r, type NpBlocksField as s, type NpCheckboxField as t, type NpCollapsibleField as u, type NpCollectionHook as v, type NpDateField as w, type NpDocumentStatus as x, type NpEditorConfig as y, type NpEmailField as z };
|