@droplinked_inc/editor-core
v0.2.2
Published
Hardened core primitives (types + validators + tree helpers) for the Droplinked page editor. Successor to droplinked-editor-core, types-and-runtime split; UI components deferred to @droplinked_inc/editor-ui.
Readme
@droplinked_inc/editor-core
Hardened type-and-runtime core primitives for the Droplinked page editor.
Successor to the legacy [email protected]. Built from the
original .d.ts API surface with the runtime re-implemented from a
behavioural oracle of the published dist/.
Scope
This package now ships only the parts that can be reasoned about and tested without React rendering:
- Public TypeScript types (
Config,Data,ComponentData,Field, …) - zod schemas +
parseEditorData/parseEditorConfigvalidators - Pure-data helpers:
defaultData,walkTree,transformProps,migrate - DoS-safe utilities:
safeDeepMerge,safeDeepClone,isPlainObject - Centralised structural limits (
LIMITS) and forbidden keys (FORBIDDEN_KEYS)
The original substrate also shipped a Puck-fork editor UI (<Puck>,
<Render>, <AutoField>, <DropZone>, etc.). Those are deferred to a
sibling @droplinked_inc/editor-ui package so this core stays
testable in a Node-only jest environment and so the dependency footprint
(no Chakra, no DnD-Kit, no Framer Motion, no zustand) stays minimal.
icons is similarly deferred.
Install
pnpm add @droplinked_inc/editor-coreQuick start
import { parseEditorData, walkTree, migrate, transformProps } from '@droplinked_inc/editor-core';
// 1. Validate untrusted input at the trust boundary
const parsed = parseEditorData(rawJsonFromDB);
if (!parsed.ok) {
throw new Error(`Editor data invalid: ${parsed.issues.join('; ')}`);
}
// 2. Migrate forward through known schema revisions
const { data, report } = migrate(parsed.value);
// 3. Walk every content array (zones + root) and transform
const walked = walkTree(data, (content, { parentId, propName }) => {
return content.filter((c) => c.type !== 'Deprecated');
});
// 4. Apply per-type prop transforms (e.g. URL rewriting)
const finalData = transformProps(walked, {
Image: (props) => ({ ...props, src: rewriteToCdn(props.src as string) }),
root: (props) => ({ ...props, title: (props.title as string).trim() }),
});Security
This package replaces a previously hostile-published predecessor.
Every external boundary funnels through a zod schema; every recursive
helper is depth-and-breadth-capped (see LIMITS). See
THREAT_MODEL.md for the full attacker model and
the mitigations.
Highlights:
- No
eval, noFunction(), no dynamicrequireanywhere in the package. - Prototype-pollution-safe deep merge.
__proto__,constructor,prototypekeys are silently dropped from both base and patch, and intermediate scratch objects useObject.create(null)so a malicioustoStringetc. cannot shadowObject.prototype. - DoS caps on object depth, key count, array length, total node
count, registered components, categories, and zones (
LIMITS). - URL allow-list on shop-default favicon:
http:/https:/mailto:only —javascript:/data:URLs are rejected by the schema. - No React/UI surface in this package — that runtime lives in
@droplinked_inc/editor-ui(deferred) and can be reviewed independently.
Compatibility note for @droplinked_inc/editor-configs
The current editor-configs (PR #1) package uses a local
EditorConfigShape placeholder. This package exports
EditorConfigShape as an alias for Config, so the follow-up swap is a
one-line import change in editor-configs.
License
MIT.
