clockworklabs/SpacetimeDB · error · TypeError

Table defines a schedule, but it seems like the associated…

Error message

Table ${tableName} defines a schedule, but it seems like the associated function was not exported.

What it means

A schedule needs the function that will run when rows mature. Resolution looks the reducer/procedure up via `this.functionExports.get(reducer())`; if the function referenced by the schedule was never exported from the module, `functionName` is undefined and the build fails with 'the associated function was not exported'. The scheduled function must be a module export registered in the same schema.

Solutions

  1. Export the scheduled function from the module: `export const processMessage = spacetimedb.reducer({ onSchedule: scheduledMessages }, ...)`.
  2. With the modern `onSchedule` API the function and schedule are declared together — prefer it over the legacy `schedule()` API to avoid identity mismatches.
  3. If using a factory, call it once, store the result, and use that same object for both the export and the schedule.

Example fix

// before (legacy style)
const processMessage = () => spacetimedb.reducer(...);
const messages = table({ schedule: scheduleRef }, row); // fn never exported

// after
export const processMessage = spacetimedb.reducer(
  { onSchedule: scheduledMessages },
  { scheduledMessage: scheduledMessages.rowType },
  (ctx, { scheduledMessage }) => {}
);
Defensive patterns

Strategy: validation

Validate before calling

// With factories, register the exact object you export.
const registeredFns = new Set<Function>();
function exportOnce<T extends Function>(name: string, fn: T): T {
  if (registeredFns.has(fn)) throw new Error(`${name} must be exported exactly once`);
  registeredFns.add(fn);
  return fn;
}

Prevention

When it happens

Trigger: Using the legacy `schedule(fn)` table API where `fn` is not exported; defining the scheduled reducer but forgetting `export const ...`; the schedule referencing a function object different from the one actually exported (two closures created by a factory).

Common situations: Factory-created reducers (`const make = () => spacetimedb.reducer(...)`): the factory is called twice — once for the export, once for the schedule — so identity lookup fails; refactors that remove an export but leave a legacy schedule pointing at it.

Related errors


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

Appendix: source

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

          'Schedule target table is not part of this schema.'
        );
      }

      const scheduleAtCol = knownScheduleAtCol ?? table.scheduleAtCol;
      if (scheduleAtCol === undefined) {
        throw new TypeError(
          `Table ${tableName} defines a schedule, but it does not have a ScheduleAt column.`
        );
      }

      const functionName =
        knownFunctionName ??
        (reducer === undefined
          ? undefined
          : this.functionExports.get(reducer()));
      if (functionName === undefined) {
        const msg = `Table ${tableName} defines a schedule, but it seems like the associated function was not exported.`;
        throw new TypeError(msg);
      }
      const existingFunctionName = scheduledTables.get(tableName);
      if (existingFunctionName !== undefined) {
        throw new TypeError(
          `Table ${tableName} defines multiple schedules: ${existingFunctionName} and ${functionName}. A schedule table can only be used by one reducer or procedure.`
        );
      }
      scheduledTables.set(tableName, functionName);
      this.moduleDef.schedules.push({
        sourceName: undefined,
        tableName,
        scheduleAtCol,
        functionName,
      });
    }
  }

  resolveHttpRoutes() {

View on GitHub (pinned to eddf9f5014)