Skip to content

feat(seed): add --upsert flag to update existing rows - #1910

Merged
toiroakr merged 9 commits into
mainfrom
feat/seed-upsert
Jul 31, 2026
Merged

feat(seed): add --upsert flag to update existing rows#1910
toiroakr merged 9 commits into
mainfrom
feat/seed-upsert

Conversation

@dqn

@dqn dqn commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Summary

seed --upsert adds only the new rows to an already-seeded workspace instead of failing the whole batch on the first duplicate id.

Before / After

Re-running seed against a workspace that already holds some rows:

$ node seed/exec.mjs
[tailordb] Customer: failed - duplicate value for unique field `id` on table `Customer`: `aaaaaaaa-…` already exists
  ✓ Customer: 0 rows inserted
✗ Seed data generation completed with errors   # exit 1

$ node seed/exec.mjs --upsert
  ✓ Customer: 0 inserted, 5 updated
  ✓ _User: 0 created, 5 updated
✓ Seed data generation completed successfully   # exit 0

Previously the only workaround was trimming seed/data/*.jsonl down to the new rows and restoring them afterwards.

Behaviour

default --upsert
Row with a new id inserted inserted
Row whose id exists batch fails, nothing written row updated from fields present in seed data; an id-only row is skipped
Optional field absent from a row keeps its stored value
Required field absent from a row rejected before writes
Built-In IdP user that exists reported as a failure updated

Notes

  • Opt-in; without the flag behaviour is unchanged.
  • TailorDB rows are matched by id.
  • --upsert overwrites existing rows from the seed data, so a workspace with hand-edited data will lose those edits for the fields present in the seed files.

dqn added 4 commits July 27, 2026 17:12
Seeding a workspace that already holds some of the rows fails the whole
batch for that type on the first duplicate id, inserting nothing and
exiting 1. That leaves no way to add only new rows to an already-seeded
workspace short of trimming the JSONL files by hand.

With --upsert, TailorDB writes use ON CONFLICT ("id") DO UPDATE. The
conflict target is fixed to id because TailorDB accepts only a single
column there, and id is excluded from the update set.

Rows are grouped by key signature before batching. Kysely fills keys
missing from a row with `default`, so mixing rows with different key
sets in one statement would overwrite stored values on conflict.

Built-In IdP users have no upsert primitive and surface an existing user
only as a createUser failure, so --upsert falls back to a name lookup
and update. Without this the whole command would still exit 1 on
re-runs even once TailorDB succeeded.

Default behavior is unchanged.
The upsert fallback cannot distinguish "user already exists" from an
unrelated createUser failure, so a validation or permission error was
reported only as whatever the lookup or update raised afterwards. Report
both causes so the real reason survives.
The usage list already covers --truncate, so a new runtime flag that
changes what seed writes belongs next to it.
@changeset-bot

changeset-bot Bot commented Jul 27, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e575aee

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@tailor-platform/sdk Minor
@tailor-platform/create-sdk Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jul 27, 2026

Copy link
Copy Markdown

Open in StackBlitz

pnpm add https://pkg.pr.new/@tailor-platform/create-sdk@e575aee
pnpm add https://pkg.pr.new/@tailor-platform/eslint-plugin-sdk@e575aee
pnpm add https://pkg.pr.new/@tailor-platform/sdk@e575aee

commit: e575aee

@github-actions

Copy link
Copy Markdown

🗺️ ERD preview

No relevant ERD changes in this update.

@github-actions

This comment has been minimized.

@dqn
dqn marked this pull request as ready for review July 27, 2026 09:15
@dqn
dqn requested a review from a team as a code owner July 27, 2026 09:15
@dqn
dqn requested a review from toiroakr July 27, 2026 09:15

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📖 Docs Quality & Consistency Check

✅ Docs are consistent with the implementation and contain no user-facing internal-detail leaks.

Checked areas:

  • packages/sdk/docs/generator/builtin.md - Updated documentation for the new --upsert flag
  • Implementation in packages/sdk/src/plugin/builtin/seed/ - Verified behavior matches documentation
  • packages/sdk/src/cli/commands/generate/seed/bundler.ts - Checked upsert logic for TailorDB rows
  • packages/sdk/src/plugin/builtin/seed/idp-user-processor.ts - Checked IdP user upsert behavior
  • packages/sdk/src/plugin/builtin/seed/seed-data-loader.ts - Checked validation logic
  • .changeset/seed-upsert-flag.md - Verified changeset description matches implementation
  • example/seed/exec.mjs and template files - Verified generated code consistency

Verified behaviors:

  1. ✅ Without --upsert: Seeding fails on duplicate IDs (batch rejected, nothing written)
  2. ✅ With --upsert: TailorDB rows require an id; new IDs inserted, existing IDs updated
  3. ✅ Optional fields absent from seed data keep their stored values in existing rows
  4. ✅ Required fields must be present in seed data for upsert (validated before execution)
  5. ✅ Built-In IdP users looked up by name, then created or updated accordingly
  6. ✅ Default behavior unchanged (upsert is opt-in)

Internal terms checked:

  • ✅ No leaks of TestExecScript / testExecScript into user-facing documentation
  • ✅ No leaks of internal module names (parser, configure, cli) into user docs
  • ✅ JSDoc on exported seedPlugin function contains no internal implementation details

Re-run this check by adding the docs-check label to the PR.


namespace: { type: "string", short: "n" },
"skip-idp": { type: "boolean", default: false },
truncate: { type: "boolean", default: false },
upsert: { type: "boolean", default: false },

@toiroakr toiroakr Jul 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make id mandatory when --upsert is set.

Generated seed schemas mark id optional (Customer.schema.ts: pickFields([...], { optional: true })), and sqlaccess drops rows whose conflict target is null from the conflict check (service/sqlaccess/src/operator/insert.rs:232-235: "Null values are not considered as conflicts, so they are filtered out"). So a row without id is inserted under a fresh UUID on every run while the output still reads N rows upserted and exits 0.

Please fail on rows missing id, naming the file and line.

If you want lines-db to backfill the ids, LinesDB.sync() is not usable: createStandardSchema has no backward, so the hook-applied values held in SQLite are written straight back and previously omitted optional fields become explicit null (lib/src/database.ts:992-996) -- exactly the value-clobbering --upsert is meant to avoid. JsonlReader + JsonlWriter (both exported) let you add a UUID only to the rows that lack one.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 908b4b1.

Comment thread packages/sdk/docs/generator/builtin.md Outdated

The `--machine-user` option is required at runtime if `machineUserName` is not configured in the generator options.

Without `--upsert`, a row whose id already exists fails the whole batch for that type, so nothing is written. Use `--upsert` to add only the new rows to a workspace that is already seeded; existing rows are updated from the seed data, and fields absent from a row keep their stored values.

@toiroakr toiroakr Jul 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"fields absent from a row keep their stored values" holds only for optional fields, so please say so.

sqlaccess runs the required-field check at insert.rs:66-80, ahead of the ON CONFLICT split at insert.rs:83, so a row missing a required field still fails the whole batch under --upsert with field \x` is required`.

The same wording in the changeset needs the qualifier too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 908b4b1.

@@ -20,11 +58,6 @@ describe("seed-bundler", () => {
aroundAll(async (runSuite) => {
await runSuite();
delete process.env.TAILOR_SDK_OUTPUT_DIR;

@toiroakr toiroakr Jul 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fs.rmSync(TEST_BUNDLER_BASE, ...) was dropped from this aroundAll, leaving cleanup only in the new describe("seed script upsert behavior") aroundAll (:132-140).

Filter the second suite out -- e.g. pnpm test bundler.test.ts -t "seed-bundler" -- and that hook never runs, so __test_bundler__/ is left behind in the source tree.

Please move the cleanup to a file-level afterAll.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 908b4b1.

throw new Error(\`create failed (\${createMessage}); upsert failed (\${updateMessage})\`);
}
}
processed++;

@toiroakr toiroakr Jul 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

created and updated can be counted separately here, so please split them.

Add let created = 0; let updated = 0;, increment each right after a successful create and a successful updateUser, and return both so exec.mjs can print _User: N created, M updated. It also makes a silent overwrite of an existing user visible.

Doing the same on the TailorDB side is not cheap, so IdP-only is enough for this PR: Kysely's .execute() only yields an affected count, and the sqlaccess ON CONFLICT path likewise returns the affected count of a single bulk upsert against its internal table (storage/postgres/mod.rs:668-702), which cannot separate inserted from updated.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 908b4b1.


// Rows must share one key set, which groupByColumns guarantees.
// TailorDB restricts the ON CONFLICT target to a single column, so "id" is the only key.
const write = (typeName: string, rows: Record<string, unknown>[]) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please drop ON CONFLICT here and split into INSERT + UPDATE instead. Sorry about the churn -- recommending ON CONFLICT was my call, so this reversal is on me.

Verified against a live workspace using example's Invoice, which carries two serial fields: --upsert burns one serial value per record per run, conflicting rows included, and the numbers never come back. The four existing rows kept sequentialId 1..4 across runs while a newly added row came out at 21, and at 31 on a later cycle, instead of 5. inject_serial_values runs at insert.rs:61, ahead of the conflict split at insert.rs:83, and nextval is non-transactional. Plain Postgres behaves the same way so this is not a platform bug, but the gaps cannot be cleaned up in place: the only reset path is resetSequences() on truncate (migrator/table.go:353), which wipes the table.

A plain UPDATE draws nothing -- Operator::update never calls inject_serial_values, and live confirmation showed the next insert after an UPDATE advancing by exactly 1. So probe with SELECT "id" ... WHERE "id" IN (...), route new rows through the existing INSERT path, and route existing rows through db.updateTable(t).set(rest).where("id", "=", id). Both statement shapes were accepted by sqlaccess in the same live check.

This also removes groupByColumns entirely: UPDATE only touches the columns you set, so the "Kysely fills missing keys with default" hazard the grouping exists to dodge simply stops applying. And because the split is decided up front, the counts become exact inserted-vs-updated, which also removes the DO NOTHING over-count.

Costs worth weighing: one probe SELECT per type plus one UPDATE per existing row (parser/update.rs has no FROM clause, so no bulk update), a TOCTOU window that a single ON CONFLICT statement did not have, and a permission difference -- the conflict probe deliberately skips read-permission filtering (insert.rs:241-249) in a way a client-side SELECT cannot reproduce.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 908b4b1.

await client.createUser(input.users[i]);
try {
await client.createUser(input.users[i]);
} catch (createError) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please look the user up first rather than inferring existence from a createUser failure.

As written, any createUser failure -- a transient 5xx, a rate limit, a policy rejection -- falls through to userByName + updateUser. If a user with that name happens to exist for an unrelated reason, this silently resets that real user's password and still exits 0. The _User seed schema is exactly { name, password }, so the blast radius is precisely a password reset.

Checking first sidesteps the part that genuinely cannot be done here, which is telling the errors apart. The IdP does return CodeAlreadyExists (service/idp/dataplane/grpc/service.go:227-228), but the runtime flattens it into new Error("createUser failed: " + status.to_string()) (ops/idp.rs, engine/js/idp.js:158-159), leaving only substring matching on a stringified status.

With a lookup first, a successful lookup means the user exists and no guessing is involved; a failed lookup falls through to createUser, so a user that really does exist surfaces as a reported AlreadyExists error instead of an overwrite. That converts a silent destructive write into a loud non-destructive one. userByName throws on not-found (GetUser returns CodeNotFound, service.go:181-185), so it still needs its own try/catch.

The cost is one extra lookup per user, and a create-after-lookup race would surface as an error rather than an overwrite -- both acceptable for a seed tool. It also makes the created/updated split fall out for free, since the branch is decided before any write.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 908b4b1.

@toiroakr toiroakr assigned dqn and unassigned toiroakr Jul 28, 2026
@dqn

dqn commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

📖 Docs Quality & Consistency Check

Fixed in 908b4b1.

@dqn dqn added the docs-check Trigger Docs Consistency Check label Jul 28, 2026
@dqn
dqn requested a review from toiroakr July 28, 2026 07:13
@dqn dqn removed their assignment Jul 28, 2026
@github-actions github-actions Bot removed the docs-check Trigger Docs Consistency Check label Jul 28, 2026
@github-actions

This comment has been minimized.

@toiroakr toiroakr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. All ten review threads are addressed in 503cb06, and the new index.test.ts coverage that pins the generated indentation is a good addition.

The merge is blocked on conflicts with main, so please rebase. Two files conflict, both stemming from fa2a0dea7 (fix(cli): resolve seed bundles from config directory):

  • packages/sdk/src/plugin/builtin/seed/index.test.ts -- add/add: main added a file with the same name. The two describe blocks are independent, so they can sit side by side.
  • packages/sdk/src/cli/commands/generate/seed/bundler.test.ts -- bundleSeedScript gained a third argument (the config directory) on main. bundler.ts itself auto-merges, but the new upsert tests need to pass it.

Ping me once it is rebased and I will merge.

@toiroakr toiroakr assigned dqn and unassigned toiroakr and dqn Jul 30, 2026
@github-actions

This comment has been minimized.

@dqn dqn assigned toiroakr and unassigned dqn Jul 30, 2026

@toiroakr toiroakr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. The merge with main resolves both conflicts correctly.

The only thing left is signatures: 0b6cab42c, 9b073817b, 8ce4cebbf and c2e16428a are unsigned, and main requires signed commits. Everything from 785f52b78 onward is fine. Please re-sign them and I will merge.

@toiroakr toiroakr assigned dqn and unassigned toiroakr Jul 31, 2026
@dqn
dqn force-pushed the feat/seed-upsert branch from f846cfd to 2164452 Compare July 31, 2026 02:18
@dqn

dqn commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

Please re-sign them and I will merge.

Done 🙏

@github-actions

This comment has been minimized.

@dqn
dqn force-pushed the feat/seed-upsert branch from 2164452 to e575aee Compare July 31, 2026 02:21
@dqn dqn assigned toiroakr and unassigned dqn Jul 31, 2026
@github-actions

Copy link
Copy Markdown

Code Metrics Report (packages/sdk)

main (c5e1a27) #1910 (ae0f932) +/-
Coverage 75.4% 75.8% +0.4%
Code to Test Ratio 1:0.4 1:0.4 +0.0
Details
  |                    | main (c5e1a27) | #1910 (ae0f932) |  +/-  |
  |--------------------|----------------|-----------------|-------|
+ | Coverage           |          75.4% |           75.8% | +0.4% |
  |   Files            |            467 |             468 |    +1 |
  |   Lines            |          17741 |           17750 |    +9 |
+ |   Covered          |          13390 |           13469 |   +79 |
+ | Code to Test Ratio |          1:0.4 |           1:0.4 |  +0.0 |
  |   Code             |         120229 |          120861 |  +632 |
+ |   Test             |          56943 |           57460 |  +517 |

Code coverage of files in pull request scope (45.7% → 75.1%)

Files Coverage +/- Status
packages/sdk/src/cli/commands/generate/seed/bundler.ts 93.3% 0.0% modified
packages/sdk/src/cli/commands/workflow/waiter.ts 72.8% -2.2% affected
packages/sdk/src/plugin/builtin/seed/idp-user-processor.ts 100.0% +11.1% modified
packages/sdk/src/plugin/builtin/seed/index.ts 88.8% +56.0% modified
packages/sdk/src/plugin/builtin/seed/lines-db-processor.ts 53.5% +53.5% affected
packages/sdk/src/plugin/builtin/seed/seed-data-loader.ts 100.0% +100.0% added
packages/sdk/src/plugin/builtin/seed/seed-type-processor.ts 66.6% +66.6% affected

SDK Configure Bundle Size

main (c5e1a27) #1910 (ae0f932) +/-
configure-index-size 40.17KB 40.17KB 0KB
dependency-chunks-size 29.87KB 29.87KB 0KB
total-bundle-size 70.04KB 70.04KB 0KB

Runtime Performance

main (c5e1a27) #1910 (ae0f932) +/-
Generate Median 3,085ms 3,071ms -14ms
Generate Max 3,104ms 3,109ms 5ms
Apply Build Median 3,154ms 3,126ms -28ms
Apply Build Max 3,178ms 3,276ms 98ms

Type Performance (instantiations)

main (c5e1a27) #1910 (ae0f932) +/-
tailordb-basic 44,253 44,253 0
tailordb-optional 4,451 4,451 0
tailordb-relation 6,220 6,220 0
tailordb-validate 753 753 0
tailordb-hooks 5,279 5,279 0
tailordb-object 12,547 12,547 0
tailordb-enum 1,486 1,486 0
resolver-basic 9,265 9,265 0
resolver-nested 26,132 26,132 0
resolver-array 18,072 18,072 0
executor-schedule 4,318 4,318 0
executor-webhook 959 959 0
executor-record 6,772 6,772 0
executor-resolver 4,121 4,121 0
executor-operation-function 947 947 0
executor-operation-gql 955 955 0
executor-operation-webhook 966 966 0
executor-operation-workflow 1,808 1,808 0

Reported by octocov

@toiroakr toiroakr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@toiroakr
toiroakr merged commit 466a0b7 into main Jul 31, 2026
51 of 53 checks passed
@toiroakr
toiroakr deleted the feat/seed-upsert branch July 31, 2026 04:05
toiroakr added a commit that referenced this pull request Jul 31, 2026
Brings in feat/seed-upsert (PR #1910) from main.

v2 already extracts seed execution into the sdk-plugin-seed CLI plugin,
so the --upsert flag is ported to that architecture instead of merged
as generated exec.mjs code:
- sdk-plugin-seed: add --upsert to `tailor seed apply`, wiring it
  through seedNamespace/seedIdpUser and the TailorDB probe-and-update
  script that bundler.ts already generates
- jsonl.ts: enforce requireId/requiredFieldsByType when --upsert is set
- seed-type-processor.ts: compute per-type requiredFields for seed-context

Also applies review follow-ups from #1910:
- treat a _User seed row with only `name` as skipped, not updated
- suppress the _User success line when every row fails
- preserve the IdP lookup error alongside a create failure
- document that --upsert updates run through hooks/validation and fire
  recordUpdatedTrigger executors

# Conflicts:
#	example/seed/exec.mjs
#	example/tests/fixtures/expected/seed/exec.mjs
#	packages/create-sdk/templates/generators/src/seed/exec.mjs
#	packages/sdk/docs/generator/builtin.md
#	packages/sdk/src/cli/commands/generate/seed/bundler.test.ts
#	packages/sdk/src/plugin/builtin/seed/index.test.ts
#	packages/sdk/src/plugin/builtin/seed/index.ts
This was referenced Jul 31, 2026
toiroakr added a commit that referenced this pull request Jul 31, 2026
Brings in feat/seed-upsert (PR #1910) from main.

v2 already extracts seed execution into the sdk-plugin-seed CLI plugin,
so the --upsert flag is ported to that architecture instead of merged
as generated exec.mjs code:
- sdk-plugin-seed: add --upsert to `tailor seed apply`, wiring it
  through seedNamespace/seedIdpUser and the TailorDB probe-and-update
  script that bundler.ts already generates
- jsonl.ts: enforce requireId/requiredFieldsByType when --upsert is set
- seed-type-processor.ts: compute per-type requiredFields for seed-context

Also applies review follow-ups from #1910:
- treat a _User seed row with only `name` as skipped, not updated
- suppress the _User success line when every row fails
- preserve the IdP lookup error alongside a create failure
- document that --upsert updates run through hooks/validation and fire
  recordUpdatedTrigger executors

# Conflicts:
#	example/seed/exec.mjs
#	example/tests/fixtures/expected/seed/exec.mjs
#	packages/create-sdk/templates/generators/src/seed/exec.mjs
#	packages/sdk/docs/generator/builtin.md
#	packages/sdk/src/cli/commands/generate/seed/bundler.test.ts
#	packages/sdk/src/plugin/builtin/seed/index.test.ts
#	packages/sdk/src/plugin/builtin/seed/index.ts
toiroakr added a commit that referenced this pull request Jul 31, 2026
Brings in feat/seed-upsert (PR #1910) from main.

v2 already extracts seed execution into the sdk-plugin-seed CLI plugin,
so the --upsert flag is ported to that architecture instead of merged
as generated exec.mjs code:
- sdk-plugin-seed: add --upsert to `tailor seed apply`, wiring it
  through seedNamespace/seedIdpUser and the TailorDB probe-and-update
  script that bundler.ts already generates
- jsonl.ts: enforce requireId/requiredFieldsByType when --upsert is set
- seed-type-processor.ts: compute per-type requiredFields for seed-context

Also applies review follow-ups from #1910:
- treat a _User seed row with only `name` as skipped, not updated
- suppress the _User success line when every row fails
- preserve the IdP lookup error alongside a create failure
- document that --upsert updates run through hooks/validation and fire
  recordUpdatedTrigger executors

# Conflicts:
#	example/seed/exec.mjs
#	example/tests/fixtures/expected/seed/exec.mjs
#	packages/create-sdk/templates/generators/src/seed/exec.mjs
#	packages/sdk/docs/generator/builtin.md
#	packages/sdk/src/cli/commands/generate/seed/bundler.test.ts
#	packages/sdk/src/plugin/builtin/seed/index.test.ts
#	packages/sdk/src/plugin/builtin/seed/index.ts
toiroakr added a commit that referenced this pull request Jul 31, 2026
Brings in feat/seed-upsert (PR #1910) from main.

v2 already extracts seed execution into the sdk-plugin-seed CLI plugin,
so the --upsert flag is ported to that architecture instead of merged
as generated exec.mjs code:
- sdk-plugin-seed: add --upsert to `tailor seed apply`, wiring it
  through seedNamespace/seedIdpUser and the TailorDB probe-and-update
  script that bundler.ts already generates
- jsonl.ts: enforce requireId/requiredFieldsByType when --upsert is set
- seed-type-processor.ts: compute per-type requiredFields for seed-context

Also applies review follow-ups from #1910:
- treat a _User seed row with only `name` as skipped, not updated
- suppress the _User success line when every row fails
- preserve the IdP lookup error alongside a create failure
- document that --upsert updates run through hooks/validation and fire
  recordUpdatedTrigger executors

# Conflicts:
#	example/seed/exec.mjs
#	example/tests/fixtures/expected/seed/exec.mjs
#	packages/create-sdk/templates/generators/src/seed/exec.mjs
#	packages/sdk/docs/generator/builtin.md
#	packages/sdk/src/cli/commands/generate/seed/bundler.test.ts
#	packages/sdk/src/plugin/builtin/seed/index.test.ts
#	packages/sdk/src/plugin/builtin/seed/index.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants