@nexpress/core 0.2.2 → 0.3.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.
Files changed (74) hide show
  1. package/dist/auth.d.ts +26 -3
  2. package/dist/auth.js +5 -3
  3. package/dist/{can-FKIEV54H.js → can-UJ2NAOIR.js} +3 -3
  4. package/dist/{chunk-CHQJG4BB.js → chunk-2N53KKIL.js} +2 -2
  5. package/dist/{chunk-DWG3RZH2.js → chunk-2VZZ7M26.js} +2 -2
  6. package/dist/{chunk-5LCLS6VE.js → chunk-56ZK5PWM.js} +19 -19
  7. package/dist/{chunk-S37WWNBB.js → chunk-6IEYOY2L.js} +28 -121
  8. package/dist/chunk-6IEYOY2L.js.map +1 -0
  9. package/dist/{chunk-QYP6E5FP.js → chunk-6UV2P5MW.js} +63 -50
  10. package/dist/chunk-6UV2P5MW.js.map +1 -0
  11. package/dist/{chunk-26RYBFTF.js → chunk-AEKO4MXK.js} +21 -4
  12. package/dist/chunk-AEKO4MXK.js.map +1 -0
  13. package/dist/{chunk-L4F5RAQ5.js → chunk-DKOCKZVG.js} +9 -9
  14. package/dist/{chunk-KSUS4UNN.js → chunk-HUESWYZJ.js} +2 -2
  15. package/dist/{chunk-CTUHJHLH.js → chunk-HVHV3IHF.js} +2 -2
  16. package/dist/chunk-P5WGQRSG.js +180 -0
  17. package/dist/chunk-P5WGQRSG.js.map +1 -0
  18. package/dist/{chunk-HM46WM45.js → chunk-RDTTK27V.js} +6 -6
  19. package/dist/{chunk-PQBJWZ7D.js → chunk-RJ76SKWQ.js} +4 -4
  20. package/dist/{chunk-74CGJJDY.js → chunk-RKM4GDWM.js} +1 -1
  21. package/dist/{chunk-7GNVXRLG.js → chunk-UIQYA3Y7.js} +5 -5
  22. package/dist/{chunk-CKT4QZDC.js → chunk-WJJ5MBH5.js} +5 -5
  23. package/dist/community.d.ts +1 -1
  24. package/dist/community.js +20 -19
  25. package/dist/{config-65OBL4YH.js → config-44MFLLIX.js} +8 -7
  26. package/dist/db-schema.d.ts +2 -2
  27. package/dist/db.d.ts +3 -3
  28. package/dist/db.js +1 -1
  29. package/dist/fields.d.ts +54 -0
  30. package/dist/fields.js +14 -0
  31. package/dist/{host-55D6RX3U.js → host-DKOWZWKA.js} +6 -5
  32. package/dist/i18n.d.ts +1 -1
  33. package/dist/i18n.js +1 -1
  34. package/dist/{index-Ccw0AkXh.d.ts → index-BmR3Z8Y5.d.ts} +1 -1
  35. package/dist/{index-BWsQUGRZ.d.ts → index-C-jKU1St.d.ts} +2 -2
  36. package/dist/{index-D6Q7DOl7.d.ts → index-Ca-WUDH5.d.ts} +1 -1
  37. package/dist/{index-BpW3PGhP.d.ts → index-lACZ9sON.d.ts} +1 -1
  38. package/dist/index.d.ts +10 -12
  39. package/dist/index.js +189 -78
  40. package/dist/index.js.map +1 -1
  41. package/dist/jobs.d.ts +2 -2
  42. package/dist/jobs.js +2 -2
  43. package/dist/media.d.ts +2 -2
  44. package/dist/media.js +2 -2
  45. package/dist/{mentions-NCQR4B72.js → mentions-U4JACYI6.js} +3 -3
  46. package/dist/{mutes-FJSSU2JP.js → mutes-MNQP6ACF.js} +3 -3
  47. package/dist/{scheduled-UC7O2HBQ.js → scheduled-VEOGI5EW.js} +7 -6
  48. package/dist/seo.js +6 -5
  49. package/dist/{settings-JODDWMDB.js → settings-OZWM6L2K.js} +2 -2
  50. package/dist/settings-OZWM6L2K.js.map +1 -0
  51. package/dist/strings-4EWJYDOG.js +1 -1
  52. package/dist/{types-C-r01wmU.d.ts → types-BY1UmEiY.d.ts} +267 -2
  53. package/package.json +6 -1
  54. package/dist/chunk-26RYBFTF.js.map +0 -1
  55. package/dist/chunk-QYP6E5FP.js.map +0 -1
  56. package/dist/chunk-S37WWNBB.js.map +0 -1
  57. /package/dist/{can-FKIEV54H.js.map → can-UJ2NAOIR.js.map} +0 -0
  58. /package/dist/{chunk-CHQJG4BB.js.map → chunk-2N53KKIL.js.map} +0 -0
  59. /package/dist/{chunk-DWG3RZH2.js.map → chunk-2VZZ7M26.js.map} +0 -0
  60. /package/dist/{chunk-5LCLS6VE.js.map → chunk-56ZK5PWM.js.map} +0 -0
  61. /package/dist/{chunk-L4F5RAQ5.js.map → chunk-DKOCKZVG.js.map} +0 -0
  62. /package/dist/{chunk-KSUS4UNN.js.map → chunk-HUESWYZJ.js.map} +0 -0
  63. /package/dist/{chunk-CTUHJHLH.js.map → chunk-HVHV3IHF.js.map} +0 -0
  64. /package/dist/{chunk-HM46WM45.js.map → chunk-RDTTK27V.js.map} +0 -0
  65. /package/dist/{chunk-PQBJWZ7D.js.map → chunk-RJ76SKWQ.js.map} +0 -0
  66. /package/dist/{chunk-74CGJJDY.js.map → chunk-RKM4GDWM.js.map} +0 -0
  67. /package/dist/{chunk-7GNVXRLG.js.map → chunk-UIQYA3Y7.js.map} +0 -0
  68. /package/dist/{chunk-CKT4QZDC.js.map → chunk-WJJ5MBH5.js.map} +0 -0
  69. /package/dist/{config-65OBL4YH.js.map → config-44MFLLIX.js.map} +0 -0
  70. /package/dist/{host-55D6RX3U.js.map → fields.js.map} +0 -0
  71. /package/dist/{mentions-NCQR4B72.js.map → host-DKOWZWKA.js.map} +0 -0
  72. /package/dist/{mutes-FJSSU2JP.js.map → mentions-U4JACYI6.js.map} +0 -0
  73. /package/dist/{scheduled-UC7O2HBQ.js.map → mutes-MNQP6ACF.js.map} +0 -0
  74. /package/dist/{settings-JODDWMDB.js.map → scheduled-VEOGI5EW.js.map} +0 -0
package/dist/jobs.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { D as DEFAULT_JOB_LOG_RETENTION_MS, L as ListJobLogsOptions, N as NpJobCountOptions, a as NpJobHandler, b as NpJobListOptions, c as NpJobListResult, d as NpJobLogEntry, e as NpJobQueue, f as NpJobState, g as NpJobStateCounts, h as NpJobSummary, i as NpJobsPauseState, j as NpPluginScheduleStats, k as NpReconcileSchedulesResult, l as NpScheduleSummary, m as NpWorkerHealthSummary, n as NpWorkerHeartbeat, P as PAUSE_SYNC_INTERVAL_MS, o as PgBossAdapter, S as SetJobsPauseStateInput, W as WORKER_HEARTBEAT_INTERVAL_MS, p as WORKER_STALE_THRESHOLD_MS, r as countAliveWorkers, s as countJobLogs, t as enqueueJob, u as getAllJobHandlers, v as getCurrentJobId, w as getJobHandler, x as getJobQueue, y as getJobsPauseState, z as getOptionalJobQueue, A as listJobLogs, B as listWorkerHealth, C as markWorkerStopped, E as pruneJobLogsOlderThan, F as purgeStaleWorkers, G as recordHeartbeat, H as recordJobLog, I as registerBuiltinHandlers, J as registerJobHandler, K as runInJobContext, M as setJobQueue, O as setJobsPauseState, Q as startProducer, R as startWorker, T as stopProducer, U as stopWorker } from './index-BpW3PGhP.js';
2
- import './types-C-r01wmU.js';
1
+ export { D as DEFAULT_JOB_LOG_RETENTION_MS, L as ListJobLogsOptions, N as NpJobCountOptions, a as NpJobHandler, b as NpJobListOptions, c as NpJobListResult, d as NpJobLogEntry, e as NpJobQueue, f as NpJobState, g as NpJobStateCounts, h as NpJobSummary, i as NpJobsPauseState, j as NpPluginScheduleStats, k as NpReconcileSchedulesResult, l as NpScheduleSummary, m as NpWorkerHealthSummary, n as NpWorkerHeartbeat, P as PAUSE_SYNC_INTERVAL_MS, o as PgBossAdapter, S as SetJobsPauseStateInput, W as WORKER_HEARTBEAT_INTERVAL_MS, p as WORKER_STALE_THRESHOLD_MS, r as countAliveWorkers, s as countJobLogs, t as enqueueJob, u as getAllJobHandlers, v as getCurrentJobId, w as getJobHandler, x as getJobQueue, y as getJobsPauseState, z as getOptionalJobQueue, A as listJobLogs, B as listWorkerHealth, C as markWorkerStopped, E as pruneJobLogsOlderThan, F as purgeStaleWorkers, G as recordHeartbeat, H as recordJobLog, I as registerBuiltinHandlers, J as registerJobHandler, K as runInJobContext, M as setJobQueue, O as setJobsPauseState, Q as startProducer, R as startWorker, T as stopProducer, U as stopWorker } from './index-lACZ9sON.js';
2
+ import './types-BY1UmEiY.js';
3
3
  import 'pg-boss';
4
4
  import './logger-DqGaOU_j.js';
package/dist/jobs.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  startWorker,
19
19
  stopProducer,
20
20
  stopWorker
21
- } from "./chunk-L4F5RAQ5.js";
21
+ } from "./chunk-DKOCKZVG.js";
22
22
  import {
23
23
  DEFAULT_JOB_LOG_RETENTION_MS,
24
24
  countJobLogs,
@@ -29,13 +29,13 @@ import {
29
29
  runInJobContext
30
30
  } from "./chunk-QBIJZZ5V.js";
31
31
  import "./chunk-LSHHRDVR.js";
32
- import "./chunk-WV272MPW.js";
33
32
  import {
34
33
  enqueueJob,
35
34
  getJobQueue,
36
35
  getOptionalJobQueue,
37
36
  setJobQueue
38
37
  } from "./chunk-V2UNHGAP.js";
38
+ import "./chunk-WV272MPW.js";
39
39
  import "./chunk-OROPGO65.js";
40
40
  import "./chunk-NFHS7CFV.js";
41
41
  import "./chunk-XANPEOJC.js";
package/dist/media.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export { D as DEFAULT_IMAGE_SIZES, q as DrizzleTransactionLike, b as NpGetMediaUrlOptions, c as NpMediaUploader, d as NpMediaUploaderKindFilter, e as NpMediaVariantName, f as NpProcessedImageResult, r as NpProcessedImageSourceMetadata, g as NpProcessedImageVariant, h as cleanupDeletedMedia, i as deleteMedia, j as extractMediaIds, k as getMediaById, l as getMediaUrl, m as getStorageAdapter, n as listMedia, p as processImage, o as processMediaImage, s as setStorageAdapter, t as syncMediaRefs, u as uploadMedia } from './index-D6Q7DOl7.js';
2
- import './types-C-r01wmU.js';
1
+ export { D as DEFAULT_IMAGE_SIZES, q as DrizzleTransactionLike, b as NpGetMediaUrlOptions, c as NpMediaUploader, d as NpMediaUploaderKindFilter, e as NpMediaVariantName, f as NpProcessedImageResult, r as NpProcessedImageSourceMetadata, g as NpProcessedImageVariant, h as cleanupDeletedMedia, i as deleteMedia, j as extractMediaIds, k as getMediaById, l as getMediaUrl, m as getStorageAdapter, n as listMedia, p as processImage, o as processMediaImage, s as setStorageAdapter, t as syncMediaRefs, u as uploadMedia } from './index-Ca-WUDH5.js';
2
+ import './types-BY1UmEiY.js';
3
3
  import 'drizzle-orm';
4
4
  import 'drizzle-orm/pg-core';
5
5
  import 'node:stream/web';
package/dist/media.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-TETTWT56.js";
5
5
  import {
6
6
  getMediaUrl
7
- } from "./chunk-CHQJG4BB.js";
7
+ } from "./chunk-2N53KKIL.js";
8
8
  import {
9
9
  DEFAULT_IMAGE_SIZES,
10
10
  cleanupDeletedMedia,
@@ -16,7 +16,7 @@ import {
16
16
  processMediaImage,
17
17
  setStorageAdapter,
18
18
  uploadMedia
19
- } from "./chunk-DWG3RZH2.js";
19
+ } from "./chunk-2VZZ7M26.js";
20
20
  import "./chunk-V2UNHGAP.js";
21
21
  import "./chunk-OROPGO65.js";
22
22
  import "./chunk-NFHS7CFV.js";
@@ -5,10 +5,10 @@ import {
5
5
  extractMentionHandlesFromRichText,
6
6
  fanOutMentionNotifications,
7
7
  resolveMentionedMembers
8
- } from "./chunk-HM46WM45.js";
8
+ } from "./chunk-RDTTK27V.js";
9
9
  import "./chunk-U4QCCLAW.js";
10
- import "./chunk-ZCINJSS4.js";
11
10
  import "./chunk-SBCVAC2Z.js";
11
+ import "./chunk-ZCINJSS4.js";
12
12
  import "./chunk-XANPEOJC.js";
13
13
  import "./chunk-X7K5F2UI.js";
14
14
  import "./chunk-PZ5AY32C.js";
@@ -20,4 +20,4 @@ export {
20
20
  fanOutMentionNotifications,
21
21
  resolveMentionedMembers
22
22
  };
23
- //# sourceMappingURL=mentions-NCQR4B72.js.map
23
+ //# sourceMappingURL=mentions-U4JACYI6.js.map
@@ -4,10 +4,10 @@ import {
4
4
  listMutes,
5
5
  muteMember,
6
6
  unmuteMember
7
- } from "./chunk-CKT4QZDC.js";
7
+ } from "./chunk-WJJ5MBH5.js";
8
8
  import "./chunk-U4QCCLAW.js";
9
- import "./chunk-ZCINJSS4.js";
10
9
  import "./chunk-SBCVAC2Z.js";
10
+ import "./chunk-ZCINJSS4.js";
11
11
  import "./chunk-XANPEOJC.js";
12
12
  import "./chunk-X7K5F2UI.js";
13
13
  import "./chunk-PZ5AY32C.js";
@@ -18,4 +18,4 @@ export {
18
18
  muteMember,
19
19
  unmuteMember
20
20
  };
21
- //# sourceMappingURL=mutes-FJSSU2JP.js.map
21
+ //# sourceMappingURL=mutes-MNQP6ACF.js.map
@@ -1,14 +1,15 @@
1
1
  import {
2
2
  publishScheduledDocuments
3
- } from "./chunk-KSUS4UNN.js";
4
- import "./chunk-S37WWNBB.js";
3
+ } from "./chunk-HUESWYZJ.js";
4
+ import "./chunk-6IEYOY2L.js";
5
+ import "./chunk-2VZZ7M26.js";
6
+ import "./chunk-P5WGQRSG.js";
5
7
  import "./chunk-4ZLMEKFX.js";
6
8
  import "./chunk-U4QCCLAW.js";
7
- import "./chunk-ZCINJSS4.js";
8
9
  import "./chunk-SBCVAC2Z.js";
9
- import "./chunk-WV272MPW.js";
10
- import "./chunk-DWG3RZH2.js";
10
+ import "./chunk-ZCINJSS4.js";
11
11
  import "./chunk-V2UNHGAP.js";
12
+ import "./chunk-WV272MPW.js";
12
13
  import "./chunk-OROPGO65.js";
13
14
  import "./chunk-NFHS7CFV.js";
14
15
  import "./chunk-XANPEOJC.js";
@@ -17,4 +18,4 @@ import "./chunk-PZ5AY32C.js";
17
18
  export {
18
19
  publishScheduledDocuments
19
20
  };
20
- //# sourceMappingURL=scheduled-UC7O2HBQ.js.map
21
+ //# sourceMappingURL=scheduled-VEOGI5EW.js.map
package/dist/seo.js CHANGED
@@ -12,15 +12,16 @@ import {
12
12
  renderSitemapIndexXml,
13
13
  renderSitemapXml,
14
14
  validateSeoSettingsPatch
15
- } from "./chunk-CTUHJHLH.js";
16
- import "./chunk-S37WWNBB.js";
15
+ } from "./chunk-HVHV3IHF.js";
16
+ import "./chunk-6IEYOY2L.js";
17
+ import "./chunk-2VZZ7M26.js";
18
+ import "./chunk-P5WGQRSG.js";
17
19
  import "./chunk-4ZLMEKFX.js";
18
20
  import "./chunk-U4QCCLAW.js";
19
- import "./chunk-ZCINJSS4.js";
20
21
  import "./chunk-SBCVAC2Z.js";
21
- import "./chunk-WV272MPW.js";
22
- import "./chunk-DWG3RZH2.js";
22
+ import "./chunk-ZCINJSS4.js";
23
23
  import "./chunk-V2UNHGAP.js";
24
+ import "./chunk-WV272MPW.js";
24
25
  import "./chunk-OROPGO65.js";
25
26
  import "./chunk-NFHS7CFV.js";
26
27
  import "./chunk-XANPEOJC.js";
@@ -3,7 +3,7 @@ import {
3
3
  getCommunitySettings,
4
4
  updateCommunitySettings,
5
5
  validateCommunitySettingsPatch
6
- } from "./chunk-74CGJJDY.js";
6
+ } from "./chunk-RKM4GDWM.js";
7
7
  import "./chunk-ZCINJSS4.js";
8
8
  import "./chunk-XANPEOJC.js";
9
9
  import "./chunk-X7K5F2UI.js";
@@ -14,4 +14,4 @@ export {
14
14
  updateCommunitySettings,
15
15
  validateCommunitySettingsPatch
16
16
  };
17
- //# sourceMappingURL=settings-JODDWMDB.js.map
17
+ //# sourceMappingURL=settings-OZWM6L2K.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -10,8 +10,8 @@ import {
10
10
  } from "./chunk-ELK6AVW5.js";
11
11
  import "./chunk-4ZLMEKFX.js";
12
12
  import "./chunk-U4QCCLAW.js";
13
- import "./chunk-ZCINJSS4.js";
14
13
  import "./chunk-SBCVAC2Z.js";
14
+ import "./chunk-ZCINJSS4.js";
15
15
  import "./chunk-NFHS7CFV.js";
16
16
  import "./chunk-XANPEOJC.js";
17
17
  import "./chunk-X7K5F2UI.js";
@@ -32,7 +32,54 @@ type NpAccessFunction = (args: {
32
32
  doc?: Record<string, unknown>;
33
33
  data?: Record<string, unknown>;
34
34
  }) => boolean | Promise<boolean>;
35
+ /**
36
+ * Free-form predicate. Server-only — functions don't survive the
37
+ * server→client boundary in Next.js (the framework's
38
+ * `toClientCollectionConfig` strips them). For conditions that
39
+ * need to run in the admin editor's browser-side renderer, use
40
+ * `NpFieldConditionExpr` (serializable JSON shape) instead.
41
+ */
35
42
  type NpFieldCondition = (data: Record<string, unknown>, siblingData: Record<string, unknown>) => boolean;
43
+ /**
44
+ * Serializable condition predicate. Evaluated client-side (admin
45
+ * editor) AND server-side (pipeline validation) from the same
46
+ * declaration — survives the RSC serialization boundary because
47
+ * it's plain JSON.
48
+ *
49
+ * Examples:
50
+ * `{ when: "kind", equals: "doc" }`
51
+ * `{ when: "kind", notEquals: "doc" }`
52
+ * `{ when: "kind", in: ["doc", "page"] }`
53
+ * `{ when: "wpOriginalAuthor", exists: true }`
54
+ * `{ all: [{ when: "kind", equals: "doc" }, { when: "publishedAt", exists: true }] }`
55
+ * `{ any: [{ when: "kind", equals: "doc" }, { when: "kind", equals: "page" }] }`
56
+ *
57
+ * `exists: true` returns true when the value is defined, not null,
58
+ * not the empty string, and not an empty array. `exists: false`
59
+ * is the inverse. `equals` / `notEquals` use strict equality.
60
+ * `in` / `notIn` check membership against an unknown[] list.
61
+ * `all` / `any` are AND / OR over a list of nested expressions.
62
+ */
63
+ type NpFieldConditionExpr = {
64
+ when: string;
65
+ equals: unknown;
66
+ } | {
67
+ when: string;
68
+ notEquals: unknown;
69
+ } | {
70
+ when: string;
71
+ in: unknown[];
72
+ } | {
73
+ when: string;
74
+ notIn: unknown[];
75
+ } | {
76
+ when: string;
77
+ exists: boolean;
78
+ } | {
79
+ all: NpFieldConditionExpr[];
80
+ } | {
81
+ any: NpFieldConditionExpr[];
82
+ };
36
83
  type NpFieldValidator = (value: unknown, args: {
37
84
  data: Record<string, unknown>;
38
85
  siblingData: Record<string, unknown>;
@@ -51,7 +98,7 @@ interface NpFieldBase {
51
98
  description?: string;
52
99
  placeholder?: string;
53
100
  readOnly?: boolean;
54
- condition?: NpFieldCondition;
101
+ condition?: NpFieldCondition | NpFieldConditionExpr;
55
102
  width?: string;
56
103
  /**
57
104
  * Optional override for the admin field renderer. The default
@@ -83,6 +130,35 @@ interface NpFieldBase {
83
130
  * surfacing a date input in the primary column.
84
131
  */
85
132
  position?: "main" | "sidebar";
133
+ /**
134
+ * Sidebar grouping label. Sidebar fields with the same
135
+ * `group` render together in one collapsible Card with the
136
+ * group name as the title. Fields with no group fall into
137
+ * the default "Publish" Card. Group order in the rendered
138
+ * sidebar follows first-seen order in the collection's
139
+ * `fields` array — operators control layout by ordering.
140
+ *
141
+ * Examples: `"Publish"`, `"Lead"`, `"Taxonomy"`, `"Author"`,
142
+ * `"Hierarchy"`, `"SEO"`. The group label is the visible
143
+ * Card title (not a slug), so it doesn't have to be
144
+ * machine-friendly.
145
+ *
146
+ * Only meaningful when `position: "sidebar"`. Main-column
147
+ * fields ignore this — they render in field-array order
148
+ * without grouping.
149
+ */
150
+ group?: string;
151
+ /**
152
+ * The id of the theme whose `requires.collections.<slug>.fields`
153
+ * contributed this field. Stamped by `mergeThemeRequirements`;
154
+ * never set this from operator config. Same convention as
155
+ * `admin._themeOrigin` at the collection level and per-`kinds`
156
+ * entry: when an operator switches to a different active
157
+ * theme, the admin filters out fields whose origin doesn't
158
+ * match. Operator-declared fields carry no origin and always
159
+ * pass through.
160
+ */
161
+ _themeOrigin?: string;
86
162
  };
87
163
  validate?: NpFieldValidator;
88
164
  }
@@ -361,6 +437,52 @@ interface NpCollectionConfig {
361
437
  * default so a typo can't break the sidebar render.
362
438
  */
363
439
  icon?: string;
440
+ /**
441
+ * Framework-set. Stamped by `mergeThemeRequirements` on
442
+ * collections it synthesised via a theme's
443
+ * `requires.collections.<slug>.createIfAbsent: true`. The
444
+ * admin sidebar uses it to hide collections whose owning
445
+ * theme isn't active — the bundled-themes prebake puts
446
+ * EVERY built-in theme's `createIfAbsent` slug into the
447
+ * schema so swap-from-admin is migration-free, but the
448
+ * operator shouldn't see `authors` in the sidebar while
449
+ * running the docs theme.
450
+ *
451
+ * NEVER set this by hand from operator config. The
452
+ * underscore is intentional — it marks "this is the
453
+ * framework's view of the config, not the operator's
454
+ * intent". Operator-declared collections (slug exists
455
+ * before merge) keep this unset and always show.
456
+ */
457
+ _themeOrigin?: string;
458
+ /**
459
+ * Framework-set. Stamped by `mergeThemeRequirements` from
460
+ * `theme.manifest.requires.collections.<slug>.kinds`,
461
+ * unioned across all registered themes. The admin sidebar
462
+ * walks this map to render per-kind entries under "Content"
463
+ * (universal-content-model #748).
464
+ *
465
+ * Keyed by the discriminator value declared on the
466
+ * `kind` field's options. Empty / missing → admin shows a
467
+ * single collection entry like before.
468
+ */
469
+ kinds?: Record<string, NpThemeCollectionKind>;
470
+ /**
471
+ * Visual metadata for sidebar field groups. Keyed by the
472
+ * `admin.group` label used on individual fields. The editor's
473
+ * `SidebarGroupCard` reads this to render an icon next to
474
+ * the group title and optionally surface a description.
475
+ *
476
+ * Operator-declared collections set this directly. Themes
477
+ * contribute their own group icons via
478
+ * `requires.collections.<slug>.groupMeta` (merged through
479
+ * `mergeThemeRequirements`, unioned across themes with
480
+ * last-write-wins on per-key props).
481
+ *
482
+ * Groups without an entry render without an icon — same
483
+ * behavior as before this surface existed.
484
+ */
485
+ groupMeta?: Record<string, NpAdminGroupMeta>;
364
486
  };
365
487
  upload?: NpUploadConfig;
366
488
  }
@@ -623,6 +745,107 @@ interface NpThemeCollectionRequirement {
623
745
  * slug is registered. Operator-authored collections of the
624
746
  * same slug always take precedence. */
625
747
  createIfAbsent?: boolean;
748
+ /**
749
+ * Per-kind metadata for the `kind` discriminator field
750
+ * (universal-content-model #748). Themes contribute one entry
751
+ * per kind they author content for; the framework's auto-merge
752
+ * unions entries across registered themes so the admin sidebar
753
+ * and the public-site router both see one canonical map.
754
+ *
755
+ * Keyed by the option value declared on `fields.kind.options`
756
+ * (e.g. `kinds.doc` matches the option whose `value: "doc"`).
757
+ * The collection slug remains `posts`; kinds are a presentation
758
+ * split, not a separate table.
759
+ *
760
+ * The `kind` field itself doesn't have to live on this
761
+ * collection's `fields` for the metadata to apply — themes that
762
+ * extend a single kind (`fields.kind.options: [{value:"doc"}]`
763
+ * + `kinds.doc: {...}`) ship both together and the merge unions
764
+ * them with whatever other themes declare. A `kinds` block on a
765
+ * collection without a corresponding select field is a no-op
766
+ * (the admin shows the regular collection list view).
767
+ */
768
+ kinds?: Record<string, NpThemeCollectionKind>;
769
+ /**
770
+ * Sidebar group metadata the theme contributes. Keyed by the
771
+ * `admin.group` label the theme uses on its contributed fields
772
+ * (e.g. theme-magazine contributes `Magazine: { icon: "Newspaper" }`).
773
+ * Merged into the collection's `admin.groupMeta` via
774
+ * last-write-wins union — two themes claiming the same group
775
+ * label get the later theme's icon / description.
776
+ *
777
+ * Declaring a group key without contributing fields with the
778
+ * same `admin.group` is allowed (the entry is unused but
779
+ * harmless) — useful for overriding a framework default's
780
+ * icon without adding any new fields.
781
+ */
782
+ groupMeta?: Record<string, NpAdminGroupMeta>;
783
+ }
784
+ /**
785
+ * One kind entry — admin nav + public URL metadata for a single
786
+ * discriminator value on a `select` field (typically `posts.kind`).
787
+ *
788
+ * Field merge: two themes declaring the same kind value get
789
+ * last-wins on every property. Operators rarely need to redefine
790
+ * a kind their theme already ships.
791
+ */
792
+ /**
793
+ * Per-group sidebar metadata. Resolves at runtime in the admin
794
+ * edit view; not codegen'd into the DB schema.
795
+ */
796
+ interface NpAdminGroupMeta {
797
+ /**
798
+ * Lucide icon name (no `Icon` suffix) shown next to the
799
+ * group title in the editor sidebar. Examples: `"Calendar"`,
800
+ * `"BookOpen"`, `"Briefcase"`. Resolved client-side; unknown
801
+ * names render no icon (silent fallback, no warning).
802
+ */
803
+ icon?: string;
804
+ /**
805
+ * One-line description shown beneath the group title. Useful
806
+ * for operator hints like "Search-result preview + social
807
+ * card." Truncated by the admin if it's long.
808
+ */
809
+ description?: string;
810
+ }
811
+ interface NpThemeCollectionKind {
812
+ /** Singular human label — "Doc", "Project", "Article". */
813
+ label: string;
814
+ /** Plural label for the admin sidebar entry — "Documentation". */
815
+ labelPlural: string;
816
+ /** Lucide icon name (no `Icon` suffix) — "BookOpen", "Briefcase". */
817
+ icon?: string;
818
+ /**
819
+ * Public-site URL pattern. `:slug` is the only supported param;
820
+ * the catch-all router (`apps/web/src/app/(site)/[[...slug]]/page.tsx`)
821
+ * matches the path, extracts the slug, and queries the host
822
+ * collection with `where: { kind: "<this-key>", slug: "<match>" }`.
823
+ *
824
+ * Omit to fall back to the framework default (`/<collection-slug>/<slug>`,
825
+ * shared with the kind=null catch-all path). Two kinds declaring
826
+ * the same urlPattern collide and the first wins; the admin
827
+ * surfaces this via the requirements diff.
828
+ */
829
+ urlPattern?: string;
830
+ /**
831
+ * True → admin's per-kind list view surfaces `parent` + `order`
832
+ * controls and renders rows as a tree. Themes with hierarchical
833
+ * content (docs, sections) opt in; flat kinds leave it false.
834
+ */
835
+ hierarchical?: boolean;
836
+ /**
837
+ * Framework-set. Stamped by `mergeThemeRequirements` with the
838
+ * id of the theme whose `requires.collections.<slug>.kinds`
839
+ * contributed this entry. The admin sidebar reads it to gate
840
+ * per-kind nav entries on the active theme — the bundled-themes
841
+ * prebake unions every built-in's kinds onto the schema, but
842
+ * only the active theme's kinds deserve sidebar real estate.
843
+ *
844
+ * NEVER set this by hand from operator config. The underscore
845
+ * is intentional — same convention as `admin._themeOrigin` at
846
+ * the collection level.
847
+ */
848
+ _themeOrigin?: string;
626
849
  }
627
850
  /**
628
851
  * One field's requirement. The `type` matches an `NpFieldConfig`
@@ -642,6 +865,48 @@ interface NpThemeFieldRequirement {
642
865
  * severity, and a future F.8 may treat it as opt-in patch.
643
866
  */
644
867
  hard?: boolean;
868
+ /**
869
+ * For `select` only — extra options to union into the existing
870
+ * select field. Two themes can contribute disjoint option sets
871
+ * (e.g. theme-docs adds `kind="doc"`, theme-portfolio adds
872
+ * `kind="project"`); the merge dedupes on `value` and last-wins
873
+ * on `label`. Universal-content-model Phase U.1 (#748).
874
+ *
875
+ * Ignored when the merge can't find an existing select with the
876
+ * same `name`; theme authors that need a brand-new select can't
877
+ * synthesise one through requirements (`NpThemeFieldRequirement`
878
+ * doesn't carry enough to construct a valid `NpSelectField`).
879
+ */
880
+ options?: Array<{
881
+ label: string;
882
+ value: string;
883
+ }>;
884
+ /**
885
+ * Optional admin hints forwarded onto the synthesised field's
886
+ * `admin` slot. Themes use these to bucket their contributed
887
+ * fields into the right sidebar group and hide fields when
888
+ * irrelevant to the active kind.
889
+ *
890
+ * `group` — sidebar Card grouping label
891
+ * (e.g. `"Media"`, `"SEO"`).
892
+ * `condition` — runtime visibility gate. Either a function
893
+ * (server-only — stripped at the RSC boundary)
894
+ * or a serializable expression (works in both
895
+ * environments). Prefer the expression form so
896
+ * the admin's client renderer can re-evaluate
897
+ * on live form values:
898
+ * `{ when: "kind", equals: "doc" }`
899
+ * `{ when: "kind", notEquals: "doc" }`
900
+ * `{ when: "kind", in: ["doc", "page"] }`
901
+ * `{ when: "wpOriginalAuthor", exists: true }`
902
+ * `position` — main vs sidebar column. Defaults to the
903
+ * framework's `isSidebarField` heuristic.
904
+ */
905
+ admin?: {
906
+ group?: string;
907
+ condition?: NpFieldCondition | NpFieldConditionExpr;
908
+ position?: "main" | "sidebar";
909
+ };
645
910
  }
646
911
  interface NpRegisteredTheme {
647
912
  manifest: NpThemeManifest;
@@ -877,4 +1142,4 @@ interface NpSaveResult {
877
1142
  */
878
1143
  declare const ROLE_HIERARCHY: Record<NpUserRole, number>;
879
1144
 
880
- 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 };
1145
+ export { ROLE_HIERARCHY as $, type NpEmailField as A, type NpFieldCondition as B, type NpFieldConditionExpr as C, type NpFieldValidator as D, type NpFindWhere as E, type NpFindWhereSystemTokens as F, type NpGroupField as G, type NpPrincipal as H, type NpI18nConfig as I, type NpImageSize as J, type NpJobType as K, type NpJsonField as L, type NpNumberField as M, type NpRichTextContent as N, type NpPluginContext as O, type NpRadioField as P, type NpRelationshipField as Q, type NpResolvedPluginLike as R, type NpRichTextField as S, type NpRowField as T, type NpSelectField as U, type NpTextField as V, type NpTextareaField as W, type NpThemeCollectionKind as X, type NpThemeCollectionRequirement as Y, type NpUploadConfig as Z, type NpUploadField as _, 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 NpRegisteredTheme as j, type NpThemeFieldRequirement as k, type NpThemeManifest as l, type NpUserRole as m, type NpFieldConfig as n, type NpPluginConfig as o, type NpAccessFunction as p, type NpAdminGroupMeta as q, type NpArrayField as r, type NpBlockConfig as s, type NpBlocksField as t, type NpCheckboxField as u, type NpCollapsibleField as v, type NpCollectionHook as w, type NpDateField as x, type NpDocumentStatus as y, type NpEditorConfig as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexpress/core",
3
- "version": "0.2.2",
3
+ "version": "0.3.1",
4
4
  "description": "Server-side core for NexPress — collections pipeline, auth, jobs, media, plugins, observability.",
5
5
  "license": "MIT",
6
6
  "author": "Nexpress",
@@ -48,6 +48,11 @@
48
48
  "import": "./dist/db.js",
49
49
  "default": "./dist/db.js"
50
50
  },
51
+ "./fields": {
52
+ "types": "./dist/fields.d.ts",
53
+ "import": "./dist/fields.js",
54
+ "default": "./dist/fields.js"
55
+ },
51
56
  "./i18n": {
52
57
  "types": "./dist/i18n.d.ts",
53
58
  "import": "./dist/i18n.js",