on-zero 0.6.13 → 0.6.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/cjs/createSchemaFromDrizzle.cjs +4 -4
  2. package/dist/cjs/createSchemaFromDrizzle.native.js +4 -4
  3. package/dist/cjs/createSchemaFromDrizzle.native.js.map +1 -1
  4. package/dist/cjs/createZeroDirectServer.cjs +279 -0
  5. package/dist/cjs/createZeroDirectServer.native.js +292 -0
  6. package/dist/cjs/createZeroDirectServer.native.js.map +1 -0
  7. package/dist/cjs/createZeroSQLiteServer.cjs +8 -277
  8. package/dist/cjs/createZeroSQLiteServer.native.js +8 -390
  9. package/dist/cjs/createZeroSQLiteServer.native.js.map +1 -1
  10. package/dist/cjs/createZeroSQLiteServer.test.cjs +30 -3
  11. package/dist/cjs/createZeroSQLiteServer.test.native.js +31 -3
  12. package/dist/cjs/createZeroSQLiteServer.test.native.js.map +1 -1
  13. package/dist/cjs/createZeroServerCore.cjs +20 -205
  14. package/dist/cjs/createZeroServerCore.native.js +19 -213
  15. package/dist/cjs/createZeroServerCore.native.js.map +1 -1
  16. package/dist/cjs/generate.cjs +10 -3
  17. package/dist/cjs/generate.native.js +10 -3
  18. package/dist/cjs/generate.native.js.map +1 -1
  19. package/dist/cjs/generate.test.cjs +39 -0
  20. package/dist/cjs/generate.test.native.js +39 -0
  21. package/dist/cjs/generate.test.native.js.map +1 -1
  22. package/dist/cjs/sqliteRuntime.cjs +318 -0
  23. package/dist/cjs/sqliteRuntime.native.js +438 -0
  24. package/dist/cjs/sqliteRuntime.native.js.map +1 -0
  25. package/dist/cjs/syncHost.cjs +16 -9
  26. package/dist/cjs/syncHost.native.js +21 -12
  27. package/dist/cjs/syncHost.native.js.map +1 -1
  28. package/dist/cjs/syncHost.test.cjs +13 -2
  29. package/dist/cjs/syncHost.test.native.js +14 -2
  30. package/dist/cjs/syncHost.test.native.js.map +1 -1
  31. package/dist/esm/createSchemaFromDrizzle.mjs +2 -2
  32. package/dist/esm/createSchemaFromDrizzle.native.js +2 -2
  33. package/dist/esm/createZeroDirectServer.mjs +251 -0
  34. package/dist/esm/createZeroDirectServer.mjs.map +1 -0
  35. package/dist/esm/createZeroDirectServer.native.js +261 -0
  36. package/dist/esm/createZeroDirectServer.native.js.map +1 -0
  37. package/dist/esm/createZeroSQLiteServer.mjs +3 -272
  38. package/dist/esm/createZeroSQLiteServer.mjs.map +1 -1
  39. package/dist/esm/createZeroSQLiteServer.native.js +3 -385
  40. package/dist/esm/createZeroSQLiteServer.native.js.map +1 -1
  41. package/dist/esm/createZeroSQLiteServer.test.mjs +30 -3
  42. package/dist/esm/createZeroSQLiteServer.test.mjs.map +1 -1
  43. package/dist/esm/createZeroSQLiteServer.test.native.js +31 -3
  44. package/dist/esm/createZeroSQLiteServer.test.native.js.map +1 -1
  45. package/dist/esm/createZeroServerCore.mjs +18 -202
  46. package/dist/esm/createZeroServerCore.mjs.map +1 -1
  47. package/dist/esm/createZeroServerCore.native.js +18 -211
  48. package/dist/esm/createZeroServerCore.native.js.map +1 -1
  49. package/dist/esm/generate.mjs +10 -3
  50. package/dist/esm/generate.mjs.map +1 -1
  51. package/dist/esm/generate.native.js +10 -3
  52. package/dist/esm/generate.native.js.map +1 -1
  53. package/dist/esm/generate.test.mjs +39 -1
  54. package/dist/esm/generate.test.mjs.map +1 -1
  55. package/dist/esm/generate.test.native.js +39 -1
  56. package/dist/esm/generate.test.native.js.map +1 -1
  57. package/dist/esm/sqliteRuntime.mjs +289 -0
  58. package/dist/esm/sqliteRuntime.mjs.map +1 -0
  59. package/dist/esm/sqliteRuntime.native.js +406 -0
  60. package/dist/esm/sqliteRuntime.native.js.map +1 -0
  61. package/dist/esm/syncHost.mjs +14 -7
  62. package/dist/esm/syncHost.mjs.map +1 -1
  63. package/dist/esm/syncHost.native.js +19 -10
  64. package/dist/esm/syncHost.native.js.map +1 -1
  65. package/dist/esm/syncHost.test.mjs +13 -2
  66. package/dist/esm/syncHost.test.mjs.map +1 -1
  67. package/dist/esm/syncHost.test.native.js +14 -2
  68. package/dist/esm/syncHost.test.native.js.map +1 -1
  69. package/package.json +5 -6
  70. package/src/createSchemaFromDrizzle.ts +2 -2
  71. package/src/createZeroDirectServer.ts +427 -0
  72. package/src/createZeroSQLiteServer.test.ts +20 -5
  73. package/src/createZeroSQLiteServer.ts +14 -481
  74. package/src/createZeroServerCore.ts +32 -337
  75. package/src/generate.test.ts +57 -1
  76. package/src/generate.ts +23 -3
  77. package/src/sqliteRuntime.ts +512 -0
  78. package/src/syncHost.test.ts +10 -3
  79. package/src/syncHost.ts +49 -12
  80. package/test-fixtures/server-return-types.ts +8 -1
  81. package/types/createSchemaFromDrizzle.d.ts +2 -2
  82. package/types/createSchemaFromDrizzle.d.ts.map +1 -1
  83. package/types/createZeroDirectServer.d.ts +80 -0
  84. package/types/createZeroDirectServer.d.ts.map +1 -0
  85. package/types/createZeroSQLiteServer.d.ts +5 -30
  86. package/types/createZeroSQLiteServer.d.ts.map +1 -1
  87. package/types/createZeroServerCore.d.ts +30 -87
  88. package/types/createZeroServerCore.d.ts.map +1 -1
  89. package/types/generate.d.ts.map +1 -1
  90. package/types/sqliteRuntime.d.ts +71 -0
  91. package/types/sqliteRuntime.d.ts.map +1 -0
  92. package/types/syncHost.d.ts +10 -19
  93. package/types/syncHost.d.ts.map +1 -1
@@ -1,47 +1,22 @@
1
- import { mustGetQuery } from '@rocicorp/zero'
2
1
  import {
3
2
  PushProcessor,
4
3
  handleQueryRequest as zeroHandleQueryRequest,
5
4
  } from '@rocicorp/zero/server'
6
5
 
7
- import { createPermissions } from './createPermissions'
8
- import { createMutators } from './helpers/createMutators'
9
6
  import {
10
- getScopedAuthData,
11
- isInZeroMutation,
12
- mutatorContext,
13
- runWithAuthScope,
14
- } from './helpers/mutatorContext'
7
+ createAsyncTaskCollector,
8
+ createZeroDirectServerRuntime,
9
+ resolveServerQuery,
10
+ type TransactionAttemptLifecycle,
11
+ type ZeroServerCommonOptions,
12
+ type ZeroServerDatabase,
13
+ } from './createZeroDirectServer'
14
+ import { createMutators } from './helpers/createMutators'
15
+ import { runWithAuthScope } from './helpers/mutatorContext'
15
16
  import { runWithQueryContext } from './helpers/queryContext'
16
- import { getMutationsPermissions } from './modelRegistry'
17
- import { setCustomQueries } from './run'
18
- import { getZQL, setEnvironment, setSchema } from './state'
19
- import { setEvaluatingPermission } from './where'
20
- import { setRunner } from './zeroRunner'
21
17
 
22
- import type {
23
- AdminRoleMode,
24
- AsyncAction,
25
- AuthData,
26
- GenericModels,
27
- MutatorContext,
28
- QueryBuilder,
29
- Transaction,
30
- } from './types'
31
- import type {
32
- AnyQueryRegistry,
33
- HumanReadable,
34
- Query,
35
- Schema as ZeroSchema,
36
- } from '@rocicorp/zero'
37
- import type { Database, TransactionProviderInput } from '@rocicorp/zero/server'
38
-
39
- type MutateAuthData = Pick<AuthData, 'email' | 'id'> & Partial<AuthData>
40
-
41
- type MutateOptions = {
42
- authData?: MutateAuthData
43
- awaitEffects?: boolean
44
- }
18
+ import type { AuthData, GenericModels } from './types'
19
+ import type { Schema as ZeroSchema } from '@rocicorp/zero'
45
20
 
46
21
  type AlreadyProcessedMutation = {
47
22
  id: {
@@ -58,122 +33,12 @@ type PushRequestBody = {
58
33
  clientGroupID?: unknown
59
34
  }
60
35
 
61
- type ServerMutate<Models extends GenericModels> = {
62
- [Key in keyof Models]: {
63
- [K in keyof Models[Key]['mutate']]: Models[Key]['mutate'][K] extends (
64
- ctx: MutatorContext,
65
- arg: infer Arg,
66
- ) => any
67
- ? (arg: Arg, options?: MutateOptions) => Promise<void>
68
- : (options?: MutateOptions) => Promise<void>
69
- }
70
- }
71
-
72
- export type ValidateQueryArgs = {
73
- authData: AuthData | null
74
- queryName: string
75
- params: unknown
76
- }
77
-
78
- export type ValidateMutationArgs = {
79
- authData: AuthData | null
80
- mutatorName: string
81
- tableName: string
82
- args: unknown
83
- }
84
-
85
- export type ValidateQueryFn = (args: ValidateQueryArgs) => void
86
- export type ValidateMutationFn = (args: ValidateMutationArgs) => void | Promise<void>
87
-
88
- export type ServerQueryResolverOptions = {
89
- authData: AuthData | null
90
- name: string
91
- args: unknown
92
- queries: AnyQueryRegistry
93
- permissions: ReturnType<typeof createPermissions>
94
- validateQuery?: ValidateQueryFn
95
- }
96
-
97
- export function resolveServerQuery({
98
- authData,
99
- name,
100
- args,
101
- queries,
102
- permissions,
103
- validateQuery,
104
- }: ServerQueryResolverOptions) {
105
- if (name.startsWith('permission.')) {
106
- const table = name.slice('permission.'.length)
107
- const { objOrId } = args as {
108
- objOrId: string | Record<string, any>
109
- }
110
- const permission = getMutationsPermissions(table)
111
- if (!permission) {
112
- throw new Error(`[permission] no permission defined for table: ${table}`)
113
- }
114
- setEvaluatingPermission(true)
115
- try {
116
- return (getZQL() as any)[table]
117
- .where((eb: any) => {
118
- return permissions.buildPermissionQuery(
119
- authData,
120
- eb,
121
- permission,
122
- objOrId,
123
- table,
124
- )
125
- })
126
- .one()
127
- } finally {
128
- setEvaluatingPermission(false)
129
- }
130
- }
131
-
132
- if (validateQuery) {
133
- validateQuery({ authData, queryName: name, params: args })
134
- }
135
-
136
- const query = (mustGetQuery as any)(queries, name)
137
- return query.fn({ args, ctx: authData })
138
- }
139
-
140
- export type TransactionAttemptLifecycle = {
141
- beginAttempt(): void
142
- completeAttempt(): void
143
- rollbackAttempt(): void
144
- commitTransaction(): void
145
- rollbackTransaction(): void
146
- }
147
-
148
- export type ZeroServerDatabase = Database<Transaction>
149
-
150
36
  export type PersistAlreadyProcessedArgs = {
151
37
  clientGroupID: string
152
38
  mutations: AlreadyProcessedMutation[]
153
39
  upstreamSchema: string
154
40
  }
155
41
 
156
- export type ZeroServerCommonOptions<
157
- Schema extends ZeroSchema,
158
- Models extends GenericModels,
159
- ServerActions extends Record<string, unknown>,
160
- > = {
161
- schema: Schema
162
- models: Models
163
- createServerActions: () => ServerActions
164
- queries?: AnyQueryRegistry
165
- /** generated validators keyed by model and mutation name */
166
- mutations?: Record<string, Record<string, any>>
167
- /** synchronous query authorization hook; throw to reject */
168
- validateQuery?: ValidateQueryFn
169
- /** mutation authorization hook; throw to reject */
170
- validateMutation?: ValidateMutationFn
171
- /** controls which operations the admin role bypasses */
172
- defaultAllowAdminRole?: AdminRoleMode
173
- /** auth data used by direct server mutations when no scope provides it */
174
- defaultMutateAuthData?: MutateAuthData
175
- }
176
-
177
42
  type ZeroServerCoreOptions<
178
43
  Schema extends ZeroSchema,
179
44
  Models extends GenericModels,
@@ -183,60 +48,6 @@ type ZeroServerCoreOptions<
183
48
  persistAlreadyProcessed?: (args: PersistAlreadyProcessedArgs) => Promise<void>
184
49
  }
185
50
 
186
- function createAsyncTaskCollector() {
187
- const committed: AsyncAction[] = []
188
- let attempt: AsyncAction[] | undefined
189
- let completedAttempt: AsyncAction[] | undefined
190
- const current = () => attempt ?? committed
191
- const queue = new Proxy([] as AsyncAction[], {
192
- deleteProperty(_target, property) {
193
- return Reflect.deleteProperty(current(), property)
194
- },
195
- get(_target, property) {
196
- const target = current()
197
- const value = Reflect.get(target, property, target)
198
- return typeof value === 'function' ? value.bind(target) : value
199
- },
200
- getOwnPropertyDescriptor(_target, property) {
201
- return Reflect.getOwnPropertyDescriptor(current(), property)
202
- },
203
- has(_target, property) {
204
- return Reflect.has(current(), property)
205
- },
206
- ownKeys() {
207
- return Reflect.ownKeys(current())
208
- },
209
- set(_target, property, value) {
210
- return Reflect.set(current(), property, value)
211
- },
212
- })
213
-
214
- const lifecycle: TransactionAttemptLifecycle = {
215
- beginAttempt() {
216
- attempt = []
217
- completedAttempt = undefined
218
- },
219
- completeAttempt() {
220
- completedAttempt = attempt
221
- attempt = undefined
222
- },
223
- rollbackAttempt() {
224
- attempt = undefined
225
- completedAttempt = undefined
226
- },
227
- commitTransaction() {
228
- if (completedAttempt) committed.push(...completedAttempt)
229
- completedAttempt = undefined
230
- },
231
- rollbackTransaction() {
232
- attempt = undefined
233
- completedAttempt = undefined
234
- },
235
- }
236
-
237
- return { committed, lifecycle, queue }
238
- }
239
-
240
51
  export function createZeroServerCore<
241
52
  Schema extends ZeroSchema,
242
53
  Models extends GenericModels,
@@ -245,7 +56,7 @@ export function createZeroServerCore<
245
56
  createDatabase,
246
57
  createServerActions,
247
58
  defaultAllowAdminRole = 'all',
248
- defaultMutateAuthData = {} as MutateAuthData,
59
+ defaultMutateAuthData,
249
60
  models,
250
61
  mutations: mutationValidators,
251
62
  persistAlreadyProcessed,
@@ -254,13 +65,17 @@ export function createZeroServerCore<
254
65
  validateMutation,
255
66
  validateQuery,
256
67
  }: ZeroServerCoreOptions<Schema, Models, ServerActions>) {
257
- setSchema(schema)
258
- setEnvironment('server')
259
-
260
- const permissions = createPermissions<Schema>({
261
- environment: 'server',
68
+ const { permissions, server } = createZeroDirectServerRuntime({
69
+ createDatabase,
70
+ createServerActions,
71
+ defaultAllowAdminRole,
72
+ defaultMutateAuthData,
73
+ models,
74
+ mutations: mutationValidators,
75
+ queries,
262
76
  schema,
263
- adminRoleMode: defaultAllowAdminRole,
77
+ validateMutation,
78
+ validateQuery,
264
79
  })
265
80
 
266
81
  const handleMutationRequest = async ({
@@ -342,135 +157,13 @@ export function createZeroServerCore<
342
157
  : zeroHandleQueryRequest({ handler, schema, request, userID }),
343
158
  )
344
159
 
345
- return {
346
- response,
347
- }
348
- }
349
-
350
- async function runTransaction<
351
- CB extends (tx: Transaction) => Promise<any>,
352
- Returns extends CB extends (tx: Transaction) => Promise<infer X> ? X : never,
353
- >(database: ZeroServerDatabase, query: CB): Promise<Returns> {
354
- try {
355
- if (isInZeroMutation()) {
356
- const { tx } = mutatorContext()
357
- return await query(tx)
358
- }
359
- return (await database.transaction(query, dummyTransactionInput)) as Returns
360
- } catch (err) {
361
- if ((err as any)?.name !== 'PermissionError') {
362
- console.error(`Error running transaction(): ${err}`)
363
- }
364
- throw err
365
- }
366
- }
367
-
368
- async function transaction<
369
- CB extends (tx: Transaction) => Promise<any>,
370
- Returns extends CB extends (tx: Transaction) => Promise<infer X> ? X : never,
371
- >(query: CB): Promise<Returns> {
372
- return runTransaction(createDatabase(), query)
160
+ return { response }
373
161
  }
374
162
 
375
- async function runMutate(
376
- modelName: string,
377
- mutatorName: string,
378
- mutatorArg: unknown,
379
- options?: MutateOptions,
380
- ) {
381
- let authData = options?.authData
382
- if (!authData) {
383
- const scoped = getScopedAuthData()
384
- if (scoped) authData = scoped as MutateAuthData
385
- }
386
-
387
- const tasks = createAsyncTaskCollector()
388
- const mutators = createMutators({
389
- models,
390
- environment: 'server',
391
- asyncTasks: tasks.queue,
392
- authData: {
393
- ...defaultMutateAuthData,
394
- ...authData,
395
- },
396
- createServerActions,
397
- can: permissions.can,
398
- validateMutation,
399
- mutationValidators,
400
- })
401
- const modelMutators = mutators[modelName as keyof typeof mutators] as Record<
402
- string,
403
- (tx: Transaction, arg?: unknown) => Promise<void>
404
- >
405
- const mutator = modelMutators[mutatorName]
406
-
407
- await runTransaction(
408
- withTransactionLifecycle(createDatabase(), tasks.lifecycle),
409
- async (tx) => {
410
- await mutator(tx, mutatorArg)
411
- },
412
- )
413
-
414
- if (tasks.committed.length) {
415
- const resolvedAuth = authData ?? null
416
- const promise = Promise.all(
417
- tasks.committed.map((task) => runWithAuthScope(resolvedAuth, task)),
418
- )
419
- if (options?.awaitEffects) {
420
- await promise
421
- } else {
422
- promise.catch((err: unknown) => {
423
- console.error('[mutate] async tasks failed', err)
424
- })
425
- }
426
- }
427
- }
428
-
429
- const mutate = new Proxy({} as ServerMutate<Models>, {
430
- get(_, modelName: string) {
431
- return new Proxy(
432
- {},
433
- {
434
- get(_, mutatorName: string) {
435
- return (arg: unknown, options?: MutateOptions) =>
436
- runMutate(modelName, mutatorName, arg, options)
437
- },
438
- },
439
- )
440
- },
441
- })
442
-
443
- function query<R>(
444
- cb: (q: QueryBuilder) => Query<any, Schema, R>,
445
- authData?: AuthData | null,
446
- ): Promise<HumanReadable<R>> {
447
- const run = () =>
448
- transaction(async (tx) => {
449
- return tx.run(cb(getZQL()))
450
- }) as any
451
-
452
- if (authData !== undefined) {
453
- return runWithQueryContext({ authData }, run)
454
- }
455
- return run()
456
- }
457
-
458
- if (queries) {
459
- setCustomQueries(queries)
460
- }
461
-
462
- setRunner((queryObj) => {
463
- return transaction(async (tx) => {
464
- return tx.run(queryObj)
465
- })
466
- })
467
-
468
163
  return {
469
164
  handleMutationRequest,
470
165
  handleQueryRequest,
471
- transaction,
472
- mutate,
473
- query,
166
+ ...server,
474
167
  }
475
168
  }
476
169
 
@@ -549,9 +242,11 @@ function alreadyProcessedMutations(response: unknown): AlreadyProcessedMutation[
549
242
  })
550
243
  }
551
244
 
552
- const dummyTransactionInput: TransactionProviderInput = {
553
- clientGroupID: 'unused',
554
- clientID: 'unused',
555
- mutationID: 42,
556
- upstreamSchema: 'unused',
557
- }
245
+ export type {
246
+ ValidateMutationArgs,
247
+ ValidateMutationFn,
248
+ ValidateQueryArgs,
249
+ ValidateQueryFn,
250
+ ZeroServerCommonOptions,
251
+ ZeroServerDatabase,
252
+ } from './createZeroDirectServer'
@@ -1,4 +1,11 @@
1
- import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ rmSync,
6
+ symlinkSync,
7
+ writeFileSync,
8
+ } from 'node:fs'
2
9
  import { tmpdir } from 'node:os'
3
10
  import { join } from 'node:path'
4
11
  import { runInNewContext } from 'node:vm'
@@ -617,6 +624,55 @@ export const mutate = mutations(schema, perm, {
617
624
  expect(content).not.toMatch(/rename:[\s\S]*count/)
618
625
  })
619
626
 
627
+ test('resolves instantiated Zero row fields through Partial and Omit', async () => {
628
+ symlinkSync(
629
+ join(import.meta.dirname, '../../../node_modules'),
630
+ join(testDir, 'node_modules'),
631
+ 'dir',
632
+ )
633
+ writeFileSync(
634
+ join(testDir, 'models/types.ts'),
635
+ `
636
+ import type { Row } from '@rocicorp/zero'
637
+ import { drizzleZeroConfig } from 'drizzle-zero-sqlite'
638
+ import { sqliteTable, text } from 'drizzle-orm/sqlite-core'
639
+
640
+ const item = sqliteTable('item', {
641
+ id: text().primaryKey(),
642
+ name: text().notNull(),
643
+ description: text().notNull(),
644
+ })
645
+ const schema = drizzleZeroConfig({ item })
646
+
647
+ export type Item = Row<(typeof schema)['tables']['item']>
648
+ `,
649
+ )
650
+
651
+ writeFileSync(
652
+ join(testDir, 'models/item.ts'),
653
+ `
654
+ import { mutations } from 'on-zero'
655
+ import type { Item } from './types'
656
+
657
+ type ItemUpdate = { id: string } & Partial<Omit<Item, 'id'>>
658
+
659
+ export const mutate = mutations('item', {
660
+ update: async ({ tx }, props: ItemUpdate) => {
661
+ await tx.mutate.item.update(props)
662
+ },
663
+ })
664
+ `,
665
+ )
666
+
667
+ await generate({ dir: testDir, silent: true })
668
+
669
+ const content = readFileSync(join(testDir, 'generated/syncedMutations.ts'), 'utf-8')
670
+ expect(content).toContain('name: v.optional(v.string())')
671
+ expect(content).toContain('description: v.optional(v.string())')
672
+ expect(content).not.toContain('name: v.optional(v.unknown())')
673
+ expect(content).not.toContain('description: v.optional(v.unknown())')
674
+ })
675
+
620
676
  test('skips symbol-keyed properties when resolving imported mutation param types', async () => {
621
677
  writeFileSync(
622
678
  join(testDir, 'models/types.ts'),
package/src/generate.ts CHANGED
@@ -442,6 +442,19 @@ function extractSchemaColumns(
442
442
  visit(sourceFile)
443
443
  }
444
444
 
445
+ function getInstantiatedPropertyType(
446
+ checker: import('typescript').TypeChecker,
447
+ parent: import('typescript').Type,
448
+ name: string,
449
+ ) {
450
+ const resolveProperty: unknown = Reflect.get(checker, 'getTypeOfPropertyOfType')
451
+ if (typeof resolveProperty !== 'function') return undefined
452
+ // typescript exposes this runtime method but omits it from TypeChecker.
453
+ return Reflect.apply(resolveProperty, checker, [parent, name]) as
454
+ | import('typescript').Type
455
+ | undefined
456
+ }
457
+
445
458
  // convert a ts.Type to valibot code by walking the type checker AST
446
459
  function tsTypeToValibot(
447
460
  ts: typeof import('typescript'),
@@ -528,7 +541,14 @@ function tsTypeToValibot(
528
541
  }
529
542
 
530
543
  // resolve symbol property type with fallbacks
531
- const resolveSymbolType = (prop: import('typescript').Symbol) => {
544
+ const resolveSymbolType = (
545
+ parent: import('typescript').Type,
546
+ prop: import('typescript').Symbol,
547
+ ) => {
548
+ // mapped and conditional types can expose transient symbols with no
549
+ // declaration, so resolve each property against its concrete parent.
550
+ const instantiated = getInstantiatedPropertyType(checker, parent, prop.getName())
551
+ if (instantiated) return instantiated
532
552
  if (prop.valueDeclaration)
533
553
  return checker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration)
534
554
  if ((prop as any).declarations?.[0])
@@ -544,7 +564,7 @@ function tsTypeToValibot(
544
564
  for (const prop of props) {
545
565
  const name = prop.getName()
546
566
  if (shouldSkipObjectKey(name)) continue
547
- const propType = resolveSymbolType(prop)
567
+ const propType = resolveSymbolType(type, prop)
548
568
  const isOptional = !!(prop.getFlags() & ts.SymbolFlags.Optional)
549
569
  let val = recurse(propType)
550
570
  if (isOptional && !val.startsWith('v.optional(')) {
@@ -589,7 +609,7 @@ function tsTypeToValibot(
589
609
  for (const prop of props) {
590
610
  const name = prop.getName()
591
611
  if (shouldSkipObjectKey(name)) continue
592
- const propType = resolveSymbolType(prop)
612
+ const propType = resolveSymbolType(type, prop)
593
613
  const isOptional = !!(prop.getFlags() & ts.SymbolFlags.Optional)
594
614
  let val = recurse(propType)
595
615
  if (isOptional && !val.startsWith('v.optional(')) {