emdash 0.0.0-a → 0.0.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.
- package/README.md +87 -1
- package/dist/adapters-BLMa4JGD.d.mts +106 -0
- package/dist/adapters-BLMa4JGD.d.mts.map +1 -0
- package/dist/apply-Bjfq_b4-.mjs +1293 -0
- package/dist/apply-Bjfq_b4-.mjs.map +1 -0
- package/dist/astro/index.d.mts +51 -0
- package/dist/astro/index.d.mts.map +1 -0
- package/dist/astro/index.mjs +1333 -0
- package/dist/astro/index.mjs.map +1 -0
- package/dist/astro/middleware/auth.d.mts +31 -0
- package/dist/astro/middleware/auth.d.mts.map +1 -0
- package/dist/astro/middleware/auth.mjs +654 -0
- package/dist/astro/middleware/auth.mjs.map +1 -0
- package/dist/astro/middleware/redirect.d.mts +22 -0
- package/dist/astro/middleware/redirect.d.mts.map +1 -0
- package/dist/astro/middleware/redirect.mjs +63 -0
- package/dist/astro/middleware/redirect.mjs.map +1 -0
- package/dist/astro/middleware/request-context.d.mts +18 -0
- package/dist/astro/middleware/request-context.d.mts.map +1 -0
- package/dist/astro/middleware/request-context.mjs +1310 -0
- package/dist/astro/middleware/request-context.mjs.map +1 -0
- package/dist/astro/middleware/setup.d.mts +20 -0
- package/dist/astro/middleware/setup.d.mts.map +1 -0
- package/dist/astro/middleware/setup.mjs +47 -0
- package/dist/astro/middleware/setup.mjs.map +1 -0
- package/dist/astro/middleware.d.mts +13 -0
- package/dist/astro/middleware.d.mts.map +1 -0
- package/dist/astro/middleware.mjs +1613 -0
- package/dist/astro/middleware.mjs.map +1 -0
- package/dist/astro/types.d.mts +250 -0
- package/dist/astro/types.d.mts.map +1 -0
- package/dist/astro/types.mjs +1 -0
- package/dist/base64-MBPo9ozB.mjs +59 -0
- package/dist/base64-MBPo9ozB.mjs.map +1 -0
- package/dist/byline-CL847F26.mjs +213 -0
- package/dist/byline-CL847F26.mjs.map +1 -0
- package/dist/bylines-C2a-2TGt.mjs +136 -0
- package/dist/bylines-C2a-2TGt.mjs.map +1 -0
- package/dist/chunk-ClPoSABd.mjs +21 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.mjs +3909 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/client/cf-access.d.mts +60 -0
- package/dist/client/cf-access.d.mts.map +1 -0
- package/dist/client/cf-access.mjs +179 -0
- package/dist/client/cf-access.mjs.map +1 -0
- package/dist/client/index.d.mts +398 -0
- package/dist/client/index.d.mts.map +1 -0
- package/dist/client/index.mjs +346 -0
- package/dist/client/index.mjs.map +1 -0
- package/dist/config-CKE8p9xM.mjs +55 -0
- package/dist/config-CKE8p9xM.mjs.map +1 -0
- package/dist/connection-B4zVnQIa.mjs +40 -0
- package/dist/connection-B4zVnQIa.mjs.map +1 -0
- package/dist/content-D6C2WsZC.mjs +824 -0
- package/dist/content-D6C2WsZC.mjs.map +1 -0
- package/dist/db/index.d.mts +4 -0
- package/dist/db/index.mjs +62 -0
- package/dist/db/index.mjs.map +1 -0
- package/dist/db/libsql.d.mts +11 -0
- package/dist/db/libsql.d.mts.map +1 -0
- package/dist/db/libsql.mjs +17 -0
- package/dist/db/libsql.mjs.map +1 -0
- package/dist/db/postgres.d.mts +11 -0
- package/dist/db/postgres.d.mts.map +1 -0
- package/dist/db/postgres.mjs +30 -0
- package/dist/db/postgres.mjs.map +1 -0
- package/dist/db/sqlite.d.mts +11 -0
- package/dist/db/sqlite.d.mts.map +1 -0
- package/dist/db/sqlite.mjs +16 -0
- package/dist/db/sqlite.mjs.map +1 -0
- package/dist/default-Cyi4aAxu.mjs +81 -0
- package/dist/default-Cyi4aAxu.mjs.map +1 -0
- package/dist/dialect-helpers-B9uSp2GJ.mjs +90 -0
- package/dist/dialect-helpers-B9uSp2GJ.mjs.map +1 -0
- package/dist/error-Cxz0tQeO.mjs +27 -0
- package/dist/error-Cxz0tQeO.mjs.map +1 -0
- package/dist/index-C1xF3OGh.d.mts +4527 -0
- package/dist/index-C1xF3OGh.d.mts.map +1 -0
- package/dist/index.d.mts +16 -0
- package/dist/index.mjs +30 -0
- package/dist/load-yOOlckBj.mjs +28 -0
- package/dist/load-yOOlckBj.mjs.map +1 -0
- package/dist/loader-fz8Q_3EO.mjs +447 -0
- package/dist/loader-fz8Q_3EO.mjs.map +1 -0
- package/dist/manifest-schema-Dcl0R6nM.mjs +184 -0
- package/dist/manifest-schema-Dcl0R6nM.mjs.map +1 -0
- package/dist/media/index.d.mts +26 -0
- package/dist/media/index.d.mts.map +1 -0
- package/dist/media/index.mjs +55 -0
- package/dist/media/index.mjs.map +1 -0
- package/dist/media/local-runtime.d.mts +39 -0
- package/dist/media/local-runtime.d.mts.map +1 -0
- package/dist/media/local-runtime.mjs +133 -0
- package/dist/media/local-runtime.mjs.map +1 -0
- package/dist/media-DqHVh136.mjs +200 -0
- package/dist/media-DqHVh136.mjs.map +1 -0
- package/dist/mode-C2EzN1uE.mjs +23 -0
- package/dist/mode-C2EzN1uE.mjs.map +1 -0
- package/dist/page/index.d.mts +140 -0
- package/dist/page/index.d.mts.map +1 -0
- package/dist/page/index.mjs +416 -0
- package/dist/page/index.mjs.map +1 -0
- package/dist/placeholder-CmGAmqeO.d.mts +276 -0
- package/dist/placeholder-CmGAmqeO.d.mts.map +1 -0
- package/dist/placeholder-SmpOx-_v.mjs +243 -0
- package/dist/placeholder-SmpOx-_v.mjs.map +1 -0
- package/dist/plugin-utils.d.mts +58 -0
- package/dist/plugin-utils.d.mts.map +1 -0
- package/dist/plugin-utils.mjs +78 -0
- package/dist/plugin-utils.mjs.map +1 -0
- package/dist/plugins/adapt-sandbox-entry.d.mts +22 -0
- package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -0
- package/dist/plugins/adapt-sandbox-entry.mjs +113 -0
- package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -0
- package/dist/query-CS_iSj34.mjs +460 -0
- package/dist/query-CS_iSj34.mjs.map +1 -0
- package/dist/redirect-DIfIni3r.mjs +329 -0
- package/dist/redirect-DIfIni3r.mjs.map +1 -0
- package/dist/registry-D_w5HW4G.mjs +863 -0
- package/dist/registry-D_w5HW4G.mjs.map +1 -0
- package/dist/request-context.d.mts +49 -0
- package/dist/request-context.d.mts.map +1 -0
- package/dist/request-context.mjs +43 -0
- package/dist/request-context.mjs.map +1 -0
- package/dist/runner-B-u2F2b6.mjs +1412 -0
- package/dist/runner-B-u2F2b6.mjs.map +1 -0
- package/dist/runner-EAtf0ZIe.d.mts +27 -0
- package/dist/runner-EAtf0ZIe.d.mts.map +1 -0
- package/dist/runtime.d.mts +26 -0
- package/dist/runtime.d.mts.map +1 -0
- package/dist/runtime.mjs +42 -0
- package/dist/runtime.mjs.map +1 -0
- package/dist/search-DG603UrT.mjs +9211 -0
- package/dist/search-DG603UrT.mjs.map +1 -0
- package/dist/seed/index.d.mts +3 -0
- package/dist/seed/index.mjs +15 -0
- package/dist/seo/index.d.mts +70 -0
- package/dist/seo/index.d.mts.map +1 -0
- package/dist/seo/index.mjs +70 -0
- package/dist/seo/index.mjs.map +1 -0
- package/dist/storage/local.d.mts +39 -0
- package/dist/storage/local.d.mts.map +1 -0
- package/dist/storage/local.mjs +166 -0
- package/dist/storage/local.mjs.map +1 -0
- package/dist/storage/s3.d.mts +32 -0
- package/dist/storage/s3.d.mts.map +1 -0
- package/dist/storage/s3.mjs +175 -0
- package/dist/storage/s3.mjs.map +1 -0
- package/dist/tokens-DpgrkrXK.mjs +171 -0
- package/dist/tokens-DpgrkrXK.mjs.map +1 -0
- package/dist/transport-BFGblqwG.d.mts +42 -0
- package/dist/transport-BFGblqwG.d.mts.map +1 -0
- package/dist/transport-yxiQsi8I.mjs +418 -0
- package/dist/transport-yxiQsi8I.mjs.map +1 -0
- package/dist/types-BRuPJGdV.d.mts +102 -0
- package/dist/types-BRuPJGdV.d.mts.map +1 -0
- package/dist/types-C4-fAxN3.d.mts +182 -0
- package/dist/types-C4-fAxN3.d.mts.map +1 -0
- package/dist/types-CMMN0pNg.mjs +31 -0
- package/dist/types-CMMN0pNg.mjs.map +1 -0
- package/dist/types-CUBbjgmP.mjs +16 -0
- package/dist/types-CUBbjgmP.mjs.map +1 -0
- package/dist/types-DRjfYOEv.d.mts +426 -0
- package/dist/types-DRjfYOEv.d.mts.map +1 -0
- package/dist/types-DY5zk5HN.mjs +73 -0
- package/dist/types-DY5zk5HN.mjs.map +1 -0
- package/dist/types-DaNLHo_T.d.mts +184 -0
- package/dist/types-DaNLHo_T.d.mts.map +1 -0
- package/dist/types-DvhsUmSJ.d.mts +1111 -0
- package/dist/types-DvhsUmSJ.d.mts.map +1 -0
- package/dist/validate-CpBtVMsD.d.mts +378 -0
- package/dist/validate-CpBtVMsD.d.mts.map +1 -0
- package/dist/validate-CqRJb_xU.mjs +97 -0
- package/dist/validate-CqRJb_xU.mjs.map +1 -0
- package/dist/validate-O7PWmlnq.mjs +328 -0
- package/dist/validate-O7PWmlnq.mjs.map +1 -0
- package/locals.d.ts +46 -0
- package/package.json +233 -19
- package/src/api/authorize.ts +63 -0
- package/src/api/csrf.ts +48 -0
- package/src/api/error.ts +99 -0
- package/src/api/errors.ts +445 -0
- package/src/api/escape.ts +9 -0
- package/src/api/handlers/api-tokens.ts +240 -0
- package/src/api/handlers/comments.ts +314 -0
- package/src/api/handlers/content.ts +1315 -0
- package/src/api/handlers/dashboard.ts +205 -0
- package/src/api/handlers/device-flow.ts +687 -0
- package/src/api/handlers/index.ts +163 -0
- package/src/api/handlers/manifest.ts +158 -0
- package/src/api/handlers/marketplace.ts +930 -0
- package/src/api/handlers/media.ts +207 -0
- package/src/api/handlers/menus.ts +493 -0
- package/src/api/handlers/oauth-authorization.ts +429 -0
- package/src/api/handlers/oauth-clients.ts +353 -0
- package/src/api/handlers/oauth-user-lookup.ts +39 -0
- package/src/api/handlers/plugins.ts +254 -0
- package/src/api/handlers/redirects.ts +360 -0
- package/src/api/handlers/revision.ts +145 -0
- package/src/api/handlers/schema.ts +534 -0
- package/src/api/handlers/sections.ts +289 -0
- package/src/api/handlers/seo.ts +115 -0
- package/src/api/handlers/settings.ts +49 -0
- package/src/api/handlers/snapshot.ts +350 -0
- package/src/api/handlers/taxonomies.ts +523 -0
- package/src/api/index.ts +6 -0
- package/src/api/openapi/document.ts +2368 -0
- package/src/api/openapi/index.ts +1 -0
- package/src/api/parse.ts +139 -0
- package/src/api/redirect.ts +14 -0
- package/src/api/rev.ts +67 -0
- package/src/api/schemas/auth.ts +112 -0
- package/src/api/schemas/bylines.ts +85 -0
- package/src/api/schemas/comments.ts +117 -0
- package/src/api/schemas/common.ts +89 -0
- package/src/api/schemas/content.ts +191 -0
- package/src/api/schemas/import.ts +52 -0
- package/src/api/schemas/index.ts +17 -0
- package/src/api/schemas/media.ts +116 -0
- package/src/api/schemas/menus.ts +111 -0
- package/src/api/schemas/redirects.ts +155 -0
- package/src/api/schemas/schema.ts +203 -0
- package/src/api/schemas/search.ts +63 -0
- package/src/api/schemas/sections.ts +67 -0
- package/src/api/schemas/settings.ts +63 -0
- package/src/api/schemas/setup.ts +37 -0
- package/src/api/schemas/taxonomies.ts +113 -0
- package/src/api/schemas/users.ts +96 -0
- package/src/api/schemas/widgets.ts +80 -0
- package/src/api/site-url.ts +25 -0
- package/src/api/types.ts +82 -0
- package/src/astro/index.ts +27 -0
- package/src/astro/integration/index.ts +303 -0
- package/src/astro/integration/routes.ts +834 -0
- package/src/astro/integration/runtime.ts +338 -0
- package/src/astro/integration/virtual-modules.ts +469 -0
- package/src/astro/integration/vite-config.ts +328 -0
- package/src/astro/middleware/auth.ts +743 -0
- package/src/astro/middleware/redirect.ts +89 -0
- package/src/astro/middleware/request-context.ts +129 -0
- package/src/astro/middleware/setup.ts +89 -0
- package/src/astro/middleware.ts +398 -0
- package/src/astro/routes/PluginRegistry.tsx +15 -0
- package/src/astro/routes/admin.astro +81 -0
- package/src/astro/routes/api/admin/allowed-domains/[domain].ts +112 -0
- package/src/astro/routes/api/admin/allowed-domains/index.ts +108 -0
- package/src/astro/routes/api/admin/api-tokens/[id].ts +40 -0
- package/src/astro/routes/api/admin/api-tokens/index.ts +68 -0
- package/src/astro/routes/api/admin/bylines/[id]/index.ts +87 -0
- package/src/astro/routes/api/admin/bylines/index.ts +72 -0
- package/src/astro/routes/api/admin/comments/[id]/status.ts +120 -0
- package/src/astro/routes/api/admin/comments/[id].ts +64 -0
- package/src/astro/routes/api/admin/comments/bulk.ts +42 -0
- package/src/astro/routes/api/admin/comments/counts.ts +30 -0
- package/src/astro/routes/api/admin/comments/index.ts +46 -0
- package/src/astro/routes/api/admin/hooks/exclusive/[hookName].ts +91 -0
- package/src/astro/routes/api/admin/hooks/exclusive/index.ts +51 -0
- package/src/astro/routes/api/admin/oauth-clients/[id].ts +110 -0
- package/src/astro/routes/api/admin/oauth-clients/index.ts +71 -0
- package/src/astro/routes/api/admin/plugins/[id]/disable.ts +39 -0
- package/src/astro/routes/api/admin/plugins/[id]/enable.ts +39 -0
- package/src/astro/routes/api/admin/plugins/[id]/index.ts +38 -0
- package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +48 -0
- package/src/astro/routes/api/admin/plugins/[id]/update.ts +59 -0
- package/src/astro/routes/api/admin/plugins/index.ts +32 -0
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/icon.ts +61 -0
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/index.ts +33 -0
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +62 -0
- package/src/astro/routes/api/admin/plugins/marketplace/index.ts +38 -0
- package/src/astro/routes/api/admin/plugins/updates.ts +28 -0
- package/src/astro/routes/api/admin/themes/marketplace/[id]/index.ts +33 -0
- package/src/astro/routes/api/admin/themes/marketplace/[id]/thumbnail.ts +61 -0
- package/src/astro/routes/api/admin/themes/marketplace/index.ts +45 -0
- package/src/astro/routes/api/admin/users/[id]/disable.ts +69 -0
- package/src/astro/routes/api/admin/users/[id]/enable.ts +48 -0
- package/src/astro/routes/api/admin/users/[id]/index.ts +146 -0
- package/src/astro/routes/api/admin/users/[id]/send-recovery.ts +72 -0
- package/src/astro/routes/api/admin/users/index.ts +66 -0
- package/src/astro/routes/api/auth/dev-bypass.ts +139 -0
- package/src/astro/routes/api/auth/invite/accept.ts +52 -0
- package/src/astro/routes/api/auth/invite/complete.ts +84 -0
- package/src/astro/routes/api/auth/invite/index.ts +99 -0
- package/src/astro/routes/api/auth/logout.ts +40 -0
- package/src/astro/routes/api/auth/magic-link/send.ts +89 -0
- package/src/astro/routes/api/auth/magic-link/verify.ts +71 -0
- package/src/astro/routes/api/auth/me.ts +60 -0
- package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +219 -0
- package/src/astro/routes/api/auth/oauth/[provider].ts +119 -0
- package/src/astro/routes/api/auth/passkey/[id].ts +124 -0
- package/src/astro/routes/api/auth/passkey/index.ts +54 -0
- package/src/astro/routes/api/auth/passkey/options.ts +82 -0
- package/src/astro/routes/api/auth/passkey/register/options.ts +86 -0
- package/src/astro/routes/api/auth/passkey/register/verify.ts +117 -0
- package/src/astro/routes/api/auth/passkey/verify.ts +66 -0
- package/src/astro/routes/api/auth/signup/complete.ts +85 -0
- package/src/astro/routes/api/auth/signup/request.ts +77 -0
- package/src/astro/routes/api/auth/signup/verify.ts +53 -0
- package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +312 -0
- package/src/astro/routes/api/content/[collection]/[id]/compare.ts +28 -0
- package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +54 -0
- package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +61 -0
- package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +33 -0
- package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +107 -0
- package/src/astro/routes/api/content/[collection]/[id]/publish.ts +56 -0
- package/src/astro/routes/api/content/[collection]/[id]/restore.ts +54 -0
- package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +31 -0
- package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +105 -0
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +140 -0
- package/src/astro/routes/api/content/[collection]/[id]/translations.ts +30 -0
- package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +56 -0
- package/src/astro/routes/api/content/[collection]/[id].ts +137 -0
- package/src/astro/routes/api/content/[collection]/index.ts +59 -0
- package/src/astro/routes/api/content/[collection]/trash.ts +33 -0
- package/src/astro/routes/api/dashboard.ts +32 -0
- package/src/astro/routes/api/dev/emails.ts +36 -0
- package/src/astro/routes/api/import/probe.ts +47 -0
- package/src/astro/routes/api/import/wordpress/analyze.ts +510 -0
- package/src/astro/routes/api/import/wordpress/execute.ts +283 -0
- package/src/astro/routes/api/import/wordpress/media.ts +338 -0
- package/src/astro/routes/api/import/wordpress/prepare.ts +181 -0
- package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +393 -0
- package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +111 -0
- package/src/astro/routes/api/import/wordpress-plugin/callback.ts +58 -0
- package/src/astro/routes/api/import/wordpress-plugin/execute.ts +347 -0
- package/src/astro/routes/api/manifest.ts +62 -0
- package/src/astro/routes/api/mcp.ts +124 -0
- package/src/astro/routes/api/media/[id]/confirm.ts +93 -0
- package/src/astro/routes/api/media/[id].ts +145 -0
- package/src/astro/routes/api/media/file/[key].ts +79 -0
- package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +86 -0
- package/src/astro/routes/api/media/providers/[providerId]/index.ts +111 -0
- package/src/astro/routes/api/media/providers/index.ts +30 -0
- package/src/astro/routes/api/media/upload-url.ts +137 -0
- package/src/astro/routes/api/media.ts +190 -0
- package/src/astro/routes/api/menus/[name]/items.ts +87 -0
- package/src/astro/routes/api/menus/[name]/reorder.ts +33 -0
- package/src/astro/routes/api/menus/[name].ts +65 -0
- package/src/astro/routes/api/menus/index.ts +47 -0
- package/src/astro/routes/api/oauth/authorize.ts +412 -0
- package/src/astro/routes/api/oauth/device/authorize.ts +45 -0
- package/src/astro/routes/api/oauth/device/code.ts +51 -0
- package/src/astro/routes/api/oauth/device/token.ts +69 -0
- package/src/astro/routes/api/oauth/token/refresh.ts +38 -0
- package/src/astro/routes/api/oauth/token/revoke.ts +38 -0
- package/src/astro/routes/api/oauth/token.ts +184 -0
- package/src/astro/routes/api/openapi.json.ts +32 -0
- package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +92 -0
- package/src/astro/routes/api/redirects/404s/index.ts +72 -0
- package/src/astro/routes/api/redirects/404s/summary.ts +33 -0
- package/src/astro/routes/api/redirects/[id].ts +84 -0
- package/src/astro/routes/api/redirects/index.ts +52 -0
- package/src/astro/routes/api/revisions/[revisionId]/index.ts +29 -0
- package/src/astro/routes/api/revisions/[revisionId]/restore.ts +62 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +76 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +52 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +32 -0
- package/src/astro/routes/api/schema/collections/[slug]/index.ts +80 -0
- package/src/astro/routes/api/schema/collections/index.ts +47 -0
- package/src/astro/routes/api/schema/index.ts +109 -0
- package/src/astro/routes/api/schema/orphans/[slug].ts +36 -0
- package/src/astro/routes/api/schema/orphans/index.ts +26 -0
- package/src/astro/routes/api/search/enable.ts +64 -0
- package/src/astro/routes/api/search/index.ts +55 -0
- package/src/astro/routes/api/search/rebuild.ts +72 -0
- package/src/astro/routes/api/search/stats.ts +35 -0
- package/src/astro/routes/api/search/suggest.ts +53 -0
- package/src/astro/routes/api/sections/[slug].ts +84 -0
- package/src/astro/routes/api/sections/index.ts +52 -0
- package/src/astro/routes/api/settings/email.ts +150 -0
- package/src/astro/routes/api/settings.ts +67 -0
- package/src/astro/routes/api/setup/admin-verify.ts +100 -0
- package/src/astro/routes/api/setup/admin.ts +94 -0
- package/src/astro/routes/api/setup/dev-bypass.ts +199 -0
- package/src/astro/routes/api/setup/dev-reset.ts +40 -0
- package/src/astro/routes/api/setup/index.ts +126 -0
- package/src/astro/routes/api/setup/status.ts +122 -0
- package/src/astro/routes/api/snapshot.ts +75 -0
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +95 -0
- package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +69 -0
- package/src/astro/routes/api/taxonomies/index.ts +59 -0
- package/src/astro/routes/api/themes/preview.ts +77 -0
- package/src/astro/routes/api/typegen.ts +114 -0
- package/src/astro/routes/api/well-known/auth.ts +68 -0
- package/src/astro/routes/api/well-known/oauth-authorization-server.ts +44 -0
- package/src/astro/routes/api/well-known/oauth-protected-resource.ts +37 -0
- package/src/astro/routes/api/widget-areas/[name]/reorder.ts +72 -0
- package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +127 -0
- package/src/astro/routes/api/widget-areas/[name]/widgets.ts +80 -0
- package/src/astro/routes/api/widget-areas/[name].ts +87 -0
- package/src/astro/routes/api/widget-areas/index.ts +99 -0
- package/src/astro/routes/api/widget-components.ts +22 -0
- package/src/astro/routes/robots.txt.ts +77 -0
- package/src/astro/routes/sitemap.xml.ts +97 -0
- package/src/astro/storage/adapters.ts +74 -0
- package/src/astro/storage/index.ts +19 -0
- package/src/astro/storage/types.ts +60 -0
- package/src/astro/types.ts +346 -0
- package/src/auth/api-tokens.ts +25 -0
- package/src/auth/challenge-store.ts +80 -0
- package/src/auth/mode.ts +96 -0
- package/src/auth/oauth-state-store.ts +96 -0
- package/src/auth/passkey-config.ts +27 -0
- package/src/auth/rate-limit.ts +158 -0
- package/src/auth/scopes.ts +33 -0
- package/src/auth/types.ts +104 -0
- package/src/aws-sdk.d.ts +100 -0
- package/src/bylines/index.ts +237 -0
- package/src/cleanup.ts +153 -0
- package/src/cli/client-factory.ts +100 -0
- package/src/cli/commands/auth.ts +46 -0
- package/src/cli/commands/bundle-utils.ts +247 -0
- package/src/cli/commands/bundle.ts +609 -0
- package/src/cli/commands/content.ts +442 -0
- package/src/cli/commands/dev.ts +191 -0
- package/src/cli/commands/doctor.ts +211 -0
- package/src/cli/commands/export-seed.ts +630 -0
- package/src/cli/commands/import/wordpress.ts +1056 -0
- package/src/cli/commands/init.ts +192 -0
- package/src/cli/commands/login.ts +547 -0
- package/src/cli/commands/media.ts +165 -0
- package/src/cli/commands/menu.ts +67 -0
- package/src/cli/commands/plugin-init.ts +291 -0
- package/src/cli/commands/plugin-validate.ts +31 -0
- package/src/cli/commands/plugin.ts +33 -0
- package/src/cli/commands/publish.ts +699 -0
- package/src/cli/commands/schema.ts +233 -0
- package/src/cli/commands/search-cmd.ts +54 -0
- package/src/cli/commands/seed.ts +288 -0
- package/src/cli/commands/taxonomy.ts +128 -0
- package/src/cli/commands/types.ts +68 -0
- package/src/cli/credentials.ts +236 -0
- package/src/cli/index.ts +70 -0
- package/src/cli/output.ts +75 -0
- package/src/cli/wxr/parser.ts +969 -0
- package/src/client/cf-access.ts +193 -0
- package/src/client/index.ts +854 -0
- package/src/client/portable-text.ts +413 -0
- package/src/client/transport.ts +200 -0
- package/src/comments/moderator.ts +46 -0
- package/src/comments/notifications.ts +144 -0
- package/src/comments/query.ts +105 -0
- package/src/comments/service.ts +213 -0
- package/src/components/Break.astro +45 -0
- package/src/components/Button.astro +71 -0
- package/src/components/Buttons.astro +49 -0
- package/src/components/Code.astro +59 -0
- package/src/components/Columns.astro +59 -0
- package/src/components/CommentForm.astro +315 -0
- package/src/components/Comments.astro +232 -0
- package/src/components/Cover.astro +128 -0
- package/src/components/EmDashBodyEnd.astro +32 -0
- package/src/components/EmDashBodyStart.astro +32 -0
- package/src/components/EmDashHead.astro +53 -0
- package/src/components/EmDashImage.astro +178 -0
- package/src/components/EmDashMedia.astro +167 -0
- package/src/components/Embed.astro +128 -0
- package/src/components/File.astro +122 -0
- package/src/components/Gallery.astro +93 -0
- package/src/components/HtmlBlock.astro +33 -0
- package/src/components/Image.astro +178 -0
- package/src/components/InlineEditor.astro +27 -0
- package/src/components/InlinePortableTextEditor.tsx +1905 -0
- package/src/components/LiveSearch.astro +614 -0
- package/src/components/PortableText.astro +51 -0
- package/src/components/Pullquote.astro +51 -0
- package/src/components/Table.astro +108 -0
- package/src/components/WidgetArea.astro +22 -0
- package/src/components/WidgetRenderer.astro +72 -0
- package/src/components/index.ts +116 -0
- package/src/components/marks/Link.astro +31 -0
- package/src/components/marks/StrikeThrough.astro +7 -0
- package/src/components/marks/Subscript.astro +7 -0
- package/src/components/marks/Superscript.astro +7 -0
- package/src/components/marks/Underline.astro +7 -0
- package/src/components/widgets/Archives.astro +65 -0
- package/src/components/widgets/Categories.astro +35 -0
- package/src/components/widgets/RecentPosts.astro +51 -0
- package/src/components/widgets/Search.astro +18 -0
- package/src/components/widgets/Tags.astro +38 -0
- package/src/content/converters/index.ts +9 -0
- package/src/content/converters/portable-text-to-prosemirror.ts +385 -0
- package/src/content/converters/prosemirror-to-portable-text.ts +413 -0
- package/src/content/converters/types.ts +120 -0
- package/src/content/index.ts +5 -0
- package/src/database/connection.ts +67 -0
- package/src/database/dialect-helpers.ts +138 -0
- package/src/database/index.ts +5 -0
- package/src/database/migrations/001_initial.ts +136 -0
- package/src/database/migrations/002_media_status.ts +26 -0
- package/src/database/migrations/003_schema_registry.ts +79 -0
- package/src/database/migrations/004_plugins.ts +62 -0
- package/src/database/migrations/005_menus.ts +67 -0
- package/src/database/migrations/006_taxonomy_defs.ts +51 -0
- package/src/database/migrations/007_widgets.ts +42 -0
- package/src/database/migrations/008_auth.ts +194 -0
- package/src/database/migrations/009_user_disabled.ts +27 -0
- package/src/database/migrations/011_sections.ts +65 -0
- package/src/database/migrations/012_search.ts +25 -0
- package/src/database/migrations/013_scheduled_publishing.ts +51 -0
- package/src/database/migrations/014_draft_revisions.ts +72 -0
- package/src/database/migrations/015_indexes.ts +82 -0
- package/src/database/migrations/016_api_tokens.ts +89 -0
- package/src/database/migrations/017_authorization_codes.ts +45 -0
- package/src/database/migrations/018_seo.ts +56 -0
- package/src/database/migrations/019_i18n.ts +618 -0
- package/src/database/migrations/020_collection_url_pattern.ts +23 -0
- package/src/database/migrations/021_remove_section_categories.ts +43 -0
- package/src/database/migrations/022_marketplace_plugin_state.ts +46 -0
- package/src/database/migrations/023_plugin_metadata.ts +33 -0
- package/src/database/migrations/024_media_placeholders.ts +32 -0
- package/src/database/migrations/025_oauth_clients.ts +28 -0
- package/src/database/migrations/026_cron_tasks.ts +49 -0
- package/src/database/migrations/027_comments.ts +87 -0
- package/src/database/migrations/028_drop_author_url.ts +9 -0
- package/src/database/migrations/029_redirects.ts +67 -0
- package/src/database/migrations/030_widen_scheduled_index.ts +48 -0
- package/src/database/migrations/031_bylines.ts +90 -0
- package/src/database/migrations/032_rate_limits.ts +42 -0
- package/src/database/migrations/runner.ts +170 -0
- package/src/database/repositories/audit.ts +294 -0
- package/src/database/repositories/byline.ts +387 -0
- package/src/database/repositories/comment.ts +458 -0
- package/src/database/repositories/content.ts +1144 -0
- package/src/database/repositories/index.ts +30 -0
- package/src/database/repositories/media.ts +347 -0
- package/src/database/repositories/options.ts +150 -0
- package/src/database/repositories/plugin-storage.ts +373 -0
- package/src/database/repositories/redirect.ts +480 -0
- package/src/database/repositories/revision.ts +200 -0
- package/src/database/repositories/seo.ts +176 -0
- package/src/database/repositories/taxonomy.ts +294 -0
- package/src/database/repositories/types.ts +132 -0
- package/src/database/repositories/user.ts +258 -0
- package/src/database/transaction.ts +54 -0
- package/src/database/types.ts +501 -0
- package/src/database/validate.ts +138 -0
- package/src/db/adapters.ts +125 -0
- package/src/db/index.ts +37 -0
- package/src/db/libsql.ts +23 -0
- package/src/db/postgres.ts +30 -0
- package/src/db/sqlite.ts +27 -0
- package/src/emdash-runtime.ts +2096 -0
- package/src/fields/boolean.ts +34 -0
- package/src/fields/datetime.ts +44 -0
- package/src/fields/file.ts +41 -0
- package/src/fields/image.ts +34 -0
- package/src/fields/index.ts +42 -0
- package/src/fields/integer.ts +50 -0
- package/src/fields/json.ts +37 -0
- package/src/fields/multiselect.ts +48 -0
- package/src/fields/number.ts +52 -0
- package/src/fields/portable-text.ts +33 -0
- package/src/fields/reference.ts +29 -0
- package/src/fields/richtext.ts +31 -0
- package/src/fields/select.ts +46 -0
- package/src/fields/slug.ts +38 -0
- package/src/fields/text.ts +55 -0
- package/src/fields/textarea.ts +52 -0
- package/src/fields/types.ts +64 -0
- package/src/i18n/config.ts +68 -0
- package/src/import/index.ts +90 -0
- package/src/import/menus.ts +436 -0
- package/src/import/registry.ts +111 -0
- package/src/import/sections.ts +103 -0
- package/src/import/settings.ts +281 -0
- package/src/import/sources/wordpress-plugin.ts +641 -0
- package/src/import/sources/wordpress-rest.ts +191 -0
- package/src/import/sources/wxr.ts +330 -0
- package/src/import/ssrf.ts +260 -0
- package/src/import/types.ts +418 -0
- package/src/import/utils.ts +412 -0
- package/src/index.ts +481 -0
- package/src/loader.ts +770 -0
- package/src/mcp/server.ts +1463 -0
- package/src/media/index.ts +32 -0
- package/src/media/local-runtime.ts +213 -0
- package/src/media/local.ts +46 -0
- package/src/media/normalize.ts +190 -0
- package/src/media/placeholder.ts +150 -0
- package/src/media/provider-loader.ts +78 -0
- package/src/media/types.ts +279 -0
- package/src/menus/index.ts +324 -0
- package/src/menus/types.ts +112 -0
- package/src/page/context.ts +93 -0
- package/src/page/fragments.ts +89 -0
- package/src/page/index.ts +58 -0
- package/src/page/jsonld.ts +94 -0
- package/src/page/metadata.ts +185 -0
- package/src/page/seo-contributions.ts +136 -0
- package/src/plugin-utils.ts +80 -0
- package/src/plugins/adapt-sandbox-entry.ts +207 -0
- package/src/plugins/context.ts +833 -0
- package/src/plugins/cron.ts +361 -0
- package/src/plugins/define-plugin.ts +259 -0
- package/src/plugins/email-console.ts +73 -0
- package/src/plugins/email.ts +209 -0
- package/src/plugins/hooks.ts +1273 -0
- package/src/plugins/index.ts +193 -0
- package/src/plugins/manager.ts +595 -0
- package/src/plugins/manifest-schema.ts +230 -0
- package/src/plugins/marketplace.ts +460 -0
- package/src/plugins/request-meta.ts +139 -0
- package/src/plugins/routes.ts +302 -0
- package/src/plugins/sandbox/index.ts +18 -0
- package/src/plugins/sandbox/noop.ts +76 -0
- package/src/plugins/sandbox/types.ts +173 -0
- package/src/plugins/scheduler/node.ts +122 -0
- package/src/plugins/scheduler/piggyback.ts +71 -0
- package/src/plugins/scheduler/types.ts +27 -0
- package/src/plugins/state.ts +208 -0
- package/src/plugins/storage-indexes.ts +326 -0
- package/src/plugins/storage-query.ts +240 -0
- package/src/plugins/types.ts +1284 -0
- package/src/preview/helpers.ts +27 -0
- package/src/preview/index.ts +40 -0
- package/src/preview/tokens.ts +279 -0
- package/src/preview/urls.ts +118 -0
- package/src/query.ts +674 -0
- package/src/redirects/patterns.ts +224 -0
- package/src/request-context.ts +67 -0
- package/src/runtime.ts +21 -0
- package/src/schema/index.ts +29 -0
- package/src/schema/query.ts +44 -0
- package/src/schema/registry.ts +965 -0
- package/src/schema/types.ts +276 -0
- package/src/schema/zod-generator.ts +413 -0
- package/src/search/fts-manager.ts +452 -0
- package/src/search/index.ts +26 -0
- package/src/search/query.ts +396 -0
- package/src/search/text-extraction.ts +162 -0
- package/src/search/types.ts +114 -0
- package/src/sections/index.ts +226 -0
- package/src/sections/types.ts +86 -0
- package/src/seed/apply.ts +1141 -0
- package/src/seed/default.ts +86 -0
- package/src/seed/index.ts +28 -0
- package/src/seed/load.ts +35 -0
- package/src/seed/types.ts +341 -0
- package/src/seed/validate.ts +642 -0
- package/src/seo/index.ts +179 -0
- package/src/settings/index.ts +203 -0
- package/src/settings/types.ts +58 -0
- package/src/storage/index.ts +28 -0
- package/src/storage/local.ts +253 -0
- package/src/storage/s3.ts +271 -0
- package/src/storage/types.ts +204 -0
- package/src/taxonomies/index.ts +309 -0
- package/src/taxonomies/types.ts +61 -0
- package/src/ui.ts +75 -0
- package/src/utils/base64.ts +73 -0
- package/src/utils/hash.ts +36 -0
- package/src/utils/sanitize.ts +20 -0
- package/src/utils/slugify.ts +29 -0
- package/src/utils/url.ts +48 -0
- package/src/virtual-modules.d.ts +111 -0
- package/src/visual-editing/editable.ts +108 -0
- package/src/visual-editing/toolbar.ts +1229 -0
- package/src/widgets/components.ts +105 -0
- package/src/widgets/index.ts +131 -0
- package/src/widgets/types.ts +81 -0
|
@@ -0,0 +1,833 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Context v2
|
|
3
|
+
*
|
|
4
|
+
* Creates the unified context object provided to plugins in all hooks and routes.
|
|
5
|
+
*
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Kysely } from "kysely";
|
|
9
|
+
import { ulid } from "ulidx";
|
|
10
|
+
|
|
11
|
+
import { ContentRepository } from "../database/repositories/content.js";
|
|
12
|
+
import { MediaRepository } from "../database/repositories/media.js";
|
|
13
|
+
import { OptionsRepository } from "../database/repositories/options.js";
|
|
14
|
+
import { PluginStorageRepository } from "../database/repositories/plugin-storage.js";
|
|
15
|
+
import { UserRepository } from "../database/repositories/user.js";
|
|
16
|
+
import type { Database } from "../database/types.js";
|
|
17
|
+
import { validateExternalUrl, SsrfError, stripCredentialHeaders } from "../import/ssrf.js";
|
|
18
|
+
import type { Storage } from "../storage/types.js";
|
|
19
|
+
import { CronAccessImpl } from "./cron.js";
|
|
20
|
+
import type { EmailPipeline } from "./email.js";
|
|
21
|
+
import type {
|
|
22
|
+
ResolvedPlugin,
|
|
23
|
+
PluginContext,
|
|
24
|
+
PluginStorageConfig,
|
|
25
|
+
StorageCollection,
|
|
26
|
+
KVAccess,
|
|
27
|
+
CronAccess,
|
|
28
|
+
EmailAccess,
|
|
29
|
+
ContentAccess,
|
|
30
|
+
ContentAccessWithWrite,
|
|
31
|
+
MediaAccess,
|
|
32
|
+
MediaAccessWithWrite,
|
|
33
|
+
HttpAccess,
|
|
34
|
+
LogAccess,
|
|
35
|
+
SiteInfo,
|
|
36
|
+
UserAccess,
|
|
37
|
+
UserInfo,
|
|
38
|
+
ContentItem,
|
|
39
|
+
MediaItem,
|
|
40
|
+
PaginatedResult,
|
|
41
|
+
QueryOptions,
|
|
42
|
+
ContentListOptions,
|
|
43
|
+
MediaListOptions,
|
|
44
|
+
} from "./types.js";
|
|
45
|
+
|
|
46
|
+
// =============================================================================
|
|
47
|
+
// KV Access
|
|
48
|
+
// =============================================================================
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Create KV accessor for a plugin
|
|
52
|
+
* All keys are automatically prefixed with the plugin ID
|
|
53
|
+
*/
|
|
54
|
+
export function createKVAccess(optionsRepo: OptionsRepository, pluginId: string): KVAccess {
|
|
55
|
+
const prefix = `plugin:${pluginId}:`;
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
async get<T>(key: string): Promise<T | null> {
|
|
59
|
+
return optionsRepo.get<T>(`${prefix}${key}`);
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
async set(key: string, value: unknown): Promise<void> {
|
|
63
|
+
await optionsRepo.set(`${prefix}${key}`, value);
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
async delete(key: string): Promise<boolean> {
|
|
67
|
+
return optionsRepo.delete(`${prefix}${key}`);
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
async list(keyPrefix?: string): Promise<Array<{ key: string; value: unknown }>> {
|
|
71
|
+
const fullPrefix = `${prefix}${keyPrefix ?? ""}`;
|
|
72
|
+
const entriesMap = await optionsRepo.getByPrefix(fullPrefix);
|
|
73
|
+
const result: Array<{ key: string; value: unknown }> = [];
|
|
74
|
+
for (const [fullKey, value] of entriesMap) {
|
|
75
|
+
result.push({
|
|
76
|
+
key: fullKey.slice(prefix.length),
|
|
77
|
+
value,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// =============================================================================
|
|
86
|
+
// Storage Access
|
|
87
|
+
// =============================================================================
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Create storage collection accessor for a plugin
|
|
91
|
+
* Wraps PluginStorageRepository with the v2 interface (no async iterators)
|
|
92
|
+
*/
|
|
93
|
+
function createStorageCollection<T>(
|
|
94
|
+
db: Kysely<Database>,
|
|
95
|
+
pluginId: string,
|
|
96
|
+
collectionName: string,
|
|
97
|
+
indexes: Array<string | string[]>,
|
|
98
|
+
): StorageCollection<T> {
|
|
99
|
+
const repo = new PluginStorageRepository<T>(db, pluginId, collectionName, indexes);
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
get: (id) => repo.get(id),
|
|
103
|
+
put: (id, data) => repo.put(id, data),
|
|
104
|
+
delete: (id) => repo.delete(id),
|
|
105
|
+
exists: (id) => repo.exists(id),
|
|
106
|
+
getMany: (ids) => repo.getMany(ids),
|
|
107
|
+
putMany: (items) => repo.putMany(items),
|
|
108
|
+
deleteMany: (ids) => repo.deleteMany(ids),
|
|
109
|
+
count: (where) => repo.count(where),
|
|
110
|
+
|
|
111
|
+
// Query returns PaginatedResult instead of the old format
|
|
112
|
+
async query(options?: QueryOptions): Promise<PaginatedResult<{ id: string; data: T }>> {
|
|
113
|
+
const result = await repo.query({
|
|
114
|
+
where: options?.where,
|
|
115
|
+
orderBy: options?.orderBy,
|
|
116
|
+
limit: options?.limit,
|
|
117
|
+
cursor: options?.cursor,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
items: result.items,
|
|
122
|
+
cursor: result.cursor,
|
|
123
|
+
hasMore: result.hasMore,
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Create storage accessor with all declared collections
|
|
131
|
+
*/
|
|
132
|
+
export function createStorageAccess<T extends PluginStorageConfig>(
|
|
133
|
+
db: Kysely<Database>,
|
|
134
|
+
pluginId: string,
|
|
135
|
+
storageConfig: T,
|
|
136
|
+
): Record<string, StorageCollection> {
|
|
137
|
+
const storage: Record<string, StorageCollection> = {};
|
|
138
|
+
|
|
139
|
+
for (const [collectionName, config] of Object.entries(storageConfig)) {
|
|
140
|
+
const allIndexes = [...config.indexes, ...(config.uniqueIndexes ?? [])];
|
|
141
|
+
storage[collectionName] = createStorageCollection(db, pluginId, collectionName, allIndexes);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return storage;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// =============================================================================
|
|
148
|
+
// Content Access
|
|
149
|
+
// =============================================================================
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Create read-only content access
|
|
153
|
+
*/
|
|
154
|
+
export function createContentAccess(db: Kysely<Database>): ContentAccess {
|
|
155
|
+
const contentRepo = new ContentRepository(db);
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
async get(collection: string, id: string): Promise<ContentItem | null> {
|
|
159
|
+
const item = await contentRepo.findById(collection, id);
|
|
160
|
+
if (!item) return null;
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
id: item.id,
|
|
164
|
+
type: item.type,
|
|
165
|
+
data: item.data,
|
|
166
|
+
createdAt: item.createdAt,
|
|
167
|
+
updatedAt: item.updatedAt,
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
async list(
|
|
172
|
+
collection: string,
|
|
173
|
+
options?: ContentListOptions,
|
|
174
|
+
): Promise<PaginatedResult<ContentItem>> {
|
|
175
|
+
// Convert orderBy format if provided
|
|
176
|
+
let orderBy: { field: string; direction: "asc" | "desc" } | undefined;
|
|
177
|
+
if (options?.orderBy) {
|
|
178
|
+
const entries = Object.entries(options.orderBy);
|
|
179
|
+
const first = entries[0];
|
|
180
|
+
if (first) {
|
|
181
|
+
orderBy = { field: first[0], direction: first[1] };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const result = await contentRepo.findMany(collection, {
|
|
186
|
+
limit: options?.limit ?? 50,
|
|
187
|
+
cursor: options?.cursor,
|
|
188
|
+
orderBy,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
items: result.items.map((item) => ({
|
|
193
|
+
id: item.id,
|
|
194
|
+
type: item.type,
|
|
195
|
+
data: item.data,
|
|
196
|
+
createdAt: item.createdAt,
|
|
197
|
+
updatedAt: item.updatedAt,
|
|
198
|
+
})),
|
|
199
|
+
cursor: result.nextCursor,
|
|
200
|
+
hasMore: !!result.nextCursor,
|
|
201
|
+
};
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Create full content access with write operations
|
|
208
|
+
*/
|
|
209
|
+
export function createContentAccessWithWrite(db: Kysely<Database>): ContentAccessWithWrite {
|
|
210
|
+
const contentRepo = new ContentRepository(db);
|
|
211
|
+
const readAccess = createContentAccess(db);
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
...readAccess,
|
|
215
|
+
|
|
216
|
+
async create(collection: string, data: Record<string, unknown>): Promise<ContentItem> {
|
|
217
|
+
const item = await contentRepo.create({
|
|
218
|
+
type: collection,
|
|
219
|
+
data,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
id: item.id,
|
|
224
|
+
type: item.type,
|
|
225
|
+
data: item.data,
|
|
226
|
+
createdAt: item.createdAt,
|
|
227
|
+
updatedAt: item.updatedAt,
|
|
228
|
+
};
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
async update(
|
|
232
|
+
collection: string,
|
|
233
|
+
id: string,
|
|
234
|
+
data: Record<string, unknown>,
|
|
235
|
+
): Promise<ContentItem> {
|
|
236
|
+
const item = await contentRepo.update(collection, id, { data });
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
id: item.id,
|
|
240
|
+
type: item.type,
|
|
241
|
+
data: item.data,
|
|
242
|
+
createdAt: item.createdAt,
|
|
243
|
+
updatedAt: item.updatedAt,
|
|
244
|
+
};
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
async delete(collection: string, id: string): Promise<boolean> {
|
|
248
|
+
return contentRepo.delete(collection, id);
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// =============================================================================
|
|
254
|
+
// Media Access
|
|
255
|
+
// =============================================================================
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Create read-only media access
|
|
259
|
+
*/
|
|
260
|
+
export function createMediaAccess(db: Kysely<Database>): MediaAccess {
|
|
261
|
+
const mediaRepo = new MediaRepository(db);
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
async get(id: string): Promise<MediaItem | null> {
|
|
265
|
+
const item = await mediaRepo.findById(id);
|
|
266
|
+
if (!item) return null;
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
id: item.id,
|
|
270
|
+
filename: item.filename,
|
|
271
|
+
mimeType: item.mimeType,
|
|
272
|
+
size: item.size,
|
|
273
|
+
// Construct URL from storage key (or use a sensible default path)
|
|
274
|
+
url: `/media/${item.id}/${item.filename}`,
|
|
275
|
+
createdAt: item.createdAt,
|
|
276
|
+
};
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
async list(options?: MediaListOptions): Promise<PaginatedResult<MediaItem>> {
|
|
280
|
+
const result = await mediaRepo.findMany({
|
|
281
|
+
limit: options?.limit ?? 50,
|
|
282
|
+
cursor: options?.cursor,
|
|
283
|
+
mimeType: options?.mimeType,
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
items: result.items.map((item) => ({
|
|
288
|
+
id: item.id,
|
|
289
|
+
filename: item.filename,
|
|
290
|
+
mimeType: item.mimeType,
|
|
291
|
+
size: item.size,
|
|
292
|
+
url: `/media/${item.id}/${item.filename}`,
|
|
293
|
+
createdAt: item.createdAt,
|
|
294
|
+
})),
|
|
295
|
+
cursor: result.nextCursor,
|
|
296
|
+
hasMore: !!result.nextCursor,
|
|
297
|
+
};
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Create full media access with write operations.
|
|
304
|
+
* If storage is not provided, upload() will throw at call time.
|
|
305
|
+
*/
|
|
306
|
+
export function createMediaAccessWithWrite(
|
|
307
|
+
db: Kysely<Database>,
|
|
308
|
+
getUploadUrlFn: (
|
|
309
|
+
filename: string,
|
|
310
|
+
contentType: string,
|
|
311
|
+
) => Promise<{ uploadUrl: string; mediaId: string }>,
|
|
312
|
+
storage?: Storage,
|
|
313
|
+
): MediaAccessWithWrite {
|
|
314
|
+
const mediaRepo = new MediaRepository(db);
|
|
315
|
+
const readAccess = createMediaAccess(db);
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
...readAccess,
|
|
319
|
+
|
|
320
|
+
getUploadUrl: getUploadUrlFn,
|
|
321
|
+
|
|
322
|
+
async upload(
|
|
323
|
+
filename: string,
|
|
324
|
+
contentType: string,
|
|
325
|
+
bytes: ArrayBuffer,
|
|
326
|
+
): Promise<{ mediaId: string; storageKey: string; url: string }> {
|
|
327
|
+
if (!storage) {
|
|
328
|
+
throw new Error(
|
|
329
|
+
"Media upload() requires a storage backend. Configure storage in PluginContextFactoryOptions.",
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const mediaId = ulid();
|
|
334
|
+
// Extract extension from basename (ignore path separators)
|
|
335
|
+
const basename = filename.split("/").pop() ?? filename;
|
|
336
|
+
const dotIdx = basename.lastIndexOf(".");
|
|
337
|
+
const ext = dotIdx > 0 ? basename.slice(dotIdx).toLowerCase() : "";
|
|
338
|
+
const storageKey = `${mediaId}${ext}`;
|
|
339
|
+
|
|
340
|
+
// Upload to storage first
|
|
341
|
+
await storage.upload({
|
|
342
|
+
key: storageKey,
|
|
343
|
+
body: new Uint8Array(bytes),
|
|
344
|
+
contentType,
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// Create DB record — clean up storage on failure
|
|
348
|
+
try {
|
|
349
|
+
await mediaRepo.create({
|
|
350
|
+
filename: basename,
|
|
351
|
+
mimeType: contentType,
|
|
352
|
+
size: bytes.byteLength,
|
|
353
|
+
storageKey,
|
|
354
|
+
status: "ready",
|
|
355
|
+
});
|
|
356
|
+
} catch (error) {
|
|
357
|
+
try {
|
|
358
|
+
await storage.delete(storageKey);
|
|
359
|
+
} catch {
|
|
360
|
+
// Best-effort cleanup
|
|
361
|
+
}
|
|
362
|
+
throw error;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
mediaId,
|
|
367
|
+
storageKey,
|
|
368
|
+
url: `/_emdash/api/media/file/${storageKey}`,
|
|
369
|
+
};
|
|
370
|
+
},
|
|
371
|
+
|
|
372
|
+
async delete(id: string): Promise<boolean> {
|
|
373
|
+
return mediaRepo.delete(id);
|
|
374
|
+
},
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// =============================================================================
|
|
379
|
+
// HTTP Access
|
|
380
|
+
// =============================================================================
|
|
381
|
+
|
|
382
|
+
/** Maximum number of redirects to follow in plugin HTTP access */
|
|
383
|
+
const MAX_PLUGIN_REDIRECTS = 5;
|
|
384
|
+
|
|
385
|
+
function isHostAllowed(host: string, allowedHosts: string[]): boolean {
|
|
386
|
+
return allowedHosts.some((pattern) => {
|
|
387
|
+
if (pattern.startsWith("*.")) {
|
|
388
|
+
const suffix = pattern.slice(1); // ".example.com"
|
|
389
|
+
return host.endsWith(suffix) || host === pattern.slice(2);
|
|
390
|
+
}
|
|
391
|
+
return host === pattern;
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Create HTTP access with host validation.
|
|
397
|
+
*
|
|
398
|
+
* Uses redirect: "manual" to re-validate each redirect target against
|
|
399
|
+
* the allowedHosts list, preventing redirects to unauthorized hosts.
|
|
400
|
+
*/
|
|
401
|
+
export function createHttpAccess(pluginId: string, allowedHosts: string[]): HttpAccess {
|
|
402
|
+
return {
|
|
403
|
+
async fetch(url: string, init?: RequestInit): Promise<Response> {
|
|
404
|
+
// Deny by default — plugins must declare allowed hosts
|
|
405
|
+
if (allowedHosts.length === 0) {
|
|
406
|
+
throw new Error(
|
|
407
|
+
`Plugin "${pluginId}" has no allowed hosts configured. ` +
|
|
408
|
+
`Add hosts to the plugin's allowedHosts array to enable HTTP requests.`,
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
let currentUrl = url;
|
|
413
|
+
let currentInit = init;
|
|
414
|
+
|
|
415
|
+
for (let i = 0; i <= MAX_PLUGIN_REDIRECTS; i++) {
|
|
416
|
+
const hostname = new URL(currentUrl).hostname;
|
|
417
|
+
if (!isHostAllowed(hostname, allowedHosts)) {
|
|
418
|
+
throw new Error(
|
|
419
|
+
`Plugin "${pluginId}" is not allowed to fetch from host "${hostname}". ` +
|
|
420
|
+
`Allowed hosts: ${allowedHosts.join(", ")}`,
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const response = await globalThis.fetch(currentUrl, {
|
|
425
|
+
...currentInit,
|
|
426
|
+
redirect: "manual",
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
// Not a redirect -- return directly
|
|
430
|
+
if (response.status < 300 || response.status >= 400) {
|
|
431
|
+
return response;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Extract redirect target
|
|
435
|
+
const location = response.headers.get("Location");
|
|
436
|
+
if (!location) {
|
|
437
|
+
return response;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Resolve relative redirects; strip credentials on cross-origin hops
|
|
441
|
+
const previousOrigin = new URL(currentUrl).origin;
|
|
442
|
+
currentUrl = new URL(location, currentUrl).href;
|
|
443
|
+
const nextOrigin = new URL(currentUrl).origin;
|
|
444
|
+
|
|
445
|
+
if (previousOrigin !== nextOrigin && currentInit) {
|
|
446
|
+
currentInit = stripCredentialHeaders(currentInit);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
throw new Error(`Plugin "${pluginId}": too many redirects (max ${MAX_PLUGIN_REDIRECTS})`);
|
|
451
|
+
},
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Create unrestricted HTTP access (for plugins with network:fetch:any capability).
|
|
457
|
+
* No host validation, but applies SSRF protection on redirect targets to
|
|
458
|
+
* prevent plugins from being tricked into reaching internal services.
|
|
459
|
+
*/
|
|
460
|
+
export function createUnrestrictedHttpAccess(pluginId: string): HttpAccess {
|
|
461
|
+
return {
|
|
462
|
+
async fetch(url: string, init?: RequestInit): Promise<Response> {
|
|
463
|
+
let currentUrl = url;
|
|
464
|
+
let currentInit = init;
|
|
465
|
+
|
|
466
|
+
for (let i = 0; i <= MAX_PLUGIN_REDIRECTS; i++) {
|
|
467
|
+
// Validate each URL against SSRF rules (private IPs, metadata endpoints)
|
|
468
|
+
try {
|
|
469
|
+
validateExternalUrl(currentUrl);
|
|
470
|
+
} catch (e) {
|
|
471
|
+
const msg = e instanceof SsrfError ? e.message : "SSRF validation failed";
|
|
472
|
+
throw new Error(
|
|
473
|
+
`Plugin "${pluginId}": blocked fetch to "${new URL(currentUrl).hostname}": ${msg}`,
|
|
474
|
+
{ cause: e },
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const response = await globalThis.fetch(currentUrl, {
|
|
479
|
+
...currentInit,
|
|
480
|
+
redirect: "manual",
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// Not a redirect -- return directly
|
|
484
|
+
if (response.status < 300 || response.status >= 400) {
|
|
485
|
+
return response;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Extract redirect target
|
|
489
|
+
const location = response.headers.get("Location");
|
|
490
|
+
if (!location) {
|
|
491
|
+
return response;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Resolve relative redirects; strip credentials on cross-origin hops
|
|
495
|
+
const previousOrigin = new URL(currentUrl).origin;
|
|
496
|
+
currentUrl = new URL(location, currentUrl).href;
|
|
497
|
+
const nextOrigin = new URL(currentUrl).origin;
|
|
498
|
+
|
|
499
|
+
if (previousOrigin !== nextOrigin && currentInit) {
|
|
500
|
+
currentInit = stripCredentialHeaders(currentInit);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
throw new Error(`Plugin "${pluginId}": too many redirects (max ${MAX_PLUGIN_REDIRECTS})`);
|
|
505
|
+
},
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Create blocked HTTP access (for plugins without network:fetch capability)
|
|
511
|
+
*/
|
|
512
|
+
export function createBlockedHttpAccess(pluginId: string): HttpAccess {
|
|
513
|
+
return {
|
|
514
|
+
async fetch(): Promise<never> {
|
|
515
|
+
throw new Error(
|
|
516
|
+
`Plugin "${pluginId}" does not have the "network:fetch" capability. ` +
|
|
517
|
+
`Add "network:fetch" to the plugin's capabilities to enable HTTP requests.`,
|
|
518
|
+
);
|
|
519
|
+
},
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// =============================================================================
|
|
524
|
+
// Log Access
|
|
525
|
+
// =============================================================================
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Create logger for a plugin
|
|
529
|
+
*/
|
|
530
|
+
export function createLogAccess(pluginId: string): LogAccess {
|
|
531
|
+
const prefix = `[plugin:${pluginId}]`;
|
|
532
|
+
|
|
533
|
+
return {
|
|
534
|
+
debug(message: string, data?: unknown): void {
|
|
535
|
+
if (data !== undefined) {
|
|
536
|
+
console.debug(prefix, message, data);
|
|
537
|
+
} else {
|
|
538
|
+
console.debug(prefix, message);
|
|
539
|
+
}
|
|
540
|
+
},
|
|
541
|
+
|
|
542
|
+
info(message: string, data?: unknown): void {
|
|
543
|
+
if (data !== undefined) {
|
|
544
|
+
console.info(prefix, message, data);
|
|
545
|
+
} else {
|
|
546
|
+
console.info(prefix, message);
|
|
547
|
+
}
|
|
548
|
+
},
|
|
549
|
+
|
|
550
|
+
warn(message: string, data?: unknown): void {
|
|
551
|
+
if (data !== undefined) {
|
|
552
|
+
console.warn(prefix, message, data);
|
|
553
|
+
} else {
|
|
554
|
+
console.warn(prefix, message);
|
|
555
|
+
}
|
|
556
|
+
},
|
|
557
|
+
|
|
558
|
+
error(message: string, data?: unknown): void {
|
|
559
|
+
if (data !== undefined) {
|
|
560
|
+
console.error(prefix, message, data);
|
|
561
|
+
} else {
|
|
562
|
+
console.error(prefix, message);
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// =============================================================================
|
|
569
|
+
// Site Info
|
|
570
|
+
// =============================================================================
|
|
571
|
+
|
|
572
|
+
const TRAILING_SLASH_RE = /\/$/;
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Options for creating site info
|
|
576
|
+
*/
|
|
577
|
+
export interface SiteInfoOptions {
|
|
578
|
+
/** Site name from options table */
|
|
579
|
+
siteName?: string;
|
|
580
|
+
/** Site URL from options table or Astro config */
|
|
581
|
+
siteUrl?: string;
|
|
582
|
+
/** Site locale from options table */
|
|
583
|
+
locale?: string;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Create site info from config and settings.
|
|
588
|
+
*
|
|
589
|
+
* Resolution order for URL:
|
|
590
|
+
* 1. options table (emdash:site_url)
|
|
591
|
+
* 2. Astro `site` config
|
|
592
|
+
* 3. fallback to empty string
|
|
593
|
+
*/
|
|
594
|
+
export function createSiteInfo(options: SiteInfoOptions): SiteInfo {
|
|
595
|
+
return {
|
|
596
|
+
name: options.siteName ?? "",
|
|
597
|
+
url: (options.siteUrl ?? "").replace(TRAILING_SLASH_RE, ""), // strip trailing slash
|
|
598
|
+
locale: options.locale ?? "en",
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Create a URL helper that generates absolute URLs from relative paths.
|
|
604
|
+
* Validates that path starts with "/" and rejects protocol-relative paths ("//").
|
|
605
|
+
*/
|
|
606
|
+
export function createUrlHelper(siteUrl: string): (path: string) => string {
|
|
607
|
+
const base = siteUrl.replace(TRAILING_SLASH_RE, ""); // strip trailing slash
|
|
608
|
+
|
|
609
|
+
return (path: string): string => {
|
|
610
|
+
if (!path.startsWith("/")) {
|
|
611
|
+
throw new Error(`URL path must start with "/", got: "${path}"`);
|
|
612
|
+
}
|
|
613
|
+
if (path.startsWith("//")) {
|
|
614
|
+
throw new Error(`URL path must not be protocol-relative, got: "${path}"`);
|
|
615
|
+
}
|
|
616
|
+
return `${base}${path}`;
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// =============================================================================
|
|
621
|
+
// User Access
|
|
622
|
+
// =============================================================================
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Convert a UserRepository user to the plugin-facing UserInfo shape.
|
|
626
|
+
* Strips sensitive fields (avatarUrl, emailVerified, data).
|
|
627
|
+
*/
|
|
628
|
+
function toUserInfo(user: {
|
|
629
|
+
id: string;
|
|
630
|
+
email: string;
|
|
631
|
+
name: string | null;
|
|
632
|
+
role: number;
|
|
633
|
+
createdAt: string;
|
|
634
|
+
}): UserInfo {
|
|
635
|
+
return {
|
|
636
|
+
id: user.id,
|
|
637
|
+
email: user.email,
|
|
638
|
+
name: user.name,
|
|
639
|
+
role: user.role,
|
|
640
|
+
createdAt: user.createdAt,
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Create read-only user access for plugins.
|
|
646
|
+
* Excludes sensitive fields (password hashes, sessions, passkeys, avatar URL, data).
|
|
647
|
+
*/
|
|
648
|
+
export function createUserAccess(db: Kysely<Database>): UserAccess {
|
|
649
|
+
const userRepo = new UserRepository(db);
|
|
650
|
+
|
|
651
|
+
return {
|
|
652
|
+
async get(id: string): Promise<UserInfo | null> {
|
|
653
|
+
const user = await userRepo.findById(id);
|
|
654
|
+
if (!user) return null;
|
|
655
|
+
return toUserInfo(user);
|
|
656
|
+
},
|
|
657
|
+
|
|
658
|
+
async getByEmail(email: string): Promise<UserInfo | null> {
|
|
659
|
+
const user = await userRepo.findByEmail(email);
|
|
660
|
+
if (!user) return null;
|
|
661
|
+
return toUserInfo(user);
|
|
662
|
+
},
|
|
663
|
+
|
|
664
|
+
async list(opts?: {
|
|
665
|
+
role?: number;
|
|
666
|
+
limit?: number;
|
|
667
|
+
cursor?: string;
|
|
668
|
+
}): Promise<{ items: UserInfo[]; nextCursor?: string }> {
|
|
669
|
+
const result = await userRepo.findMany({
|
|
670
|
+
role: opts?.role as 10 | 20 | 30 | 40 | 50 | undefined,
|
|
671
|
+
cursor: opts?.cursor,
|
|
672
|
+
limit: opts?.limit,
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
return {
|
|
676
|
+
items: result.items.map(toUserInfo),
|
|
677
|
+
nextCursor: result.nextCursor,
|
|
678
|
+
};
|
|
679
|
+
},
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// =============================================================================
|
|
684
|
+
// Plugin Context Factory
|
|
685
|
+
// =============================================================================
|
|
686
|
+
|
|
687
|
+
export interface PluginContextFactoryOptions {
|
|
688
|
+
db: Kysely<Database>;
|
|
689
|
+
/**
|
|
690
|
+
* Storage backend for direct media uploads.
|
|
691
|
+
* If not provided, upload() will throw.
|
|
692
|
+
*/
|
|
693
|
+
storage?: Storage;
|
|
694
|
+
/**
|
|
695
|
+
* Function to generate upload URLs for media.
|
|
696
|
+
* If not provided, media write operations will throw.
|
|
697
|
+
*/
|
|
698
|
+
getUploadUrl?: (
|
|
699
|
+
filename: string,
|
|
700
|
+
contentType: string,
|
|
701
|
+
) => Promise<{ uploadUrl: string; mediaId: string }>;
|
|
702
|
+
/**
|
|
703
|
+
* Site information for ctx.site and ctx.url().
|
|
704
|
+
* If not provided, site info will have empty defaults.
|
|
705
|
+
*/
|
|
706
|
+
siteInfo?: SiteInfoOptions;
|
|
707
|
+
/**
|
|
708
|
+
* Callback to notify the cron scheduler that the next due time may have changed.
|
|
709
|
+
* If not provided, ctx.cron will not be available.
|
|
710
|
+
*/
|
|
711
|
+
cronReschedule?: () => void;
|
|
712
|
+
/**
|
|
713
|
+
* Email pipeline instance for ctx.email.
|
|
714
|
+
* If not provided (or no provider configured), ctx.email will be undefined.
|
|
715
|
+
*/
|
|
716
|
+
emailPipeline?: EmailPipeline;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Factory for creating plugin contexts
|
|
721
|
+
*/
|
|
722
|
+
export class PluginContextFactory {
|
|
723
|
+
private optionsRepo: OptionsRepository;
|
|
724
|
+
private db: Kysely<Database>;
|
|
725
|
+
private storage?: Storage;
|
|
726
|
+
private getUploadUrl?: (
|
|
727
|
+
filename: string,
|
|
728
|
+
contentType: string,
|
|
729
|
+
) => Promise<{ uploadUrl: string; mediaId: string }>;
|
|
730
|
+
private site: SiteInfo;
|
|
731
|
+
private urlHelper: (path: string) => string;
|
|
732
|
+
private cronReschedule?: () => void;
|
|
733
|
+
private emailPipeline?: EmailPipeline;
|
|
734
|
+
|
|
735
|
+
constructor(options: PluginContextFactoryOptions) {
|
|
736
|
+
this.db = options.db;
|
|
737
|
+
this.optionsRepo = new OptionsRepository(options.db);
|
|
738
|
+
this.storage = options.storage;
|
|
739
|
+
this.getUploadUrl = options.getUploadUrl;
|
|
740
|
+
this.site = createSiteInfo(options.siteInfo ?? {});
|
|
741
|
+
this.urlHelper = createUrlHelper(this.site.url);
|
|
742
|
+
this.cronReschedule = options.cronReschedule;
|
|
743
|
+
this.emailPipeline = options.emailPipeline;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* Create the unified plugin context
|
|
748
|
+
*/
|
|
749
|
+
createContext(plugin: ResolvedPlugin): PluginContext {
|
|
750
|
+
const capabilities = new Set(plugin.capabilities);
|
|
751
|
+
|
|
752
|
+
// Always available
|
|
753
|
+
const kv = createKVAccess(this.optionsRepo, plugin.id);
|
|
754
|
+
const log = createLogAccess(plugin.id);
|
|
755
|
+
const storage = createStorageAccess(this.db, plugin.id, plugin.storage);
|
|
756
|
+
|
|
757
|
+
// Capability-gated: content
|
|
758
|
+
let content: ContentAccess | ContentAccessWithWrite | undefined;
|
|
759
|
+
if (capabilities.has("write:content")) {
|
|
760
|
+
content = createContentAccessWithWrite(this.db);
|
|
761
|
+
} else if (capabilities.has("read:content")) {
|
|
762
|
+
content = createContentAccess(this.db);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Capability-gated: media
|
|
766
|
+
let media: MediaAccess | MediaAccessWithWrite | undefined;
|
|
767
|
+
if (capabilities.has("write:media") && this.getUploadUrl) {
|
|
768
|
+
media = createMediaAccessWithWrite(this.db, this.getUploadUrl, this.storage);
|
|
769
|
+
} else if (capabilities.has("read:media")) {
|
|
770
|
+
media = createMediaAccess(this.db);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// Capability-gated: http
|
|
774
|
+
let http: HttpAccess | undefined;
|
|
775
|
+
if (capabilities.has("network:fetch:any")) {
|
|
776
|
+
http = createUnrestrictedHttpAccess(plugin.id);
|
|
777
|
+
} else if (capabilities.has("network:fetch")) {
|
|
778
|
+
http = createHttpAccess(plugin.id, plugin.allowedHosts);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Capability-gated: users
|
|
782
|
+
let users: UserAccess | undefined;
|
|
783
|
+
if (capabilities.has("read:users")) {
|
|
784
|
+
users = createUserAccess(this.db);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Cron access ��� always available (scoped to plugin), but only if
|
|
788
|
+
// the runtime provided a reschedule callback (i.e. cron is wired up).
|
|
789
|
+
let cron: CronAccess | undefined;
|
|
790
|
+
if (this.cronReschedule) {
|
|
791
|
+
cron = new CronAccessImpl(this.db, plugin.id, this.cronReschedule);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// Email access — requires email:send capability AND a configured provider
|
|
795
|
+
let email: EmailAccess | undefined;
|
|
796
|
+
if (capabilities.has("email:send") && this.emailPipeline?.isAvailable()) {
|
|
797
|
+
const pipeline = this.emailPipeline;
|
|
798
|
+
const pluginId = plugin.id;
|
|
799
|
+
email = {
|
|
800
|
+
send: (message) => pipeline.send(message, pluginId),
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
return {
|
|
805
|
+
plugin: {
|
|
806
|
+
id: plugin.id,
|
|
807
|
+
version: plugin.version,
|
|
808
|
+
},
|
|
809
|
+
storage,
|
|
810
|
+
kv,
|
|
811
|
+
content,
|
|
812
|
+
media,
|
|
813
|
+
http,
|
|
814
|
+
log,
|
|
815
|
+
site: this.site,
|
|
816
|
+
url: this.urlHelper,
|
|
817
|
+
users,
|
|
818
|
+
cron,
|
|
819
|
+
email,
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* Create a plugin context for a resolved plugin
|
|
826
|
+
*/
|
|
827
|
+
export function createPluginContext(
|
|
828
|
+
options: PluginContextFactoryOptions,
|
|
829
|
+
plugin: ResolvedPlugin,
|
|
830
|
+
): PluginContext {
|
|
831
|
+
const factory = new PluginContextFactory(options);
|
|
832
|
+
return factory.createContext(plugin);
|
|
833
|
+
}
|