@plumix/core 0.6.0 → 0.7.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/cache/embedded-tags.d.ts +5 -0
- package/dist/cache/embedded-tags.d.ts.map +1 -0
- package/dist/cache/embedded-tags.js +30 -0
- package/dist/cache/embedded-tags.js.map +1 -0
- package/dist/entries/read-service.d.ts.map +1 -1
- package/dist/entries/read-service.js +5 -3
- package/dist/entries/read-service.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/plugin/errors.d.ts +7 -1
- package/dist/plugin/errors.d.ts.map +1 -1
- package/dist/plugin/errors.js +5 -0
- package/dist/plugin/errors.js.map +1 -1
- package/dist/plugin/fields/builder.d.ts +106 -0
- package/dist/plugin/fields/builder.d.ts.map +1 -0
- package/dist/plugin/fields/builder.js +162 -0
- package/dist/plugin/fields/builder.js.map +1 -0
- package/dist/plugin/fields/color.d.ts +78 -18
- package/dist/plugin/fields/color.d.ts.map +1 -1
- package/dist/plugin/fields/color.js +116 -25
- package/dist/plugin/fields/color.js.map +1 -1
- package/dist/plugin/fields/condition.d.ts +40 -0
- package/dist/plugin/fields/condition.d.ts.map +1 -0
- package/dist/plugin/fields/condition.js +115 -0
- package/dist/plugin/fields/condition.js.map +1 -0
- package/dist/plugin/fields/contributions.d.ts +233 -0
- package/dist/plugin/fields/contributions.d.ts.map +1 -0
- package/dist/plugin/fields/contributions.js +1 -0
- package/dist/plugin/fields/contributions.js.map +1 -0
- package/dist/plugin/fields/entry.d.ts +18 -22
- package/dist/plugin/fields/entry.d.ts.map +1 -1
- package/dist/plugin/fields/entry.js +13 -21
- package/dist/plugin/fields/entry.js.map +1 -1
- package/dist/plugin/fields/errors.d.ts +23 -11
- package/dist/plugin/fields/errors.d.ts.map +1 -1
- package/dist/plugin/fields/errors.js +20 -13
- package/dist/plugin/fields/errors.js.map +1 -1
- package/dist/plugin/fields/group.d.ts +90 -0
- package/dist/plugin/fields/group.d.ts.map +1 -0
- package/dist/plugin/fields/group.js +124 -0
- package/dist/plugin/fields/group.js.map +1 -0
- package/dist/plugin/fields/index.d.ts +22 -45
- package/dist/plugin/fields/index.d.ts.map +1 -1
- package/dist/plugin/fields/index.js +25 -30
- package/dist/plugin/fields/index.js.map +1 -1
- package/dist/plugin/fields/json.d.ts +71 -9
- package/dist/plugin/fields/json.d.ts.map +1 -1
- package/dist/plugin/fields/json.js +110 -16
- package/dist/plugin/fields/json.js.map +1 -1
- package/dist/plugin/fields/link.d.ts +96 -0
- package/dist/plugin/fields/link.d.ts.map +1 -0
- package/dist/plugin/fields/link.js +160 -0
- package/dist/plugin/fields/link.js.map +1 -0
- package/dist/plugin/fields/number.d.ts +86 -13
- package/dist/plugin/fields/number.d.ts.map +1 -1
- package/dist/plugin/fields/number.js +133 -20
- package/dist/plugin/fields/number.js.map +1 -1
- package/dist/plugin/fields/range.d.ts +101 -17
- package/dist/plugin/fields/range.d.ts.map +1 -1
- package/dist/plugin/fields/range.js +153 -38
- package/dist/plugin/fields/range.js.map +1 -1
- package/dist/plugin/fields/reference.d.ts +171 -0
- package/dist/plugin/fields/reference.d.ts.map +1 -0
- package/dist/plugin/fields/reference.js +193 -0
- package/dist/plugin/fields/reference.js.map +1 -0
- package/dist/plugin/fields/repeater.d.ts +111 -8
- package/dist/plugin/fields/repeater.d.ts.map +1 -1
- package/dist/plugin/fields/repeater.js +157 -66
- package/dist/plugin/fields/repeater.js.map +1 -1
- package/dist/plugin/fields/richtext-validate.d.ts +7 -0
- package/dist/plugin/fields/richtext-validate.d.ts.map +1 -1
- package/dist/plugin/fields/richtext-validate.js +11 -5
- package/dist/plugin/fields/richtext-validate.js.map +1 -1
- package/dist/plugin/fields/richtext.d.ts +78 -12
- package/dist/plugin/fields/richtext.d.ts.map +1 -1
- package/dist/plugin/fields/richtext.js +121 -26
- package/dist/plugin/fields/richtext.js.map +1 -1
- package/dist/plugin/fields/select.d.ts +150 -10
- package/dist/plugin/fields/select.d.ts.map +1 -1
- package/dist/plugin/fields/select.js +205 -17
- package/dist/plugin/fields/select.js.map +1 -1
- package/dist/plugin/fields/sub-fields.d.ts +12 -0
- package/dist/plugin/fields/sub-fields.d.ts.map +1 -0
- package/dist/plugin/fields/sub-fields.js +62 -0
- package/dist/plugin/fields/sub-fields.js.map +1 -0
- package/dist/plugin/fields/temporal.d.ts +124 -0
- package/dist/plugin/fields/temporal.d.ts.map +1 -0
- package/dist/plugin/fields/temporal.js +180 -0
- package/dist/plugin/fields/temporal.js.map +1 -0
- package/dist/plugin/fields/term.d.ts +11 -16
- package/dist/plugin/fields/term.d.ts.map +1 -1
- package/dist/plugin/fields/term.js +11 -19
- package/dist/plugin/fields/term.js.map +1 -1
- package/dist/plugin/fields/toggle.d.ts +97 -0
- package/dist/plugin/fields/toggle.d.ts.map +1 -0
- package/dist/plugin/fields/toggle.js +135 -0
- package/dist/plugin/fields/toggle.js.map +1 -0
- package/dist/plugin/fields/user.d.ts +14 -26
- package/dist/plugin/fields/user.d.ts.map +1 -1
- package/dist/plugin/fields/user.js +11 -25
- package/dist/plugin/fields/user.js.map +1 -1
- package/dist/plugin/lookup.d.ts +70 -1
- package/dist/plugin/lookup.d.ts.map +1 -1
- package/dist/plugin/manifest.d.ts +308 -128
- package/dist/plugin/manifest.d.ts.map +1 -1
- package/dist/plugin/manifest.js +94 -4
- package/dist/plugin/manifest.js.map +1 -1
- package/dist/plugin/setup-context.d.ts +5 -4
- package/dist/plugin/setup-context.d.ts.map +1 -1
- package/dist/plugin/setup-context.js +10 -3
- package/dist/plugin/setup-context.js.map +1 -1
- package/dist/plugin/validation/meta-box-fields.d.ts.map +1 -1
- package/dist/plugin/validation/meta-box-fields.js +16 -0
- package/dist/plugin/validation/meta-box-fields.js.map +1 -1
- package/dist/route/render/build-resolved-entries.d.ts.map +1 -1
- package/dist/route/render/build-resolved-entries.js +7 -2
- package/dist/route/render/build-resolved-entries.js.map +1 -1
- package/dist/route/render/render-template.d.ts.map +1 -1
- package/dist/route/render/render-template.js +12 -2
- package/dist/route/render/render-template.js.map +1 -1
- package/dist/route/render/template-builders.d.ts +15 -10
- package/dist/route/render/template-builders.d.ts.map +1 -1
- package/dist/route/render/template-builders.js.map +1 -1
- package/dist/rpc/base.d.ts +7 -0
- package/dist/rpc/base.d.ts.map +1 -1
- package/dist/rpc/errors.d.ts +7 -0
- package/dist/rpc/errors.d.ts.map +1 -1
- package/dist/rpc/errors.js +16 -0
- package/dist/rpc/errors.js.map +1 -1
- package/dist/rpc/meta/core.d.ts +89 -10
- package/dist/rpc/meta/core.d.ts.map +1 -1
- package/dist/rpc/meta/core.js +465 -282
- package/dist/rpc/meta/core.js.map +1 -1
- package/dist/rpc/meta/field-messages.d.ts +67 -0
- package/dist/rpc/meta/field-messages.d.ts.map +1 -0
- package/dist/rpc/meta/field-messages.js +67 -0
- package/dist/rpc/meta/field-messages.js.map +1 -0
- package/dist/rpc/meta/field-pipeline.d.ts +52 -0
- package/dist/rpc/meta/field-pipeline.d.ts.map +1 -0
- package/dist/rpc/meta/field-pipeline.js +501 -0
- package/dist/rpc/meta/field-pipeline.js.map +1 -0
- package/dist/rpc/procedures/auth/allowed-domains/create.d.ts +7 -0
- package/dist/rpc/procedures/auth/allowed-domains/create.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/allowed-domains/delete.d.ts +7 -0
- package/dist/rpc/procedures/auth/allowed-domains/delete.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/allowed-domains/index.d.ts +28 -0
- package/dist/rpc/procedures/auth/allowed-domains/index.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/allowed-domains/list.d.ts +7 -0
- package/dist/rpc/procedures/auth/allowed-domains/list.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/allowed-domains/update.d.ts +7 -0
- package/dist/rpc/procedures/auth/allowed-domains/update.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/api-tokens/admin-list.d.ts +7 -0
- package/dist/rpc/procedures/auth/api-tokens/admin-list.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/api-tokens/admin-revoke.d.ts +7 -0
- package/dist/rpc/procedures/auth/api-tokens/admin-revoke.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/api-tokens/create.d.ts +7 -0
- package/dist/rpc/procedures/auth/api-tokens/create.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/api-tokens/index.d.ts +35 -0
- package/dist/rpc/procedures/auth/api-tokens/index.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/api-tokens/list.d.ts +7 -0
- package/dist/rpc/procedures/auth/api-tokens/list.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/api-tokens/revoke.d.ts +7 -0
- package/dist/rpc/procedures/auth/api-tokens/revoke.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/credentials/delete.d.ts +7 -0
- package/dist/rpc/procedures/auth/credentials/delete.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/credentials/index.d.ts +21 -0
- package/dist/rpc/procedures/auth/credentials/index.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/credentials/list.d.ts +7 -0
- package/dist/rpc/procedures/auth/credentials/list.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/credentials/rename.d.ts +7 -0
- package/dist/rpc/procedures/auth/credentials/rename.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/device-flow/approve.d.ts +7 -0
- package/dist/rpc/procedures/auth/device-flow/approve.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/device-flow/deny.d.ts +7 -0
- package/dist/rpc/procedures/auth/device-flow/deny.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/device-flow/index.d.ts +21 -0
- package/dist/rpc/procedures/auth/device-flow/index.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/device-flow/lookup.d.ts +7 -0
- package/dist/rpc/procedures/auth/device-flow/lookup.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/index.d.ts +154 -0
- package/dist/rpc/procedures/auth/index.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/login-links.d.ts +7 -0
- package/dist/rpc/procedures/auth/login-links.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/mailer/index.d.ts +7 -0
- package/dist/rpc/procedures/auth/mailer/index.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/mailer/test-send.d.ts +7 -0
- package/dist/rpc/procedures/auth/mailer/test-send.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/oauth-providers.d.ts +7 -0
- package/dist/rpc/procedures/auth/oauth-providers.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/session.d.ts +7 -0
- package/dist/rpc/procedures/auth/session.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/sessions/index.d.ts +21 -0
- package/dist/rpc/procedures/auth/sessions/index.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/sessions/list.d.ts +7 -0
- package/dist/rpc/procedures/auth/sessions/list.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/sessions/revoke-others.d.ts +7 -0
- package/dist/rpc/procedures/auth/sessions/revoke-others.d.ts.map +1 -1
- package/dist/rpc/procedures/auth/sessions/revoke.d.ts +7 -0
- package/dist/rpc/procedures/auth/sessions/revoke.d.ts.map +1 -1
- package/dist/rpc/procedures/entry/activity.d.ts +7 -0
- package/dist/rpc/procedures/entry/activity.d.ts.map +1 -1
- package/dist/rpc/procedures/entry/bulk.d.ts +21 -0
- package/dist/rpc/procedures/entry/bulk.d.ts.map +1 -1
- package/dist/rpc/procedures/entry/create-preview-link.d.ts +7 -0
- package/dist/rpc/procedures/entry/create-preview-link.d.ts.map +1 -1
- package/dist/rpc/procedures/entry/create.d.ts +8 -1
- package/dist/rpc/procedures/entry/create.d.ts.map +1 -1
- package/dist/rpc/procedures/entry/create.js +8 -8
- package/dist/rpc/procedures/entry/create.js.map +1 -1
- package/dist/rpc/procedures/entry/dashboard.d.ts +14 -0
- package/dist/rpc/procedures/entry/dashboard.d.ts.map +1 -1
- package/dist/rpc/procedures/entry/delete-permanent.d.ts +7 -0
- package/dist/rpc/procedures/entry/delete-permanent.d.ts.map +1 -1
- package/dist/rpc/procedures/entry/discard-draft.d.ts +7 -0
- package/dist/rpc/procedures/entry/discard-draft.d.ts.map +1 -1
- package/dist/rpc/procedures/entry/duplicate.d.ts +7 -0
- package/dist/rpc/procedures/entry/duplicate.d.ts.map +1 -1
- package/dist/rpc/procedures/entry/duplicate.js +2 -2
- package/dist/rpc/procedures/entry/duplicate.js.map +1 -1
- package/dist/rpc/procedures/entry/get.d.ts +7 -0
- package/dist/rpc/procedures/entry/get.d.ts.map +1 -1
- package/dist/rpc/procedures/entry/get.js +2 -2
- package/dist/rpc/procedures/entry/get.js.map +1 -1
- package/dist/rpc/procedures/entry/index.d.ts +156 -2
- package/dist/rpc/procedures/entry/index.d.ts.map +1 -1
- package/dist/rpc/procedures/entry/list.d.ts +7 -0
- package/dist/rpc/procedures/entry/list.d.ts.map +1 -1
- package/dist/rpc/procedures/entry/lookup.d.ts +8 -2
- package/dist/rpc/procedures/entry/lookup.d.ts.map +1 -1
- package/dist/rpc/procedures/entry/lookup.js +60 -1
- package/dist/rpc/procedures/entry/lookup.js.map +1 -1
- package/dist/rpc/procedures/entry/meta.d.ts +37 -3
- package/dist/rpc/procedures/entry/meta.d.ts.map +1 -1
- package/dist/rpc/procedures/entry/meta.js +63 -7
- package/dist/rpc/procedures/entry/meta.js.map +1 -1
- package/dist/rpc/procedures/entry/publish.d.ts +7 -0
- package/dist/rpc/procedures/entry/publish.d.ts.map +1 -1
- package/dist/rpc/procedures/entry/publish.js +6 -4
- package/dist/rpc/procedures/entry/publish.js.map +1 -1
- package/dist/rpc/procedures/entry/refresh-block-loader.d.ts +7 -0
- package/dist/rpc/procedures/entry/refresh-block-loader.d.ts.map +1 -1
- package/dist/rpc/procedures/entry/restore.d.ts +7 -0
- package/dist/rpc/procedures/entry/restore.d.ts.map +1 -1
- package/dist/rpc/procedures/entry/revisions.d.ts +56 -0
- package/dist/rpc/procedures/entry/revisions.d.ts.map +1 -1
- package/dist/rpc/procedures/entry/schemas.d.ts +2 -2
- package/dist/rpc/procedures/entry/schemas.d.ts.map +1 -1
- package/dist/rpc/procedures/entry/schemas.js +9 -2
- package/dist/rpc/procedures/entry/schemas.js.map +1 -1
- package/dist/rpc/procedures/entry/trash.d.ts +7 -0
- package/dist/rpc/procedures/entry/trash.d.ts.map +1 -1
- package/dist/rpc/procedures/entry/update.d.ts +8 -1
- package/dist/rpc/procedures/entry/update.d.ts.map +1 -1
- package/dist/rpc/procedures/entry/update.js +77 -18
- package/dist/rpc/procedures/entry/update.js.map +1 -1
- package/dist/rpc/procedures/lookup/index.d.ts +14 -0
- package/dist/rpc/procedures/lookup/index.d.ts.map +1 -1
- package/dist/rpc/procedures/lookup/list.d.ts +7 -0
- package/dist/rpc/procedures/lookup/list.d.ts.map +1 -1
- package/dist/rpc/procedures/lookup/resolve.d.ts +7 -0
- package/dist/rpc/procedures/lookup/resolve.d.ts.map +1 -1
- package/dist/rpc/procedures/lookup/resolve.js +1 -1
- package/dist/rpc/procedures/lookup/resolve.js.map +1 -1
- package/dist/rpc/procedures/search/index.d.ts +7 -0
- package/dist/rpc/procedures/search/index.d.ts.map +1 -1
- package/dist/rpc/procedures/settings/get.d.ts +7 -0
- package/dist/rpc/procedures/settings/get.d.ts.map +1 -1
- package/dist/rpc/procedures/settings/index.d.ts +14 -0
- package/dist/rpc/procedures/settings/index.d.ts.map +1 -1
- package/dist/rpc/procedures/settings/upsert.d.ts +7 -0
- package/dist/rpc/procedures/settings/upsert.d.ts.map +1 -1
- package/dist/rpc/procedures/settings/upsert.js +9 -0
- package/dist/rpc/procedures/settings/upsert.js.map +1 -1
- package/dist/rpc/procedures/term/create.d.ts +7 -0
- package/dist/rpc/procedures/term/create.d.ts.map +1 -1
- package/dist/rpc/procedures/term/create.js +3 -3
- package/dist/rpc/procedures/term/create.js.map +1 -1
- package/dist/rpc/procedures/term/delete.d.ts +7 -0
- package/dist/rpc/procedures/term/delete.d.ts.map +1 -1
- package/dist/rpc/procedures/term/get.d.ts +7 -0
- package/dist/rpc/procedures/term/get.d.ts.map +1 -1
- package/dist/rpc/procedures/term/index.d.ts +35 -0
- package/dist/rpc/procedures/term/index.d.ts.map +1 -1
- package/dist/rpc/procedures/term/list.d.ts +7 -0
- package/dist/rpc/procedures/term/list.d.ts.map +1 -1
- package/dist/rpc/procedures/term/lookup.d.ts +7 -2
- package/dist/rpc/procedures/term/lookup.d.ts.map +1 -1
- package/dist/rpc/procedures/term/lookup.js +26 -0
- package/dist/rpc/procedures/term/lookup.js.map +1 -1
- package/dist/rpc/procedures/term/meta.d.ts +3 -2
- package/dist/rpc/procedures/term/meta.d.ts.map +1 -1
- package/dist/rpc/procedures/term/meta.js +7 -5
- package/dist/rpc/procedures/term/meta.js.map +1 -1
- package/dist/rpc/procedures/term/update.d.ts +7 -0
- package/dist/rpc/procedures/term/update.d.ts.map +1 -1
- package/dist/rpc/procedures/term/update.js +4 -4
- package/dist/rpc/procedures/term/update.js.map +1 -1
- package/dist/rpc/procedures/user/cancel-email-change.d.ts +7 -0
- package/dist/rpc/procedures/user/cancel-email-change.d.ts.map +1 -1
- package/dist/rpc/procedures/user/delete.d.ts +7 -0
- package/dist/rpc/procedures/user/delete.d.ts.map +1 -1
- package/dist/rpc/procedures/user/disable.d.ts +7 -0
- package/dist/rpc/procedures/user/disable.d.ts.map +1 -1
- package/dist/rpc/procedures/user/enable.d.ts +7 -0
- package/dist/rpc/procedures/user/enable.d.ts.map +1 -1
- package/dist/rpc/procedures/user/get.d.ts +7 -0
- package/dist/rpc/procedures/user/get.d.ts.map +1 -1
- package/dist/rpc/procedures/user/get.js +2 -2
- package/dist/rpc/procedures/user/get.js.map +1 -1
- package/dist/rpc/procedures/user/index.d.ts +77 -0
- package/dist/rpc/procedures/user/index.d.ts.map +1 -1
- package/dist/rpc/procedures/user/invite.d.ts +7 -0
- package/dist/rpc/procedures/user/invite.d.ts.map +1 -1
- package/dist/rpc/procedures/user/list.d.ts +7 -0
- package/dist/rpc/procedures/user/list.d.ts.map +1 -1
- package/dist/rpc/procedures/user/lookup.d.ts +6 -2
- package/dist/rpc/procedures/user/lookup.d.ts.map +1 -1
- package/dist/rpc/procedures/user/lookup.js +29 -0
- package/dist/rpc/procedures/user/lookup.js.map +1 -1
- package/dist/rpc/procedures/user/meta.d.ts +3 -2
- package/dist/rpc/procedures/user/meta.d.ts.map +1 -1
- package/dist/rpc/procedures/user/meta.js +7 -5
- package/dist/rpc/procedures/user/meta.js.map +1 -1
- package/dist/rpc/procedures/user/pending-email-change.d.ts +7 -0
- package/dist/rpc/procedures/user/pending-email-change.d.ts.map +1 -1
- package/dist/rpc/procedures/user/request-email-change.d.ts +7 -0
- package/dist/rpc/procedures/user/request-email-change.d.ts.map +1 -1
- package/dist/rpc/procedures/user/set-locale.d.ts +7 -0
- package/dist/rpc/procedures/user/set-locale.d.ts.map +1 -1
- package/dist/rpc/procedures/user/update.d.ts +7 -0
- package/dist/rpc/procedures/user/update.d.ts.map +1 -1
- package/dist/rpc/procedures/user/update.js +4 -4
- package/dist/rpc/procedures/user/update.js.map +1 -1
- package/dist/rpc/router.d.ts +457 -2
- package/dist/rpc/router.d.ts.map +1 -1
- package/dist/rpc/validation.d.ts +1 -0
- package/dist/rpc/validation.d.ts.map +1 -1
- package/dist/rpc/validation.js +1 -0
- package/dist/rpc/validation.js.map +1 -1
- package/dist/runtime/dispatcher.d.ts.map +1 -1
- package/dist/runtime/dispatcher.js +20 -10
- package/dist/runtime/dispatcher.js.map +1 -1
- package/dist/seo/feed.d.ts.map +1 -1
- package/dist/seo/feed.js +7 -1
- package/dist/seo/feed.js.map +1 -1
- package/dist/settings-core.js +1 -1
- package/dist/settings-core.js.map +1 -1
- package/dist/template-registry.d.ts +0 -16
- package/dist/template-registry.d.ts.map +1 -1
- package/dist/terms/read-service.js +2 -2
- package/dist/terms/read-service.js.map +1 -1
- package/dist/test/traced-context.d.ts +1 -1
- package/dist/test/traced-context.d.ts.map +1 -1
- package/dist/test/traced-context.js +2 -2
- package/dist/test/traced-context.js.map +1 -1
- package/package.json +2 -2
- package/dist/plugin/fields/checkbox.d.ts +0 -19
- package/dist/plugin/fields/checkbox.d.ts.map +0 -1
- package/dist/plugin/fields/checkbox.js +0 -20
- package/dist/plugin/fields/checkbox.js.map +0 -1
- package/dist/plugin/fields/date.d.ts +0 -24
- package/dist/plugin/fields/date.d.ts.map +0 -1
- package/dist/plugin/fields/date.js +0 -23
- package/dist/plugin/fields/date.js.map +0 -1
- package/dist/plugin/fields/datetime.d.ts +0 -26
- package/dist/plugin/fields/datetime.d.ts.map +0 -1
- package/dist/plugin/fields/datetime.js +0 -25
- package/dist/plugin/fields/datetime.js.map +0 -1
- package/dist/plugin/fields/email.d.ts +0 -17
- package/dist/plugin/fields/email.d.ts.map +0 -1
- package/dist/plugin/fields/email.js +0 -18
- package/dist/plugin/fields/email.js.map +0 -1
- package/dist/plugin/fields/entry-list.d.ts +0 -28
- package/dist/plugin/fields/entry-list.d.ts.map +0 -1
- package/dist/plugin/fields/entry-list.js +0 -31
- package/dist/plugin/fields/entry-list.js.map +0 -1
- package/dist/plugin/fields/multiselect.d.ts +0 -21
- package/dist/plugin/fields/multiselect.d.ts.map +0 -1
- package/dist/plugin/fields/multiselect.js +0 -43
- package/dist/plugin/fields/multiselect.js.map +0 -1
- package/dist/plugin/fields/password.d.ts +0 -24
- package/dist/plugin/fields/password.d.ts.map +0 -1
- package/dist/plugin/fields/password.js +0 -25
- package/dist/plugin/fields/password.js.map +0 -1
- package/dist/plugin/fields/radio.d.ts +0 -16
- package/dist/plugin/fields/radio.d.ts.map +0 -1
- package/dist/plugin/fields/radio.js +0 -17
- package/dist/plugin/fields/radio.js.map +0 -1
- package/dist/plugin/fields/repeater-validate.d.ts +0 -34
- package/dist/plugin/fields/repeater-validate.d.ts.map +0 -1
- package/dist/plugin/fields/repeater-validate.js +0 -104
- package/dist/plugin/fields/repeater-validate.js.map +0 -1
- package/dist/plugin/fields/term-list.d.ts +0 -27
- package/dist/plugin/fields/term-list.d.ts.map +0 -1
- package/dist/plugin/fields/term-list.js +0 -30
- package/dist/plugin/fields/term-list.js.map +0 -1
- package/dist/plugin/fields/text.d.ts +0 -37
- package/dist/plugin/fields/text.d.ts.map +0 -1
- package/dist/plugin/fields/text.js +0 -31
- package/dist/plugin/fields/text.js.map +0 -1
- package/dist/plugin/fields/textarea.d.ts +0 -20
- package/dist/plugin/fields/textarea.d.ts.map +0 -1
- package/dist/plugin/fields/textarea.js +0 -21
- package/dist/plugin/fields/textarea.js.map +0 -1
- package/dist/plugin/fields/time.d.ts +0 -24
- package/dist/plugin/fields/time.d.ts.map +0 -1
- package/dist/plugin/fields/time.js +0 -23
- package/dist/plugin/fields/time.js.map +0 -1
- package/dist/plugin/fields/url.d.ts +0 -17
- package/dist/plugin/fields/url.d.ts.map +0 -1
- package/dist/plugin/fields/url.js +0 -18
- package/dist/plugin/fields/url.js.map +0 -1
- package/dist/plugin/fields/user-list.d.ts +0 -29
- package/dist/plugin/fields/user-list.d.ts.map +0 -1
- package/dist/plugin/fields/user-list.js +0 -31
- package/dist/plugin/fields/user-list.js.map +0 -1
package/dist/rpc/meta/core.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { sql } from "drizzle-orm";
|
|
2
|
+
import { accumulateEmbeddedTags } from "../../cache/embedded-tags.js";
|
|
2
3
|
import { eq } from "../../db/index.js";
|
|
4
|
+
import { isConditionHidden } from "../../plugin/fields/condition.js";
|
|
5
|
+
import { anchorTemporalUtc } from "../../plugin/manifest.js";
|
|
3
6
|
import { MetaReferenceError } from "./errors.js";
|
|
7
|
+
import { META_FIELD_MESSAGES } from "./field-messages.js";
|
|
8
|
+
import { extractStringId, healReferenceValue, isGroupField, isRepeaterField, referenceTargetOf, runFieldPipeline, } from "./field-pipeline.js";
|
|
4
9
|
// Shared meta plumbing for every entity that stores a `meta` JSON
|
|
5
10
|
// column (entries, terms, and — eventually — users). The storage
|
|
6
11
|
// contract: writes merge into the bag via SQLite `json_set` /
|
|
@@ -32,70 +37,100 @@ export class MetaSanitizationError extends Error {
|
|
|
32
37
|
return new MetaSanitizationError(ctx.key, "value_too_large");
|
|
33
38
|
}
|
|
34
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* The whole-patch rejection produced by `sanitizeMetaInput` when any
|
|
42
|
+
* field's pipeline reports errors: every `{ path, message }` across
|
|
43
|
+
* every key of the request, so the admin form can address each
|
|
44
|
+
* offending input in one round-trip. Nothing is written when this
|
|
45
|
+
* throws.
|
|
46
|
+
*/
|
|
47
|
+
export class MetaValidationError extends Error {
|
|
48
|
+
static {
|
|
49
|
+
MetaValidationError.prototype.name = "MetaValidationError";
|
|
50
|
+
}
|
|
51
|
+
errors;
|
|
52
|
+
constructor(errors) {
|
|
53
|
+
super(`meta validation failed: ${errors.map((error) => error.path).join(", ")}`);
|
|
54
|
+
this.errors = errors;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
35
57
|
/**
|
|
36
58
|
* Validate an incoming meta map against a field-lookup fn produced by
|
|
37
59
|
* the caller (entry vs term differ only in which registry they walk).
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
60
|
+
* `null` / `undefined` values are deletion requests; everything else
|
|
61
|
+
* runs the per-field pipeline (coercion → `.sanitize()` → declarative
|
|
62
|
+
* constraints → `.validate()`). Pipeline rejections aggregate across
|
|
63
|
+
* the whole patch into one `MetaValidationError`; unregistered keys
|
|
64
|
+
* and oversized values keep the legacy fail-fast
|
|
65
|
+
* `MetaSanitizationError` surface.
|
|
41
66
|
*/
|
|
42
|
-
export function sanitizeMetaInput(findField, input) {
|
|
67
|
+
export async function sanitizeMetaInput(findField, input, mode = "strict") {
|
|
43
68
|
if (input === undefined)
|
|
44
69
|
return null;
|
|
45
70
|
const upserts = new Map();
|
|
46
71
|
const deletes = [];
|
|
72
|
+
const fieldErrors = [];
|
|
47
73
|
for (const [key, rawValue] of Object.entries(input)) {
|
|
48
74
|
const field = findField(key);
|
|
49
75
|
if (!field) {
|
|
76
|
+
// A delete request (null/undefined) for a key the field system doesn't
|
|
77
|
+
// own is a harmless no-op — never fail the whole write over an untracked
|
|
78
|
+
// foreign key (e.g. one written by another plugin). Unknown *upserts*
|
|
79
|
+
// still reject so junk can't enter through this gate.
|
|
80
|
+
if (rawValue === null || rawValue === undefined)
|
|
81
|
+
continue;
|
|
50
82
|
throw MetaSanitizationError.notRegistered({ key });
|
|
51
83
|
}
|
|
52
|
-
if (
|
|
84
|
+
if (isConditionHidden(field, input))
|
|
85
|
+
continue;
|
|
86
|
+
const result = await runFieldPipeline(field, rawValue, key, mode);
|
|
87
|
+
if (result.errors.length > 0) {
|
|
88
|
+
fieldErrors.push(...result.errors);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (result.isDeletion === true) {
|
|
53
92
|
deletes.push(key);
|
|
54
93
|
continue;
|
|
55
94
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
upserts.set(key, sanitized);
|
|
95
|
+
assertEncodedSize(key, result.value);
|
|
96
|
+
upserts.set(key, result.value);
|
|
97
|
+
}
|
|
98
|
+
if (fieldErrors.length > 0) {
|
|
99
|
+
throw new MetaValidationError(fieldErrors);
|
|
62
100
|
}
|
|
63
101
|
return { upserts, deletes };
|
|
64
102
|
}
|
|
65
103
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* throw vanilla errors without importing the package-internal error
|
|
73
|
-
* class.
|
|
104
|
+
* Thin wrapper that translates thrown meta errors into the RPC
|
|
105
|
+
* handler's CONFLICT envelope. Pipeline rejections ship their
|
|
106
|
+
* `{ path, message }` list under `data.errors` (with `key` pointing
|
|
107
|
+
* at the first error's top-level field for legacy consumers); the
|
|
108
|
+
* fail-fast `MetaSanitizationError` reasons keep their existing
|
|
109
|
+
* `data.reason`/`data.key` shape.
|
|
74
110
|
*/
|
|
75
|
-
function runSanitize(sanitize, key, value) {
|
|
76
|
-
try {
|
|
77
|
-
return sanitize(value);
|
|
78
|
-
}
|
|
79
|
-
catch (error) {
|
|
80
|
-
if (error instanceof MetaSanitizationError)
|
|
81
|
-
throw error;
|
|
82
|
-
// Buggy sanitize callbacks otherwise round to a generic
|
|
83
|
-
// `invalid_value` envelope, which is fine for the editor's UX
|
|
84
|
-
// but loses the underlying stack. Log it before translating so
|
|
85
|
-
// server logs preserve the diagnostic trail.
|
|
86
|
-
console.error(`[plumix] sanitize callback for meta key ${JSON.stringify(key)} threw:`, error);
|
|
87
|
-
throw MetaSanitizationError.invalidValue({ key });
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
111
|
/**
|
|
91
|
-
*
|
|
92
|
-
* the
|
|
112
|
+
* Map a whole-patch meta validation failure onto the RPC `CONFLICT`
|
|
113
|
+
* envelope — one place so the wire shape (`key` at the first error's
|
|
114
|
+
* top-level field, the full `errors` list) can't drift between the write
|
|
115
|
+
* path and the publish gate.
|
|
93
116
|
*/
|
|
94
|
-
export function
|
|
117
|
+
export function metaValidationConflict(error, errors) {
|
|
118
|
+
return errors.CONFLICT({
|
|
119
|
+
data: {
|
|
120
|
+
reason: "meta_invalid_value",
|
|
121
|
+
key: error.errors[0]?.path.split(".")[0],
|
|
122
|
+
errors: [...error.errors],
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
export async function sanitizeMetaForRpc(findField, input, errors, mode = "strict") {
|
|
95
127
|
try {
|
|
96
|
-
return sanitizeMetaInput(findField, input);
|
|
128
|
+
return await sanitizeMetaInput(findField, input, mode);
|
|
97
129
|
}
|
|
98
130
|
catch (error) {
|
|
131
|
+
if (error instanceof MetaValidationError) {
|
|
132
|
+
throw metaValidationConflict(error, errors);
|
|
133
|
+
}
|
|
99
134
|
if (error instanceof MetaSanitizationError) {
|
|
100
135
|
throw errors.CONFLICT({
|
|
101
136
|
data: { reason: `meta_${error.reason}`, key: error.key },
|
|
@@ -104,6 +139,58 @@ export function sanitizeMetaForRpc(findField, input, errors) {
|
|
|
104
139
|
throw error;
|
|
105
140
|
}
|
|
106
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Strict publish gate: validate a whole *stored* meta bag against the full
|
|
144
|
+
* field list before `entry.publish` promotes it onto the live row. Draft
|
|
145
|
+
* autosaves are lenient (business rules skipped so work-in-progress always
|
|
146
|
+
* saves), so publish is where required fields, bounds, formats, option
|
|
147
|
+
* membership, and row counts are finally enforced. It walks the field
|
|
148
|
+
* *definitions*, not just the bag, so a required field ABSENT from the bag
|
|
149
|
+
* is caught as well as one stored empty — `runFieldPipeline` sees
|
|
150
|
+
* `undefined` and rejects it in strict mode.
|
|
151
|
+
*
|
|
152
|
+
* Rejections aggregate across the bag into one `MetaValidationError` the
|
|
153
|
+
* RPC layer maps onto the admin inputs, blocking the publish. A passing bag
|
|
154
|
+
* comes back canonicalized (each registered value re-run through
|
|
155
|
+
* `.sanitize()`); a conditionally-hidden field can't be required, so it's
|
|
156
|
+
* skipped; keys the field system doesn't own (e.g. from an uninstalled
|
|
157
|
+
* plugin) pass through untouched, matching the read path. Field
|
|
158
|
+
* capabilities and reference existence are not re-checked here — a
|
|
159
|
+
* whole-bag gate would block a publisher over a co-author's field, and both
|
|
160
|
+
* were already enforced at write time.
|
|
161
|
+
*/
|
|
162
|
+
export async function validateAndPromoteMetaBag(fields, bag) {
|
|
163
|
+
const out = {};
|
|
164
|
+
const owned = new Set();
|
|
165
|
+
const fieldErrors = [];
|
|
166
|
+
for (const field of fields) {
|
|
167
|
+
owned.add(field.key);
|
|
168
|
+
// A hidden field is inactive, so it can't be required — but its stored
|
|
169
|
+
// value is kept untouched (not validated, not dropped), or the value a
|
|
170
|
+
// driver hides would be lost on publish and gone when the driver flips
|
|
171
|
+
// back.
|
|
172
|
+
if (isConditionHidden(field, bag)) {
|
|
173
|
+
if (field.key in bag)
|
|
174
|
+
out[field.key] = bag[field.key];
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const result = await runFieldPipeline(field, bag[field.key], field.key);
|
|
178
|
+
if (result.errors.length > 0) {
|
|
179
|
+
fieldErrors.push(...result.errors);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (result.isDeletion === true)
|
|
183
|
+
continue;
|
|
184
|
+
out[field.key] = result.value;
|
|
185
|
+
}
|
|
186
|
+
for (const [key, value] of Object.entries(bag)) {
|
|
187
|
+
if (!owned.has(key))
|
|
188
|
+
out[key] = value;
|
|
189
|
+
}
|
|
190
|
+
if (fieldErrors.length > 0)
|
|
191
|
+
throw new MetaValidationError(fieldErrors);
|
|
192
|
+
return out;
|
|
193
|
+
}
|
|
107
194
|
/**
|
|
108
195
|
* Walk a sanitized patch, group every reference upsert by
|
|
109
196
|
* `(kind, scope)`, and issue one `LookupAdapter.list({ ids })`
|
|
@@ -118,7 +205,7 @@ export function sanitizeMetaForRpc(findField, input, errors) {
|
|
|
118
205
|
* TOCTOU note: validate runs in a separate query from the eventual
|
|
119
206
|
* `applyMetaPatch`, and callers don't share a transaction. A
|
|
120
207
|
* concurrent delete between validate and apply leaves an orphan id
|
|
121
|
-
* in the meta bag; `
|
|
208
|
+
* in the meta bag; `hydrateMetaBags` masks it on read. Wrap the
|
|
122
209
|
* validate/apply pair in `ctx.db.transaction()` if a caller needs
|
|
123
210
|
* serializable consistency.
|
|
124
211
|
*/
|
|
@@ -142,17 +229,15 @@ export async function validateMetaReferences(ctx, findField, patch) {
|
|
|
142
229
|
group.contributions.push({ errorKey: key, ids });
|
|
143
230
|
continue;
|
|
144
231
|
}
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
// `
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
// log, not the wire response
|
|
151
|
-
if (
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
continue;
|
|
155
|
-
collectRepeaterReferences(ctx, key, field, value, groups);
|
|
232
|
+
// Composite fields (repeater / group) don't carry a
|
|
233
|
+
// `referenceTarget` themselves but their rows / members can, at any
|
|
234
|
+
// nesting depth. Walk them so nested `entry` / `term` / `user` /
|
|
235
|
+
// `media` refs flow through the same `(kind, scope)` batch as
|
|
236
|
+
// top-level fields. Errors attribute to the top-level key — the
|
|
237
|
+
// dotted sub-path lives in the developer log, not the wire response.
|
|
238
|
+
if (isRepeaterField(field) || isGroupField(field)) {
|
|
239
|
+
collectCompositeReferences(ctx, key, key, field, value, groups);
|
|
240
|
+
}
|
|
156
241
|
}
|
|
157
242
|
for (const group of groups.values()) {
|
|
158
243
|
if (group.ids.size === 0)
|
|
@@ -163,15 +248,13 @@ export async function validateMetaReferences(ctx, findField, patch) {
|
|
|
163
248
|
if (!liveIds.has(id)) {
|
|
164
249
|
if (contribution.diagnostic !== undefined) {
|
|
165
250
|
// Wire error keys on the top-level field; this log line
|
|
166
|
-
// is the only place the
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
`row ${String(contribution.diagnostic.rowIdx)} ` +
|
|
174
|
-
`subField ${JSON.stringify(contribution.diagnostic.subKey)} ` +
|
|
251
|
+
// is the only place the nested sub-path surfaces, so an
|
|
252
|
+
// engineer debugging a `meta_invalid_value` on a composite
|
|
253
|
+
// field can locate the offending cell without bisecting the
|
|
254
|
+
// saved bag. `JSON.stringify` escapes control characters so
|
|
255
|
+
// a user-supplied id with newlines can't poison the log.
|
|
256
|
+
console.error(`[plumix] meta composite ${JSON.stringify(contribution.errorKey)} ` +
|
|
257
|
+
`at ${JSON.stringify(contribution.diagnostic.path)} ` +
|
|
175
258
|
`references missing id ${JSON.stringify(id)}`);
|
|
176
259
|
}
|
|
177
260
|
throw MetaSanitizationError.invalidValue({
|
|
@@ -182,44 +265,57 @@ export async function validateMetaReferences(ctx, findField, patch) {
|
|
|
182
265
|
}
|
|
183
266
|
}
|
|
184
267
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
function
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (!row || typeof row !== "object" || Array.isArray(row))
|
|
197
|
-
continue;
|
|
198
|
-
const rowObj = row;
|
|
199
|
-
for (const subField of field.subFields) {
|
|
200
|
-
const target = referenceTargetOf(subField);
|
|
201
|
-
if (!target)
|
|
268
|
+
// Walk a composite field's live value, collecting + normalizing every
|
|
269
|
+
// nested reference in place. `path` is the dotted address of `field`
|
|
270
|
+
// from the top-level key (developer-log only). Recurses through nested
|
|
271
|
+
// repeaters and groups, so a reference at any depth flows through the
|
|
272
|
+
// same `(kind, scope)` batch and is orphan-checked at write time.
|
|
273
|
+
function collectCompositeReferences(ctx, topKey, path, field, value, groups) {
|
|
274
|
+
if (isRepeaterField(field)) {
|
|
275
|
+
if (!Array.isArray(value))
|
|
276
|
+
return;
|
|
277
|
+
for (const [rowIdx, row] of value.entries()) {
|
|
278
|
+
if (!isPlainObject(row))
|
|
202
279
|
continue;
|
|
203
|
-
|
|
280
|
+
collectMemberReferences(ctx, topKey, `${path}.${String(rowIdx)}`, field.subFields, row, groups);
|
|
281
|
+
}
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (isGroupField(field)) {
|
|
285
|
+
if (!isPlainObject(value))
|
|
286
|
+
return;
|
|
287
|
+
collectMemberReferences(ctx, topKey, path, field.fields, value, groups);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function collectMemberReferences(ctx, topKey, path, members, container, groups) {
|
|
291
|
+
for (const member of members) {
|
|
292
|
+
const memberPath = `${path}.${member.key}`;
|
|
293
|
+
const subValue = container[member.key];
|
|
294
|
+
const target = referenceTargetOf(member);
|
|
295
|
+
if (target) {
|
|
204
296
|
if (subValue === undefined || subValue === null)
|
|
205
297
|
continue;
|
|
206
298
|
const registered = ctx.plugins.lookupAdapters.get(target.kind);
|
|
207
299
|
if (!registered) {
|
|
208
300
|
throw MetaSanitizationError.invalidValue({ key: topKey });
|
|
209
301
|
}
|
|
210
|
-
const ids = referenceIdsForValidation(topKey, subValue, target, fieldMax(
|
|
211
|
-
// Normalize the
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
|
|
302
|
+
const ids = referenceIdsForValidation(topKey, subValue, target, fieldMax(member));
|
|
303
|
+
// Normalize the cell in place — the top-level upsert value is the
|
|
304
|
+
// live storage shape, so mutating an object inside it is what the
|
|
305
|
+
// caller serializes.
|
|
306
|
+
container[member.key] = target.multiple ? ids : ids[0];
|
|
215
307
|
const group = upsertGroup(groups, target, registered);
|
|
216
308
|
for (const id of ids)
|
|
217
309
|
group.ids.add(id);
|
|
218
310
|
group.contributions.push({
|
|
219
311
|
errorKey: topKey,
|
|
220
312
|
ids,
|
|
221
|
-
diagnostic: {
|
|
313
|
+
diagnostic: { path: memberPath },
|
|
222
314
|
});
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (isRepeaterField(member) || isGroupField(member)) {
|
|
318
|
+
collectCompositeReferences(ctx, topKey, memberPath, member, subValue, groups);
|
|
223
319
|
}
|
|
224
320
|
}
|
|
225
321
|
}
|
|
@@ -319,15 +415,6 @@ function referenceItemId(key, item) {
|
|
|
319
415
|
return id;
|
|
320
416
|
throw MetaSanitizationError.invalidValue({ key });
|
|
321
417
|
}
|
|
322
|
-
// Returns the `id` string of a `{ id: string, ... }` object, or null
|
|
323
|
-
// for any other shape (string, array, null, primitive, missing key).
|
|
324
|
-
function extractStringId(value) {
|
|
325
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
326
|
-
return null;
|
|
327
|
-
}
|
|
328
|
-
const id = value.id;
|
|
329
|
-
return typeof id === "string" ? id : null;
|
|
330
|
-
}
|
|
331
418
|
function fieldMax(field) {
|
|
332
419
|
return field?.max;
|
|
333
420
|
}
|
|
@@ -344,145 +431,303 @@ export async function validateMetaReferencesForRpc(ctx, findField, patch, errors
|
|
|
344
431
|
catch (error) {
|
|
345
432
|
if (error instanceof MetaSanitizationError) {
|
|
346
433
|
throw errors.CONFLICT({
|
|
347
|
-
data: {
|
|
434
|
+
data: {
|
|
435
|
+
reason: `meta_${error.reason}`,
|
|
436
|
+
key: error.key,
|
|
437
|
+
// Reference failures address the top-level field (row/subKey
|
|
438
|
+
// detail stays in the server log) — shipping them under
|
|
439
|
+
// `errors` too lets the admin form surface them inline.
|
|
440
|
+
errors: [
|
|
441
|
+
{ path: error.key, message: META_FIELD_MESSAGES.invalidOption },
|
|
442
|
+
],
|
|
443
|
+
},
|
|
348
444
|
});
|
|
349
445
|
}
|
|
350
446
|
throw error;
|
|
351
447
|
}
|
|
352
448
|
}
|
|
449
|
+
// A reference field authored with `.returns("id")` reads the bare
|
|
450
|
+
// stored id — the hydration walk skips it so no lookup query runs and
|
|
451
|
+
// the id survives untouched. `"returns" in field` narrows to the field
|
|
452
|
+
// variants that carry the flag (temporal's `"date"` never matches).
|
|
453
|
+
function readsRawReferenceId(field) {
|
|
454
|
+
return field !== undefined && "returns" in field && field.returns === "id";
|
|
455
|
+
}
|
|
353
456
|
/**
|
|
354
457
|
* Yield every reference-field occurrence in a decoded meta bag: top-level
|
|
355
|
-
* reference fields, plus
|
|
356
|
-
* structural walk so the
|
|
357
|
-
*
|
|
458
|
+
* reference fields, plus references nested inside repeater rows and
|
|
459
|
+
* groups at any depth. One structural walk so the hydration pass doesn't
|
|
460
|
+
* reinline the traversal twice.
|
|
358
461
|
*/
|
|
359
462
|
function* referenceOccurrences(entries, findField) {
|
|
360
463
|
for (const [key, value] of entries) {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
464
|
+
yield* fieldOccurrences([key], findField(key), value);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
// Recurse a single field's decoded value, yielding reference occurrences
|
|
468
|
+
// with their full path. Nested field definitions come straight off the
|
|
469
|
+
// composite field (`subFields` / `fields`) — `findField` only resolves
|
|
470
|
+
// top-level keys.
|
|
471
|
+
function* fieldOccurrences(path, field, value) {
|
|
472
|
+
const target = referenceTargetOf(field);
|
|
473
|
+
if (target) {
|
|
474
|
+
// `.returns("id")` opts out of the hydration join at any depth —
|
|
475
|
+
// leave the stored id(s) untouched (no resolve, no orphan-strip).
|
|
476
|
+
if (!readsRawReferenceId(field))
|
|
477
|
+
yield { path, target, value };
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
if (isRepeaterField(field)) {
|
|
369
481
|
if (!Array.isArray(value))
|
|
370
|
-
|
|
482
|
+
return;
|
|
371
483
|
for (const [rowIdx, row] of value.entries()) {
|
|
372
|
-
if (!
|
|
484
|
+
if (!isPlainObject(row))
|
|
373
485
|
continue;
|
|
374
|
-
const rowObj = row;
|
|
375
486
|
for (const subField of field.subFields) {
|
|
376
|
-
|
|
377
|
-
if (!subTarget)
|
|
378
|
-
continue;
|
|
379
|
-
yield {
|
|
380
|
-
key,
|
|
381
|
-
target: subTarget,
|
|
382
|
-
value: rowObj[subField.key],
|
|
383
|
-
nested: { rowIdx, subKey: subField.key },
|
|
384
|
-
};
|
|
487
|
+
yield* fieldOccurrences([...path, rowIdx, subField.key], subField, row[subField.key]);
|
|
385
488
|
}
|
|
386
489
|
}
|
|
490
|
+
return;
|
|
387
491
|
}
|
|
492
|
+
if (isGroupField(field)) {
|
|
493
|
+
if (!isPlainObject(value))
|
|
494
|
+
return;
|
|
495
|
+
for (const member of field.fields) {
|
|
496
|
+
yield* fieldOccurrences([...path, member.key], member, value[member.key]);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
/** Single-bag convenience over {@link hydrateMetaBags}. */
|
|
501
|
+
export async function hydrateMetaReferences(ctx, findField, decoded) {
|
|
502
|
+
const [bag] = await hydrateMetaBags(ctx, [{ findField, decoded }]);
|
|
503
|
+
// hydrateMetaBags returns one bag per input by construction.
|
|
504
|
+
return bag ?? decoded;
|
|
388
505
|
}
|
|
389
|
-
export async function
|
|
506
|
+
export async function hydrateMetaBags(ctx, bags) {
|
|
390
507
|
const candidates = [];
|
|
391
508
|
const groups = new Map();
|
|
392
|
-
// Top-level refs and repeater
|
|
393
|
-
// treatment, so the single `referenceOccurrences` walk feeds both.
|
|
394
|
-
//
|
|
395
|
-
//
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
const groupKey = referenceGroupKey(occ.target);
|
|
404
|
-
candidates.push({
|
|
405
|
-
key: occ.key,
|
|
406
|
-
multiple: occ.target.multiple === true,
|
|
407
|
-
groupKey,
|
|
408
|
-
ids,
|
|
409
|
-
nested: occ.nested,
|
|
410
|
-
});
|
|
411
|
-
if (ids.length === 0)
|
|
412
|
-
continue;
|
|
413
|
-
let group = groups.get(groupKey);
|
|
414
|
-
if (!group) {
|
|
415
|
-
group = { registered, scope: occ.target.scope, ids: new Set() };
|
|
416
|
-
groups.set(groupKey, group);
|
|
417
|
-
}
|
|
418
|
-
for (const id of ids)
|
|
419
|
-
group.ids.add(id);
|
|
420
|
-
}
|
|
421
|
-
// Pass 2: one `list({ ids })` per group, keyed for O(1) apply lookup.
|
|
422
|
-
const liveIdsByGroup = new Map();
|
|
423
|
-
for (const [groupKey, group] of groups) {
|
|
424
|
-
const liveIds = await fetchLiveIds(ctx, group.registered, group.scope, group.ids, "filterMetaOrphans");
|
|
425
|
-
liveIdsByGroup.set(groupKey, liveIds);
|
|
426
|
-
}
|
|
427
|
-
// Pass 3: apply the filter. Multi refs filter the string[] dense
|
|
428
|
-
// and in order; single refs null on missing. Nested-in-repeater
|
|
429
|
-
// candidates rewrite the row's subField slot in a per-key cloned
|
|
430
|
-
// array so the input bag stays untouched.
|
|
431
|
-
const out = { ...decoded };
|
|
432
|
-
const empty = new Set();
|
|
433
|
-
for (const candidate of candidates) {
|
|
434
|
-
const { key, multiple, groupKey, ids, nested } = candidate;
|
|
435
|
-
const liveIds = liveIdsByGroup.get(groupKey) ?? empty;
|
|
436
|
-
if (nested !== undefined) {
|
|
437
|
-
const rowObj = takeWritableRow(out, decoded, key, nested.rowIdx);
|
|
438
|
-
if (!rowObj)
|
|
509
|
+
// Top-level refs and nested (repeater / group) refs get identical
|
|
510
|
+
// treatment, so the single `referenceOccurrences` walk feeds both.
|
|
511
|
+
// Every candidate carries its full path; Pass 3 rewrites the slot in a
|
|
512
|
+
// per-path copy-on-write so callers' bags stay untouched.
|
|
513
|
+
const out = [];
|
|
514
|
+
for (const bag of bags) {
|
|
515
|
+
const outBag = { ...bag.decoded };
|
|
516
|
+
out.push(outBag);
|
|
517
|
+
for (const occ of referenceOccurrences(Object.entries(bag.decoded), bag.findField)) {
|
|
518
|
+
const registered = ctx.plugins.lookupAdapters.get(occ.target.kind);
|
|
519
|
+
if (!registered)
|
|
439
520
|
continue;
|
|
440
|
-
|
|
441
|
-
|
|
521
|
+
const ids = referenceCandidateIds(occ.target, occ.value);
|
|
522
|
+
if (ids === null)
|
|
523
|
+
continue; // non-array multi / non-string single — leave untouched
|
|
524
|
+
const groupKey = referenceGroupKey(occ.target);
|
|
525
|
+
candidates.push({
|
|
526
|
+
outBag,
|
|
527
|
+
decoded: bag.decoded,
|
|
528
|
+
path: occ.path,
|
|
529
|
+
multiple: occ.target.multiple === true,
|
|
530
|
+
groupKey,
|
|
531
|
+
ids,
|
|
532
|
+
});
|
|
533
|
+
if (ids.length === 0)
|
|
534
|
+
continue;
|
|
535
|
+
let group = groups.get(groupKey);
|
|
536
|
+
if (!group) {
|
|
537
|
+
group = { registered, scope: occ.target.scope, ids: new Set() };
|
|
538
|
+
groups.set(groupKey, group);
|
|
539
|
+
}
|
|
540
|
+
for (const id of ids)
|
|
541
|
+
group.ids.add(id);
|
|
442
542
|
}
|
|
443
|
-
|
|
444
|
-
|
|
543
|
+
}
|
|
544
|
+
// Pass 2: one `hydrate({ ids })` (or `list({ ids })` fallback) per
|
|
545
|
+
// group, keyed for O(1) apply lookup. Groups are independent, so a
|
|
546
|
+
// bag mixing entry + user + media refs resolves them concurrently.
|
|
547
|
+
const resolutions = new Map(await Promise.all([...groups].map(async ([groupKey, group]) => [
|
|
548
|
+
groupKey,
|
|
549
|
+
await resolveGroup(ctx, group.registered.adapter, group.scope, group.ids),
|
|
550
|
+
])));
|
|
551
|
+
// Pass 3: apply. Hydrated groups replace ids with their payloads;
|
|
552
|
+
// id-only groups keep ids. Multi refs stay dense and in stored
|
|
553
|
+
// order; single refs null on missing. Nested candidates copy-on-write
|
|
554
|
+
// each container down their path so callers' bags stay untouched.
|
|
555
|
+
const emptyResolution = { kind: "ids", liveIds: new Set() };
|
|
556
|
+
for (const candidate of candidates) {
|
|
557
|
+
const { outBag, decoded, path, multiple, groupKey, ids } = candidate;
|
|
558
|
+
const resolution = resolutions.get(groupKey) ?? emptyResolution;
|
|
559
|
+
const slot = takeWritableSlot(outBag, decoded, path);
|
|
560
|
+
if (!slot)
|
|
445
561
|
continue;
|
|
446
|
-
|
|
447
|
-
const [singleId] = ids;
|
|
448
|
-
if (singleId === undefined)
|
|
449
|
-
continue; // single non-string already filtered upstream
|
|
450
|
-
if (!liveIds.has(singleId))
|
|
451
|
-
out[key] = null;
|
|
562
|
+
applyResolutionToSlot(slot.parent, slot.leafKey, multiple, ids, resolution);
|
|
452
563
|
}
|
|
453
564
|
return out;
|
|
454
565
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
566
|
+
/**
|
|
567
|
+
* Batched, tag-accounted hydration of a raw id set for one reference kind
|
|
568
|
+
* — the theme-facing counterpart to the meta pipeline's hydration (#1508).
|
|
569
|
+
* A theme holding an id-only reference field (a field declared
|
|
570
|
+
* `.returns("id")`, or one contributed by a third-party plugin) resolves
|
|
571
|
+
* it here instead of hand-rolled per-item fetches: ids resolve through the
|
|
572
|
+
* adapter's batched `hydrate` (chunked, one in-query per chunk) and every
|
|
573
|
+
* resolved entity folds its cache tag into the page through the same
|
|
574
|
+
* accumulator the meta pipeline uses, so the page is purged when an
|
|
575
|
+
* embedded entity changes.
|
|
576
|
+
*
|
|
577
|
+
* Returns the hydrated payloads dense and in the requested id order — ids
|
|
578
|
+
* that are gone or out of scope are dropped, mirroring multi-reference
|
|
579
|
+
* field hydration. An unregistered kind, an adapter without the `hydrate`
|
|
580
|
+
* contract, or an empty id set yields `[]`.
|
|
581
|
+
*/
|
|
582
|
+
export async function hydrateReferences(ctx, kind, ids, options = {}) {
|
|
583
|
+
const registered = ctx.plugins.lookupAdapters.get(kind);
|
|
584
|
+
if (!registered?.adapter.hydrate)
|
|
585
|
+
return [];
|
|
586
|
+
const unique = new Set(ids.filter((id) => id !== ""));
|
|
587
|
+
if (unique.size === 0)
|
|
588
|
+
return [];
|
|
589
|
+
const resolution = await resolveGroup(ctx, registered.adapter, options.scope, unique);
|
|
590
|
+
if (resolution.kind !== "hydrated")
|
|
591
|
+
return [];
|
|
592
|
+
return ids
|
|
593
|
+
.map((id) => resolution.byId.get(id))
|
|
594
|
+
.filter((p) => p !== undefined);
|
|
595
|
+
}
|
|
596
|
+
// Resolve one `(kind, scope)` group's aggregated ids. Chunked at
|
|
597
|
+
// `HYDRATION_QUERY_ID_LIMIT` per in-query: a response-level group can
|
|
598
|
+
// legitimately aggregate more ids than one query may carry (a
|
|
599
|
+
// 100-entry archive × multi-reference fields), and a read-path throw
|
|
600
|
+
// would kill the render — unlike the write-side `fetchLiveIds`, which
|
|
601
|
+
// keeps throwing because a single patch exceeding the ceiling is a
|
|
602
|
+
// caller bug. Ids are de-duped before chunking, so per-query batches
|
|
603
|
+
// stay bounded and nothing is truncated.
|
|
604
|
+
async function resolveGroup(ctx, adapter, scope, ids) {
|
|
605
|
+
const idList = [...ids];
|
|
606
|
+
if (adapter.hydrate) {
|
|
607
|
+
const byId = new Map();
|
|
608
|
+
for (const chunk of chunkIds(idList)) {
|
|
609
|
+
const payloads = await adapter.hydrate(ctx, { ids: chunk, scope });
|
|
610
|
+
for (const payload of payloads) {
|
|
611
|
+
byId.set(payload.id, payload);
|
|
612
|
+
// Fold this embedded entity's cache tag into the page's tags so
|
|
613
|
+
// a change to it purges the page that hydrated it (#1508). Runs
|
|
614
|
+
// on every read surface; only the public read-through reads the
|
|
615
|
+
// accumulator back, so admin/REST reads populate it harmlessly.
|
|
616
|
+
if (adapter.embeddedCacheTags) {
|
|
617
|
+
accumulateEmbeddedTags(ctx, adapter.embeddedCacheTags(payload));
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return { kind: "hydrated", byId };
|
|
622
|
+
}
|
|
623
|
+
const liveIds = new Set();
|
|
624
|
+
for (const chunk of chunkIds(idList)) {
|
|
625
|
+
const rows = await adapter.list(ctx, {
|
|
626
|
+
ids: chunk,
|
|
627
|
+
scope,
|
|
628
|
+
limit: chunk.length,
|
|
629
|
+
});
|
|
630
|
+
for (const row of rows)
|
|
631
|
+
liveIds.add(row.id);
|
|
632
|
+
}
|
|
633
|
+
return { kind: "ids", liveIds };
|
|
634
|
+
}
|
|
635
|
+
// Per-query id cap for the read path. 100 (not the 1000 aggregate
|
|
636
|
+
// ceiling) because Cloudflare D1 caps bound parameters at 100 per
|
|
637
|
+
// statement and `inArray` binds one per id — a bigger chunk works on
|
|
638
|
+
// local SQLite and dies in production.
|
|
639
|
+
const HYDRATION_QUERY_ID_LIMIT = 100;
|
|
640
|
+
function* chunkIds(ids) {
|
|
641
|
+
for (let i = 0; i < ids.length; i += HYDRATION_QUERY_ID_LIMIT) {
|
|
642
|
+
yield ids.slice(i, i + HYDRATION_QUERY_ID_LIMIT);
|
|
468
643
|
}
|
|
469
|
-
if (!Array.isArray(arr))
|
|
470
|
-
return null;
|
|
471
|
-
const row = arr[rowIdx];
|
|
472
|
-
if (!row || typeof row !== "object" || Array.isArray(row))
|
|
473
|
-
return null;
|
|
474
|
-
return row;
|
|
475
644
|
}
|
|
476
|
-
|
|
645
|
+
// Write one candidate's resolved value into its slot — the leaf object
|
|
646
|
+
// key of a container reached by walking the candidate's path.
|
|
647
|
+
function applyResolutionToSlot(slot, key, multiple, ids, resolution) {
|
|
477
648
|
if (multiple) {
|
|
478
|
-
|
|
649
|
+
slot[key] =
|
|
650
|
+
resolution.kind === "hydrated"
|
|
651
|
+
? ids
|
|
652
|
+
.map((id) => resolution.byId.get(id))
|
|
653
|
+
.filter((payload) => payload !== undefined)
|
|
654
|
+
: ids.filter((id) => resolution.liveIds.has(id));
|
|
479
655
|
return;
|
|
480
656
|
}
|
|
481
657
|
const [singleId] = ids;
|
|
482
658
|
if (singleId === undefined)
|
|
659
|
+
return; // single non-string already filtered upstream
|
|
660
|
+
if (resolution.kind === "hydrated") {
|
|
661
|
+
slot[key] = resolution.byId.get(singleId) ?? null;
|
|
483
662
|
return;
|
|
484
|
-
|
|
485
|
-
|
|
663
|
+
}
|
|
664
|
+
if (!resolution.liveIds.has(singleId))
|
|
665
|
+
slot[key] = null;
|
|
666
|
+
}
|
|
667
|
+
// Walk the candidate's path from the output bag to the parent container
|
|
668
|
+
// of its leaf slot, copy-on-writing each container the first time it's
|
|
669
|
+
// descended so the caller's `decoded` bag stays untouched. Containers
|
|
670
|
+
// cloned by an earlier candidate (identity no longer matches `decoded`)
|
|
671
|
+
// are reused, so sibling references in the same row/group land in one
|
|
672
|
+
// clone. Returns the writable parent object and the leaf key, or null if
|
|
673
|
+
// any segment's runtime shape doesn't match the declared structure
|
|
674
|
+
// (hand-edited / migrated bags).
|
|
675
|
+
function takeWritableSlot(outBag, decoded, path) {
|
|
676
|
+
let outContainer = outBag;
|
|
677
|
+
let decContainer = decoded;
|
|
678
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
679
|
+
const seg = path[i];
|
|
680
|
+
if (seg === undefined)
|
|
681
|
+
return null;
|
|
682
|
+
const outChild = readSegment(outContainer, seg);
|
|
683
|
+
const decChild = readSegment(decContainer, seg);
|
|
684
|
+
let writable = outChild;
|
|
685
|
+
// Still the shared decoded child → clone it into the output tree.
|
|
686
|
+
if (outChild === decChild) {
|
|
687
|
+
const clone = cloneContainer(outChild);
|
|
688
|
+
if (clone === null)
|
|
689
|
+
return null;
|
|
690
|
+
writeSegment(outContainer, seg, clone);
|
|
691
|
+
writable = clone;
|
|
692
|
+
}
|
|
693
|
+
outContainer = writable;
|
|
694
|
+
decContainer = decChild;
|
|
695
|
+
}
|
|
696
|
+
const leaf = path[path.length - 1];
|
|
697
|
+
if (typeof leaf !== "string")
|
|
698
|
+
return null;
|
|
699
|
+
if (!isPlainObject(outContainer))
|
|
700
|
+
return null;
|
|
701
|
+
return { parent: outContainer, leafKey: leaf };
|
|
702
|
+
}
|
|
703
|
+
function readSegment(container, seg) {
|
|
704
|
+
if (typeof seg === "number") {
|
|
705
|
+
return Array.isArray(container) ? container[seg] : undefined;
|
|
706
|
+
}
|
|
707
|
+
return isPlainObject(container) ? container[seg] : undefined;
|
|
708
|
+
}
|
|
709
|
+
function writeSegment(container, seg, value) {
|
|
710
|
+
if (typeof seg === "number") {
|
|
711
|
+
if (Array.isArray(container))
|
|
712
|
+
container[seg] = value;
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
if (isPlainObject(container))
|
|
716
|
+
container[seg] = value;
|
|
717
|
+
}
|
|
718
|
+
// Shallow clone an array or plain object; null for any other shape (the
|
|
719
|
+
// path expected a container but the stored value isn't one).
|
|
720
|
+
function cloneContainer(value) {
|
|
721
|
+
// `Array.isArray` widens to `any[]`; cast before spread so the clone
|
|
722
|
+
// stays `unknown[]` rather than leaking `any` into the walk.
|
|
723
|
+
if (Array.isArray(value))
|
|
724
|
+
return [...value];
|
|
725
|
+
if (isPlainObject(value))
|
|
726
|
+
return { ...value };
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
729
|
+
function isPlainObject(value) {
|
|
730
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
486
731
|
}
|
|
487
732
|
// Returns `null` to mean "no candidates from this key" (skip), or
|
|
488
733
|
// the ids to feed into the group's batch query. Mirrors the storage-
|
|
@@ -490,7 +735,7 @@ function applyOrphanToSlot(rowObj, subKey, multiple, ids, liveIds) {
|
|
|
490
735
|
// going to be skipped. Callers pass values already decoded through
|
|
491
736
|
// `decodeMetaBag`, so legacy object shapes have been healed to plain
|
|
492
737
|
// ids by the time this runs.
|
|
493
|
-
function
|
|
738
|
+
function referenceCandidateIds(target, value) {
|
|
494
739
|
if (target.multiple) {
|
|
495
740
|
if (!Array.isArray(value))
|
|
496
741
|
return null;
|
|
@@ -498,12 +743,6 @@ function orphanCandidateIds(target, value) {
|
|
|
498
743
|
}
|
|
499
744
|
return typeof value === "string" ? [value] : null;
|
|
500
745
|
}
|
|
501
|
-
function referenceTargetOf(field) {
|
|
502
|
-
if (!field)
|
|
503
|
-
return undefined;
|
|
504
|
-
return field
|
|
505
|
-
.referenceTarget;
|
|
506
|
-
}
|
|
507
746
|
/**
|
|
508
747
|
* Decode a raw meta bag (as returned by drizzle's JSON-mode column)
|
|
509
748
|
* into the plugin-typed shape RPC consumers expect. Unregistered keys
|
|
@@ -532,20 +771,30 @@ function decodeFieldValue(field, value) {
|
|
|
532
771
|
if (isRepeaterField(field) && Array.isArray(value)) {
|
|
533
772
|
return value.map((row) => healRepeaterRow(field, row));
|
|
534
773
|
}
|
|
774
|
+
if (isTemporalField(field) && field.returns === "date") {
|
|
775
|
+
return projectTemporalDate(field.inputType, value);
|
|
776
|
+
}
|
|
535
777
|
return coerceOnRead(field.type, value);
|
|
536
778
|
}
|
|
537
|
-
function
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
779
|
+
function isTemporalField(field) {
|
|
780
|
+
return (field.inputType === "date" ||
|
|
781
|
+
field.inputType === "datetime" ||
|
|
782
|
+
field.inputType === "time");
|
|
783
|
+
}
|
|
784
|
+
// `.returns("date")` decode projection. All three variants anchor to
|
|
785
|
+
// UTC — `date` at UTC midnight, `time` on 1970-01-01 UTC — so the
|
|
786
|
+
// wall-clock components survive every server/browser timezone
|
|
787
|
+
// combination (decode runs server-side, often UTC on Workers, while
|
|
788
|
+
// the admin formats in the viewer's browser). Consumers read the
|
|
789
|
+
// parts back with `getUTC*` or `timeZone: "UTC"` formatting; the
|
|
790
|
+
// projection is the exact inverse of the write-side `Date` encoding
|
|
791
|
+
// (`formatTemporalValue`). Unparseable stored values round to "no
|
|
792
|
+
// value", matching the forgiving-read posture of `coerceOnRead`.
|
|
793
|
+
function projectTemporalDate(inputType, value) {
|
|
794
|
+
if (typeof value !== "string" || value === "")
|
|
795
|
+
return undefined;
|
|
796
|
+
const parsed = new Date(anchorTemporalUtc(inputType, value));
|
|
797
|
+
return Number.isNaN(parsed.getTime()) ? undefined : parsed;
|
|
549
798
|
}
|
|
550
799
|
function healRepeaterRow(field, row) {
|
|
551
800
|
if (!row || typeof row !== "object" || Array.isArray(row))
|
|
@@ -609,72 +858,6 @@ export async function loadMeta(ctx, table, idColumn, id, findField) {
|
|
|
609
858
|
return decodeMetaBag(findField, row?.meta);
|
|
610
859
|
}
|
|
611
860
|
// --- internals below ---------------------------------------------------
|
|
612
|
-
function coerceToType(type, key, value) {
|
|
613
|
-
switch (type) {
|
|
614
|
-
case "string":
|
|
615
|
-
return coerceString(key, value);
|
|
616
|
-
case "number":
|
|
617
|
-
return coerceNumber(key, value);
|
|
618
|
-
case "boolean":
|
|
619
|
-
return coerceBoolean(key, value);
|
|
620
|
-
case "json":
|
|
621
|
-
return coerceJson(key, value);
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
function coerceString(key, value) {
|
|
625
|
-
if (typeof value === "string")
|
|
626
|
-
return value;
|
|
627
|
-
if (typeof value === "number" && Number.isFinite(value))
|
|
628
|
-
return String(value);
|
|
629
|
-
if (typeof value === "boolean")
|
|
630
|
-
return String(value);
|
|
631
|
-
throw MetaSanitizationError.invalidValue({ key });
|
|
632
|
-
}
|
|
633
|
-
function coerceNumber(key, value) {
|
|
634
|
-
if (typeof value === "number" && Number.isFinite(value))
|
|
635
|
-
return value;
|
|
636
|
-
if (typeof value === "string") {
|
|
637
|
-
// Empty string comes from cleared form inputs; the admin dispatcher
|
|
638
|
-
// already sends `null` for those, but a direct RPC caller might send
|
|
639
|
-
// "" — reject here so we don't silently coerce to 0 (`Number("") === 0`).
|
|
640
|
-
if (value.trim() === "") {
|
|
641
|
-
throw MetaSanitizationError.invalidValue({ key });
|
|
642
|
-
}
|
|
643
|
-
const parsed = Number(value);
|
|
644
|
-
if (Number.isFinite(parsed))
|
|
645
|
-
return parsed;
|
|
646
|
-
}
|
|
647
|
-
if (typeof value === "boolean")
|
|
648
|
-
return value ? 1 : 0;
|
|
649
|
-
throw MetaSanitizationError.invalidValue({ key });
|
|
650
|
-
}
|
|
651
|
-
function coerceBoolean(key, value) {
|
|
652
|
-
if (typeof value === "boolean")
|
|
653
|
-
return value;
|
|
654
|
-
if (value === 1 || value === "1" || value === "true")
|
|
655
|
-
return true;
|
|
656
|
-
if (value === 0 || value === "0" || value === "false")
|
|
657
|
-
return false;
|
|
658
|
-
throw MetaSanitizationError.invalidValue({ key });
|
|
659
|
-
}
|
|
660
|
-
function coerceJson(key, value) {
|
|
661
|
-
// json keys take anything round-trippable through JSON.stringify —
|
|
662
|
-
// we reject values that throw (BigInt) or silently drop (functions,
|
|
663
|
-
// Symbols) so reads don't hand back `undefined` for something a
|
|
664
|
-
// plugin thought it stored. TS types JSON.stringify as always-
|
|
665
|
-
// string, but at runtime it returns `undefined` for unserializable
|
|
666
|
-
// inputs — hence the cast.
|
|
667
|
-
try {
|
|
668
|
-
const encoded = JSON.stringify(value);
|
|
669
|
-
if (encoded === undefined) {
|
|
670
|
-
throw MetaSanitizationError.invalidValue({ key });
|
|
671
|
-
}
|
|
672
|
-
return JSON.parse(encoded);
|
|
673
|
-
}
|
|
674
|
-
catch {
|
|
675
|
-
throw MetaSanitizationError.invalidValue({ key });
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
861
|
function assertEncodedSize(key, value) {
|
|
679
862
|
const encoded = JSON.stringify(value);
|
|
680
863
|
if (encoded === undefined)
|