Skip to content

Version Packages - #883

Merged
borisno2 merged 1 commit into
mainfrom
changeset-release/main
Aug 4, 2026
Merged

Version Packages#883
borisno2 merged 1 commit into
mainfrom
changeset-release/main

Conversation

@github-actions

@github-actions github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

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-auth@0.38.0

Minor Changes

  • #888 8183827 Thanks @borisno2! - buildBetterAuthOptions() and createAuth() now accept an optional third argument — your app's betterAuthPlugins array, the same array passed to authPlugin({ betterAuthPlugins }) — so the returned options/Auth type carries the literal plugin tuple instead of the widened BetterAuthOptions/Auth<BetterAuthOptions>. Without this, betterAuth() constructed from the widened return loses plugin-derived auth.api.* endpoints (e.g. emailOTP()'s signInEmailOTP) and a customSession() plugin's replaced session shape.

    export const appBetterAuthPlugins = [emailOTP({ sendVerificationOTP })] // same array passed to authPlugin({ betterAuthPlugins })
    
    export const auth = betterAuth({
      ...(await buildBetterAuthOptions(config, rawOpensaasContext, appBetterAuthPlugins)),
    })
    // auth.api.signInEmailOTP is now typed, and auth.api.getSession() returns your customSession() shape.

    The supplied tuple is for typing only — the plugin array used at runtime is always the one resolved from authPlugin({ betterAuthPlugins }). Passing a tuple that isn't the same plugin instances in the same order throws, naming the mismatch, so the two can't silently drift apart. Calling either function with no third argument is unchanged — same widened return type, same runtime options, fully backwards compatible.

    Also, AuthConfig/NormalizedAuthConfig's betterAuthPlugins field is now typed as better-auth's own BetterAuthPlugin[] instead of any[].

  • #889 b9b9357 Thanks @borisno2! - Fix getSessionFromAuth to project sessionFields from the resolved better-auth session instead of only its user sub-object. A customSession plugin's replaced shape with no user key is now correctly treated as a signed-in session (never misreported as anonymous), and a session-only field (e.g. the admin plugin's impersonatedBy) is now resolvable. Errors from the underlying session lookup now propagate instead of silently becoming null, and a sessionFields entry that can't be resolved is omitted and logs a warning (once per field, per process) instead of vanishing silently.

    The scaffolded getSession() — the CLI feature generator's lib/auth.ts template, and examples/starter-auth/examples/auth-demo — now call this single shared helper, reading sessionFields from the resolved config at runtime instead of baking a field list in at generation time. examples/auth-demo's getSession() also now correctly returns null for an anonymous visitor (previously returned a truthy object of undefined values).

    authPlugin({ sessionFields: ['userId', 'email', 'name', 'role'] })
    // lib/auth.ts
    export async function getSession() {
      const resolvedConfig = await config
      const authConfig = resolvedConfig._pluginData?.auth as NormalizedAuthConfig | undefined
      const sessionFields = authConfig?.sessionFields ?? ['userId', 'email', 'name']
      return getSessionFromAuth(auth, sessionFields, await headers())
    }

@opensaas/stack-cli@0.38.0

Minor Changes

  • #889 b9b9357 Thanks @borisno2! - Fix getSessionFromAuth to project sessionFields from the resolved better-auth session instead of only its user sub-object. A customSession plugin's replaced shape with no user key is now correctly treated as a signed-in session (never misreported as anonymous), and a session-only field (e.g. the admin plugin's impersonatedBy) is now resolvable. Errors from the underlying session lookup now propagate instead of silently becoming null, and a sessionFields entry that can't be resolved is omitted and logs a warning (once per field, per process) instead of vanishing silently.

    The scaffolded getSession() — the CLI feature generator's lib/auth.ts template, and examples/starter-auth/examples/auth-demo — now call this single shared helper, reading sessionFields from the resolved config at runtime instead of baking a field list in at generation time. examples/auth-demo's getSession() also now correctly returns null for an anonymous visitor (previously returned a truthy object of undefined values).

    authPlugin({ sessionFields: ['userId', 'email', 'name', 'role'] })
    // lib/auth.ts
    export async function getSession() {
      const resolvedConfig = await config
      const authConfig = resolvedConfig._pluginData?.auth as NormalizedAuthConfig | undefined
      const sessionFields = authConfig?.sessionFields ?? ['userId', 'email', 'name']
      return getSessionFromAuth(auth, sessionFields, await headers())
    }

Patch Changes

@opensaas/stack-core@0.38.0

Minor Changes

  • #873 b21d8b2 Thanks @borisno2! - Naming a relation in an include now fetches only that relation's own columns and stops, at every level — not just the root. This completes ADR-0024 (a bare read fetches scalars, never relations): reaching a relation's own relations means naming them too, e.g. include: { author: { include: { organization: true } } } rather than relying on include: { author: true } to pull organization in automatically. A relation nobody named (caller include, fragment query, or a field's needs) never has its list's operation-level query access evaluated at all.

    This is a silent break — detect it before you upgrade. An include that named a relation bare and read past it (item.<named>[0].<unnamed>) now gets undefined for the unnamed part, with no error. Grep your codebase for include: { calls whose consumers read a second hop off a bare-named relation, and add the deeper relation explicitly:

    // Before: relied on `author` auto-expanding its own `organization` relation
    const post = await context.db.post.findUnique({
      where: { id },
      include: { author: true },
    })
    post.author.organization // silently undefined now
    
    // After: name the relation you actually need
    const post = await context.db.post.findUnique({
      where: { id },
      include: { author: { include: { organization: true } } },
    })
    post.author.organization // present

    AccessScopeDepthExceededError (thrown when an include names a relation past READ_INCLUDE_MAX_DEPTH) keeps its type, fields, and throw sites — only its message wording changed, from describing an inability to scope to describing a cost refusal, since the depth cap is now a cost limit rather than a security boundary (nothing walks the relationship graph unprompted anymore).

  • #890 17eb72f Thanks [@list({](https://github.com/list({)! - A computed field — any field carrying a resolveOutput hook, virtual or not — is now computed if and only if a read is actually going to return it. A fragment query that selects three fields no longer runs every resolveOutput on the list and discards the rest: an unselected field's field-level read access is never evaluated and its hook never runs. Its declared relations (needs, ADR-0025) are fetched under exactly the same condition, folded recursively at every nesting level — a nested fragment selecting a subset computes only that subset, while a nested include still computes every computed field at that level, matching bare and include-based reads, which are unaffected: they still compute every computed field on the list, exactly as before. See ADR-0027.

    This is a silent break — detect it before you upgrade, the same way ADR-0024's and ADR-0026's were. Two independent behaviors changed with no thrown error:

    1. A hook's item never carries another computed field's resolved output, on any read path. Previously a virtual field received the already-assembled, already-resolved object, so a virtual field could read an earlier-declared virtual (or any field carrying its own resolveOutput, e.g. a password()'s wrapper or a formatted display field) and see its resolved value — working only by declaration order, with reordering two fields silently changing the result. Now every computed field's hook sees only the row's stored columns and its own declared dependencies; reaching for a sibling that is itself computed finds nothing there (or its raw stored form, never the wrapped/resolved value), the same as reaching for a field that was never declared. Grep your config for a resolveOutput whose item reads a field that is itself computed — virtual fields reading other virtual fields, or a hook reading a stored field that carries its own resolveOutput (a password wrapper, a formatted date) — and recompute from the shared stored columns instead of relying on another field's hook having already run.
    2. A field's hook no longer runs just because it's on the list — only because a read selects it. If you relied on a resolveOutput hook running for a side effect (logging, cache warming) on every read regardless of a fragment's own field selection, that side effect now only fires when the fragment actually names the field. Grep for a fragment query that intentionally omits a field whose hook you were relying on for a side effect, and select that field explicitly (or move the side effect to a hook that isn't projection-gated, e.g. afterOperation).

    A hookless virtual field (one with access.read but no resolveOutput) no longer has its read access evaluated at all on any read — such a field can never produce output, so under this rule it does no work at all.

    // Before: `displayName` (declared after `fullNameCached`) could read the
    // latter's resolved value purely because of declaration order.
    
      fields: {
        firstName: text(),
        lastName: text(),
        fullNameCached: virtual({
          type: 'string',
          hooks: { resolveOutput: ({ item }) => `${item.firstName} ${item.lastName}` },
        }),
        displayName: virtual({
          type: 'string',
          // item.fullNameCached is now always undefined here — recompute from
          // the shared stored columns instead.
          hooks: { resolveOutput: ({ item }) => `${item.fullNameCached} (${item.firstName[0]}.)` },
        }),
      },
    })
    
    // After: compute from the stored columns both fields actually share.
    displayName: virtual({
      type: 'string',
      hooks: {
        resolveOutput: ({ item }) => `${item.firstName} ${item.lastName} (${item.firstName[0]}.)`,
      },
    }),

Patch Changes

  • #873 b21d8b2 Thanks @borisno2! - Fix needs declarations being dropped beneath a caller-named relation that revisits a list (e.g. include: { author: { include: { posts: true } } }, or a self-referential parent), which left the revisited list's computed fields resolving over undefined.

@opensaas/stack-rag@0.38.0

@opensaas/stack-storage@0.38.0

@opensaas/stack-storage-s3@0.38.0

@opensaas/stack-storage-vercel@0.38.0

@opensaas/stack-tiptap@0.38.0

@opensaas/stack-ui@0.38.0

@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
stack-docs Ready Ready Preview Aug 4, 2026 12:06pm

@github-actions
github-actions Bot force-pushed the changeset-release/main branch from aa0a7c2 to f985bce Compare August 4, 2026 12:05
@borisno2
borisno2 enabled auto-merge (squash) August 4, 2026 12:07
@borisno2
borisno2 merged commit 54386a0 into main Aug 4, 2026
6 checks passed
@borisno2
borisno2 deleted the changeset-release/main branch August 4, 2026 12:14
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Coverage Report for Core Package Coverage (./packages/core)

Status Category Percentage Covered / Total
🟢 Lines 93.56% (🎯 65%) 1251 / 1337
🟢 Statements 92.03% (🎯 65%) 1352 / 1469
🟢 Functions 98.12% (🎯 62%) 209 / 213
🟢 Branches 83.74% (🎯 50%) 917 / 1095
File CoverageNo changed files found.
Generated in workflow #1621 for commit f985bce by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Coverage Report for UI Package Coverage (./packages/ui)

Status Category Percentage Covered / Total
🔵 Lines 76.72% 244 / 318
🔵 Statements 76.29% 251 / 329
🔵 Functions 69.15% 74 / 107
🔵 Branches 64.25% 160 / 249
File CoverageNo changed files found.
Generated in workflow #1621 for commit f985bce by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Coverage Report for CLI Package Coverage (./packages/cli)

Status Category Percentage Covered / Total
🔵 Lines 79.16% 1539 / 1944
🔵 Statements 78.86% 1601 / 2030
🔵 Functions 85.94% 214 / 249
🔵 Branches 67.84% 690 / 1017
File CoverageNo changed files found.
Generated in workflow #1621 for commit f985bce by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Coverage Report for Auth Package Coverage (./packages/auth)

Status Category Percentage Covered / Total
🔵 Lines 98.33% 118 / 120
🔵 Statements 98.37% 121 / 123
🔵 Functions 100% 38 / 38
🔵 Branches 94.44% 85 / 90
File CoverageNo changed files found.
Generated in workflow #1621 for commit f985bce by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Coverage Report for Storage Package Coverage (./packages/storage)

Status Category Percentage Covered / Total
🔵 Lines 78.57% 220 / 280
🔵 Statements 80.06% 245 / 306
🔵 Functions 86.07% 68 / 79
🔵 Branches 75.88% 214 / 282
File CoverageNo changed files found.
Generated in workflow #1621 for commit f985bce by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Coverage Report for RAG Package Coverage (./packages/rag)

Status Category Percentage Covered / Total
🔵 Lines 47.97% 355 / 740
🔵 Statements 48.14% 377 / 783
🔵 Functions 54.26% 70 / 129
🔵 Branches 42.55% 180 / 423
File CoverageNo changed files found.
Generated in workflow #1621 for commit f985bce by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Coverage Report for Storage S3 Package Coverage (./packages/storage-s3)

Status Category Percentage Covered / Total
🔵 Lines 100% 40 / 40
🔵 Statements 100% 40 / 40
🔵 Functions 100% 9 / 9
🔵 Branches 100% 19 / 19
File CoverageNo changed files found.
Generated in workflow #1621 for commit f985bce by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Coverage Report for Storage Vercel Package Coverage (./packages/storage-vercel)

Status Category Percentage Covered / Total
🔵 Lines 100% 68 / 68
🔵 Statements 100% 71 / 71
🔵 Functions 100% 15 / 15
🔵 Branches 97.87% 46 / 47
File CoverageNo changed files found.
Generated in workflow #1621 for commit f985bce by the Vitest Coverage Report Action

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.

1 participant