ObjectStackObjectStack

Seed Loader

Seed Loader protocol schemas

Seed Loader Protocol

Defines the schemas for metadata-driven seed data loading with automatic relationship resolution, dependency ordering, and multi-pass insertion.

Architecture Alignment

  • Salesforce Data Loader: External ID-based upsert with relationship resolution
  • ServiceNow: Sys ID and display value mapping during import
  • Airtable: Linked record resolution via display names

Loading Flow

1. Build object dependency graph from field metadata (lookup/master_detail)
2. Topological sort → determine insert order (parents before children)
3. Pass 1: Insert/upsert records, resolve references via externalId
4. Pass 2: Fill deferred references (circular/delayed dependencies)
5. Validate & report unresolved references
6. Return structured result with per-object stats

Source: packages/spec/src/data/seed-loader.zod.ts

TypeScript Usage

import { ObjectDependencyGraphSchema, ObjectDependencyNodeSchema, ReferenceResolutionSchema, ReferenceResolutionErrorSchema, SeedIdentitySchema, SeedLoadResultSchema, SeedLoaderConfigSchema, SeedLoaderRequestSchema, SeedLoaderResultSchema } from '@objectstack/spec/data';
import type { ObjectDependencyGraph, ObjectDependencyNode, ReferenceResolution, ReferenceResolutionError, SeedIdentity, SeedLoadResult, SeedLoaderConfig, SeedLoaderRequest, SeedLoaderResult } from '@objectstack/spec/data';

// Validate data
const result = ObjectDependencyGraphSchema.parse(data);

ObjectDependencyGraph

Complete object dependency graph for seed data loading

Properties

PropertyTypeRequiredDescription
nodes{ object: string; dependsOn: string[]; references: object[] }[]All objects in the dependency graph
insertOrderstring[]Topologically sorted insert order
circularDependenciesstring[][]optional (default: [])Circular dependency chains (e.g., [["a", "b", "a"]])

Nested Shape: ObjectDependencyGraph.nodes[number]

Object node in the seed data dependency graph

PropertyTypeRequiredDescription
objectstringObject name (snake_case)
dependsOnstring[]Objects this object depends on
references{ field: string; targetObject: string; targetField: string; fieldType: Enum<'lookup' | 'master_detail' | 'user'>; … }[]Field-level reference details

ObjectDependencyNode

Object node in the seed data dependency graph

Properties

PropertyTypeRequiredDescription
objectstringObject name (snake_case)
dependsOnstring[]Objects this object depends on
references{ field: string; targetObject: string; targetField: string; fieldType: Enum<'lookup' | 'master_detail' | 'user'>; … }[]Field-level reference details

Nested Shape: ObjectDependencyNode.references[number]

Describes how a field reference is resolved during seed loading

PropertyTypeRequiredDescription
fieldstringSource field name containing the reference value
targetObjectstringTarget object name (snake_case)
targetFieldstringoptional (default: "name")Field on target object used for matching
fieldTypeEnum<'lookup' | 'master_detail' | 'user'>Relationship field type
multiplebooleanoptionalField stores an array of references (multiple: true)

ReferenceResolution

Describes how a field reference is resolved during seed loading

Properties

PropertyTypeRequiredDescription
fieldstringSource field name containing the reference value
targetObjectstringTarget object name (snake_case)
targetFieldstringoptional (default: "name")Field on target object used for matching
fieldTypeEnum<'lookup' | 'master_detail' | 'user'>Relationship field type
multiplebooleanoptionalField stores an array of references (multiple: true)

ReferenceResolutionError

Actionable error for a failed reference resolution

Properties

PropertyTypeRequiredDescription
sourceObjectstringObject with the broken reference
fieldstringField name with unresolved reference
targetObjectstringTarget object searched for the reference
targetFieldstringExternalId field used for matching
attemptedValueanyValue that failed to resolve
recordIndexintegerIndex of the record in the dataset
messagestringHuman-readable error description

SeedIdentity

Identity context for resolving os.user / os.org in seed CEL values

Properties

PropertyTypeRequiredDescription
user{ id: string; role?: string; email?: string }optionalSubject bound to os.user in seed CEL expressions
org{ id: string; tier?: string }optionalOrganization bound to os.org in seed CEL expressions

SeedLoadResult

Result of loading a single dataset

Properties

PropertyTypeRequiredDescription
objectstringObject that was loaded
modeEnum<'insert' | 'update' | 'upsert' | 'replace' | 'ignore'>Import mode used
insertedintegerRecords inserted
updatedintegerRecords updated
skippedintegerRecords skipped
erroredintegerRecords with errors
totalintegerTotal records in dataset
referencesResolvedintegerReferences resolved via externalId
referencesDeferredintegerReferences deferred to second pass
referencesDroppedintegeroptional (default: 0)Reference fields dropped from records that were still written
summariesStaleintegeroptional (default: 0)Roll-up summary values left stale by writes for this dataset
errors{ sourceObject: string; field: string; targetObject: string; targetField: string; … }[]optional (default: [])Reference resolution errors

Nested Shape: SeedLoadResult.errors[number]

Actionable error for a failed reference resolution

PropertyTypeRequiredDescription
sourceObjectstringObject with the broken reference
fieldstringField name with unresolved reference
targetObjectstringTarget object searched for the reference
targetFieldstringExternalId field used for matching
attemptedValueanyValue that failed to resolve
recordIndexintegerIndex of the record in the dataset
messagestringHuman-readable error description

SeedLoaderConfig

Seed data loader configuration

Properties

PropertyTypeRequiredDescription
dryRunbooleanoptional (default: false)Validate references without writing data
haltOnErrorbooleanoptional (default: false)Stop on first reference resolution error
multiPassbooleanoptional (default: true)Enable multi-pass loading for circular dependencies
defaultModeEnum<'insert' | 'update' | 'upsert' | 'replace' | 'ignore'>optional (default: "upsert")Default conflict resolution strategy
batchSizeintegeroptional (default: 1000)Maximum records per batch insert/upsert
transactionbooleanoptional (default: false)Wrap entire load in a transaction (all-or-nothing)
envEnum<'prod' | 'dev' | 'test'>optionalOnly load datasets matching this environment
organizationIdstringoptionalTarget organization id for per-tenant seed replay
identity{ user?: object; org?: object }optionalIdentity bound to os.user / os.org when resolving CEL seed values

Nested Shape: SeedLoaderConfig.identity

PropertyTypeRequiredDescription
user{ id: string; role?: string; email?: string }optionalSubject bound to os.user in seed CEL expressions
org{ id: string; tier?: string }optionalOrganization bound to os.org in seed CEL expressions

SeedLoaderRequest

Seed loader request with datasets and configuration

Properties

PropertyTypeRequiredDescription
seeds{ object: string; externalId: string | string[]; mode: Enum<'insert' | 'update' | 'upsert' | 'replace' | 'ignore'>; env: Enum<'prod' | 'dev' | 'test'>[]; … }[]Seeds to load
config{ dryRun: boolean; haltOnError: boolean; multiPass: boolean; defaultMode: Enum<'insert' | 'update' | 'upsert' | 'replace' | 'ignore'>; … }optional (has default)Loader configuration

Nested Shape: SeedLoaderRequest.seeds[number]

PropertyTypeRequiredDescription
objectstringTarget Object Name
externalIdstring | string[]optional (default: "name")Field (or composite list of fields) matched for the uniqueness check
modeEnum<'insert' | 'update' | 'upsert' | 'replace' | 'ignore'>optional (default: "upsert")Conflict resolution strategy
envEnum<'prod' | 'dev' | 'test'>[]optional (default: ["prod","dev","test"])Applicable environments
recordsRecord<string, any>[]Data records
_lockEnum<'none' | 'no-overlay' | 'no-delete' | 'full'>optionalItem-level lock — controls overlay & delete (ADR-0010).
_lockReasonstringoptionalHuman-readable reason shown when a write is refused by _lock.
_lockSourceEnum<'artifact' | 'package' | 'env-forced'>optionalLayer that set _lock (artifact | package | env-forced).
_provenanceEnum<'package' | 'org' | 'env-forced'>optionalOrigin of the item (package | org | env-forced).
_packageIdstringoptionalOwning package machine id.
_packageVersionstringoptionalOwning package version.
_lockDocsUrlstringoptionalOptional documentation link surfaced next to _lockReason.

Nested Shape: SeedLoaderRequest.config

PropertyTypeRequiredDescription
dryRunbooleanoptional (default: false)Validate references without writing data
haltOnErrorbooleanoptional (default: false)Stop on first reference resolution error
multiPassbooleanoptional (default: true)Enable multi-pass loading for circular dependencies
defaultModeEnum<'insert' | 'update' | 'upsert' | 'replace' | 'ignore'>optional (default: "upsert")Default conflict resolution strategy
batchSizeintegeroptional (default: 1000)Maximum records per batch insert/upsert
transactionbooleanoptional (default: false)Wrap entire load in a transaction (all-or-nothing)
envEnum<'prod' | 'dev' | 'test'>optionalOnly load datasets matching this environment
organizationIdstringoptionalTarget organization id for per-tenant seed replay
identity{ user?: object; org?: object }optionalIdentity bound to os.user / os.org when resolving CEL seed values

SeedLoaderResult

Complete seed loader result

Properties

PropertyTypeRequiredDescription
successbooleanOverall success status
dryRunbooleanWhether this was a dry-run
dependencyGraph{ nodes: object[]; insertOrder: string[]; circularDependencies: string[][] }Object dependency graph
results{ object: string; mode: Enum<'insert' | 'update' | 'upsert' | 'replace' | 'ignore'>; inserted: integer; updated: integer; … }[]Per-object load results
errors{ sourceObject: string; field: string; targetObject: string; targetField: string; … }[]All reference resolution errors
summary{ objectsProcessed: integer; totalRecords: integer; totalInserted: integer; totalUpdated: integer; … }Summary statistics

Nested Shape: SeedLoaderResult.dependencyGraph

PropertyTypeRequiredDescription
nodes{ object: string; dependsOn: string[]; references: object[] }[]All objects in the dependency graph
insertOrderstring[]Topologically sorted insert order
circularDependenciesstring[][]optional (default: [])Circular dependency chains (e.g., [["a", "b", "a"]])

Nested Shape: SeedLoaderResult.results[number]

Result of loading a single dataset

PropertyTypeRequiredDescription
objectstringObject that was loaded
modeEnum<'insert' | 'update' | 'upsert' | 'replace' | 'ignore'>Import mode used
insertedintegerRecords inserted
updatedintegerRecords updated
skippedintegerRecords skipped
erroredintegerRecords with errors
totalintegerTotal records in dataset
referencesResolvedintegerReferences resolved via externalId
referencesDeferredintegerReferences deferred to second pass
referencesDroppedintegeroptional (default: 0)Reference fields dropped from records that were still written
summariesStaleintegeroptional (default: 0)Roll-up summary values left stale by writes for this dataset
errors{ sourceObject: string; field: string; targetObject: string; targetField: string; … }[]optional (default: [])Reference resolution errors

Nested Shape: SeedLoaderResult.errors[number]

Actionable error for a failed reference resolution

PropertyTypeRequiredDescription
sourceObjectstringObject with the broken reference
fieldstringField name with unresolved reference
targetObjectstringTarget object searched for the reference
targetFieldstringExternalId field used for matching
attemptedValueanyValue that failed to resolve
recordIndexintegerIndex of the record in the dataset
messagestringHuman-readable error description

Nested Shape: SeedLoaderResult.summary

PropertyTypeRequiredDescription
objectsProcessedintegerTotal objects processed
totalRecordsintegerTotal records across all objects
totalInsertedintegerTotal records inserted
totalUpdatedintegerTotal records updated
totalSkippedintegerTotal records skipped
totalErroredintegerTotal records with errors
totalReferencesResolvedintegerTotal references resolved
totalReferencesDeferredintegerTotal references deferred
totalReferencesDroppedintegeroptional (default: 0)Total reference fields dropped from written records
totalSummariesStaleintegeroptional (default: 0)Total roll-up summary values left stale across the load
circularDependencyCountintegerCircular dependency chains detected
durationMsnumberLoad duration in milliseconds

On this page