(
input: SaveArtifactInput,
)
| 4513 | }); |
| 4514 | |
| 4515 | const artifactsSave = ( |
| 4516 | input: SaveArtifactInput, |
| 4517 | ): Effect.Effect<Artifact, ArtifactNotFoundError | StorageFailure> => |
| 4518 | Effect.gen(function* () { |
| 4519 | const now = new Date(); |
| 4520 | const description = input.description ?? null; |
| 4521 | // An explicit id overwrites in place (v1 keeps no version history). It |
| 4522 | // must already resolve to a visible row: minting a caller-chosen id |
| 4523 | // would let a stale client resurrect a deleted artifact silently. |
| 4524 | if (input.id !== undefined) { |
| 4525 | const where = artifactById(input.id); |
| 4526 | const existing = yield* core.findFirst("artifact", { where }); |
| 4527 | if (!existing) { |
| 4528 | return yield* new ArtifactNotFoundError({ id: ArtifactId.make(input.id) }); |
| 4529 | } |
| 4530 | // `bindings` is written on every overwrite, including back to null: |
| 4531 | // it interprets `code`, so carrying the previous value forward under |
| 4532 | // new source would bind roles the new code never declares. `preview` |
| 4533 | // is written for exactly the same reason, and the case it prevents is |
| 4534 | // visible: an image preview captured from the OLD render would |
| 4535 | // otherwise keep advertising a version of the artifact that no longer |
| 4536 | // exists. |
| 4537 | const set = { |
| 4538 | title: input.title, |
| 4539 | description, |
| 4540 | code: input.code, |
| 4541 | bindings: input.bindings ?? null, |
| 4542 | preview: input.preview ?? null, |
| 4543 | updated_at: now, |
| 4544 | }; |
| 4545 | yield* core.updateMany("artifact", { where, set }); |
| 4546 | return rowToArtifact({ ...existing, ...set }); |
| 4547 | } |
| 4548 | yield* requireUserSubject("user"); |
| 4549 | const keys = yield* Effect.try({ |
| 4550 | try: () => ownedKeys("user"), |
| 4551 | catch: (cause) => storageFailureFromUnknown("invalid owner", cause), |
| 4552 | }); |
| 4553 | const created = yield* core.create("artifact", { |
| 4554 | tenant: keys.tenant, |
| 4555 | owner: keys.owner, |
| 4556 | subject: keys.subject, |
| 4557 | id: `art_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`, |
| 4558 | title: input.title, |
| 4559 | description, |
| 4560 | code: input.code, |
| 4561 | bindings: input.bindings ?? null, |
| 4562 | preview: input.preview ?? null, |
| 4563 | created_at: now, |
| 4564 | updated_at: now, |
| 4565 | }); |
| 4566 | return rowToArtifact(created); |
| 4567 | }); |
| 4568 | |
| 4569 | /** |
| 4570 | * Replace an artifact's preview with a snapshot of a settled render. |
nothing calls this directly
no test coverage detected