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

@quad-education/sdk

v0.7.0

Published

The @quad-education/sdk contract, its in-browser engine, transports and contract tests

Readme

@quad-education/sdk

The contract every Quad app is built against. It has no UI framework dependency (D24): React helpers live in @quad-education/sdk-react. In V1 an in-browser engine implements it inside the demo shell; the platform implements the same contract later, and both must pass the contract suite in contract-tests/.

import { connect } from '@quad-education/sdk/client'

const sdk = await connect()
const { items } = await sdk.school.students.list({ where: { yearLevel: 'Y9' } })

Modules: auth (who is signed in, and whether the school made them an app admin: me(), appAdmins(), D133), school (canonical school data, filtered by your scopes and the user's visibility), db (your app's own data), notify, clock. Slice B adds tasks, schedule, cron and audit.

Access control for app data (D25)

The engine enforces who can read and write each record. Declare it in the schema:

consent: entity({
  fields: {
    studentId: ref('school.students'),
    status: enumOf(['pending', 'given', 'declined']),
    staffNote: text({ optional: true, visibleTo: ['staff'] }),
  },
  subject: 'studentId',
  access: {
    staff: { read: 'all', create: 'all', update: 'all', delete: 'all' },
    parent: { read: 'subject', update: 'subject' },
    student: { read: 'subject' },
  },
})
  • subject names one ref('school.students') field: the student the record is about.
  • Rules, per role and operation (read, create, update, delete):
    • subject: the user can see the record's student (staff: everyone; parents: their own children, never through a restricted link; students: themselves).
    • own: the user created the record.
    • all: every record. Staff only.
    • An array means any of them. Anything not listed is denied, and an entity with no access block is staff-only.
  • visibleTo on a field: other roles never receive it, can't filter on it and can't write it. Its type is optional unless every role your app serves may see it.
  • What you get back:
    • get on a record you can't read: NOT_FOUND, the same as a missing record.
    • list, filters, pages and include only ever cover records you can read.
    • A denied operation or write: NOT_PERMITTED. So is setting subject to a student you can't see.
  • Scheduled and cron jobs run as the app (system principal) with full access to its data.

Pattern: shared record staff-only, per-student rows for parents

subject is a single student in v0. When something concerns many students (an excursion, a class event), don't give the shared record a subject or parent access. Keep it staff-only, and create one row per student for anything parents see or do:

excursion: entity({ fields: { title: text(), date: date() } }), // staff-only
attendee: entity({
  fields: { excursionId: ref('excursion'), studentId: ref('school.students'), consent: enumOf([...]) },
  subject: 'studentId',
  access: { staff: { read: 'all', create: 'all', update: 'all', delete: 'all' },
            parent: { read: 'subject', update: 'subject' } },
})

Parents see and answer only their own child's rows. Copy the few excursion details they need (title, date) onto the row, or show them from the notification or task that sends them there. An include of the staff-only excursion returns null for parents.

Querying app data: sorts and ranges need an index (D37)

On the platform, app data lives in DynamoDB, so sdk.db lists follow the same rule everywhere, including the in-browser engine:

  • Equality, in and ne filters work on any field you can see. They only ever scan your app's own data.
  • orderBy and range filters (gt, gte, lt, lte) need an index. Declare one in the schema with indexes: [['status', 'dueDate']]. Every entity is also indexed on id and createdAt.
  • An index covers a query when:
    • its leading fields have plain equality filters;
    • the range filter is on the next field;
    • the orderBy fields follow the index in order, all in one direction.
// indexes: [['status', 'dueDate']]
tasks.list({ where: { status: 'open' }, orderBy: { field: 'dueDate' } }) // covered
tasks.list({ where: { status: 'open', dueDate: { lt: today } } }) // covered
tasks.list({ orderBy: { field: 'dueDate' } }) // VALIDATION: needs an equality on status first

The types allow orderBy and ranges only on indexed fields, so most mistakes are compile errors. The engine checks the rest at runtime and returns VALIDATION with the index to add. sdk.school, tasks.list and audit.list aren't affected.

Versions

  • 0.6.0 (D133, 30 Oct 2026). App admins: auth.me() gains appAdmin for a user (staff the school put in charge of the app), and auth.appAdmins() lists them for staff and jobs (FORBIDDEN for a student or parent). The engine gains the platform-only setAppAdmins(appId, personIds), the testing kit createTestPlatform({ appAdmins }) and setAppAdmins, and the contract tests the setAppAdmins control. A contract change; every @quad-education/* package moves to 0.6.0.

  • 0.4.0 (D103, 21 Oct 2026). A small canonical model, and no field-level coverage anywhere an app can see it (a contract change across all the @quad-education/* packages, which move together). The model is 13 entities and 56 fields: the foundation (schools, students, staff, parents, parentStudentLinks, cohorts, cohortMemberships) and the extended ones some school systems supply (periods, rooms, lessons, sessionAttendance, medicalAlerts, medicalNotes). Removed: enrolments, courses, courseEnrolments, subjects, calendarEvents and sdk.school.calendar, assignments, submissions, results, studentAbsences, attendanceSummaries, behaviours, achievements, learningSupportNeeds, medicalEvents, employeeAbsences, applicants and pastoralNotes; and fields: students.preferredName and the seven sensitive demographic fields, staff.positionTitle and phone, a parent's title, mobile, address and language, livesWith, primaryContact, cohorts.parentCohortId and subjectId, the register's comment and minutesLate, a medical alert's severity and actionPlan, and a note's title, conditionId and eventId. Added: students.gender, parents.phone, cohorts.subject (text, such as "English"), the form cohort kind, and the capability map (CAPABILITIES, capabilityOf, capabilitiesOf, entityKind in @quad-education/canonical). Changed: apps no longer declare the fields they read, so the manifest data block is accepted and ignored (manifestWarnings says to remove it), the engine strips nothing, and core is gone; scopes are checked directly. Ids, references and names are required and every other field is typed optional, because a school's system may not supply it. A parent's contact flags (receivesCorrespondence, canConsent, contactRestricted) are optional too, and where a school's system does not supply one the parent is treated as contactable (D17 amended by D103). The lms backing goes, and fieldTier, coreFields and varyingFields with it.

  • 0.3.0 (D101, 20 Oct 2026). Every app runs in a school, so what 0.2.0 added for apps with no school is removed (a removal across all the @quad-education/* packages, which move together). Gone from the main entry: the account and guest principals, ActingAs, the account directory and staticDirectory, the account and guest results of auth.me(), the NOT_AVAILABLE error code (sdk.school is always available: the fictional school in dev and the Sandbox, the real school in a school instance), the { account } recipient, and { role } taking any string (it is a school role again). Gone from @quad-education/sdk/client: createHttpTransport, connectHttp, the live connection (connectLive, getLiveStatus, signIn, continueAsGuest, SignInRequired) and the live branch of connect(), which now only talks to the shell. Gone from @quad-education/sdk/engine (platform-side only): school: false, accounts, accountsFor, a per-app timezone, attachApp, hasApp, setAccounts and isStandalone. Gone from @quad-education/sdk/contract-tests: the STANDALONE_APP, ACCOUNT_APP and TEST_ACCOUNTS fixtures and the school option. @quad-education/sdk-react loses SignInChoices and SignInActions; @quad-education/schema loses the manifest's guests, the reserved role guest and the SDK-version rule for guests. The subject rule now applies to every app. Kept: the createdBy, createdAt implicit index (a list of one's own records can be sorted), and the engine's state behind an AppStore, so a school instance's data API can run the same rules over DynamoDB; its inbox, runs, auditLog, effectsSnapshot and runPrincipal still return promises. Guest access inside a school instance is wanted, and comes back as a contract change when it is designed. Every earlier version, 0.2.0 included, is withdrawn from npm after this one is out.

  • 0.2.0 (20 Oct 2026, withdrawn). Account and guest principals, NOT_AVAILABLE, the manifest guests flag and the live connection, for apps that were to run live outside a school (V2c, D100). Removed again by D101 in 0.3.0.

  • 0.1.4 (19 Oct 2026). The same SDK calls. The canonical model grows to what V2s S4 builds, which is what an app's sdk.school.<entity> and its types come from, so 0.1.3 cannot use any of it: the attendance register (sessionAttendance, which must be filtered by student or date; studentAbsences, attendanceSummaries), behaviour and achievements, learning support, medical events and notes, staff absences and admissions applicants, and the demographic fields on students. It also adds two sensitivity tiers, sensitive and hr, which are staff-only (a student or parent never receives them), audited, and need manifest version 1 and a reason. A version 1 manifest still needs 0.1.3 or newer.

  • 0.1.3 (18 Oct 2026). The same SDK calls, plus what V2s needs to run: manifest version 1 (data, reasons, roles and schoolRoles), declared-field types and stripping for school data, the new school entities (periods, rooms, subjects, lessons, with lessons generated on demand) and the test toolkit that understands all of it. A version 1 manifest needs 0.1.3 or newer: the test toolkit in 0.1.2 only reads version 0 manifests and fails every test with invalid manifest: manifestVersion: expected 0. The starter templates ask for ^0.1.3.

  • 0.1.2 (9 Oct 2026). The same contract as 0.1.1, and the first version to stay on npm. All the @quad-education/* packages were published on 9 Oct 2026 (0.1.0, and 0.1.1 for this one), then unpublished the same day to move the npm org to a new owner, and npm never allows an unpublished version again; every package is now 0.1.2. SDK_VERSION (sdk.version) now matches the package version; it had stayed at 0.1.0 through 0.1.1.

  • 0.1.1 (8 Oct 2026). Contract change: sdk.db list orderBy and range filters need a declared index (or id/createdAt), and both the types and the engine enforce it (D37). The schema DSL keeps indexes as literal types, and DbListOptions gains DbWhere, DbOrderBy and IndexedField. The packages also moved from @quad/* to @quad-education/*, because the @quad npm scope belongs to someone else (D41); only the import paths change, and the version stays 0.1.1 because nothing was published under the old name. Before the first publish, the engine also stopped treating names inherited from Object.prototype (constructor, toString) as entities or fields; they now fail with VALIDATION, as any unknown name does.

  • 0.1.0 (renamed 7 Oct 2026). This was @orca/sdk 0.1.0, with the same contract and behaviour, renamed for Quad:

    • the @orca/* packages became @quad/*;
    • the Orca* exports became Quad* (QuadError, QuadSdk, QuadApp and the rest);
    • orca gen and orca-env.d.ts became quad gen and quad-env.d.ts;
    • the handshake and jobs messages became quad:*.

    The version stays 0.1.0 because nothing was published under the old name.