Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/adapter-nuxt/tests/module.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,9 +337,9 @@ export default defineDatabaseConfig({
expect(addImports).toHaveBeenCalledTimes(1)
expect(addImports.mock.calls[0]?.[0]).toHaveLength(6)
expect(addImports.mock.calls[0]?.[0]).toEqual(expect.arrayContaining([
expect.objectContaining({ name: 'holo', as: 'holo', from: './runtime/composables' }),
expect.objectContaining({ name: 'useStorage', as: 'useStorage', from: './runtime/composables/storage' }),
expect.objectContaining({ name: 'Storage', as: 'Storage', from: './runtime/composables/storage' }),
expect.objectContaining({ name: 'holo', as: 'holo', from: '@holo-js/adapter-nuxt/runtime' }),
expect.objectContaining({ name: 'useStorage', as: 'useStorage', from: '@holo-js/adapter-nuxt/storage' }),
expect.objectContaining({ name: 'Storage', as: 'Storage', from: '@holo-js/adapter-nuxt/storage' }),
]))
expect(addServerImportsDir).toHaveBeenCalledWith('./runtime/server/imports')
expect(addServerImportsDir).toHaveBeenCalledTimes(1)
Expand Down
6 changes: 3 additions & 3 deletions packages/adapter-nuxt/tests/setup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,9 +423,9 @@ export default defineStorageConfig({
expect(addImports).toHaveBeenCalledTimes(1)
expect(addImports.mock.calls[0]?.[0]).toHaveLength(6)
expect(addImports.mock.calls[0]?.[0]).toEqual(expect.arrayContaining([
expect.objectContaining({ name: 'holo', as: 'holo', from: './runtime/composables' }),
expect.objectContaining({ name: 'useStorage', as: 'useStorage', from: './runtime/composables/storage' }),
expect.objectContaining({ name: 'Storage', as: 'Storage', from: './runtime/composables/storage' }),
expect.objectContaining({ name: 'holo', as: 'holo', from: '@holo-js/adapter-nuxt/runtime' }),
expect.objectContaining({ name: 'useStorage', as: 'useStorage', from: '@holo-js/adapter-nuxt/storage' }),
expect.objectContaining({ name: 'Storage', as: 'Storage', from: '@holo-js/adapter-nuxt/storage' }),
]))
expect(addServerImportsDir).toHaveBeenCalledWith('./runtime/server/imports')
expect(addServerImportsDir).toHaveBeenCalledTimes(1)
Expand Down
57 changes: 37 additions & 20 deletions packages/adapter-sveltekit/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { AsyncLocalStorage } from 'node:async_hooks'
import {
createHoloFrameworkAdapter,
type HoloAdapterProject,
type HoloFrameworkOptions,
} from '@holo-js/core'
import { getRequestEvent } from '$app/server'
import type { HoloConfigMap } from '@holo-js/config'
export {
holoSvelteKitTransport,
Expand All @@ -14,49 +14,66 @@ export type SvelteKitHoloOptions = HoloFrameworkOptions

export type SvelteKitHoloProject<TCustom extends HoloConfigMap = HoloConfigMap> = HoloAdapterProject<TCustom>

function withSvelteKitAuthRequest(options: SvelteKitHoloOptions = {}): SvelteKitHoloOptions {
if (options.authRequest) {
return options
type SvelteKitRequestEvent = {
readonly cookies: {
get(name: string): string | undefined
}

return {
...options,
authRequest: {
async getCookie(name: string) {
const event = getRequestEvent()
return event.cookies.get(name) ?? undefined
},
async getHeader(name: string) {
const event = getRequestEvent()
return event.request.headers.get(name) ?? undefined
},
},
readonly request: {
readonly headers: Headers
}
}

const svelteKitAdapter = createHoloFrameworkAdapter<SvelteKitHoloOptions>({
stateKey: '__holoSvelteKitAdapter__',
displayName: 'SvelteKit',
})
const svelteKitRequestEventStore = new AsyncLocalStorage<SvelteKitRequestEvent>()

function resolveSvelteKitAuthRequestAccessors(): NonNullable<SvelteKitHoloOptions['authRequest']> {
return {
async getCookie(name: string) {
const event = svelteKitRequestEventStore.getStore()
return event?.cookies.get(name) ?? undefined
},
async getHeader(name: string) {
const event = svelteKitRequestEventStore.getStore()
return event?.request.headers.get(name) ?? undefined
},
}
}

function resolveSvelteKitOptions(options: SvelteKitHoloOptions): SvelteKitHoloOptions {
return {
...options,
authRequest: options.authRequest ?? resolveSvelteKitAuthRequestAccessors(),
}
}

export const svelteKitHoloCapabilities = svelteKitAdapter.capabilities

export function runWithSvelteKitRequestEvent<TValue>(
event: SvelteKitRequestEvent,
callback: () => TValue,
): TValue {
return svelteKitRequestEventStore.run(event, callback)
}

export async function createSvelteKitHoloProject<TCustom extends HoloConfigMap = HoloConfigMap>(
options: SvelteKitHoloOptions = {},
): Promise<SvelteKitHoloProject<TCustom>> {
return svelteKitAdapter.createProject<TCustom>(withSvelteKitAuthRequest(options))
return svelteKitAdapter.createProject<TCustom>(resolveSvelteKitOptions(options))
}

export async function initializeSvelteKitHoloProject<TCustom extends HoloConfigMap = HoloConfigMap>(
options: SvelteKitHoloOptions = {},
): Promise<SvelteKitHoloProject<TCustom>> {
return svelteKitAdapter.initializeProject<TCustom>(withSvelteKitAuthRequest(options))
return svelteKitAdapter.initializeProject<TCustom>(resolveSvelteKitOptions(options))
}

export function createSvelteKitHoloHelpers<TCustom extends HoloConfigMap = HoloConfigMap>(
options: SvelteKitHoloOptions = {},
) {
return svelteKitAdapter.createHelpers<TCustom>(withSvelteKitAuthRequest(options))
return svelteKitAdapter.createHelpers<TCustom>(resolveSvelteKitOptions(options))
}

export async function resetSvelteKitHoloProject(): Promise<void> {
Expand Down
10 changes: 0 additions & 10 deletions packages/adapter-sveltekit/src/sveltekit.d.ts

This file was deleted.

15 changes: 0 additions & 15 deletions packages/adapter-sveltekit/tests/adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,20 +121,6 @@ async function loadAdapterModule() {
if (!adapterModulePromise) {
const { coreEntryUrl } = await ensureIsolatedCoreBuild()
vi.doMock('@holo-js/core', () => import(coreEntryUrl))
vi.doMock('$app/server', () => ({
getRequestEvent() {
return {
cookies: {
get() {
return undefined
},
},
request: {
headers: new Headers(),
},
}
},
}))
adapterModulePromise = import('../src')
}

Expand Down Expand Up @@ -187,7 +173,6 @@ afterEach(async () => {
await resetSvelteKitHoloProject()
adapterModulePromise = null
vi.doUnmock('@holo-js/core')
vi.doUnmock('$app/server')
vi.resetModules()
await Promise.all(tempDirs.splice(0).map(dir => rm(dir, { recursive: true, force: true })))
}, 45000)
Expand Down
10 changes: 7 additions & 3 deletions packages/adapter-sveltekit/tests/adapter.type.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { execFileSync } from 'node:child_process'
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join, resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
import { createSvelteKitHoloHelpers } from '../src'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { SerializedSvelteKitData } from '../src/transport'
import {
linkInstalledDependenciesForPackage,
Expand Down Expand Up @@ -33,8 +32,13 @@ declare module '@holo-js/config' {
}
}

afterEach(() => {
vi.resetModules()
})

describe('@holo-js/adapter-sveltekit typing', () => {
it('preserves inference for helper accessors', () => {
it('preserves inference for helper accessors', async () => {
const { createSvelteKitHoloHelpers } = await import('../src')
const helpers = createSvelteKitHoloHelpers()

type Helpers = typeof helpers
Expand Down
133 changes: 133 additions & 0 deletions packages/adapter-sveltekit/tests/runtime.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { afterEach, describe, expect, it, vi } from 'vitest'

type MockAuthRequest = {
getCookie(name: string): Promise<string | undefined>
getHeader(name: string): Promise<string | undefined>
}

function makeHoloCoreMock(
setCapturedAuthRequest: (authRequest: MockAuthRequest | undefined) => void,
) {
return {
createHoloFrameworkAdapter: () => ({
capabilities: {},
async createProject() {
return {}
},
async initializeProject() {
return {}
},
createHelpers(options: {
authRequest?: MockAuthRequest
}) {
setCapturedAuthRequest(options.authRequest)

return {
async getApp() {
return {}
},
async getProject() {
return {}
},
async getSession() {
return undefined
},
async getAuth() {
return undefined
},
async useConfig() {
return undefined
},
async config() {
return undefined
},
}
},
async resetProject() {},
internals: {
getState() {
return {}
},
resolveOptions() {
return {}
},
},
}),
}
}

afterEach(() => {
vi.doUnmock('@holo-js/core')
vi.resetModules()
})

describe('@holo-js/adapter-sveltekit request context', () => {
it('owns auth request accessors inside the adapter and resolves them from the current request event', async () => {
let capturedAuthRequest: MockAuthRequest | undefined

vi.doMock('@holo-js/core', () => makeHoloCoreMock((authRequest) => {
capturedAuthRequest = authRequest
}))

const { createSvelteKitHoloHelpers, runWithSvelteKitRequestEvent } = await import('../src')
const helpers = createSvelteKitHoloHelpers({
projectRoot: '/tmp/holo-sveltekit-runtime',
})

await runWithSvelteKitRequestEvent({
cookies: {
get(name: string) {
return name === 'session' ? 'cookie-value' : undefined
},
},
request: {
headers: new Headers({
'x-request-id': 'header-value',
}),
},
}, async () => {
await helpers.getProject()

expect(capturedAuthRequest).toBeDefined()
if (!capturedAuthRequest) {
throw new Error('Expected auth request accessors to be captured.')
}
await expect(capturedAuthRequest.getCookie('session')).resolves.toBe('cookie-value')
await expect(capturedAuthRequest.getHeader('x-request-id')).resolves.toBe('header-value')
})

expect(capturedAuthRequest).toBeDefined()
if (!capturedAuthRequest) {
throw new Error('Expected auth request accessors to be captured.')
}
await expect(capturedAuthRequest.getCookie('session')).resolves.toBeUndefined()
await expect(capturedAuthRequest.getHeader('x-request-id')).resolves.toBeUndefined()
})

it('preserves explicit auth request overrides', async () => {
let capturedAuthRequest: MockAuthRequest | undefined

vi.doMock('@holo-js/core', () => makeHoloCoreMock((authRequest) => {
capturedAuthRequest = authRequest
}))

const customAuthRequest = {
async getCookie() {
return 'custom-cookie'
},
async getHeader() {
return 'custom-header'
},
}

const { createSvelteKitHoloHelpers } = await import('../src')
const helpers = createSvelteKitHoloHelpers({
projectRoot: '/tmp/holo-sveltekit-runtime',
authRequest: customAuthRequest,
})

await helpers.getProject()

expect(capturedAuthRequest).toBe(customAuthRequest)
})
})
5 changes: 5 additions & 0 deletions packages/auth/src/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ type AuthProviderAdapterBase<TUser> = {
findById(id: string | number): Promise<TUser | null>
findByCredentials(credentials: Readonly<Record<string, unknown>>): Promise<TUser | null>
create(input: Readonly<Record<string, unknown>>): Promise<TUser>
delete?(id: string | number): Promise<void>
update?(user: TUser, input: Readonly<Record<string, unknown>>): Promise<TUser>
matchesUser?(user: unknown): boolean
getId(user: TUser): string | number
Expand Down Expand Up @@ -438,6 +439,10 @@ export interface AuthSessionRuntime {
sessionId: string,
options?: { readonly store?: string },
): Promise<string>
consumeRememberMeToken?(
token: string,
options?: { readonly store?: string },
): Promise<AuthSessionRecord | null>
cookie?(
name: string,
value: string,
Expand Down
Loading