clockworklabs/SpacetimeDB · error · TypeError

exporting something that is not a spacetime export

Error message

exporting something that is not a spacetime export

What it means

During `buildRawModuleDefV10`, `registerModuleExports` iterates the module's exports and requires every non-`default` export to be a spacetime export — an object/function carrying the hidden `registerExport` marker (`isModuleExport`). Plain functions, constants, classes, or third-party values exported from the module entry throw 'exporting something that is not a spacetime export'. The `default` export (the schema/router) is explicitly skipped.

Solutions

  1. Remove non-spacetime exports from the module entry file: keep helpers module-local (no `export`) or move them to a separate non-entry file.
  2. Keep only spacetime exports (reducers, procedures, views, tables via the schema, HTTP handlers/router) and the `default` schema/router export in the entry.
  3. If you need to expose constants, put them in a sibling file that is not the module entry compiled for publish.

Example fix

// before (module entry)
export const TICK_RATE = 5; // plain constant
export const tick = spacetimedb.reducer(...);
export default schema({ ... });

// after (module entry)
const TICK_RATE = 5; // module-local
export const tick = spacetimedb.reducer(...);
export default schema({ ... });
Defensive patterns

Strategy: type-guard

Validate before calling

// Before publish, scan entry exports and fail with your own message listing offenders.
function assertOnlySpacetimeExports(exports: Record<string, unknown>) {
  const bad = Object.entries(exports)
    .filter(([name, exp]) => name !== 'default' && !spacetimeExportNames.has(exp));
  if (bad.length > 0) throw new Error(`non-spacetime exports: ${bad.map(b => b[0]).join(', ')}`);
}
// spacetimeExportNames = a Set you populate whenever you create a reducer/view/handler

Type guard

// Structural guard mirroring isModuleExport (needs access to the same symbol module):
function isModuleExport(x: unknown): boolean {
  return (typeof x === 'function' || typeof x === 'object') && x !== null && 'registerExport' in (x as object);
}

Prevention

When it happens

Trigger: `export const VERSION = '1.0';`, `export function helper() {}`, `export class Foo {}`, or re-exporting a non-spacetime symbol from the module entry; exporting a type-assertion helper next to reducers. Submodule dispatch skips non-exports internally via `ignoreNonModuleExports: true`, but the top-level registration does not.

Common situations: Growing a module entry with utility exports (`export { connect }` helpers for tests); barrel files re-exporting test fixtures; library modules whose public API mixes spacetime exports with ordinary constants.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@eddf9f5014 (2026-08-20). Data as JSON: /api/errors/cc2b02a43c661484. Report an issue: GitHub.

Appendix: source

Thrown at crates/bindings-typescript/src/server/schema.ts:829

}

function registerModuleExports(
  schema: SchemaInner,
  exports: object,
  opts?: { ignoreNonModuleExports?: boolean }
) {
  if (schema.exportsRegistered) {
    return;
  }
  schema.exportsRegistered = true;

  for (const [name, moduleExport] of Object.entries(exports)) {
    if (name === 'default') continue;
    if (!isModuleExport(moduleExport)) {
      if (opts?.ignoreNonModuleExports) {
        continue;
      }
      throw new TypeError('exporting something that is not a spacetime export');
    }
    checkExportContext(moduleExport, schema);
    moduleExport[registerExport](schema, name);
  }
}

export function schema<
  const H extends Record<string, SchemaEntry>,
  const E extends EnvironmentSchema = {},
>(
  entries: H,
  moduleSettings?: ModuleSettings<E>
): Schema<SchemaDefForEntries<H> & { env: E }> {
  const ctx = new SchemaInner<SchemaDefForEntries<H> & { env: E }>(ctx => {
    ctx.moduleDef.environment = environmentDeclarations(
      moduleSettings?.env ?? {}
    );
    // Apply module settings.

View on GitHub (pinned to eddf9f5014)