Version Packages#585
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
ed30ba3 to
b35e6f4
Compare
b35e6f4 to
c5597c3
Compare
c5597c3 to
b4df4d0
Compare
b4df4d0 to
2bb69d4
Compare
2bb69d4 to
8788f9d
Compare
8788f9d to
12dda6e
Compare
12dda6e to
e789aab
Compare
e789aab to
bd51814
Compare
bd51814 to
aaa2a34
Compare
aaa2a34 to
90b1fb1
Compare
Contributor
Author
Coverage Report for Core Package Coverage (./packages/core)
File CoverageNo changed files found. |
Contributor
Author
Coverage Report for UI Package Coverage (./packages/ui)
File CoverageNo changed files found. |
Contributor
Author
Coverage Report for CLI Package Coverage (./packages/cli)
File CoverageNo changed files found. |
Contributor
Author
Coverage Report for Auth Package Coverage (./packages/auth)
File CoverageNo changed files found. |
Contributor
Author
Coverage Report for Storage Package Coverage (./packages/storage)
File CoverageNo changed files found. |
Contributor
Author
Coverage Report for RAG Package Coverage (./packages/rag)
File CoverageNo changed files found. |
Contributor
Author
Coverage Report for Storage S3 Package Coverage (./packages/storage-s3)
File CoverageNo changed files found. |
Contributor
Author
Coverage Report for Storage Vercel Package Coverage (./packages/storage-vercel)
File CoverageNo changed files found. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
@opensaas/stack-cli@0.25.0
Minor Changes
#606
801230eThanks @borisno2! - Enforce field-level scalar narrowing at the write call site, and fixcheckbox({ defaultValue: false })optionalityThe generated
context.db.<list>.create()/update()/createMany()/updateMany()datatype now narrows scalar fields to their OpenSaaS
getTypeScriptType()types instead ofinheriting Prisma's wider input types. Field-level narrowing (e.g.
calendarDay→string)is now a genuine compile-time error to violate, not just a runtime validation failure.
Relationship nested writes (
connect/create/connectOrCreate), unchecked foreign keys(e.g.
authorId), anddecimal/jsonwrites are unaffected:decimalstill acceptsDecimal | number | stringandjsonstill accepts Prisma'sJsonNull/DbNullsentinels.Also fixes a latent bug where
checkbox({ defaultValue: false })(and any field with afalsy-but-present default) was generated as a required field on create — it is now correctly
optional.
Note: this may surface pre-existing type errors in consumer code that passed a
Dateto acalendarDayfield. Such code already failed at runtime; it now fails at compile time. Pass aYYYY-MM-DDstring instead.#609
1d79fe6Thanks @borisno2! - Consolidate nullability between the standalone{List}CreateInput/{List}UpdateInputexports and the call-site write-dataoverride into a single source of truth (#608).The generated types previously described a list's create/update input shape in two places that disagreed on how a nullable scalar was represented: the write-
dataoverride emittedname?: string | null(matching Prisma's nullable-column input) while the standalone{List}CreateInput/{List}UpdateInputemittedname?: string. Both paths now render each scalar member through one shared helper, so a nullable scalar is consistentlyname?: T | nullin every input representation. Required scalars stay required, anddecimal/json/relationship/multi-column handling is unchanged.This is a non-breaking type refinement, but if you assigned the standalone
{List}CreateInput/{List}UpdateInputtypes into a stricter local type, a nullable scalar may now be inferred asT | null:#594
4f0d407Thanks @borisno2! - Add an opt-in Node build of the generated.opensaas/bundle (ADR-0011, #579).Setting
output: { buildTarget: 'node' }inopensaas.config.tsmakesopensaas generateadditionally compile the bundle to a plain-Node-loadable ESM form under.opensaas/dist/—.js+.d.tswith a{"type":"module"}marker — alongside the default.tsbundler form. The compiled entry is.opensaas/dist/context.js, with the Prisma client subtree at.opensaas/dist/prisma-client/**and the project config compiled in as a sibling, so a live module (e.g. better-auth's Prisma adapter) can be imported in a bundler-less runtime — plain Node, a Playwright e2e helper, or a build-time script — that the default.tsform cannot execute.The Node build is purely additive: with
output.buildTargetabsent (the default), generation behaves exactly as before and no.opensaas/dist/is emitted.The compile runs via the TypeScript compiler API with
rewriteRelativeImportExtensions(turning the bundle's.ts-extension imports into runnable.jsspecifiers),declaration,skipLibCheck, andnoEmitOnError: false, so it reuses the bundle's type-clean guarantee without adding a build dependency.'node'is the onlybuildTargettoday; the field is a string-literal union so future compiled targets can be added without a breaking change.#592
e355c05Thanks @borisno2! - Make the generated.opensaas/prisma-clientsubtree statically resolvable by default and add adb.prismaGeneratorOptionspassthrough.The generated
generator client { ... }block now emitsimportFileExtension = "ts"andmoduleFormat = "esm"by default, so the prisma-client subtree uses explicit.tsimport extensions and matches the extension style the rest of the.opensaasbundle already uses — the whole import graph is statically resolvable by a bundler out of the box, no post-generation surgery required.A new optional
db.prismaGeneratorOptionslets you override these values when you need a different module/extension story (e.g. emitting.jsextensions for a plain-Node consumer). Any value you supply wins; omitted keys fall back to thets/esmdefaults. The existingpreviewFeatures = ["multiSchema"]emission (whendb.schemasis set) is preserved and coexists with the new options.#584
b17ec45Thanks @borisno2! - AddfindFirstto access-controlledcontext.db.<list>delegatesfindFirstis sugar over the existing access-filteredfindMany(take: 1), soit introduces no new access surface: it applies the exact same query-access checks
and access-controlled include building as
findMany, then returns the firstmatching row or
null. It honours the read-side silent-failure contract — anaccess-denied query yields
nullrather than throwing.The CLI type generator now emits a
findFirstmethod (and<List>FindFirstArgstype) for each list in the generated
.opensaas/types.ts, so migrated apps thatreach for the familiar Prisma
findFirstpattern get full type support.Patch Changes
#606
801230eThanks @borisno2! - Remove unused getRelatedListName helper from the types generator (dead code, no behavior change)#591
c741055Thanks @borisno2! - Fixtscfailure in generatedprisma-extensions.tsfor multi-column storage fields indb: { columns: 'keystone' }mode. The result extension'sneedsnow references the physical part columns (e.g.image_url,image_pathname, …) derived from the field'sgetColumnNames, instead of the logical field name which has no scalar on the model (previously typedtrueagainstnever). This removes the last error forcing@ts-nocheckon the generated bundle (#559).Updated dependencies [
44ec937,be9a896,e39d6e9,fadd9db,4f0d407,e355c05,ca4973b,44ec937,ecbf834,a93cebb,481d6e0,4622b5f,b17ec45,8f98e25]:@opensaas/stack-core@0.25.0
Minor Changes
#602
44ec937Thanks @borisno2! - MakecalendarDayaYYYY-MM-DDstring end-to-end (Keystone's CalendarDay scalar)calendarDayis now aYYYY-MM-DDstring at thecontext.dbboundary inboth directions, so its type, validation, and runtime value finally agree.
Previously the field validated a
YYYY-MM-DDstring but its TypeScript type wasDate, so a typed caller passingnew Date(...)hit a runtimeValidationError.CreateInput/UpdateInputinput typesare now
string.YYYY-MM-DDstring; a malformed string or aDateisrejected at runtime by validation (a
ValidationError).DateTime @db.Dateon Postgres/MySQL, the SQLite TEXTfallback as before.
Behavioral change (reads): reading a
calendarDaynow returns aYYYY-MM-DDstring instead of aDate. A fieldresolveOutputtransformnormalises the value Prisma returns from the
@db.Datecolumn, using UTCcomponents to avoid timezone off-by-one. Consumers that previously relied on a
Dateon read should update to the string form:#593
fadd9dbThanks @{! - Nested relation writes now run the full hook pipeline inside one transaction (#569)A record written via a nested
create,update, ordeletenow fires the SAMElist- and field-level
beforeOperation/afterOperationhooks as the equivalenttop-level write — so side effects (workflows, notifications, billing) are
identical whether a record is written nested or top-level. Previously nested
writes ran only
resolveInput/validate/field-rules and silently skipped thebefore/after side-effect hooks.
beforeOperation(create) → persist →afterOperationreceiving the created
item.afterOperationreceiving bothoriginalItem(the rowbefore) and the updated
item.beforeOperation/afterOperationreceiving theoriginalItem.Existing access control, validation, silent-failure, sudo-bypass, and the #578
nested-
connect/connectOrCreateread-access + DB-reachability behavior areunchanged. Pass-through nested kinds (
disconnect/set/updateMany/deleteMany) are out of scope and behave as before. See ADR-0010.For to-many nested creates (
create: [{A},{B}]), each created record'safterOperationnow fires exactly once against its OWN distinct row, recoveredby id-diff against the rows that existed before the write — so a pre-existing
sibling is never passed as the "created" item, and multiple creates no longer
collapse to a single row.
BEHAVIOR CHANGE — every write is now transactional, and a throwing
beforeOperation/afterOperation(or validation) rolls the whole write back.The entire operation (parent + all nested writes) now runs inside one
prisma.$transaction, so it is atomic. Previously anafterOperationthat threwleft the row committed; now it rolls back with the transaction (more
Keystone-correct). If you relied on a thrown
afterOperationleaving the rowpersisted, move that work to run after the write returns.
Inside a
beforeOperation/afterOperationhook,context.db(andcontext.prisma) are now bound to the write's transaction, so anycontext.dbwrite a hook performs participates in — and rolls back with — the same
transaction. Externally-visible side effects that must survive a rollback should
not use
context.dbfrom within these hooks (transaction-boundary hooks forthat are deferred — see #590).
#594
4f0d407Thanks @borisno2! - Add an opt-in Node build of the generated.opensaas/bundle (ADR-0011, #579).Setting
output: { buildTarget: 'node' }inopensaas.config.tsmakesopensaas generateadditionally compile the bundle to a plain-Node-loadable ESM form under.opensaas/dist/—.js+.d.tswith a{"type":"module"}marker — alongside the default.tsbundler form. The compiled entry is.opensaas/dist/context.js, with the Prisma client subtree at.opensaas/dist/prisma-client/**and the project config compiled in as a sibling, so a live module (e.g. better-auth's Prisma adapter) can be imported in a bundler-less runtime — plain Node, a Playwright e2e helper, or a build-time script — that the default.tsform cannot execute.The Node build is purely additive: with
output.buildTargetabsent (the default), generation behaves exactly as before and no.opensaas/dist/is emitted.The compile runs via the TypeScript compiler API with
rewriteRelativeImportExtensions(turning the bundle's.ts-extension imports into runnable.jsspecifiers),declaration,skipLibCheck, andnoEmitOnError: false, so it reuses the bundle's type-clean guarantee without adding a build dependency.'node'is the onlybuildTargettoday; the field is a string-literal union so future compiled targets can be added without a breaking change.#592
e355c05Thanks @borisno2! - Make the generated.opensaas/prisma-clientsubtree statically resolvable by default and add adb.prismaGeneratorOptionspassthrough.The generated
generator client { ... }block now emitsimportFileExtension = "ts"andmoduleFormat = "esm"by default, so the prisma-client subtree uses explicit.tsimport extensions and matches the extension style the rest of the.opensaasbundle already uses — the whole import graph is statically resolvable by a bundler out of the box, no post-generation surgery required.A new optional
db.prismaGeneratorOptionslets you override these values when you need a different module/extension story (e.g. emitting.jsextensions for a plain-Node consumer). Any value you supply wins; omitted keys fall back to thets/esmdefaults. The existingpreviewFeatures = ["multiSchema"]emission (whendb.schemasis set) is preserved and coexists with the new options.#600
a93cebbThanks [@relationship({](https://github.com/relationship({)! - Gate nestedconnectby the owning relationship field's field-level accessNested
connect(and the connect branch ofconnectOrCreate) is now gated bythe owning relationship field's create/update field-level access, in addition to
the target list's read/query access and DB-reachability check. This completes
the Keystone-parity rule that a connect requires both read access on the target
AND write access on the owning relationship field.
sudobypasses the check.#584
b17ec45Thanks @borisno2! - AddfindFirstto access-controlledcontext.db.<list>delegatesfindFirstis sugar over the existing access-filteredfindMany(take: 1), soit introduces no new access surface: it applies the exact same query-access checks
and access-controlled include building as
findMany, then returns the firstmatching row or
null. It honours the read-side silent-failure contract — anaccess-denied query yields
nullrather than throwing.The CLI type generator now emits a
findFirstmethod (and<List>FindFirstArgstype) for each list in the generated
.opensaas/types.ts, so migrated apps thatreach for the familiar Prisma
findFirstpattern get full type support.#601
8f98e25Thanks @borisno2! - AddbeforeTransaction/afterTransactiontransaction-boundary hooks (list- and field-level)These run OUTSIDE the write's database transaction (in addition to the in-transaction
beforeOperation/afterOperation), for non-transactional side effects like external API calls that must not hold a transaction open and cannot be rolled back. They fire per(list, operation)involved in the write (the top-level list plus each nested create/update/delete list) and form a symmetric compensation bracket:afterTransactionalways runs when its pairedbeforeTransactionran, receiving the outcome (status: 'committed' | 'rolled-back'pluserroron rollback). On commit it gets the persisteditem(andoriginalItemfor update/delete) only for the top-level record — for nested lists these areundefined, since the per-record persisted row is not recoverable outside the transaction; use the in-transactionafterOperationfor per-record nested compensation. On rollback it gets noitemso it can undo whatbeforeTransactiondid.connectOrCreateis enumerated as a best-effort create involvement (a resolve-to-connect still fires the bracket with no write), so compensators should be idempotent.A throwing
beforeTransactionaborts the write (the transaction never opens) and firesafterTransaction(rolled-back) only for lists whosebeforeTransactionalready ran. A throwingafterTransactiondoes not stop the other compensators; errors are surfaced afterward. Sudo does not affect these hooks. This is an additive, non-Keystone extension and does not change the existingbeforeOperation/afterOperationsemantics.Patch Changes
#603
be9a896Thanks @borisno2! - Enforce required json fields on create: an omitted key is now rejected while anypresent value (object, array, primitive, or null) is still accepted.
#583
e39d6e9Thanks @borisno2! - Make non-sudo writes fail loud infilterWritableFields(Keystone parity).Undeclared
datakeys on create/update now throw instead of passing through unchecked (#564), and fields denied by field-level access now throw instead of being silently stripped (#568).sudoremains the single trusted bypass; system fields and relationship foreign keys still pass through. Raw multi-column split columns (e.g.media_url/media_sizefrom animage()/file()field) are now gated by their owning field's write access — supplying them directly under non-sudo when that field denies the write throws, instead of bypassing the field'saccess.create/access.update.Behavioural narrowing: a list-level
resolveInputhook that adds keys toresolvedDatawhich are not declared fields will now be rejected by the undeclared-key throw. No production hook does this today.#605
ca4973bThanks @borisno2! - Required json fields now reject a presentnullduring validation rather than failing later as a DB NOT NULL violation. Omitted keys on update are still allowed; the Prisma column nullability is unchanged.#602
44ec937Thanks @borisno2! - Fix update validation rejecting omitted required fields under zod 4.4 by using key-optionality (.optional()) instead ofz.union([schema, z.undefined()]). Partial updates that omit a required-on-create field now validate; present values still enforce their rules.#587
ecbf834Thanks @borisno2! - Fix false denial of nestedconnect(andconnectOrCreate's connect branch): connect now requires read/query access on the target and evaluates filter results via DB reachability (findFirst({ where: { AND: [connection, accessFilter] } })), so nested-relation andAND/OR/some/none/notfilters no longer always fail.#589
481d6e0Thanks @borisno2! - Fix row-level access bypass when an explicitincludeis passed to non-sudofindUnique/findMany. The caller'sincludeis now merged with (not replaced by) the access-controlled include: denied relations are dropped, each relation's accesswhereis AND-combined with any caller nestedwhere, and nested includes are filtered at every level. Sudo and query-fragment paths are unchanged. When no access-controlled include is computed (inside aresolveOutput/virtual-field context, at max include depth, or for a list with no relationships), the caller'sincludeis passed through unchanged rather than dropped — avoiding fail-closed data loss.#586
4622b5fThanks @borisno2! - Enforce unique-whereforcontext.db.<list>.findUnique— a non-uniquewherenow throws a clear error instead of silently returning a nondeterministic row. UsefindFirstfor non-unique single-row lookups.@opensaas/stack-ui@0.25.0
Patch Changes
#607
61547beThanks @borisno2! - Fixui.listView.initialSortapplying sort client-side instead of as a DB-levelorderByPreviously,
initialSortwas applied to the already-fetched page in memory, meaning a 500-row list withinitialSort: { field: 'sentAt', direction: 'desc' }would only show the 50 most recent rows of the current page rather than the 50 most recent rows overall. The sort is now passed asorderBytofindManyso pagination and sorting compose correctly.Column-header clicks also now navigate with a
?sort=field:directionURL param (instead of mutating local state), so subsequent sorts are also DB-level and work correctly across pages.@opensaas/stack-auth@0.25.0
@opensaas/stack-rag@0.25.0
@opensaas/stack-storage@0.25.0
@opensaas/stack-storage-s3@0.25.0
@opensaas/stack-storage-vercel@0.25.0
@opensaas/stack-tiptap@0.25.0