npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@plasius/schema

v1.4.7

Published

Entity schema definition & validation helpers for Plasius ecosystem

Readme

@plasius/schema

npm version Build Status coverage License Code of Conduct Security Policy Changelog

Entity definition & validation helpers for the Plasius ecosystem.

Apache-2.0. ESM + CJS builds. TypeScript types included.


Installation

npm install @plasius/schema

Personal names and display names

Use validateName for first, middle, and last names. It accepts Unicode letters and combining marks together with common name punctuation, but it does not accept digits. Use validateDisplayName for public display names, where Unicode decimal digits are also valid:

import { validateDisplayName, validateName } from "@plasius/schema";

validateDisplayName("Player 2"); // true
validateName("Player 2"); // false

Both validators reject empty values, control characters, unsupported symbols, and values longer than 256 characters.

Packaging policy (template baseline)

@plasius/schema is the baseline template for new @plasius/* packages. Dual-module packages should prefer .cjs outputs for CommonJS. If a package uses dist-cjs/*.js with type: module, it must generate dist-cjs/package.json with { "type": "commonjs" } and validate this in pack:check before publish.

Private-artifact policy

Signed contributor agreements and contributor acceptance records belong only in the approved access-controlled system outside source control. The zero-dependency repository gate inspects path metadata without opening, hashing, copying, or logging suspected private artifacts:

npm run privacy:check
npm run test:privacy
npm run build
npm run pack:check

privacy:check checks both filesystem metadata and the proposed Git index. It rejects CSV files, contributor/CLA registry variants, contributor acceptance, signature, and submission record categories, signed CLA/contributor agreement storage, and paths that combine privacy and registry markers. Explicit public process, template, schema, validator, and policy files remain allowed only as terminal path components; matching private-record categories used as directories fail closed regardless of their descendants. Unicode compatibility normalization occurs before separator folding so hierarchical, case-variant, and compatibility-separated protected paths cannot evade the same path-only classification. An unstaged deletion remains a failure until the deletion is staged. Invalid Git worktree metadata, symbolic links, and non-regular entries fail closed, and failures report only rule IDs and counts.

pack:check requires the exact dist, THIRD_PARTY_NOTICES.md, and unicode package.json.files entries, then retains prefix-stripped raw member identities and cardinality while comparing the canonical npm pack --dry-run manifest with the package's exact public allowlist. Duplicate members, raw aliases, and many-to-one normalization collisions fail without logging member values. The same zero-dependency inventory policy runs over the digest-verified sealed tarball immediately before publication. The isolated npm cache is removed whether the check passes or fails. CI runs the repository gate before dependency installation; release preparation and publication repeat the gate. These path controls are defense in depth and do not replace secret scanning, access controls, or incident response.


Player-system event contracts

The package exports versioned contracts for a blob-backed, privacy-safe player event pipeline:

import {
  PlayerSystemCuratedSnapshotSchema,
  PlayerSystemNormalizedEventBatchSchema,
  PlayerSystemObservedEventBatchSchema,
} from "@plasius/schema";

The contracts cover raw observed-event ingress, normalized batches with deterministic keys, and curated per-player Event Log/Achievement snapshots. Only the stable playerId is treated as a personal identifier. It is retained for storage partitioning and pseudonymized by sanitizeForLog; unknown fields and hidden server truth are excluded by schema serialization.

Privacy-safe feedback contracts

The package exports closed, versioned contracts for feedback intake and reporting:

import {
  FeedbackBugHealthMetricsProjectionSchema,
  FeedbackBugPacketSchema,
  FeedbackCommittedAcceptanceEvidenceSchema,
  FeedbackPublicSummarySchema,
  FeedbackRichTextAstSchema,
  FeedbackReviewPacketSchema,
  FeedbackTransientAnalysisRequestSchema,
  validateFeedbackTransientAnalysisRequest,
} from "@plasius/schema";
import type {
  FeedbackBugTransientAnalysisRequest,
  FeedbackReviewTransientAnalysisRequest,
} from "@plasius/schema";

Renderer and browser packages that need only the game-diagnostics vocabulary should import @plasius/schema/feedback-diagnostics-vocabulary. This lightweight entrypoint exports the canonical surface/provenance pairs, buckets, feature/counter/error allowlists, bounds, and discriminated type without loading the schema builder. Server validation and analysis tools should import @plasius/schema/feedback-diagnostics, which adds the canonical runtime schema. Both entrypoints derive from the same source definitions and preserve structural parity in ESM and CommonJS builds, so consumers cannot maintain a second diagnostics dialect. Use validateFeedbackGameDiagnostics() when a consumer needs a validation result whose value retains the correlated FeedbackGameDiagnostics TypeScript type.

Free-form feedback exists only in FeedbackRichTextAstSchema, which is intended for a transient no-retention scanner. Its text nodes are marked high-PII, clear-on-storage, and omit-from-logs. The interoperable document is rooted at { type: "doc", version: "1.0.0", schemaVersion: "1", children }; it permits only paragraph and bullet-list-item blocks with bounded indentation and text nodes with bold, italic, and underline marks. Narrative must already be NFKC normalised against the exported unicode-15.1.0-nfkc-v1 profile before validation. Assignment is checked against the original code units before host NFKC is invoked. Its 4,000-character ceiling counts Unicode code points and inter-block newlines consistently with the private scanner, with a separate 8,000 UTF-16-unit safety ceiling. HTML, Markdown links, protocol-relative links, and http, https, ftp, mailto, javascript, data, blob, and file schemes are rejected.

FeedbackTransientAnalysisRequestSchema is closed and discriminated by purpose. A bug analysis request must carry a closed surfaceId; the service must verify it against the caller's capability-projected catalog before handing ciphertext to the private scanner. A review analysis request must omit surfaceId. The exported bug/review request types preserve the same discriminator for TypeScript consumers; validateFeedbackTransientAnalysisRequest() preserves that union on successful runtime validation.

Final bug and review JSON bodies deliberately contain no submissionId. Consuming HTTP APIs require Idempotency-Key as the sole final-submission correlation mechanism and keep it in the isolated abuse/idempotency control plane. It must never be copied into a draft, accepted packet, report, log, or public/Admin/MCP projection.

The complete Unicode 15.1 unassigned corpus is shipped independently of the host runtime:

import {
  FEEDBACK_UNICODE_PROFILE_ENDPOINT_SHA256,
  FEEDBACK_UNICODE_PROFILE_ID,
  containsFeedbackUnicodeProfileUnsupportedText,
  isFeedbackUnicodeProfileUnassigned,
} from "@plasius/schema/feedback-unicode-profile";

containsFeedbackUnicodeProfileUnsupportedText rejects every code point unassigned in Unicode 15.1 and every lone UTF-16 surrogate. The code-point helper decodes canonical, delta ULEB128/base64url once and uses binary search over 707 half-open ranges. Non-JavaScript scanners can consume the same language-neutral corpus from @plasius/schema/unicode/feedback-unicode-15.1.0-unassigned.json and verify its published endpoint digest. The corpus is generated from the exact build-time dependency @unicode/[email protected]; run npm run unicode:check to prove the checked-in artifact is current. See ADR-0007 and third-party notices.

The three typed feedback subpaths publish both modern exports.types entries and exact typesVersions fallbacks. This keeps their declarations resolvable for consumers that still compile CommonJS with classic TypeScript moduleResolution: "node", without opening a wildcard route for unknown subpaths.

Validated reusable fragments retain exact type and version metadata when embedded in parent contracts. This makes the output of the encrypted-envelope, derived-analysis, and game-diagnostics schemas directly composable into their request or packet schemas without a metadata-stripping step.

Accepted packet, diagnostics, report, checkpoint, and public-summary schemas cannot express narrative, reporter identity or pseudonyms, network/client metadata, arbitrary URLs, pixels, quotes, summaries, embeddings, content hashes, or model traces. Game surfaces, provenance contracts, features, counters, and error codes are closed enums rather than attacker-controlled safe-looking strings. The public summary omits counts, averages, and trends entirely when the ten-review privacy threshold is not met and always carries 13 canonical UTC week slots, using suppressed gaps where a week is below the same threshold. A trend is emitted only with a preceding 90-day comparison that independently meets the ten-review threshold; the schema derives its delta and enforces up/down only at a change of at least 0.1 stars.

Hourly reports bind targets to the closed surface registry and include renderer/backend/viewport/frame-rate/frame-time plus closed diagnostic feature/counter/error distributions. Release IDs and build IDs are server-registry values: intake clients never submit them, and consumers must resolve them from trusted deployment metadata before packet construction. Advisory trigger counts and ordered recommendation IDs are derived exactly from the report distributions and processor lag rather than accepted as free-standing claims. Feedback context caps remaining bug cooldown at 24 hours and review suppression at 30 days; acceptance receipts distinguish bugs from reviews and accept only the five-step bug ladder or exact 30-day review period.

FeedbackCommittedAcceptanceEvidenceSchema is a separate, durable proof that the control-plane commit for one immutable packet completed. It contains only the canonical packet UUID, bug/review kind, and the packet's canonical server-owned UTC acceptance time. Canonical timestamp validation rejects impossible calendar or clock values as ordinary closed validation failures; it does not expose the rejected value or a native date exception. It cannot express a control-state key, reservation, idempotency value, reporter pseudonym, request metadata, Blob locator, content hash, narrative, ciphertext, or pixels, and its PII audit is empty. Only a trusted delivery worker may construct this evidence after an atomic control-plane commit; it is not an intake request. Report processors must select packets through this evidence rather than by enumerating packet storage, preventing an immutable Blob written before a failed commit from entering aggregates. See ADR-0011.

FeedbackBugHealthMetricsProjectionSchema is the separate trusted input for facts that accepted packets cannot supply: application rejection counts, privacy-safe traffic denominators, and allowlisted abuse-control block bands. It covers one exact UTC hour, is finalized no earlier than the window end, and contains no raw edge event, request, reporter, client, route, narrative, or pixel data. Abuse bands are unique, non-zero, and ordered by the frozen FEEDBACK_ABUSE_BLOCK_BANDS vocabulary. A producer must derive the projectionId deterministically from the hour and consumers must verify that binding before materialization; the schema deliberately performs no environment-dependent hashing. See ADR-0012.

Materialization manifests may identify a revision as a metrics-source correction when an independently finalized identifier-free metric changed but no packet arrived late. That reason requires lateArrivalCount: 0; ordinary late-arrival corrections continue to require a positive late-arrival count.

All feedback contracts opt in to recursive unknown-field rejection, including direct references and arrays of references. Unshaped references admit only type and id; shaped references admit only those keys plus their declared fields. Closed feedback vocabularies and definition objects are runtime frozen before schemas capture them. Before recursive cloning, strict validation also performs an iterative whole-value node/depth budget and inspects own data property descriptors. It rejects enumerable accessors, symbol keys, exotic prototypes, sparse or oversized arrays, and malformed proxy traps without evaluating accessor values or reflecting thrown messages, so malformed containers hidden under scalar fields cannot exhaust the call stack or expand into an attacker-selected logical array length. The general schema default remains backwards-compatible stripping:

const ClosedPacket = createSchema(fields, "closed-packet", {
  version: "1.0.0",
  piiEnforcement: "strict",
  unknownFields: "reject",
  identity: "exact",
});

Schema-level validators receive an optional, frozen SchemaValidationContext with only wasProvided(fieldName). This lets a closed discriminator distinguish omission from an explicit null without giving validation hooks access to raw field values.

Unknown-field errors name only the schema-owned container and never echo an untrusted key or value. Exact identity rejects a mismatched contract type or version. The schemas constrain data shape; consumers must still perform the documented private PII scan, discard narrative immediately, and validate again before every storage write. See the feedback contract design and ADR-0006.

Demo

npm run build
node demo/example.mjs

See demo/README.md for the local sanity-check scaffold.


Node.js Version

This project uses Node.js 24 by default. The version is pinned in the .nvmrc file.

If you use nvm, simply run:

nvm use

This ensures your local development environment matches the version used in CI/CD.


Usage Example

Imports

import {
  // core
  createSchema,
  field,
  getSchemaForType,
  getAllSchemas,
  Infer
} from "@plasius/schema";

1) Define fields with the field() builder

Below uses the fluent builder exported via field/field.builder.

const UserFields = {
  id: field.uuid().required().description("Unique user id"),
  email: field.email().required(),
  name: field.generalText().optional(),
  age: field.number().min(0).optional(),
  roles: field.array(field.string().enum(["admin", "user"]))
    .default(["user"])
    .description("RBAC roles"),
  createdAt: field.dateTimeISO().default(() => new Date().toISOString()),
};

Common methods (non‑exhaustive): .required(), .optional(), .default(v|fn), .description(text), and type‑specific helpers like .email(), .uuid(), .min(), .max(), .enum([...]).

Validation standards notes:

  • languageCode() checks a documented RFC 5646/BCP 47 subset case-insensitively, including grandfathered and private-use tags; two-letter primaries are checked against the package's ISO 639-1 registry. Registered 4-8 letter primaries remain outside this dependency-free subset.
  • uuid() accepts the RFC 9562 versions 1-8, Nil, and Max text forms. Legacy RFC 4122-named module paths remain compatibility aliases.
  • email() is a pragmatic dot-domain mailbox subset, not a claim of complete RFC 5322 grammar support.

Runtime rollout inherits governance.rfc-compliance-remediation.enabled; disabled consumers may retain the prior validators only during a documented migration window. Defaults are applied during validation when inputs are missing/undefined. Fields are required by default; call .optional() (or provide .default()) to allow omission. Use .internal() or .exposure("internal") on fields that the server may validate/store but must not include in client-facing payloads.

2) Create a versioned schema (enforces type + version)

export const UserSchema = createSchema(UserFields, "user", {
  version: "1.0.0",
  piiEnforcement: "strict",
});

// Strongly-typed entity from a schema definition
export type User = Infer<typeof UserSchema>;

Schemas are discoverable at runtime if you register them during module init:

// later in app code
const s = getSchemaForType("user"); // returns UserSchema
const all = getAllSchemas(); // Map<string, Map<string, Schema>> or similar

3) Validate data against the schema

const raw = {
  type: "user",
  version: "1.0.0",
  id: crypto.randomUUID(),
  email: "[email protected]",
};

const result = UserSchema.validate(raw);
if (result.valid && result.errors.length == 0) {
  // result.value is typed as User
  const user: User = result.value;
} else {
  // result.errors: ValidationError[] (path/code/message)
  console.error(result.errors);
}

If your validation layer also exposes a throwing variant (e.g. validateOrThrow(UserSchema, raw)), you can use that in places where exceptions are preferred.

  • Validation highlights:
    • Array item validators (e.g. .pattern(), .min(), .max()) run per element for primitive arrays.
    • Arrays of refs validate nested ref shapes (defaults, required fields, and validators) when provided.
    • Ref fields enforce their declared refType during validation, catching mismatches early.
    • PII helpers recurse through nested objects/arrays/refs so encrypted/hashed/cleared fields are handled throughout the structure (including array items).
    • ISO lists stay current (PS country code, SLE currency code) and the validation package exports validateLanguage for BCP 47 tags.
    • Numeric enums are enforced like string enums instead of accepting out-of-range values.
    • Immutable flags are honored on nested object/array/ref children when an existing entity is provided.
    • PII strict/warn enforcement runs on nested fields, preventing empty high-PII subfields from slipping through.
    • Validation deep-clones inputs before applying defaults, so caller-provided objects/arrays aren’t mutated and non-JSON-safe values (e.g., Date) are preserved.

3b) Serialize only client-safe fields

Schema serialization is separate from validation. serialize() keeps only schema-known fields and drops fields marked .internal() unless explicitly requested.

const PersistedUserSchema = createSchema(
  {
    id: field.string(),
    partitionKey: field.string().internal(),
    email: field.email(),
    audit: field.object({
      createdBy: field.string().internal(),
      createdAt: field.dateTimeISO(),
    }),
  },
  "persisted-user",
  { version: "1.0.0", piiEnforcement: "strict" }
);

const publicPayload = PersistedUserSchema.serialize({
  type: "persisted-user",
  version: "1.0.0",
  id: "123",
  partitionKey: "tenant-a",
  email: "[email protected]",
  audit: {
    createdBy: "admin-1",
    createdAt: "2026-03-09T00:00:00.000Z",
  },
  _rid: "cosmos-only",
});

// => {
//   type: "persisted-user",
//   version: "1.0.0",
//   id: "123",
//   email: "[email protected]",
//   audit: { createdAt: "2026-03-09T00:00:00.000Z" }
// }

4) Version enforcement in action

If either type or version doesn’t match the schema, validation fails.

const wrong = { type: "User", version: "2.0.0", id: "123", email: "x@x" };
const bad = UserSchema.validate(wrong);
// bad.valid === false; errors will include mismatches for type/version

5) Evolving your schema

Keep new versions side‑by‑side and migrate at edges:

export const UserV2 = createSchema(
  {
    ...UserFields,
    displayName: field.string().min(1).max(100).optional(),
  },
  "user",
  { version: "2.0.0", piiEnforcement: "strict" }
);

Write a small migration function in your app to transform User (1.0.0) → User (2.0.0) where needed.

6) Field-level upgrades

The schema supports a new .upgrade() method on fields to define field-level upgrade logic. This is useful when tightening restrictions on a field, such as reducing maximum length, strengthening format constraints, or normalizing values, without changing the field’s overall shape.

For example, suppose a displayName field previously allowed strings up to 60 characters, but you want to reduce the max length to 55 characters and normalize whitespace by trimming and collapsing spaces. You can define an upgrader function that attempts to fix old values to meet the new constraints:

const UserV3Fields = {
  ...UserFields,
  displayName: field.string().max(55).optional()
    .upgrade((oldValue) => {
      if (typeof oldValue !== "string") {
        return { ok: false, error: "Expected string" };
      }
      // Normalize whitespace: trim and collapse multiple spaces
      const normalized = oldValue.trim().replace(/\s+/g, " ");
      if (normalized.length > 55) {
        return { ok: false, error: "Display name too long after normalization" };
      }
      return { ok: true, value: normalized };
    }),
};

export const UserV3 = createSchema(UserV3Fields, "user", {
  version: "3.0.0",
  piiEnforcement: "strict",
});

Other typical upgrade strategies include:

  • Clamping numeric values to new min/max bounds
  • Remapping enum values to new sets or keys
  • Normalizing whitespace or case in strings
  • Converting deprecated flag values to new formats

During validation, if the entity version is less than the schema version and the field's value fails validation, the upgrader function will be invoked to attempt to transform the old value into a valid new value. If the upgrade succeeds and the transformed value passes validation, the upgraded value is used. If the upgrade fails or the transformed value still does not validate, validation errors will be returned.

Note: Field-level upgrades only run when the schema version is greater than the entity version and the field validation initially fails. This provides a convenient way to handle incremental field changes without requiring full schema migrations.

You can still write schema-level migration functions for larger or more complex changes that affect multiple fields or require more extensive transformation logic. Field-level upgrades complement these by handling simpler, localized upgrades directly within the schema definition.


Contributing

We welcome contributions! Please see CONTRIBUTING.md for guidelines.


License

Licensed under the Apache-2.0 License.

Release integrity

CI keeps the administrative contributor registry outside Git and npm package artifacts using exact, case-normalised path checks. Pull requests run on GitHub-hosted runners after same-repository admission; protected main CI uses the workflow-restricted self-hosted group. Release preparation and npm publication use GitHub-hosted runners with Node.js 24.18.0 LTS.

Dispatch cd.yml from protected main with phase: prepare. Release metadata lands through a unique pull request. After successful push-triggered CI for the exact merge SHA, the workflow dispatches a separate publish run from that same SHA. Publication fails closed if main, CI evidence, the version, tag, prerelease identity, artifact digests, or npm registry integrity differ.

The read-only validation job installs dependencies, validates the package, builds an SBOM, and seals an immutable tarball. The production job runs no package lifecycle or dependency code; it verifies the exact artifact hand-off and rechecks the collision-free exact raw-member inventory before publishing that tarball through npm OIDC with provenance. No npm write token or fallback is configured. The trusted publisher must be bound to Plasius-LTD/schema, cd.yml, production, and npm publish. Rollback is to disable cd.yml; never restore token-based publication.