emdash 0.9.0 → 0.11.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/dist/{adapters-DoNJiveC.d.mts → adapters-BktHA7EO.d.mts} +1 -1
- package/dist/{adapters-DoNJiveC.d.mts.map → adapters-BktHA7EO.d.mts.map} +1 -1
- package/dist/{apply-BzltprvY.mjs → apply-Ded_1vng.mjs} +167 -254
- package/dist/apply-Ded_1vng.mjs.map +1 -0
- package/dist/astro/index.d.mts +6 -6
- package/dist/astro/index.mjs +10 -2
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +5 -5
- package/dist/astro/middleware/auth.mjs +5 -5
- package/dist/astro/middleware/redirect.mjs +5 -5
- package/dist/astro/middleware/request-context.mjs +4 -4
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +94 -43
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/types.d.mts +12 -11
- package/dist/astro/types.d.mts.map +1 -1
- package/dist/{base64-BRICGH2l.mjs → base64-MBPo9ozB.mjs} +1 -1
- package/dist/{base64-BRICGH2l.mjs.map → base64-MBPo9ozB.mjs.map} +1 -1
- package/dist/{byline-BSaNL1w7.mjs → byline-gFn1r0vA.mjs} +4 -4
- package/dist/{byline-BSaNL1w7.mjs.map → byline-gFn1r0vA.mjs.map} +1 -1
- package/dist/{bylines-CvJ3PYz2.mjs → bylines-DTFI8nDM.mjs} +5 -5
- package/dist/{bylines-CvJ3PYz2.mjs.map → bylines-DTFI8nDM.mjs.map} +1 -1
- package/dist/{cache-C6N_hhN7.mjs → cache-BAJbeoZ8.mjs} +3 -3
- package/dist/{cache-C6N_hhN7.mjs.map → cache-BAJbeoZ8.mjs.map} +1 -1
- package/dist/{chunks-NBQVDOci.mjs → chunks-BK1oZS-l.mjs} +2 -2
- package/dist/{chunks-NBQVDOci.mjs.map → chunks-BK1oZS-l.mjs.map} +1 -1
- package/dist/cli/index.mjs +342 -95
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/cf-access.d.mts +1 -1
- package/dist/client/index.d.mts +1 -1
- package/dist/client/index.mjs +1 -1
- package/dist/{config-BI0V3ICQ.mjs → config-CVssduLe.mjs} +1 -1
- package/dist/{config-BI0V3ICQ.mjs.map → config-CVssduLe.mjs.map} +1 -1
- package/dist/{content-8lOYF0pr.mjs → content-CERxPUN0.mjs} +14 -3
- package/dist/content-CERxPUN0.mjs.map +1 -0
- package/dist/database/instrumentation.d.mts +6 -4
- package/dist/database/instrumentation.d.mts.map +1 -1
- package/dist/database/instrumentation.mjs +19 -7
- package/dist/database/instrumentation.mjs.map +1 -1
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +1 -1
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/{db-errors-WRezodiz.mjs → db-errors-B7P2pSCn.mjs} +1 -1
- package/dist/{db-errors-WRezodiz.mjs.map → db-errors-B7P2pSCn.mjs.map} +1 -1
- package/dist/{default-D8ksjWhO.mjs → default-pHuz9WF6.mjs} +1 -1
- package/dist/{default-D8ksjWhO.mjs.map → default-pHuz9WF6.mjs.map} +1 -1
- package/dist/{error-D_-tqP-I.mjs → error-DqnRMM5z.mjs} +1 -1
- package/dist/{error-D_-tqP-I.mjs.map → error-DqnRMM5z.mjs.map} +1 -1
- package/dist/{index-BFRaVcD6.d.mts → index-Cg-rC4Gj.d.mts} +110 -87
- package/dist/index-Cg-rC4Gj.d.mts.map +1 -0
- package/dist/index.d.mts +11 -11
- package/dist/index.mjs +29 -28
- package/dist/{load-DDqMMvZL.mjs → load-DR1VwFXR.mjs} +2 -2
- package/dist/{load-DDqMMvZL.mjs.map → load-DR1VwFXR.mjs.map} +1 -1
- package/dist/{loader-CKLbBnhK.mjs → loader-ou_PXAjg.mjs} +31 -6
- package/dist/loader-ou_PXAjg.mjs.map +1 -0
- package/dist/{manifest-schema-DqWNC3lM.mjs → manifest-schema-CXAbd1vH.mjs} +1 -1
- package/dist/{manifest-schema-DqWNC3lM.mjs.map → manifest-schema-CXAbd1vH.mjs.map} +1 -1
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +1 -1
- package/dist/media/local-runtime.d.mts +7 -7
- package/dist/media/local-runtime.mjs +3 -3
- package/dist/{media-BW32b4gi.mjs → media-1fFhub9c.mjs} +22 -10
- package/dist/media-1fFhub9c.mjs.map +1 -0
- package/dist/{mode-ier8jbBk.mjs → mode-YhqNVef_.mjs} +1 -1
- package/dist/{mode-ier8jbBk.mjs.map → mode-YhqNVef_.mjs.map} +1 -1
- package/dist/{options-BVp3UsTS.mjs → options-nPxWnrya.mjs} +1 -1
- package/dist/{options-BVp3UsTS.mjs.map → options-nPxWnrya.mjs.map} +1 -1
- package/dist/page/index.d.mts +2 -2
- package/dist/{patterns-CrCYkMBb.mjs → patterns-DsUZ4uxI.mjs} +1 -1
- package/dist/{patterns-CrCYkMBb.mjs.map → patterns-DsUZ4uxI.mjs.map} +1 -1
- package/dist/{placeholder-BE4o_2dc.d.mts → placeholder-CDPtkelt.d.mts} +1 -1
- package/dist/{placeholder-BE4o_2dc.d.mts.map → placeholder-CDPtkelt.d.mts.map} +1 -1
- package/dist/{placeholder-CIJejMlK.mjs → placeholder-Ci0RLeCk.mjs} +1 -1
- package/dist/{placeholder-CIJejMlK.mjs.map → placeholder-Ci0RLeCk.mjs.map} +1 -1
- package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
- package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
- package/dist/{public-url-DByxYjUw.mjs → public-url-B1AxbbbQ.mjs} +1 -1
- package/dist/{public-url-DByxYjUw.mjs.map → public-url-B1AxbbbQ.mjs.map} +1 -1
- package/dist/{query-Cg9ZKRQ0.mjs → query-8c_meo_K.mjs} +13 -13
- package/dist/{query-Cg9ZKRQ0.mjs.map → query-8c_meo_K.mjs.map} +1 -1
- package/dist/{redirect-BhUBKRc1.mjs → redirect-C5H7VGIX.mjs} +3 -3
- package/dist/{redirect-BhUBKRc1.mjs.map → redirect-C5H7VGIX.mjs.map} +1 -1
- package/dist/{registry-Dw70ChxB.mjs → registry-Do34mz_P.mjs} +7 -6
- package/dist/registry-Do34mz_P.mjs.map +1 -0
- package/dist/{request-cache-B-bmkipQ.mjs → request-cache-D4I69LeL.mjs} +6 -2
- package/dist/request-cache-D4I69LeL.mjs.map +1 -0
- package/dist/request-context.d.mts +27 -1
- package/dist/request-context.d.mts.map +1 -1
- package/dist/request-context.mjs +16 -3
- package/dist/request-context.mjs.map +1 -1
- package/dist/{runner-C7ADox5q.mjs → runner-DIcU2UCC.mjs} +465 -148
- package/dist/runner-DIcU2UCC.mjs.map +1 -0
- package/dist/{runner-Bnoj7vjK.d.mts → runner-Iu3IZSDM.d.mts} +2 -2
- package/dist/{runner-Bnoj7vjK.d.mts.map → runner-Iu3IZSDM.d.mts.map} +1 -1
- package/dist/runtime.d.mts +6 -6
- package/dist/runtime.mjs +3 -3
- package/dist/{search-dOGEccMa.mjs → search-DuWhx4NG.mjs} +322 -108
- package/dist/search-DuWhx4NG.mjs.map +1 -0
- package/dist/{secrets-CW3reAnU.mjs → secrets-CZ8rxLX3.mjs} +3 -3
- package/dist/{secrets-CW3reAnU.mjs.map → secrets-CZ8rxLX3.mjs.map} +1 -1
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +15 -14
- package/dist/seo/index.d.mts +1 -1
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.mjs +1 -1
- package/dist/taxonomies-Bw76xAxo.mjs +407 -0
- package/dist/taxonomies-Bw76xAxo.mjs.map +1 -0
- package/dist/taxonomy-D6NvlKo8.mjs +218 -0
- package/dist/taxonomy-D6NvlKo8.mjs.map +1 -0
- package/dist/{tokens-D7zMmWi2.mjs → tokens-CyRDPVW2.mjs} +2 -2
- package/dist/{tokens-D7zMmWi2.mjs.map → tokens-CyRDPVW2.mjs.map} +1 -1
- package/dist/{transaction-Cn2rjY78.mjs → transaction-D44LBXvU.mjs} +1 -1
- package/dist/{transaction-Cn2rjY78.mjs.map → transaction-D44LBXvU.mjs.map} +1 -1
- package/dist/{transport-DNEfeMaU.d.mts → transport-DX_5rpsq.d.mts} +1 -1
- package/dist/{transport-DNEfeMaU.d.mts.map → transport-DX_5rpsq.d.mts.map} +1 -1
- package/dist/{transport-BeMCmin1.mjs → transport-xpzIjCIB.mjs} +1 -1
- package/dist/{transport-BeMCmin1.mjs.map → transport-xpzIjCIB.mjs.map} +1 -1
- package/dist/{types-CIOg5AR8.mjs → types-56BKbld_.mjs} +1 -1
- package/dist/types-56BKbld_.mjs.map +1 -0
- package/dist/{types-CRxNbK-Z.mjs → types-BIgulNsW.mjs} +2 -2
- package/dist/{types-CRxNbK-Z.mjs.map → types-BIgulNsW.mjs.map} +1 -1
- package/dist/{types-CrtWgIvl.d.mts → types-BQx6ZXpR.d.mts} +10 -1
- package/dist/types-BQx6ZXpR.d.mts.map +1 -0
- package/dist/{types-CJsYGpco.d.mts → types-B_CXXnzh.d.mts} +1 -1
- package/dist/{types-CJsYGpco.d.mts.map → types-B_CXXnzh.d.mts.map} +1 -1
- package/dist/{types-M78DQ1lx.d.mts → types-C-aFbqmA.d.mts} +1 -1
- package/dist/{types-M78DQ1lx.d.mts.map → types-C-aFbqmA.d.mts.map} +1 -1
- package/dist/types-DiI8NOG_.mjs +16 -0
- package/dist/types-DiI8NOG_.mjs.map +1 -0
- package/dist/{types-BuBIptGk.d.mts → types-IN5z_S3P.d.mts} +158 -92
- package/dist/types-IN5z_S3P.d.mts.map +1 -0
- package/dist/{types-BSyXeCFW.d.mts → types-IZSZfEwv.d.mts} +4 -3
- package/dist/types-IZSZfEwv.d.mts.map +1 -0
- package/dist/{types-CDbKp7ND.mjs → types-K-EkEQCI.mjs} +1 -1
- package/dist/{types-CDbKp7ND.mjs.map → types-K-EkEQCI.mjs.map} +1 -1
- package/dist/{validate-BfQh_C_y.d.mts → validate-CO3JjFV5.d.mts} +22 -5
- package/dist/validate-CO3JjFV5.d.mts.map +1 -0
- package/dist/{validate-Baqf0slj.mjs → validate-UK4Ja1uo.mjs} +14 -10
- package/dist/validate-UK4Ja1uo.mjs.map +1 -0
- package/dist/{validation-BfEI7tNe.mjs → validation-Vc5DQkJa.mjs} +5 -5
- package/dist/{validation-BfEI7tNe.mjs.map → validation-Vc5DQkJa.mjs.map} +1 -1
- package/dist/version-Bg31I_Ff.mjs +7 -0
- package/dist/{version-DoxrVdYf.mjs.map → version-Bg31I_Ff.mjs.map} +1 -1
- package/dist/{zod-generator-CC0xNe_K.mjs → zod-generator-CHnJUP2l.mjs} +8 -3
- package/dist/zod-generator-CHnJUP2l.mjs.map +1 -0
- package/package.json +9 -8
- package/src/api/errors.ts +5 -0
- package/src/api/handlers/content.ts +20 -0
- package/src/api/handlers/dashboard.ts +29 -36
- package/src/api/handlers/media-allowlist.ts +40 -0
- package/src/api/handlers/media.ts +1 -1
- package/src/api/handlers/menus.ts +400 -89
- package/src/api/handlers/taxonomies.ts +273 -97
- package/src/api/handlers/validate-media-fields.ts +125 -0
- package/src/api/schemas/common.ts +7 -0
- package/src/api/schemas/media.ts +23 -3
- package/src/api/schemas/menus.ts +23 -0
- package/src/api/schemas/schema.ts +11 -2
- package/src/api/schemas/taxonomies.ts +39 -0
- package/src/astro/integration/routes.ts +10 -0
- package/src/astro/middleware.ts +46 -11
- package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +1 -1
- package/src/astro/routes/api/import/wordpress/rewrite-url-helpers.ts +196 -0
- package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +9 -177
- package/src/astro/routes/api/media/upload-url.ts +10 -4
- package/src/astro/routes/api/media.ts +12 -4
- package/src/astro/routes/api/menus/[name]/items.ts +16 -6
- package/src/astro/routes/api/menus/[name]/reorder.ts +8 -3
- package/src/astro/routes/api/menus/[name]/translations.ts +82 -0
- package/src/astro/routes/api/menus/[name].ts +19 -10
- package/src/astro/routes/api/menus/index.ts +9 -6
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug]/translations.ts +89 -0
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +22 -22
- package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +11 -14
- package/src/astro/routes/api/taxonomies/index.ts +9 -6
- package/src/astro/types.ts +5 -1
- package/src/auth/rate-limit.ts +3 -3
- package/src/cli/commands/bundle-utils.ts +81 -6
- package/src/cli/commands/bundle.ts +18 -15
- package/src/cli/commands/export-seed.ts +139 -24
- package/src/cli/commands/plugin-init.ts +216 -90
- package/src/database/instrumentation.ts +22 -8
- package/src/database/migrations/016_api_tokens.ts +18 -3
- package/src/database/migrations/036_i18n_menus_and_taxonomies.ts +477 -0
- package/src/database/migrations/037_credential_algorithm.ts +18 -0
- package/src/database/migrations/runner.ts +4 -0
- package/src/database/repositories/content.ts +11 -0
- package/src/database/repositories/media.ts +40 -10
- package/src/database/repositories/taxonomy.ts +193 -89
- package/src/database/types.ts +12 -3
- package/src/emdash-runtime.ts +16 -3
- package/src/fields/file.ts +7 -6
- package/src/fields/image.ts +12 -11
- package/src/fields/types.ts +3 -0
- package/src/i18n/resolve.ts +37 -0
- package/src/index.ts +1 -1
- package/src/loader.ts +49 -2
- package/src/mcp/server.ts +114 -26
- package/src/media/mime.ts +75 -0
- package/src/menus/index.ts +143 -124
- package/src/menus/types.ts +15 -1
- package/src/plugins/types.ts +81 -191
- package/src/request-cache.ts +6 -2
- package/src/request-context.ts +42 -2
- package/src/schema/registry.ts +5 -5
- package/src/schema/types.ts +3 -2
- package/src/schema/zod-generator.ts +12 -2
- package/src/seed/apply.ts +157 -54
- package/src/seed/types.ts +18 -1
- package/src/seed/validate.ts +27 -13
- package/src/taxonomies/index.ts +230 -213
- package/src/taxonomies/types.ts +10 -0
- package/dist/apply-BzltprvY.mjs.map +0 -1
- package/dist/content-8lOYF0pr.mjs.map +0 -1
- package/dist/index-BFRaVcD6.d.mts.map +0 -1
- package/dist/loader-CKLbBnhK.mjs.map +0 -1
- package/dist/media-BW32b4gi.mjs.map +0 -1
- package/dist/registry-Dw70ChxB.mjs.map +0 -1
- package/dist/request-cache-B-bmkipQ.mjs.map +0 -1
- package/dist/runner-C7ADox5q.mjs.map +0 -1
- package/dist/search-dOGEccMa.mjs.map +0 -1
- package/dist/taxonomies-ZlRtD6AG.mjs +0 -315
- package/dist/taxonomies-ZlRtD6AG.mjs.map +0 -1
- package/dist/types-4fVtCIm0.mjs +0 -68
- package/dist/types-4fVtCIm0.mjs.map +0 -1
- package/dist/types-BSyXeCFW.d.mts.map +0 -1
- package/dist/types-BuBIptGk.d.mts.map +0 -1
- package/dist/types-CIOg5AR8.mjs.map +0 -1
- package/dist/types-CrtWgIvl.d.mts.map +0 -1
- package/dist/validate-Baqf0slj.mjs.map +0 -1
- package/dist/validate-BfQh_C_y.d.mts.map +0 -1
- package/dist/version-DoxrVdYf.mjs +0 -7
- package/dist/zod-generator-CC0xNe_K.mjs.map +0 -1
package/src/plugins/types.ts
CHANGED
|
@@ -10,187 +10,56 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { Element } from "@emdash-cms/blocks";
|
|
13
|
+
// The plugin capability vocabulary, the legacy-rename map, and the manifest
|
|
14
|
+
// shape are authored once in @emdash-cms/plugin-types and shared between core
|
|
15
|
+
// (the manifest reader at install/runtime) and @emdash-cms/registry-cli (the
|
|
16
|
+
// manifest writer at bundle/publish time).
|
|
17
|
+
//
|
|
18
|
+
// We import-and-re-export here so existing internal callers keep working
|
|
19
|
+
// (e.g. `import { PluginCapability } from "../plugins/types.js"`).
|
|
20
|
+
import {
|
|
21
|
+
CAPABILITY_RENAMES,
|
|
22
|
+
isDeprecatedCapability,
|
|
23
|
+
normalizeCapabilities,
|
|
24
|
+
normalizeCapability,
|
|
25
|
+
type CurrentPluginCapability,
|
|
26
|
+
type DeprecatedPluginCapability,
|
|
27
|
+
type ManifestHookEntry,
|
|
28
|
+
type ManifestRouteEntry,
|
|
29
|
+
type PluginCapability,
|
|
30
|
+
type PluginStorageConfig,
|
|
31
|
+
type StorageCollectionConfig,
|
|
32
|
+
} from "@emdash-cms/plugin-types";
|
|
13
33
|
import type { JSX } from "astro/jsx-runtime";
|
|
14
34
|
import type { z } from "astro/zod";
|
|
15
|
-
|
|
16
|
-
import type { FieldType } from "../schema/types.js";
|
|
17
|
-
|
|
18
35
|
// =============================================================================
|
|
19
36
|
// Core Types
|
|
20
37
|
// =============================================================================
|
|
21
38
|
|
|
22
|
-
|
|
23
|
-
* Plugin capabilities determine what APIs are available in context.
|
|
24
|
-
*
|
|
25
|
-
* Capabilities follow the formula `<resource>[.<sub-resource>]:<verb>[:<qualifier>]`
|
|
26
|
-
* — resource first, verb second, matching RBAC. The `unrestricted` qualifier
|
|
27
|
-
* (used by `network:request:unrestricted`) is intentionally verbose so that
|
|
28
|
-
* granting it stands out in manifest review.
|
|
29
|
-
*
|
|
30
|
-
* Hook-registration capabilities (`hooks.<family>:register`) are a distinct
|
|
31
|
-
* audit category from data-access capabilities — they gate which hooks a
|
|
32
|
-
* plugin is allowed to register, not which context APIs it gets.
|
|
33
|
-
*
|
|
34
|
-
* @see CAPABILITY_RENAMES for the legacy → current mapping, and
|
|
35
|
-
* `normalizeCapability()` for the runtime alias layer.
|
|
36
|
-
*/
|
|
37
|
-
export type PluginCapability =
|
|
38
|
-
// ── Network ─────────────────────────────────────────────────
|
|
39
|
-
| "network:request" // ctx.http is available (host-restricted via allowedHosts)
|
|
40
|
-
| "network:request:unrestricted" // ctx.http is available (unrestricted outbound — use for user-configured URLs)
|
|
41
|
-
// ── Content ─────────────────────────────────────────────────
|
|
42
|
-
| "content:read" // ctx.content.get/list available
|
|
43
|
-
| "content:write" // ctx.content.create/update/delete available
|
|
44
|
-
// ── Media ───────────────────────────────────────────────────
|
|
45
|
-
| "media:read" // ctx.media.get/list available
|
|
46
|
-
| "media:write" // ctx.media.getUploadUrl/delete available
|
|
47
|
-
// ── Users ───────────────────────────────────────────────────
|
|
48
|
-
| "users:read" // ctx.users is available
|
|
49
|
-
// ── Email ───────────────────────────────────────────────────
|
|
50
|
-
| "email:send" // ctx.email is available (when a provider is configured)
|
|
51
|
-
// ── Hook registration ───────────────────────────────────────
|
|
52
|
-
| "hooks.email-transport:register" // can register email:deliver exclusive hook (transport provider)
|
|
53
|
-
| "hooks.email-events:register" // can register email:beforeSend / email:afterSend hooks
|
|
54
|
-
| "hooks.page-fragments:register" // can register page:fragments hook (inject scripts/styles into pages)
|
|
55
|
-
// ── Deprecated (legacy aliases) ─────────────────────────────
|
|
56
|
-
// Kept in the union for one minor with @deprecated tags so existing
|
|
57
|
-
// plugins typecheck during migration. Normalized to current names at
|
|
58
|
-
// definition time via normalizeCapability(). Will be removed in the
|
|
59
|
-
// following minor.
|
|
60
|
-
/** @deprecated Use `network:request` instead. */
|
|
61
|
-
| "network:fetch"
|
|
62
|
-
/** @deprecated Use `network:request:unrestricted` instead. */
|
|
63
|
-
| "network:fetch:any"
|
|
64
|
-
/** @deprecated Use `content:read` instead. */
|
|
65
|
-
| "read:content"
|
|
66
|
-
/** @deprecated Use `content:write` instead. */
|
|
67
|
-
| "write:content"
|
|
68
|
-
/** @deprecated Use `media:read` instead. */
|
|
69
|
-
| "read:media"
|
|
70
|
-
/** @deprecated Use `media:write` instead. */
|
|
71
|
-
| "write:media"
|
|
72
|
-
/** @deprecated Use `users:read` instead. */
|
|
73
|
-
| "read:users"
|
|
74
|
-
/** @deprecated Use `hooks.email-transport:register` instead. */
|
|
75
|
-
| "email:provide"
|
|
76
|
-
/** @deprecated Use `hooks.email-events:register` instead. */
|
|
77
|
-
| "email:intercept"
|
|
78
|
-
/** @deprecated Use `hooks.page-fragments:register` instead. */
|
|
79
|
-
| "page:inject";
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Deprecated capability names that map to current names.
|
|
83
|
-
*
|
|
84
|
-
* These are accepted at every external boundary (manifest parse, definePlugin,
|
|
85
|
-
* adaptSandboxEntry) and silently normalized to the new names before reaching
|
|
86
|
-
* the runtime. The runtime never sees deprecated names.
|
|
87
|
-
*
|
|
88
|
-
* Authors are warned at `bundle` / `validate`, and hard-failed at `publish`.
|
|
89
|
-
*/
|
|
90
|
-
export type DeprecatedPluginCapability =
|
|
91
|
-
| "network:fetch"
|
|
92
|
-
| "network:fetch:any"
|
|
93
|
-
| "read:content"
|
|
94
|
-
| "write:content"
|
|
95
|
-
| "read:media"
|
|
96
|
-
| "write:media"
|
|
97
|
-
| "read:users"
|
|
98
|
-
| "email:provide"
|
|
99
|
-
| "email:intercept"
|
|
100
|
-
| "page:inject";
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Current (non-deprecated) capability names.
|
|
104
|
-
*/
|
|
105
|
-
export type CurrentPluginCapability = Exclude<PluginCapability, DeprecatedPluginCapability>;
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Mapping from deprecated capability names to their current replacements.
|
|
109
|
-
*
|
|
110
|
-
* Used by `normalizeCapability()` and the marketplace `diffCapabilities`
|
|
111
|
-
* helper to compare manifests across the rename without flagging spurious
|
|
112
|
-
* "capability changed" prompts on upgrade.
|
|
113
|
-
*/
|
|
114
|
-
export const CAPABILITY_RENAMES: Readonly<
|
|
115
|
-
Record<DeprecatedPluginCapability, CurrentPluginCapability>
|
|
116
|
-
> = Object.freeze({
|
|
117
|
-
"network:fetch": "network:request",
|
|
118
|
-
"network:fetch:any": "network:request:unrestricted",
|
|
119
|
-
"read:content": "content:read",
|
|
120
|
-
"write:content": "content:write",
|
|
121
|
-
"read:media": "media:read",
|
|
122
|
-
"write:media": "media:write",
|
|
123
|
-
"read:users": "users:read",
|
|
124
|
-
"email:provide": "hooks.email-transport:register",
|
|
125
|
-
"email:intercept": "hooks.email-events:register",
|
|
126
|
-
"page:inject": "hooks.page-fragments:register",
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Type guard: is this capability one of the deprecated legacy names?
|
|
131
|
-
*
|
|
132
|
-
* Uses an own-property check so that prototype keys like "toString" or
|
|
133
|
-
* "constructor" don't accidentally pass.
|
|
134
|
-
*/
|
|
135
|
-
export function isDeprecatedCapability(cap: string): cap is DeprecatedPluginCapability {
|
|
136
|
-
return Object.hasOwn(CAPABILITY_RENAMES, cap);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Normalize a capability string — deprecated names map to current names,
|
|
141
|
-
* current names pass through unchanged. Unknown strings are returned as-is
|
|
142
|
-
* so that downstream validators can produce a precise error.
|
|
143
|
-
*/
|
|
144
|
-
export function normalizeCapability(cap: string): string {
|
|
145
|
-
if (isDeprecatedCapability(cap)) {
|
|
146
|
-
return CAPABILITY_RENAMES[cap];
|
|
147
|
-
}
|
|
148
|
-
return cap;
|
|
149
|
-
}
|
|
39
|
+
import type { FieldType } from "../schema/types.js";
|
|
150
40
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
return out;
|
|
167
|
-
}
|
|
41
|
+
export {
|
|
42
|
+
CAPABILITY_RENAMES,
|
|
43
|
+
isDeprecatedCapability,
|
|
44
|
+
normalizeCapabilities,
|
|
45
|
+
normalizeCapability,
|
|
46
|
+
type CurrentPluginCapability,
|
|
47
|
+
type DeprecatedPluginCapability,
|
|
48
|
+
type ManifestHookEntry,
|
|
49
|
+
type ManifestRouteEntry,
|
|
50
|
+
type PluginCapability,
|
|
51
|
+
type PluginStorageConfig,
|
|
52
|
+
type StorageCollectionConfig,
|
|
53
|
+
};
|
|
168
54
|
|
|
169
55
|
// =============================================================================
|
|
170
56
|
// Storage Types
|
|
171
57
|
// =============================================================================
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Fields to index for querying.
|
|
179
|
-
* Each entry can be a single field name or an array for composite indexes.
|
|
180
|
-
*/
|
|
181
|
-
indexes: Array<string | string[]>;
|
|
182
|
-
/**
|
|
183
|
-
* Fields with unique constraints.
|
|
184
|
-
* Each entry can be a single field name or an array for composite unique indexes.
|
|
185
|
-
* Unique indexes are also queryable (no need to duplicate in `indexes`).
|
|
186
|
-
*/
|
|
187
|
-
uniqueIndexes?: Array<string | string[]>;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Plugin storage configuration
|
|
192
|
-
*/
|
|
193
|
-
export type PluginStorageConfig = Record<string, StorageCollectionConfig>;
|
|
58
|
+
//
|
|
59
|
+
// `StorageCollectionConfig` and `PluginStorageConfig` are re-exported above
|
|
60
|
+
// from `@emdash-cms/plugin-types`. The manifest carries these shapes
|
|
61
|
+
// verbatim; both this package (reader) and registry-cli (writer) agree on
|
|
62
|
+
// the same types via the shared package.
|
|
194
63
|
|
|
195
64
|
/**
|
|
196
65
|
* Query filter operators
|
|
@@ -1128,27 +997,14 @@ export interface PluginHooks {
|
|
|
1128
997
|
/**
|
|
1129
998
|
* Hook names
|
|
1130
999
|
*/
|
|
1131
|
-
export type HookName = keyof PluginHooks;
|
|
1132
|
-
|
|
1133
1000
|
/**
|
|
1134
|
-
* Hook
|
|
1135
|
-
*
|
|
1001
|
+
* Hook name in a manifest. Core's exhaustive union of recognised hook names,
|
|
1002
|
+
* derived from the `PluginHooks` registry. The serialised manifest carries
|
|
1003
|
+
* these as opaque strings; this stricter type is only used for type-checking
|
|
1004
|
+
* inside core. `ManifestHookEntry` is re-exported from
|
|
1005
|
+
* `@emdash-cms/plugin-types` near the top of this file.
|
|
1136
1006
|
*/
|
|
1137
|
-
export
|
|
1138
|
-
name: string;
|
|
1139
|
-
exclusive?: boolean;
|
|
1140
|
-
priority?: number;
|
|
1141
|
-
timeout?: number;
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
/**
|
|
1145
|
-
* Route metadata entry in a plugin manifest.
|
|
1146
|
-
* Replaces the plain route name string with structured metadata.
|
|
1147
|
-
*/
|
|
1148
|
-
export interface ManifestRouteEntry {
|
|
1149
|
-
name: string;
|
|
1150
|
-
public?: boolean;
|
|
1151
|
-
}
|
|
1007
|
+
export type HookName = keyof PluginHooks;
|
|
1152
1008
|
|
|
1153
1009
|
/**
|
|
1154
1010
|
* Resolved hook with normalized config
|
|
@@ -1543,8 +1399,16 @@ export interface PluginAdminExports {
|
|
|
1543
1399
|
// =============================================================================
|
|
1544
1400
|
|
|
1545
1401
|
/**
|
|
1546
|
-
* Plugin manifest
|
|
1547
|
-
*
|
|
1402
|
+
* Plugin manifest — the metadata portion of a plugin bundle, used for
|
|
1403
|
+
* sandboxed plugins loaded from the marketplace.
|
|
1404
|
+
*
|
|
1405
|
+
* This interface is core's stricter version of the manifest contract: it
|
|
1406
|
+
* uses the exhaustive `HookName` union and core's typed `PluginAdminConfig`.
|
|
1407
|
+
* The wire-shape lives in `@emdash-cms/plugin-types` as `PluginManifest`
|
|
1408
|
+
* with looser types (so the registry CLI can serialise hook names it
|
|
1409
|
+
* doesn't know about). Both must stay structurally compatible: every value
|
|
1410
|
+
* of this type must be assignable to the shared one. The static assertion
|
|
1411
|
+
* below catches any drift at compile time.
|
|
1548
1412
|
*/
|
|
1549
1413
|
export interface PluginManifest {
|
|
1550
1414
|
id: string;
|
|
@@ -1558,3 +1422,29 @@ export interface PluginManifest {
|
|
|
1558
1422
|
routes: Array<ManifestRouteEntry | string>;
|
|
1559
1423
|
admin: PluginAdminConfig;
|
|
1560
1424
|
}
|
|
1425
|
+
|
|
1426
|
+
// Type-level guard: core's `PluginManifest` is intentionally a SUBTYPE of
|
|
1427
|
+
// the shared wire shape (`@emdash-cms/plugin-types` `PluginManifest`). The
|
|
1428
|
+
// wire shape uses looser types like `string` for hook names so the registry
|
|
1429
|
+
// CLI can serialise plugins targeting hook versions this core doesn't yet
|
|
1430
|
+
// know about. Core narrows `string` to `HookName` and `Record<string,
|
|
1431
|
+
// unknown>` to `PluginAdminConfig` because core's loader actually executes
|
|
1432
|
+
// against those types.
|
|
1433
|
+
//
|
|
1434
|
+
// We assert one direction at compile time: `core extends shared`. The
|
|
1435
|
+
// reverse direction (`shared extends core`) intentionally does NOT hold
|
|
1436
|
+
// because shared is wider -- a manifest written against the wire shape
|
|
1437
|
+
// could carry a hook name core doesn't know. That runtime narrowing is the
|
|
1438
|
+
// job of `manifest-schema.ts` (zod-validated, called at every JSON.parse
|
|
1439
|
+
// of a manifest.json), not of the type system. The static check below
|
|
1440
|
+
// catches the OTHER failure mode: core adding a required field or
|
|
1441
|
+
// non-assignable type that the wire shape doesn't allow.
|
|
1442
|
+
//
|
|
1443
|
+
// `type X = never` is itself legal as a type alias, so the assertion has to
|
|
1444
|
+
// be in a value position (`const _check: T = true`) for the compiler to
|
|
1445
|
+
// error when T resolves to `never`. Don't replace this with a bare type
|
|
1446
|
+
// alias.
|
|
1447
|
+
type _AssertManifestCompat =
|
|
1448
|
+
PluginManifest extends import("@emdash-cms/plugin-types").PluginManifest ? true : never;
|
|
1449
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1450
|
+
const _MANIFEST_COMPAT: _AssertManifestCompat = true;
|
package/src/request-cache.ts
CHANGED
|
@@ -48,8 +48,12 @@ export function requestCached<T>(key: string, fn: () => Promise<T>): Promise<T>
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
const existing = cache.get(key);
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
if (existing) {
|
|
52
|
+
if (ctx.metrics) ctx.metrics.cacheHits += 1;
|
|
53
|
+
// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- heterogeneous cache; key namespacing guarantees the stored promise resolves to T
|
|
54
|
+
return existing as Promise<T>;
|
|
55
|
+
}
|
|
56
|
+
if (ctx.metrics) ctx.metrics.cacheMisses += 1;
|
|
53
57
|
|
|
54
58
|
const promise = Promise.resolve()
|
|
55
59
|
.then(fn)
|
package/src/request-context.ts
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
* without requiring explicit parameter passing. The middleware wraps next()
|
|
6
6
|
* in als.run(), making the context available to all code during rendering.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Middleware always wraps each request in a context so per-request
|
|
9
|
+
* metrics (db.*, cache.*) can be surfaced via Server-Timing. The cost is
|
|
10
|
+
* one ALS frame per request — sub-microsecond, negligible compared to
|
|
11
|
+
* any real work.
|
|
10
12
|
*
|
|
11
13
|
* The AsyncLocalStorage instance is stored on globalThis with a Symbol key
|
|
12
14
|
* to guarantee a singleton even when bundlers duplicate this module across
|
|
@@ -19,6 +21,38 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
19
21
|
|
|
20
22
|
import type { QueryRecorder } from "./database/instrumentation.js";
|
|
21
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Lightweight always-on counters surfaced in Server-Timing.
|
|
26
|
+
*
|
|
27
|
+
* Bumped by the Kysely log hook (db queries) and by `requestCached`
|
|
28
|
+
* (cache hits/misses). Read by middleware after the response is
|
|
29
|
+
* generated to emit `db.*` and `cache.*` Server-Timing fields.
|
|
30
|
+
*
|
|
31
|
+
* Offsets are milliseconds from `start` (the request's entry into
|
|
32
|
+
* middleware), captured via `performance.now()`.
|
|
33
|
+
*/
|
|
34
|
+
export interface RequestMetrics {
|
|
35
|
+
start: number;
|
|
36
|
+
dbCount: number;
|
|
37
|
+
dbTotalMs: number;
|
|
38
|
+
dbFirstOffset: number | null;
|
|
39
|
+
dbLastOffset: number | null;
|
|
40
|
+
cacheHits: number;
|
|
41
|
+
cacheMisses: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function createRequestMetrics(start: number): RequestMetrics {
|
|
45
|
+
return {
|
|
46
|
+
start,
|
|
47
|
+
dbCount: 0,
|
|
48
|
+
dbTotalMs: 0,
|
|
49
|
+
dbFirstOffset: null,
|
|
50
|
+
dbLastOffset: null,
|
|
51
|
+
cacheHits: 0,
|
|
52
|
+
cacheMisses: 0,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
22
56
|
export interface EmDashRequestContext {
|
|
23
57
|
/** Whether the current request is in visual editing mode */
|
|
24
58
|
editMode: boolean;
|
|
@@ -54,6 +88,12 @@ export interface EmDashRequestContext {
|
|
|
54
88
|
* to NDJSON after the response.
|
|
55
89
|
*/
|
|
56
90
|
queryRecorder?: QueryRecorder;
|
|
91
|
+
/**
|
|
92
|
+
* Per-request metrics for Server-Timing. Always attached by middleware
|
|
93
|
+
* for requests that emit timing headers; bumped by the Kysely log hook
|
|
94
|
+
* and `requestCached`.
|
|
95
|
+
*/
|
|
96
|
+
metrics?: RequestMetrics;
|
|
57
97
|
}
|
|
58
98
|
|
|
59
99
|
const ALS_KEY = Symbol.for("emdash:request-context");
|
package/src/schema/registry.ts
CHANGED
|
@@ -526,6 +526,10 @@ export class SchemaRegistry {
|
|
|
526
526
|
);
|
|
527
527
|
}
|
|
528
528
|
|
|
529
|
+
// `input.validation === undefined` means "no change" (keep existing);
|
|
530
|
+
// an explicit `null` clears the column.
|
|
531
|
+
const nextValidation = input.validation === undefined ? field.validation : input.validation;
|
|
532
|
+
|
|
529
533
|
return withTransaction(this.db, async (trx) => {
|
|
530
534
|
await trx
|
|
531
535
|
.updateTable("_emdash_fields")
|
|
@@ -550,11 +554,7 @@ export class SchemaRegistry {
|
|
|
550
554
|
: field.defaultValue !== undefined
|
|
551
555
|
? JSON.stringify(field.defaultValue)
|
|
552
556
|
: null,
|
|
553
|
-
validation:
|
|
554
|
-
? JSON.stringify(input.validation)
|
|
555
|
-
: field.validation
|
|
556
|
-
? JSON.stringify(field.validation)
|
|
557
|
-
: null,
|
|
557
|
+
validation: nextValidation ? JSON.stringify(nextValidation) : null,
|
|
558
558
|
widget: input.widget ?? field.widget ?? null,
|
|
559
559
|
options: input.options
|
|
560
560
|
? JSON.stringify(input.options)
|
package/src/schema/types.ts
CHANGED
|
@@ -131,6 +131,7 @@ export interface FieldValidation {
|
|
|
131
131
|
subFields?: RepeaterSubField[]; // For repeater fields
|
|
132
132
|
minItems?: number; // For repeater fields
|
|
133
133
|
maxItems?: number; // For repeater fields
|
|
134
|
+
allowedMimeTypes?: string[];
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
/**
|
|
@@ -238,7 +239,7 @@ export interface CreateFieldInput {
|
|
|
238
239
|
required?: boolean;
|
|
239
240
|
unique?: boolean;
|
|
240
241
|
defaultValue?: unknown;
|
|
241
|
-
validation?: FieldValidation;
|
|
242
|
+
validation?: FieldValidation | null;
|
|
242
243
|
widget?: string;
|
|
243
244
|
options?: FieldWidgetOptions;
|
|
244
245
|
sortOrder?: number;
|
|
@@ -256,7 +257,7 @@ export interface UpdateFieldInput {
|
|
|
256
257
|
required?: boolean;
|
|
257
258
|
unique?: boolean;
|
|
258
259
|
defaultValue?: unknown;
|
|
259
|
-
validation?: FieldValidation;
|
|
260
|
+
validation?: FieldValidation | null;
|
|
260
261
|
widget?: string;
|
|
261
262
|
options?: FieldWidgetOptions;
|
|
262
263
|
sortOrder?: number;
|
|
@@ -131,6 +131,12 @@ function getBaseSchema(type: FieldType, field: Field): ZodTypeAny {
|
|
|
131
131
|
alt: z.string().optional(),
|
|
132
132
|
width: z.number().optional(),
|
|
133
133
|
height: z.number().optional(),
|
|
134
|
+
/** Provider ID (e.g. "local", "cloudflare-images") */
|
|
135
|
+
provider: z.string().optional(),
|
|
136
|
+
/** Admin-side preview URL for external providers (not persisted by plugins) */
|
|
137
|
+
previewUrl: z.string().optional(),
|
|
138
|
+
/** Provider-specific metadata; for local media this carries storageKey */
|
|
139
|
+
meta: z.record(z.string(), z.unknown()).optional(),
|
|
134
140
|
});
|
|
135
141
|
|
|
136
142
|
case "file":
|
|
@@ -140,6 +146,10 @@ function getBaseSchema(type: FieldType, field: Field): ZodTypeAny {
|
|
|
140
146
|
filename: z.string().optional(),
|
|
141
147
|
mimeType: z.string().optional(),
|
|
142
148
|
size: z.number().optional(),
|
|
149
|
+
/** Provider ID (e.g. "local", "s3") */
|
|
150
|
+
provider: z.string().optional(),
|
|
151
|
+
/** Provider-specific metadata; for local media this carries storageKey */
|
|
152
|
+
meta: z.record(z.string(), z.unknown()).optional(),
|
|
143
153
|
});
|
|
144
154
|
|
|
145
155
|
case "reference":
|
|
@@ -384,10 +394,10 @@ function fieldTypeToTypeScript(field: Field): string {
|
|
|
384
394
|
return "PortableTextBlock[]";
|
|
385
395
|
|
|
386
396
|
case "image":
|
|
387
|
-
return "{ id: string; src?: string; alt?: string; width?: number; height?: number }";
|
|
397
|
+
return "{ id: string; src?: string; alt?: string; width?: number; height?: number; provider?: string; previewUrl?: string; meta?: Record<string, unknown> }";
|
|
388
398
|
|
|
389
399
|
case "file":
|
|
390
|
-
return "{ id: string; src?: string; filename?: string; mimeType?: string; size?: number }";
|
|
400
|
+
return "{ id: string; src?: string; filename?: string; mimeType?: string; size?: number; provider?: string; meta?: Record<string, unknown> }";
|
|
391
401
|
|
|
392
402
|
case "reference":
|
|
393
403
|
// Could be enhanced to include the referenced collection type
|